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
This commit is contained in:
Noritada Kobayashi 2022-12-11 14:58:49 +09:00
parent 240fc255ef
commit 0eb537fa31

View File

@ -83,38 +83,42 @@ impl<'a, 'b, R: BufRead> Converter<'a, 'b, R> {
} }
fn process_list(&mut self) -> anyhow::Result<()> { fn process_list(&mut self) -> anyhow::Result<()> {
let mut nesting = ListNesting::new();
while let Some(line) = self.iter.next() { while let Some(line) = self.iter.next() {
let line = line?; let line = line?;
if line.is_empty() { if line.is_empty() {
break; break;
} }
if let Some(item) = get_list_item(&line) { if let Some((marker, item)) = get_list_item(&line) {
self.write_list_item(item); nesting.set_current(marker);
self.write_list_item(item, &nesting);
} else if line == "+" { } else if line == "+" {
let line = self let line = self
.iter .iter
.peek() .peek()
.ok_or_else(|| anyhow!("list continuation unexpectedly terminated"))?; .ok_or_else(|| anyhow!("list continuation unexpectedly terminated"))?;
let line = line.as_deref().map_err(|e| anyhow!("{e}"))?; let line = line.as_deref().map_err(|e| anyhow!("{e}"))?;
let indent = nesting.indent();
if line.starts_with('[') { if line.starts_with('[') {
self.write_line("", 0); self.write_line("", 0);
self.process_source_code_block(1)?; self.process_source_code_block(indent)?;
} else if line.starts_with(LISTING_DELIMITER) { } else if line.starts_with(LISTING_DELIMITER) {
self.write_line("", 0); self.write_line("", 0);
self.process_listing_block(None, 1)?; self.process_listing_block(None, indent)?;
} else if line.starts_with('.') { } else if line.starts_with('.') {
self.write_line("", 0); self.write_line("", 0);
self.process_block_with_title(1)?; self.process_block_with_title(indent)?;
} else if line.starts_with(IMAGE_BLOCK_PREFIX) { } else if line.starts_with(IMAGE_BLOCK_PREFIX) {
self.write_line("", 0); self.write_line("", 0);
self.process_image_block(None, 1)?; self.process_image_block(None, indent)?;
} else if line.starts_with(VIDEO_BLOCK_PREFIX) { } else if line.starts_with(VIDEO_BLOCK_PREFIX) {
self.write_line("", 0); self.write_line("", 0);
self.process_video_block(None, 1)?; self.process_video_block(None, indent)?;
} else { } else {
self.write_line("", 0); self.write_line("", 0);
self.process_paragraph(1)?; self.process_paragraph(indent)?;
} }
} else { } else {
bail!("not a list block") bail!("not a list block")
@ -263,8 +267,8 @@ impl<'a, 'b, R: BufRead> Converter<'a, 'b, R> {
Ok(()) Ok(())
} }
fn write_title(&mut self, level: usize, title: &str) { fn write_title(&mut self, indent: usize, title: &str) {
for _ in 0..level { for _ in 0..indent {
self.output.push('#'); self.output.push('#');
} }
self.output.push(' '); self.output.push(' ');
@ -272,27 +276,29 @@ impl<'a, 'b, R: BufRead> Converter<'a, 'b, R> {
self.output.push('\n'); self.output.push('\n');
} }
fn write_list_item(&mut self, item: &str) { fn write_list_item(&mut self, item: &str, nesting: &ListNesting) {
self.output.push_str("- "); let (marker, indent) = nesting.marker();
self.write_indent(indent);
self.output.push_str(marker);
self.output.push_str(item); self.output.push_str(item);
self.output.push('\n'); self.output.push('\n');
} }
fn write_caption_line(&mut self, caption: &str, level: usize) { fn write_caption_line(&mut self, caption: &str, indent: usize) {
self.write_indent(level); self.write_indent(indent);
self.output.push('_'); self.output.push('_');
self.output.push_str(caption); self.output.push_str(caption);
self.output.push_str("_\\\n"); self.output.push_str("_\\\n");
} }
fn write_indent(&mut self, level: usize) { fn write_indent(&mut self, indent: usize) {
for _ in 0..level { for _ in 0..indent {
self.output.push_str(" "); self.output.push(' ');
} }
} }
fn write_line(&mut self, line: &str, level: usize) { fn write_line(&mut self, line: &str, indent: usize) {
self.write_indent(level); self.write_indent(indent);
self.output.push_str(line); self.output.push_str(line);
self.output.push('\n'); self.output.push('\n');
} }
@ -312,15 +318,31 @@ where
} }
fn get_title(line: &str) -> Option<(usize, &str)> { fn get_title(line: &str) -> Option<(usize, &str)> {
const MARKER: char = '='; strip_prefix_symbol(line, '=')
}
fn get_list_item(line: &str) -> Option<(ListMarker, &str)> {
const HYPHYEN_MARKER: &'static str = "- ";
if let Some(text) = line.strip_prefix(HYPHYEN_MARKER) {
Some((ListMarker::Hyphen, text))
} else if let Some((count, text)) = strip_prefix_symbol(line, '*') {
Some((ListMarker::Asterisk(count), text))
} else if let Some((count, text)) = strip_prefix_symbol(line, '.') {
Some((ListMarker::Dot(count), text))
} else {
None
}
}
fn strip_prefix_symbol(line: &str, symbol: char) -> Option<(usize, &str)> {
let mut iter = line.chars(); let mut iter = line.chars();
if iter.next()? != MARKER { if iter.next()? != symbol {
return None; return None;
} }
let mut count = 1; let mut count = 1;
loop { loop {
match iter.next() { match iter.next() {
Some(MARKER) => { Some(ch) if ch == symbol => {
count += 1; count += 1;
} }
Some(' ') => { Some(' ') => {
@ -332,16 +354,6 @@ fn get_title(line: &str) -> Option<(usize, &str)> {
Some((count, iter.as_str())) Some((count, iter.as_str()))
} }
fn get_list_item(line: &str) -> Option<&str> {
const MARKER: &'static str = "* ";
if line.starts_with(MARKER) {
let item = &line[MARKER.len()..];
Some(item)
} else {
None
}
}
fn parse_media_block<'a>(line: &'a str, prefix: &str) -> Option<(&'a str, &'a str)> { fn parse_media_block<'a>(line: &'a str, prefix: &str) -> Option<(&'a str, &'a str)> {
if let Some(line) = line.strip_prefix(prefix) { if let Some(line) = line.strip_prefix(prefix) {
if let Some((url, rest)) = line.split_once('[') { 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
None None
} }
#[derive(Debug)]
struct ListNesting(Vec<ListMarker>);
impl ListNesting {
fn new() -> Self {
Self(Vec::<ListMarker>::with_capacity(6))
}
fn set_current(&mut self, marker: ListMarker) {
let Self(markers) = self;
if let Some(index) = markers.iter().position(|m| *m == marker) {
markers.truncate(index + 1);
} else {
markers.push(marker);
}
}
fn indent(&self) -> usize {
self.0.iter().map(|m| m.in_markdown().len()).sum()
}
fn marker(&self) -> (&str, usize) {
let Self(markers) = self;
let indent = markers.iter().take(markers.len() - 1).map(|m| m.in_markdown().len()).sum();
let marker = match markers.last() {
None => "",
Some(marker) => marker.in_markdown(),
};
(marker, indent)
}
}
#[derive(Debug, PartialEq, Eq)]
enum ListMarker {
Asterisk(usize),
Hyphen,
Dot(usize),
}
impl ListMarker {
fn in_markdown(&self) -> &str {
match self {
ListMarker::Asterisk(_) => "- ",
ListMarker::Hyphen => "- ",
ListMarker::Dot(_) => "1. ",
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -372,7 +433,19 @@ Release: release:2022-01-01[]
== New Features == New Features
* pr:1111[] foo bar baz * pr:1111[] foo bar baz
* pr:2222[] foo bar baz - hyphen-prefixed list item
* nested list item
** `foo` -> `foofoo`
** `bar` -> `barbar`
* listing in the secondary level
. install
. add to config
+
[source,json]
----
{\"foo\":\"bar\"}
----
* list item with continuation
+ +
image::https://example.com/animation.gif[] image::https://example.com/animation.gif[]
+ +
@ -400,15 +473,10 @@ This is a plain listing.
paragraph paragraph
paragraph paragraph
== Fixes == Another Section
* pr:3333[] foo bar baz * foo bar baz
* pr:4444[] foo bar baz * foo bar baz
== Internal Improvements
* pr:5555[] foo bar baz
* pr:6666[] foo bar baz
The highlight of the month is probably pr:1111[]. The highlight of the month is probably pr:1111[].
@ -437,7 +505,18 @@ Release: release:2022-01-01[]
## New Features ## New Features
- pr:1111[] foo bar baz - pr:1111[] foo bar baz
- pr:2222[] foo bar baz - hyphen-prefixed list item
- nested list item
- `foo` -> `foofoo`
- `bar` -> `barbar`
- listing in the secondary level
1. install
1. add to config
```json
{\"foo\":\"bar\"}
```
- list item with continuation
![](https://example.com/animation.gif) ![](https://example.com/animation.gif)
@ -464,15 +543,10 @@ Release: release:2022-01-01[]
paragraph paragraph
paragraph paragraph
## Fixes ## Another Section
- pr:3333[] foo bar baz - foo bar baz
- pr:4444[] foo bar baz - foo bar baz
## Internal Improvements
- pr:5555[] foo bar baz
- pr:6666[] foo bar baz
The highlight of the month is probably pr:1111[]. The highlight of the month is probably pr:1111[].