parser: parse lists and list items
This commit is contained in:
parent
6befcad52a
commit
2f616c41b7
2 changed files with 186 additions and 8 deletions
|
@ -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 {
|
||||||
|
|
190
src/lib.rs
190
src/lib.rs
|
@ -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)),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue