parser: parse lists and list items

This commit is contained in:
Noah Hellman 2023-01-21 21:21:43 +01:00
parent 6befcad52a
commit 2f616c41b7
2 changed files with 186 additions and 8 deletions

View file

@ -101,7 +101,7 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
Container::Blockquote => self.out.write_str("<blockquote")?, Container::Blockquote => self.out.write_str("<blockquote")?,
Container::List(..) => todo!(), Container::List(..) => todo!(),
Container::ListItem => self.out.write_str("<li")?, Container::ListItem => self.out.write_str("<li")?,
Container::DescriptionList => self.out.write_str("<dl")?, Container::TaskListItem { .. } => todo!(),
Container::DescriptionDetails => self.out.write_str("<dd")?, Container::DescriptionDetails => self.out.write_str("<dd")?,
Container::Footnote { number, .. } => { Container::Footnote { number, .. } => {
assert!(self.footnote_number.is_none()); assert!(self.footnote_number.is_none());
@ -225,7 +225,7 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
Container::Blockquote => self.out.write_str("</blockquote>")?, Container::Blockquote => self.out.write_str("</blockquote>")?,
Container::List(..) => todo!(), Container::List(..) => todo!(),
Container::ListItem => self.out.write_str("</li>")?, Container::ListItem => self.out.write_str("</li>")?,
Container::DescriptionList => self.out.write_str("</dl>")?, Container::TaskListItem { .. } => todo!(),
Container::DescriptionDetails => self.out.write_str("</dd>")?, Container::DescriptionDetails => self.out.write_str("</dd>")?,
Container::Footnote { number, .. } => { Container::Footnote { number, .. } => {
if !self.footnote_backlink_written { if !self.footnote_backlink_written {

View file

@ -36,8 +36,8 @@ pub enum Container<'s> {
List(List), List(List),
/// An item of a list /// An item of a list
ListItem, ListItem,
/// A description list element. /// An item of a task list, either checked or unchecked.
DescriptionList, TaskListItem { checked: bool },
/// Details describing a term within a description list. /// Details describing a term within a description list.
DescriptionDetails, DescriptionDetails,
/// A footnote definition. /// A footnote definition.
@ -99,7 +99,7 @@ impl<'s> Container<'s> {
Self::Blockquote Self::Blockquote
| Self::List(..) | Self::List(..)
| Self::ListItem | Self::ListItem
| Self::DescriptionList | Self::TaskListItem { .. }
| Self::DescriptionDetails | Self::DescriptionDetails
| Self::Footnote { .. } | Self::Footnote { .. }
| Self::Table | Self::Table
@ -135,7 +135,7 @@ impl<'s> Container<'s> {
Self::Blockquote Self::Blockquote
| Self::List(..) | Self::List(..)
| Self::ListItem | Self::ListItem
| Self::DescriptionList | Self::TaskListItem { .. }
| Self::DescriptionDetails | Self::DescriptionDetails
| Self::Footnote { .. } | Self::Footnote { .. }
| Self::Table | Self::Table
@ -260,6 +260,23 @@ impl<'s> Container<'s> {
} }
} }
impl OrderedListNumbering {
fn parse_number(self, n: &str) -> u32 {
match self {
Self::Decimal => n.parse().unwrap(),
Self::AlphaLower | Self::AlphaUpper => 1,
Self::RomanLower => 1,
Self::RomanUpper => 1,
}
}
}
impl OrderedListStyle {
fn number(self, marker: &str) -> &str {
&marker[usize::from(matches!(self, Self::ParenParen))..marker.len() - 1]
}
}
pub struct Parser<'s> { pub struct Parser<'s> {
src: &'s str, src: &'s str,
@ -482,8 +499,30 @@ impl<'s> Parser<'s> {
self.footnotes.insert(content, self.tree.take_branch()); self.footnotes.insert(content, self.tree.take_branch());
continue; continue;
} }
block::Container::List(ty) => todo!(), block::Container::List(ty) => match ty {
block::Container::ListItem(ty) => todo!(), block::ListType::Unordered(..) => Container::List(List::Unordered),
block::ListType::Task => Container::List(List::Task),
block::ListType::Ordered(numbering, style) => {
let marker = ev.span.of(self.src);
let start = numbering.parse_number(style.number(marker)).max(1);
Container::List(List::Ordered {
numbering,
style,
start,
})
}
block::ListType::Description => panic!(),
},
block::Container::ListItem(ty) => {
if matches!(ty, block::ListType::Task) {
let marker = ev.span.of(self.src);
Container::TaskListItem {
checked: marker.as_bytes()[3] != b' ',
}
} else {
Container::ListItem
}
}
}, },
}; };
if enter { if enter {
@ -545,6 +584,10 @@ mod test {
use super::Container::*; use super::Container::*;
use super::Event::*; use super::Event::*;
use super::LinkType; use super::LinkType;
use super::List;
use super::List::*;
use super::OrderedListNumbering::*;
use super::OrderedListStyle::*;
use super::SpanLinkType; use super::SpanLinkType;
macro_rules! test_parse { macro_rules! test_parse {
@ -962,4 +1005,139 @@ mod test {
End(Paragraph), End(Paragraph),
); );
} }
#[test]
fn list_item_unordered() {
test_parse!(
"- abc",
Start(List(List::Unordered), Attributes::new()),
Start(ListItem, Attributes::new()),
Start(Paragraph, Attributes::new()),
Str("abc".into()),
End(Paragraph),
End(ListItem),
End(List(List::Unordered)),
);
}
#[test]
fn list_item_ordered_decimal() {
test_parse!(
"123. abc",
Start(
List(List::Ordered {
numbering: Decimal,
style: Period,
start: 123
}),
Attributes::new()
),
Start(ListItem, Attributes::new()),
Start(Paragraph, Attributes::new()),
Str("abc".into()),
End(Paragraph),
End(ListItem),
End(List(List::Ordered {
numbering: Decimal,
style: Period,
start: 123
})),
);
}
#[test]
fn list_mixed() {
test_parse!(
concat!(
"- a\n", //
"+ b\n", //
"0) c\n", //
"0) d\n", //
"(b) e\n", //
),
Start(List(List::Unordered), Attributes::new()),
Start(ListItem, Attributes::new()),
Start(Paragraph, Attributes::new()),
Str("a".into()),
End(Paragraph),
End(ListItem),
End(List(List::Unordered)),
Start(List(List::Unordered), Attributes::new()),
Start(ListItem, Attributes::new()),
Start(Paragraph, Attributes::new()),
Str("b".into()),
End(Paragraph),
End(ListItem),
End(List(List::Unordered)),
Start(
List(List::Ordered {
numbering: Decimal,
style: Paren,
start: 1,
}),
Attributes::new()
),
Start(ListItem, Attributes::new()),
Start(Paragraph, Attributes::new()),
Str("c".into()),
End(Paragraph),
End(ListItem),
Start(ListItem, Attributes::new()),
Start(Paragraph, Attributes::new()),
Str("d".into()),
End(Paragraph),
End(ListItem),
End(List(List::Ordered {
numbering: Decimal,
style: Paren,
start: 1,
})),
Start(
List(List::Ordered {
numbering: AlphaLower,
style: ParenParen,
start: 2
}),
Attributes::new()
),
Start(ListItem, Attributes::new()),
Start(Paragraph, Attributes::new()),
Str("e".into()),
End(Paragraph),
End(ListItem),
End(List(List::Ordered {
numbering: AlphaLower,
style: ParenParen,
start: 2,
})),
);
}
#[test]
fn list_task() {
test_parse!(
concat!(
"- [ ] a\n", //
"- [x] b\n", //
"- [X] c\n", //
),
Start(List(List::Task), Attributes::new()),
Start(TaskListItem { checked: false }, Attributes::new()),
Start(Paragraph, Attributes::new()),
Str("a".into()),
End(Paragraph),
End(TaskListItem { checked: false }),
Start(TaskListItem { checked: true }, Attributes::new()),
Start(Paragraph, Attributes::new()),
Str("b".into()),
End(Paragraph),
End(TaskListItem { checked: true }),
Start(TaskListItem { checked: true }, Attributes::new()),
Start(Paragraph, Attributes::new()),
Str("c".into()),
End(Paragraph),
End(TaskListItem { checked: true }),
End(List(List::Task)),
);
}
} }