3
3
//! #[derive(Parser)] works in terms of "paragraphs". Paragraph is a sequence of
4
4
//! non-empty adjacent lines, delimited by sequences of blank (whitespace only) lines.
5
5
6
- use std :: iter ;
6
+ use markdown :: parse_markdown ;
7
7
8
8
pub ( crate ) fn extract_doc_comment ( attrs : & [ syn:: Attribute ] ) -> Vec < String > {
9
9
// multiline comments (`/** ... */`) may have LFs (`\n`) in them,
@@ -54,58 +54,24 @@ pub(crate) fn format_doc_comment(
54
54
preprocess : bool ,
55
55
force_long : bool ,
56
56
) -> ( Option < String > , Option < String > ) {
57
- if let Some ( first_blank) = lines. iter ( ) . position ( |s| is_blank ( s) ) {
58
- let ( short, long) = if preprocess {
59
- let paragraphs = split_paragraphs ( lines) ;
60
- let short = paragraphs[ 0 ] . clone ( ) ;
61
- let long = paragraphs. join ( "\n \n " ) ;
62
- ( remove_period ( short) , long)
63
- } else {
64
- let short = lines[ ..first_blank] . join ( "\n " ) ;
65
- let long = lines. join ( "\n " ) ;
66
- ( short, long)
67
- } ;
57
+ if preprocess {
58
+ let ( short, long) = parse_markdown ( lines) ;
59
+ let long = long. or_else ( || force_long. then ( || short. clone ( ) ) ) ;
60
+
61
+ ( Some ( remove_period ( short) ) , long)
62
+ } else if let Some ( first_blank) = lines. iter ( ) . position ( |s| is_blank ( s) ) {
63
+ let short = lines[ ..first_blank] . join ( "\n " ) ;
64
+ let long = lines. join ( "\n " ) ;
68
65
69
66
( Some ( short) , Some ( long) )
70
67
} else {
71
- let ( short, long) = if preprocess {
72
- let short = merge_lines ( lines) ;
73
- let long = force_long. then ( || short. clone ( ) ) ;
74
- let short = remove_period ( short) ;
75
- ( short, long)
76
- } else {
77
- let short = lines. join ( "\n " ) ;
78
- let long = force_long. then ( || short. clone ( ) ) ;
79
- ( short, long)
80
- } ;
68
+ let short = lines. join ( "\n " ) ;
69
+ let long = force_long. then ( || short. clone ( ) ) ;
81
70
82
71
( Some ( short) , long)
83
72
}
84
73
}
85
74
86
- fn split_paragraphs ( lines : & [ String ] ) -> Vec < String > {
87
- let mut last_line = 0 ;
88
- iter:: from_fn ( || {
89
- let slice = & lines[ last_line..] ;
90
- let start = slice. iter ( ) . position ( |s| !is_blank ( s) ) . unwrap_or ( 0 ) ;
91
-
92
- let slice = & slice[ start..] ;
93
- let len = slice
94
- . iter ( )
95
- . position ( |s| is_blank ( s) )
96
- . unwrap_or ( slice. len ( ) ) ;
97
-
98
- last_line += start + len;
99
-
100
- if len != 0 {
101
- Some ( merge_lines ( & slice[ ..len] ) )
102
- } else {
103
- None
104
- }
105
- } )
106
- . collect ( )
107
- }
108
-
109
75
fn remove_period ( mut s : String ) -> String {
110
76
if s. ends_with ( '.' ) && !s. ends_with ( ".." ) {
111
77
s. pop ( ) ;
@@ -117,10 +83,200 @@ fn is_blank(s: &str) -> bool {
117
83
s. trim ( ) . is_empty ( )
118
84
}
119
85
120
- fn merge_lines ( lines : impl IntoIterator < Item = impl AsRef < str > > ) -> String {
121
- lines
122
- . into_iter ( )
123
- . map ( |s| s. as_ref ( ) . trim ( ) . to_owned ( ) )
124
- . collect :: < Vec < _ > > ( )
125
- . join ( " " )
86
+ mod markdown {
87
+ use anstyle:: { Reset , Style } ;
88
+ use pulldown_cmark:: { Event , Options , Parser , Tag , TagEnd } ;
89
+ use std:: fmt;
90
+ use std:: fmt:: Write ;
91
+ use std:: ops:: AddAssign ;
92
+
93
+ #[ derive( Default ) ]
94
+ struct MarkdownWriter {
95
+ output : String ,
96
+ indentation : usize ,
97
+ styles : Vec < Style > ,
98
+ }
99
+
100
+ impl MarkdownWriter {
101
+ fn newline ( & mut self ) {
102
+ self . output . push ( '\n' ) ;
103
+ }
104
+ fn endline ( & mut self ) {
105
+ if !self . output . ends_with ( '\n' ) {
106
+ self . newline ( ) ;
107
+ }
108
+ }
109
+
110
+ fn write_fmt ( & mut self , arguments : fmt:: Arguments < ' _ > ) {
111
+ if self . output . ends_with ( '\n' ) {
112
+ write ! ( self . output, "{0: <1$}" , "" , self . indentation) . unwrap ( ) ;
113
+ }
114
+ self . output . write_fmt ( arguments) . unwrap ( ) ;
115
+ }
116
+
117
+ fn start_link ( & mut self , dest_url : pulldown_cmark:: CowStr < ' _ > ) {
118
+ write ! ( self , "\x1B ]8;;{dest_url}\x1B \\ " ) ;
119
+ }
120
+ fn end_link ( & mut self ) {
121
+ write ! ( self , "\x1B ]8;;\x1B \\ " ) ;
122
+ }
123
+
124
+ fn start_style ( & mut self , style : Style ) {
125
+ self . styles . push ( style) ;
126
+ write ! ( self , "{style}" ) ;
127
+ }
128
+ fn end_style ( & mut self , style : Style ) {
129
+ let last_style = self . styles . pop ( ) ;
130
+ debug_assert_eq ! ( last_style. unwrap( ) , style) ;
131
+
132
+ write ! ( self , "{Reset}" ) ;
133
+ // Reapplying all, because anstyle doesn't support merging styles
134
+ // (probably because the ambiguity around colors)
135
+ // TODO If we decide not to support any colors, we can replace this with
136
+ // anstyle::Effects and remove the need for applying them all individually.
137
+ for style in & self . styles {
138
+ write ! ( self . output, "{style}" ) . unwrap ( ) ;
139
+ }
140
+ }
141
+ }
142
+
143
+ pub ( super ) fn parse_markdown ( input : & [ String ] ) -> ( String , Option < String > ) {
144
+ // Markdown Configuration
145
+ let parsing_options = Options :: ENABLE_STRIKETHROUGH /* TODO UNICODE | Options::ENABLE_SMART_PUNCTUATION */ ;
146
+ // Minimal Styling for now, because we cannot configure it
147
+ let style_heading = Style :: new ( ) . bold ( ) ;
148
+ let style_emphasis = Style :: new ( ) . italic ( ) ;
149
+ let style_strong = Style :: new ( ) . bold ( ) ;
150
+ let style_strike_through = Style :: new ( ) . strikethrough ( ) ;
151
+ let style_link = Style :: new ( ) . underline ( ) ;
152
+ // TODO decide how to style code
153
+ let style_code = Style :: new ( ) . dimmed ( ) ;
154
+ let tab_width = 2 ;
155
+ // TODO UNICODE let list_symbol = '•';
156
+ let list_symbol = '-' ;
157
+
158
+ let input = input. join ( "\n " ) ;
159
+ let input = Parser :: new_ext ( & input, parsing_options) ;
160
+
161
+ let mut short = None ;
162
+
163
+ let mut writer = MarkdownWriter :: default ( ) ;
164
+
165
+ let mut list_indices = Vec :: new ( ) ;
166
+
167
+ for event in input {
168
+ match event {
169
+ Event :: Start ( Tag :: Paragraph ) => { /* nothing to do */ }
170
+ Event :: Start ( Tag :: Heading { .. } ) => writer. start_style ( style_heading) ,
171
+ Event :: Start (
172
+ Tag :: Image { .. } | Tag :: BlockQuote ( _) | Tag :: CodeBlock ( _) | Tag :: HtmlBlock ,
173
+ ) => { /* IGNORED */ }
174
+ Event :: Start ( Tag :: List ( list_start) ) => {
175
+ list_indices. push ( list_start) ;
176
+ writer. endline ( ) ;
177
+ }
178
+ Event :: Start ( Tag :: Item ) => {
179
+ if let Some ( Some ( index) ) = list_indices. last_mut ( ) {
180
+ write ! ( writer, "{index}. " ) ;
181
+ index. add_assign ( 1 ) ;
182
+ } else {
183
+ write ! ( writer, "{list_symbol} " ) ;
184
+ }
185
+ writer. indentation += tab_width;
186
+ }
187
+ Event :: Start ( Tag :: Emphasis ) => writer. start_style ( style_emphasis) ,
188
+ Event :: Start ( Tag :: Strong ) => writer. start_style ( style_strong) ,
189
+ Event :: Start ( Tag :: Strikethrough ) => writer. start_style ( style_strike_through) ,
190
+ Event :: Start ( Tag :: Link { dest_url, .. } ) => {
191
+ writer. start_link ( dest_url) ;
192
+ writer. start_style ( style_link) ;
193
+ }
194
+
195
+ Event :: End ( TagEnd :: Paragraph ) => {
196
+ if short. is_none ( ) {
197
+ short = Some ( writer. output . trim ( ) . to_owned ( ) ) ;
198
+ }
199
+ writer. endline ( ) ;
200
+ writer. newline ( ) ;
201
+ }
202
+ Event :: End ( TagEnd :: Heading ( ..) ) => {
203
+ writer. end_style ( style_heading) ;
204
+ writer. endline ( ) ;
205
+ writer. newline ( ) ;
206
+ }
207
+ Event :: End ( TagEnd :: List ( _) ) => {
208
+ let list = list_indices. pop ( ) ;
209
+ debug_assert ! ( list. is_some( ) ) ;
210
+ if list_indices. is_empty ( ) {
211
+ writer. newline ( ) ;
212
+ }
213
+ }
214
+ Event :: End ( TagEnd :: Item ) => {
215
+ writer. indentation -= tab_width;
216
+ writer. endline ( ) ;
217
+ }
218
+ Event :: End ( TagEnd :: Emphasis ) => writer. end_style ( style_emphasis) ,
219
+ Event :: End ( TagEnd :: Strong ) => writer. end_style ( style_strong) ,
220
+ Event :: End ( TagEnd :: Strikethrough ) => writer. end_style ( style_strike_through) ,
221
+ Event :: End ( TagEnd :: Link ) => {
222
+ writer. end_link ( ) ;
223
+ writer. end_style ( style_link) ;
224
+ }
225
+ Event :: End (
226
+ TagEnd :: Image | TagEnd :: BlockQuote ( _) | TagEnd :: HtmlBlock | TagEnd :: CodeBlock ,
227
+ ) => { /* IGNORED */ }
228
+
229
+ Event :: Text ( segment) => write ! ( writer, "{segment}" ) ,
230
+ Event :: Code ( code) => {
231
+ writer. start_style ( style_code) ;
232
+ write ! ( writer, "{code}" ) ;
233
+ writer. end_style ( style_code) ;
234
+ }
235
+ // There is not really anything useful to do with block level html.
236
+ Event :: Html ( html) => write ! ( writer, "{html}" ) ,
237
+ // At some point we could support custom tags like `<red>`
238
+ Event :: InlineHtml ( html) => write ! ( writer, "{html}" ) ,
239
+ Event :: SoftBreak => write ! ( writer, " " ) ,
240
+ Event :: HardBreak => writer. endline ( ) ,
241
+ // TODO for anything useful we'd need to know the terminal width
242
+ Event :: Rule => {
243
+ writer. endline ( ) ;
244
+ writer. newline ( ) ;
245
+ write ! ( writer, "---\n \n " ) ;
246
+ }
247
+ Event :: Start (
248
+ Tag :: FootnoteDefinition ( _)
249
+ | Tag :: DefinitionList
250
+ | Tag :: DefinitionListTitle
251
+ | Tag :: DefinitionListDefinition
252
+ | Tag :: Table ( _)
253
+ | Tag :: TableHead
254
+ | Tag :: TableRow
255
+ | Tag :: TableCell
256
+ | Tag :: MetadataBlock ( _) ,
257
+ )
258
+ | Event :: End (
259
+ TagEnd :: FootnoteDefinition
260
+ | TagEnd :: DefinitionList
261
+ | TagEnd :: DefinitionListTitle
262
+ | TagEnd :: DefinitionListDefinition
263
+ | TagEnd :: Table
264
+ | TagEnd :: TableHead
265
+ | TagEnd :: TableRow
266
+ | TagEnd :: TableCell
267
+ | TagEnd :: MetadataBlock ( _) ,
268
+ )
269
+ | Event :: InlineMath ( _)
270
+ | Event :: DisplayMath ( _)
271
+ | Event :: FootnoteReference ( _)
272
+ | Event :: TaskListMarker ( _) => {
273
+ unimplemented ! ( "feature not enabled {event:?}" )
274
+ }
275
+ }
276
+ }
277
+ let short = short. unwrap_or_else ( || writer. output . trim ( ) . to_owned ( ) ) ;
278
+ let long = writer. output . trim ( ) ;
279
+ let long = ( short != long) . then ( || long. to_owned ( ) ) ;
280
+ ( short, long)
281
+ }
126
282
}
0 commit comments