Skip to content

Commit 0eb537f

Browse files
committed
Add support for list nesting in AsciiDoc-to-Markdown conversion
Support for following list item types are also added: - `-`-prefixed unordered list items - `.`-prefixed ordered list items
1 parent 240fc25 commit 0eb537f

File tree

1 file changed

+124
-50
lines changed

1 file changed

+124
-50
lines changed

xtask/src/publish/notes.rs

Lines changed: 124 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -83,38 +83,42 @@ impl<'a, 'b, R: BufRead> Converter<'a, 'b, R> {
8383
}
8484

8585
fn process_list(&mut self) -> anyhow::Result<()> {
86+
let mut nesting = ListNesting::new();
8687
while let Some(line) = self.iter.next() {
8788
let line = line?;
8889
if line.is_empty() {
8990
break;
9091
}
9192

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);
9496
} else if line == "+" {
9597
let line = self
9698
.iter
9799
.peek()
98100
.ok_or_else(|| anyhow!("list continuation unexpectedly terminated"))?;
99101
let line = line.as_deref().map_err(|e| anyhow!("{e}"))?;
102+
103+
let indent = nesting.indent();
100104
if line.starts_with('[') {
101105
self.write_line("", 0);
102-
self.process_source_code_block(1)?;
106+
self.process_source_code_block(indent)?;
103107
} else if line.starts_with(LISTING_DELIMITER) {
104108
self.write_line("", 0);
105-
self.process_listing_block(None, 1)?;
109+
self.process_listing_block(None, indent)?;
106110
} else if line.starts_with('.') {
107111
self.write_line("", 0);
108-
self.process_block_with_title(1)?;
112+
self.process_block_with_title(indent)?;
109113
} else if line.starts_with(IMAGE_BLOCK_PREFIX) {
110114
self.write_line("", 0);
111-
self.process_image_block(None, 1)?;
115+
self.process_image_block(None, indent)?;
112116
} else if line.starts_with(VIDEO_BLOCK_PREFIX) {
113117
self.write_line("", 0);
114-
self.process_video_block(None, 1)?;
118+
self.process_video_block(None, indent)?;
115119
} else {
116120
self.write_line("", 0);
117-
self.process_paragraph(1)?;
121+
self.process_paragraph(indent)?;
118122
}
119123
} else {
120124
bail!("not a list block")
@@ -263,36 +267,38 @@ impl<'a, 'b, R: BufRead> Converter<'a, 'b, R> {
263267
Ok(())
264268
}
265269

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 {
268272
self.output.push('#');
269273
}
270274
self.output.push(' ');
271275
self.output.push_str(title);
272276
self.output.push('\n');
273277
}
274278

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);
277283
self.output.push_str(item);
278284
self.output.push('\n');
279285
}
280286

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);
283289
self.output.push('_');
284290
self.output.push_str(caption);
285291
self.output.push_str("_\\\n");
286292
}
287293

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(' ');
291297
}
292298
}
293299

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);
296302
self.output.push_str(line);
297303
self.output.push('\n');
298304
}
@@ -312,15 +318,31 @@ where
312318
}
313319

314320
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)> {
316338
let mut iter = line.chars();
317-
if iter.next()? != MARKER {
339+
if iter.next()? != symbol {
318340
return None;
319341
}
320342
let mut count = 1;
321343
loop {
322344
match iter.next() {
323-
Some(MARKER) => {
345+
Some(ch) if ch == symbol => {
324346
count += 1;
325347
}
326348
Some(' ') => {
@@ -332,16 +354,6 @@ fn get_title(line: &str) -> Option<(usize, &str)> {
332354
Some((count, iter.as_str()))
333355
}
334356

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-
345357
fn parse_media_block<'a>(line: &'a str, prefix: &str) -> Option<(&'a str, &'a str)> {
346358
if let Some(line) = line.strip_prefix(prefix) {
347359
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
353365
None
354366
}
355367

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+
356417
#[cfg(test)]
357418
mod tests {
358419
use super::*;
@@ -372,7 +433,19 @@ Release: release:2022-01-01[]
372433
== New Features
373434
374435
* 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
376449
+
377450
image::https://example.com/animation.gif[]
378451
+
@@ -400,15 +473,10 @@ This is a plain listing.
400473
paragraph
401474
paragraph
402475
403-
== Fixes
476+
== Another Section
404477
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
412480
413481
The highlight of the month is probably pr:1111[].
414482
@@ -437,7 +505,18 @@ Release: release:2022-01-01[]
437505
## New Features
438506
439507
- 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
441520
442521
![](https://example.com/animation.gif)
443522
@@ -464,15 +543,10 @@ Release: release:2022-01-01[]
464543
paragraph
465544
paragraph
466545
467-
## Fixes
468-
469-
- pr:3333[] foo bar baz
470-
- pr:4444[] foo bar baz
471-
472-
## Internal Improvements
546+
## Another Section
473547
474-
- pr:5555[] foo bar baz
475-
- pr:6666[] foo bar baz
548+
- foo bar baz
549+
- foo bar baz
476550
477551
The highlight of the month is probably pr:1111[].
478552

0 commit comments

Comments
 (0)