From f27965001d4ac7f52463e3e1b9ab30f817e2da65 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Wed, 12 Apr 2023 13:09:44 -0400 Subject: [PATCH] End plain lists when there are two blank lines. --- Makefile | 2 +- src/parser/element.rs | 6 +++++ src/parser/plain_list.rs | 55 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 6e63e0c..48749fd 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ clean: .PHONY: test test: -> cargo test +> cargo test --bin toy .PHONY: run run: diff --git a/src/parser/element.rs b/src/parser/element.rs index 9a2d189..4c6760c 100644 --- a/src/parser/element.rs +++ b/src/parser/element.rs @@ -38,6 +38,12 @@ impl<'s> Source<'s> for Paragraph<'s> { } } +impl<'s> Source<'s> for PlainList<'s> { + fn get_source(&'s self) -> &'s str { + self.source + } +} + #[tracing::instrument(ret, level = "debug")] pub fn element<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Element<'s>> { let non_paragraph_matcher = parser_with_context!(non_paragraph_element)(context); diff --git a/src/parser/plain_list.rs b/src/parser/plain_list.rs index cbdc295..0e881e7 100644 --- a/src/parser/plain_list.rs +++ b/src/parser/plain_list.rs @@ -11,6 +11,7 @@ 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::blank_line; use crate::parser::util::exit_matcher_parser; use crate::parser::util::get_consumed; use crate::parser::util::start_of_line; @@ -24,15 +25,20 @@ use nom::character::complete::space1; use nom::combinator::eof; use nom::combinator::recognize; use nom::combinator::verify; +use nom::multi::many1; use nom::multi::many_till; use nom::sequence::tuple; +#[tracing::instrument(ret, level = "debug")] pub fn plain_list<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, PlainList<'s>> { - // TODO: Are we handling 2 blank lines causing the end of all plain lists? - let (mut remaining, first_item) = plain_list_item(context, input)?; + let parser_context = + context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { + exit_matcher: ChainBehavior::AndParent(Some(&plain_list_end)), + })); + let (mut remaining, first_item) = plain_list_item(&parser_context, input)?; let first_item_indentation = first_item.indentation; - let plain_list_item_matcher = parser_with_context!(plain_list_item)(context); - let exit_matcher = parser_with_context!(exit_matcher_parser)(context); + let plain_list_item_matcher = parser_with_context!(plain_list_item)(&parser_context); + let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let mut children = Vec::new(); children.push(first_item); loop { @@ -125,6 +131,15 @@ fn counter<'s>(i: &'s str) -> Res<&'s str, &'s str> { alt((recognize(one_of("abcdefghijklmnopqrstuvwxyz")), digit1))(i) } +#[tracing::instrument(ret, level = "debug")] +fn plain_list_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { + let start_of_line_matcher = parser_with_context!(start_of_line)(context); + recognize(tuple(( + start_of_line_matcher, + verify(many1(blank_line), |lines: &Vec<&str>| lines.len() >= 2), + )))(input) +} + #[tracing::instrument(ret, level = "debug")] fn plain_list_item_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { let current_item_indent_level: &usize = @@ -138,7 +153,6 @@ fn plain_list_item_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res< pli.indentation <= *current_item_indent_level })), recognize(line_indented_lte_matcher), - eof, ))(input) } @@ -175,6 +189,7 @@ mod tests { use crate::parser::parser_context::ContextElement; use crate::parser::parser_context::ContextTree; use crate::parser::parser_with_context::parser_with_context; + use crate::parser::Source; use super::*; @@ -249,4 +264,34 @@ mod tests { let result = plain_list_matcher(input); assert!(result.is_ok()); } + + #[test] + fn two_blank_lines_ends_list() { + // Plain lists with an asterisk bullet must be indented or else they would be a headline + let input = r#"1. foo +2. bar + baz +3. lorem + + + ipsum +"#; + 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).expect("Should parse the plain list successfully."); + assert_eq!(remaining, " ipsum\n"); + assert_eq!( + result.get_source(), + r#"1. foo +2. bar + baz +3. lorem + + +"# + ); + } }