@@ -83,38 +83,42 @@ impl<'a, 'b, R: BufRead> Converter<'a, 'b, R> {
83
83
}
84
84
85
85
fn process_list ( & mut self ) -> anyhow:: Result < ( ) > {
86
+ let mut nesting = ListNesting :: new ( ) ;
86
87
while let Some ( line) = self . iter . next ( ) {
87
88
let line = line?;
88
89
if line. is_empty ( ) {
89
90
break ;
90
91
}
91
92
92
- if let Some ( item) = get_list_item ( & line) {
93
- self . write_list_item ( item) ;
93
+ if let Some ( ( marker, item) ) = get_list_item ( & line) {
94
+ nesting. set_current ( marker) ;
95
+ self . write_list_item ( item, & nesting) ;
94
96
} else if line == "+" {
95
97
let line = self
96
98
. iter
97
99
. peek ( )
98
100
. ok_or_else ( || anyhow ! ( "list continuation unexpectedly terminated" ) ) ?;
99
101
let line = line. as_deref ( ) . map_err ( |e| anyhow ! ( "{e}" ) ) ?;
102
+
103
+ let indent = nesting. indent ( ) ;
100
104
if line. starts_with ( '[' ) {
101
105
self . write_line ( "" , 0 ) ;
102
- self . process_source_code_block ( 1 ) ?;
106
+ self . process_source_code_block ( indent ) ?;
103
107
} else if line. starts_with ( LISTING_DELIMITER ) {
104
108
self . write_line ( "" , 0 ) ;
105
- self . process_listing_block ( None , 1 ) ?;
109
+ self . process_listing_block ( None , indent ) ?;
106
110
} else if line. starts_with ( '.' ) {
107
111
self . write_line ( "" , 0 ) ;
108
- self . process_block_with_title ( 1 ) ?;
112
+ self . process_block_with_title ( indent ) ?;
109
113
} else if line. starts_with ( IMAGE_BLOCK_PREFIX ) {
110
114
self . write_line ( "" , 0 ) ;
111
- self . process_image_block ( None , 1 ) ?;
115
+ self . process_image_block ( None , indent ) ?;
112
116
} else if line. starts_with ( VIDEO_BLOCK_PREFIX ) {
113
117
self . write_line ( "" , 0 ) ;
114
- self . process_video_block ( None , 1 ) ?;
118
+ self . process_video_block ( None , indent ) ?;
115
119
} else {
116
120
self . write_line ( "" , 0 ) ;
117
- self . process_paragraph ( 1 ) ?;
121
+ self . process_paragraph ( indent ) ?;
118
122
}
119
123
} else {
120
124
bail ! ( "not a list block" )
@@ -263,36 +267,38 @@ impl<'a, 'b, R: BufRead> Converter<'a, 'b, R> {
263
267
Ok ( ( ) )
264
268
}
265
269
266
- fn write_title ( & mut self , level : usize , title : & str ) {
267
- for _ in 0 ..level {
270
+ fn write_title ( & mut self , indent : usize , title : & str ) {
271
+ for _ in 0 ..indent {
268
272
self . output . push ( '#' ) ;
269
273
}
270
274
self . output . push ( ' ' ) ;
271
275
self . output . push_str ( title) ;
272
276
self . output . push ( '\n' ) ;
273
277
}
274
278
275
- fn write_list_item ( & mut self , item : & str ) {
276
- self . output . push_str ( "- " ) ;
279
+ fn write_list_item ( & mut self , item : & str , nesting : & ListNesting ) {
280
+ let ( marker, indent) = nesting. marker ( ) ;
281
+ self . write_indent ( indent) ;
282
+ self . output . push_str ( marker) ;
277
283
self . output . push_str ( item) ;
278
284
self . output . push ( '\n' ) ;
279
285
}
280
286
281
- fn write_caption_line ( & mut self , caption : & str , level : usize ) {
282
- self . write_indent ( level ) ;
287
+ fn write_caption_line ( & mut self , caption : & str , indent : usize ) {
288
+ self . write_indent ( indent ) ;
283
289
self . output . push ( '_' ) ;
284
290
self . output . push_str ( caption) ;
285
291
self . output . push_str ( "_\\ \n " ) ;
286
292
}
287
293
288
- fn write_indent ( & mut self , level : usize ) {
289
- for _ in 0 ..level {
290
- self . output . push_str ( " " ) ;
294
+ fn write_indent ( & mut self , indent : usize ) {
295
+ for _ in 0 ..indent {
296
+ self . output . push ( ' ' ) ;
291
297
}
292
298
}
293
299
294
- fn write_line ( & mut self , line : & str , level : usize ) {
295
- self . write_indent ( level ) ;
300
+ fn write_line ( & mut self , line : & str , indent : usize ) {
301
+ self . write_indent ( indent ) ;
296
302
self . output . push_str ( line) ;
297
303
self . output . push ( '\n' ) ;
298
304
}
@@ -312,15 +318,31 @@ where
312
318
}
313
319
314
320
fn get_title ( line : & str ) -> Option < ( usize , & str ) > {
315
- const MARKER : char = '=' ;
321
+ strip_prefix_symbol ( line, '=' )
322
+ }
323
+
324
+ fn get_list_item ( line : & str ) -> Option < ( ListMarker , & str ) > {
325
+ const HYPHYEN_MARKER : & ' static str = "- " ;
326
+ if let Some ( text) = line. strip_prefix ( HYPHYEN_MARKER ) {
327
+ Some ( ( ListMarker :: Hyphen , text) )
328
+ } else if let Some ( ( count, text) ) = strip_prefix_symbol ( line, '*' ) {
329
+ Some ( ( ListMarker :: Asterisk ( count) , text) )
330
+ } else if let Some ( ( count, text) ) = strip_prefix_symbol ( line, '.' ) {
331
+ Some ( ( ListMarker :: Dot ( count) , text) )
332
+ } else {
333
+ None
334
+ }
335
+ }
336
+
337
+ fn strip_prefix_symbol ( line : & str , symbol : char ) -> Option < ( usize , & str ) > {
316
338
let mut iter = line. chars ( ) ;
317
- if iter. next ( ) ? != MARKER {
339
+ if iter. next ( ) ? != symbol {
318
340
return None ;
319
341
}
320
342
let mut count = 1 ;
321
343
loop {
322
344
match iter. next ( ) {
323
- Some ( MARKER ) => {
345
+ Some ( ch ) if ch == symbol => {
324
346
count += 1 ;
325
347
}
326
348
Some ( ' ' ) => {
@@ -332,16 +354,6 @@ fn get_title(line: &str) -> Option<(usize, &str)> {
332
354
Some ( ( count, iter. as_str ( ) ) )
333
355
}
334
356
335
- fn get_list_item ( line : & str ) -> Option < & str > {
336
- const MARKER : & ' static str = "* " ;
337
- if line. starts_with ( MARKER ) {
338
- let item = & line[ MARKER . len ( ) ..] ;
339
- Some ( item)
340
- } else {
341
- None
342
- }
343
- }
344
-
345
357
fn parse_media_block < ' a > ( line : & ' a str , prefix : & str ) -> Option < ( & ' a str , & ' a str ) > {
346
358
if let Some ( line) = line. strip_prefix ( prefix) {
347
359
if let Some ( ( url, rest) ) = line. split_once ( '[' ) {
@@ -353,6 +365,55 @@ fn parse_media_block<'a>(line: &'a str, prefix: &str) -> Option<(&'a str, &'a st
353
365
None
354
366
}
355
367
368
+ #[ derive( Debug ) ]
369
+ struct ListNesting ( Vec < ListMarker > ) ;
370
+
371
+ impl ListNesting {
372
+ fn new ( ) -> Self {
373
+ Self ( Vec :: < ListMarker > :: with_capacity ( 6 ) )
374
+ }
375
+
376
+ fn set_current ( & mut self , marker : ListMarker ) {
377
+ let Self ( markers) = self ;
378
+ if let Some ( index) = markers. iter ( ) . position ( |m| * m == marker) {
379
+ markers. truncate ( index + 1 ) ;
380
+ } else {
381
+ markers. push ( marker) ;
382
+ }
383
+ }
384
+
385
+ fn indent ( & self ) -> usize {
386
+ self . 0 . iter ( ) . map ( |m| m. in_markdown ( ) . len ( ) ) . sum ( )
387
+ }
388
+
389
+ fn marker ( & self ) -> ( & str , usize ) {
390
+ let Self ( markers) = self ;
391
+ let indent = markers. iter ( ) . take ( markers. len ( ) - 1 ) . map ( |m| m. in_markdown ( ) . len ( ) ) . sum ( ) ;
392
+ let marker = match markers. last ( ) {
393
+ None => "" ,
394
+ Some ( marker) => marker. in_markdown ( ) ,
395
+ } ;
396
+ ( marker, indent)
397
+ }
398
+ }
399
+
400
+ #[ derive( Debug , PartialEq , Eq ) ]
401
+ enum ListMarker {
402
+ Asterisk ( usize ) ,
403
+ Hyphen ,
404
+ Dot ( usize ) ,
405
+ }
406
+
407
+ impl ListMarker {
408
+ fn in_markdown ( & self ) -> & str {
409
+ match self {
410
+ ListMarker :: Asterisk ( _) => "- " ,
411
+ ListMarker :: Hyphen => "- " ,
412
+ ListMarker :: Dot ( _) => "1. " ,
413
+ }
414
+ }
415
+ }
416
+
356
417
#[ cfg( test) ]
357
418
mod tests {
358
419
use super :: * ;
@@ -372,7 +433,19 @@ Release: release:2022-01-01[]
372
433
== New Features
373
434
374
435
* pr:1111[] foo bar baz
375
- * pr:2222[] foo bar baz
436
+ - hyphen-prefixed list item
437
+ * nested list item
438
+ ** `foo` -> `foofoo`
439
+ ** `bar` -> `barbar`
440
+ * listing in the secondary level
441
+ . install
442
+ . add to config
443
+ +
444
+ [source,json]
445
+ ----
446
+ {\" foo\" :\" bar\" }
447
+ ----
448
+ * list item with continuation
376
449
+
377
450
image::https://example.com/animation.gif[]
378
451
+
@@ -400,15 +473,10 @@ This is a plain listing.
400
473
paragraph
401
474
paragraph
402
475
403
- == Fixes
476
+ == Another Section
404
477
405
- * pr:3333[] foo bar baz
406
- * pr:4444[] foo bar baz
407
-
408
- == Internal Improvements
409
-
410
- * pr:5555[] foo bar baz
411
- * pr:6666[] foo bar baz
478
+ * foo bar baz
479
+ * foo bar baz
412
480
413
481
The highlight of the month is probably pr:1111[].
414
482
@@ -437,7 +505,18 @@ Release: release:2022-01-01[]
437
505
## New Features
438
506
439
507
- pr:1111[] foo bar baz
440
- - pr:2222[] foo bar baz
508
+ - hyphen-prefixed list item
509
+ - nested list item
510
+ - `foo` -> `foofoo`
511
+ - `bar` -> `barbar`
512
+ - listing in the secondary level
513
+ 1. install
514
+ 1. add to config
515
+
516
+ ```json
517
+ {\" foo\" :\" bar\" }
518
+ ```
519
+ - list item with continuation
441
520
442
521

443
522
@@ -464,15 +543,10 @@ Release: release:2022-01-01[]
464
543
paragraph
465
544
paragraph
466
545
467
- ## Fixes
468
-
469
- - pr:3333[] foo bar baz
470
- - pr:4444[] foo bar baz
471
-
472
- ## Internal Improvements
546
+ ## Another Section
473
547
474
- - pr:5555[] foo bar baz
475
- - pr:6666[] foo bar baz
548
+ - foo bar baz
549
+ - foo bar baz
476
550
477
551
The highlight of the month is probably pr:1111[].
478
552
0 commit comments