From 2f616c41b7f5aadcbc377d4f286d33934071d608 Mon Sep 17 00:00:00 2001 From: Noah Hellman Date: Sat, 21 Jan 2023 21:21:43 +0100 Subject: [PATCH] parser: parse lists and list items --- src/html.rs | 4 +- src/lib.rs | 190 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 186 insertions(+), 8 deletions(-) diff --git a/src/html.rs b/src/html.rs index f4a1d7c..87738fd 100644 --- a/src/html.rs +++ b/src/html.rs @@ -101,7 +101,7 @@ impl<'s, I: Iterator>, W: std::fmt::Write> Writer<'s, I, W> { Container::Blockquote => self.out.write_str(" todo!(), Container::ListItem => self.out.write_str(" self.out.write_str(" todo!(), Container::DescriptionDetails => self.out.write_str(" { assert!(self.footnote_number.is_none()); @@ -225,7 +225,7 @@ impl<'s, I: Iterator>, W: std::fmt::Write> Writer<'s, I, W> { Container::Blockquote => self.out.write_str("")?, Container::List(..) => todo!(), Container::ListItem => self.out.write_str("")?, - Container::DescriptionList => self.out.write_str("")?, + Container::TaskListItem { .. } => todo!(), Container::DescriptionDetails => self.out.write_str("")?, Container::Footnote { number, .. } => { if !self.footnote_backlink_written { diff --git a/src/lib.rs b/src/lib.rs index 0563564..53874a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,8 +36,8 @@ pub enum Container<'s> { List(List), /// An item of a list ListItem, - /// A description list element. - DescriptionList, + /// An item of a task list, either checked or unchecked. + TaskListItem { checked: bool }, /// Details describing a term within a description list. DescriptionDetails, /// A footnote definition. @@ -99,7 +99,7 @@ impl<'s> Container<'s> { Self::Blockquote | Self::List(..) | Self::ListItem - | Self::DescriptionList + | Self::TaskListItem { .. } | Self::DescriptionDetails | Self::Footnote { .. } | Self::Table @@ -135,7 +135,7 @@ impl<'s> Container<'s> { Self::Blockquote | Self::List(..) | Self::ListItem - | Self::DescriptionList + | Self::TaskListItem { .. } | Self::DescriptionDetails | Self::Footnote { .. } | 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> { src: &'s str, @@ -482,8 +499,30 @@ impl<'s> Parser<'s> { self.footnotes.insert(content, self.tree.take_branch()); continue; } - block::Container::List(ty) => todo!(), - block::Container::ListItem(ty) => todo!(), + block::Container::List(ty) => match ty { + 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 { @@ -545,6 +584,10 @@ mod test { use super::Container::*; use super::Event::*; use super::LinkType; + use super::List; + use super::List::*; + use super::OrderedListNumbering::*; + use super::OrderedListStyle::*; use super::SpanLinkType; macro_rules! test_parse { @@ -962,4 +1005,139 @@ mod test { 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)), + ); + } }