diff --git a/src/parser/greater_element.rs b/src/parser/greater_element.rs index 51fbf5da..59717d73 100644 --- a/src/parser/greater_element.rs +++ b/src/parser/greater_element.rs @@ -9,6 +9,7 @@ pub struct PlainList<'s> { #[derive(Debug)] pub struct PlainListItem<'s> { pub source: &'s str, + pub indentation: usize, pub bullet: &'s str, pub contents: Vec>, } diff --git a/src/parser/plain_list.rs b/src/parser/plain_list.rs index 805e3c59..3afc6288 100644 --- a/src/parser/plain_list.rs +++ b/src/parser/plain_list.rs @@ -8,12 +8,14 @@ use nom::combinator::not; use nom::combinator::recognize; use nom::combinator::verify; use nom::multi::many0; +use nom::multi::many_till; use nom::sequence::tuple; use crate::parser::element::element; use crate::parser::parser_context::ChainBehavior; use crate::parser::parser_context::ContextElement; use crate::parser::parser_context::ExitMatcherNode; +use crate::parser::util::exit_matcher_parser; use crate::parser::util::get_consumed; use crate::parser::util::start_of_line; @@ -28,7 +30,18 @@ use super::Context; #[allow(dead_code)] pub fn plain_list<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, PlainList<'s>> { - todo!() + let (remaining, first_item) = plain_list_item(context, input)?; + let plain_list_item_matcher = parser_with_context!(plain_list_item)(context); + let exit_matcher = parser_with_context!(exit_matcher_parser)(context); + let (remaining, (mut children, _exit_contents)) = many_till( + verify(plain_list_item_matcher, |pli| { + pli.indentation == first_item.indentation + }), + exit_matcher, + )(remaining)?; + let source = get_consumed(input, remaining); + children.insert(0, first_item); + Ok((remaining, PlainList { source, children })) } #[allow(dead_code)] @@ -36,7 +49,6 @@ pub fn plain_list_item<'r, 's>( context: Context<'r, 's>, input: &'s str, ) -> Res<&'s str, PlainListItem<'s>> { - not(|i| context.check_exit_matcher(i))(input)?; start_of_line(context, input)?; let (remaining, leading_whitespace) = space0(input)?; // It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09) @@ -56,6 +68,7 @@ pub fn plain_list_item<'r, 's>( remaining, PlainListItem { source, + indentation: indent_level, bullet: bull, contents, }, @@ -143,4 +156,28 @@ mod tests { assert_eq!(remaining, ""); assert_eq!(result.source, "1. foo"); } + + #[test] + fn plain_list_empty() { + let input = "1."; + let initial_context: ContextTree<'_, '_> = ContextTree::new(); + let document_context = + initial_context.with_additional_node(ContextElement::DocumentRoot(input)); + let plain_list_matcher = parser_with_context!(plain_list)(&document_context); + let (remaining, result) = plain_list_matcher(input).unwrap(); + assert_eq!(remaining, ""); + assert_eq!(result.source, "1."); + } + + #[test] + fn plain_list_simple() { + let input = "1. foo"; + let initial_context: ContextTree<'_, '_> = ContextTree::new(); + let document_context = + initial_context.with_additional_node(ContextElement::DocumentRoot(input)); + let plain_list_matcher = parser_with_context!(plain_list)(&document_context); + let (remaining, result) = plain_list_matcher(input).unwrap(); + assert_eq!(remaining, ""); + assert_eq!(result.source, "1. foo"); + } } diff --git a/src/parser/util.rs b/src/parser/util.rs index 8fe28ee3..b8a0e216 100644 --- a/src/parser/util.rs +++ b/src/parser/util.rs @@ -4,6 +4,7 @@ use nom::character::complete::none_of; use nom::character::complete::space0; use nom::combinator::eof; use nom::combinator::not; +use nom::combinator::peek; use nom::combinator::recognize; use nom::multi::many0; use nom::sequence::tuple; @@ -106,6 +107,14 @@ pub fn non_whitespace_character(input: &str) -> Res<&str, char> { none_of(" \t\r\n")(input) } +/// Check that we are at the start of a line +pub fn exit_matcher_parser<'r, 's>( + context: Context<'r, 's>, + input: &'s str, +) -> Res<&'s str, &'s str> { + peek(|i| context.check_exit_matcher(i))(input) +} + #[cfg(test)] mod tests { use super::*;