diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 968803b..b6e19c8 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -2,9 +2,7 @@ use nom::branch::alt; use nom::bytes::complete::escaped_transform; use nom::bytes::complete::is_a; use nom::bytes::complete::is_not; -use nom::bytes::complete::tag; -use nom::bytes::complete::take_until; -use nom::bytes::complete::take_until_parser_matches; +use nom::bytes::complete::{tag, take_until, take_until_parser_matches}; use nom::character::complete::line_ending; use nom::character::complete::multispace0; use nom::character::complete::one_of; @@ -53,6 +51,11 @@ pub enum Special { RightCurlyBrace, } +#[derive(Clone, Debug, PartialEq)] +pub enum IgnoredWhitespace<'a> { + StartOfLine(&'a str), +} + #[derive(Clone, Debug, PartialEq)] pub struct Comment<'a> { value: &'a str, @@ -85,7 +88,7 @@ pub enum Filter { #[derive(Clone, Debug, PartialEq)] pub struct Span<'a> { - pub contents: Vec<&'a str>, + pub contents: &'a str, } #[derive(Clone, Debug, PartialEq)] @@ -132,7 +135,7 @@ pub struct Body<'a> { pub elements: Vec>, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct Template<'a> { pub contents: Body<'a>, } @@ -141,6 +144,7 @@ pub struct Template<'a> { pub enum TemplateElement<'a> { TESpan(Span<'a>), TETag(DustTag<'a>), + TEIgnoredWhitespace(IgnoredWhitespace<'a>), } /// Any element significant to dust that isn't plain text @@ -475,36 +479,35 @@ fn filter(i: &str) -> IResult<&str, Filter> { )(i) } -/// Whitespace at the beginning of lines is ignored so inside a span -/// we are matching a newline character followed by as much contiguous -/// whitespace as possible, all of which will be thrown away by other -/// parsers. -fn span_end_of_line(i: &str) -> IResult<&str, (&str, &str)> { - tuple((line_ending, multispace0))(i) +/// Whitespace at the beginning of lines is ignored so we are matching +/// a newline character followed by as much contiguous whitespace as +/// possible, all of which will be thrown away by other parsers. +fn ignore_new_line_leading_whitespace(i: &str) -> IResult<&str, IgnoredWhitespace> { + map( + recognize(tuple((line_ending, multispace0))), + IgnoredWhitespace::StartOfLine, + )(i) } -fn span_line(i: &str) -> IResult<&str, &str> { - verify( +/// Any text that is not a Dust element or ignored whitespace +fn span(i: &str) -> IResult<&str, Span> { + let (remaining, line) = verify( take_until_parser_matches(alt(( tag("{"), line_ending, recognize(all_consuming(eof_whitespace)), ))), |s: &str| s.len() > 0, - )(i) -} - -/// Any text that is not a Dust element -fn span(i: &str) -> IResult<&str, Span> { - let (remaining, lines) = preceded( - opt(span_end_of_line), - many1(terminated(span_line, opt(span_end_of_line))), )(i)?; - Ok((remaining, Span { contents: lines })) + Ok((remaining, Span { contents: line })) } fn body(i: &str) -> IResult<&str, Body> { let (remaining, template_elements) = many1(alt(( + map( + ignore_new_line_leading_whitespace, + TemplateElement::TEIgnoredWhitespace, + ), map(span, TemplateElement::TESpan), map(dust_tag, TemplateElement::TETag), )))(i)?; @@ -613,24 +616,8 @@ mod tests { #[test] fn test_span_end_of_line() { assert_eq!( - super::span_end_of_line("\n \t \n\nfoo"), - Ok(("foo", ("\n", " \t \n\n"))) - ); - } - - #[test] - fn test_span_line() { - assert_eq!( - super::span_line("this is just some text"), - Ok(("", "this is just some text")) - ); - assert_eq!( - super::span_line("this is just some text {~lb}"), - Ok(("{~lb}", "this is just some text ")) - ); - assert_eq!( - super::span_line("{~lb}"), - Err(Error(("{~lb}", ErrorKind::Verify))) + super::ignore_new_line_leading_whitespace("\n \t \n\nfoo"), + Ok(("foo", IgnoredWhitespace::StartOfLine("\n \t \n\n"))) ); } @@ -641,7 +628,7 @@ mod tests { Ok(( "", Span { - contents: vec!["this is just some text"] + contents: "this is just some text" } )) ); @@ -650,7 +637,7 @@ mod tests { Ok(( "{~lb}", Span { - contents: vec!["this is just some text "] + contents: "this is just some text " } )) ); @@ -659,20 +646,44 @@ mod tests { Err(Error(("{~lb}", ErrorKind::Verify))) ); assert_eq!( - super::span("this is \t \n\n \t \n \t multiline text\n {foo}"), + super::body("this is \t \n\n \t \n \t multiline text\n {foo}"), Ok(( - "{foo}", - Span { - contents: vec!["this is \t ", "multiline text"] + "", + Body { + elements: vec![ + TemplateElement::TESpan(Span { + contents: "this is \t " + }), + TemplateElement::TEIgnoredWhitespace(IgnoredWhitespace::StartOfLine( + "\n\n \t \n \t " + )), + TemplateElement::TESpan(Span { + contents: "multiline text" + }), + TemplateElement::TEIgnoredWhitespace(IgnoredWhitespace::StartOfLine( + "\n " + )), + TemplateElement::TETag(DustTag::DTReference(Reference { + path: Path { keys: vec!["foo"] }, + filters: vec![] + })) + ] } )) ); assert_eq!( - super::span("\n leading whitespace"), + super::body("\n leading whitespace"), Ok(( "", - Span { - contents: vec!["leading whitespace"] + Body { + elements: vec![ + TemplateElement::TEIgnoredWhitespace(IgnoredWhitespace::StartOfLine( + "\n " + )), + TemplateElement::TESpan(Span { + contents: "leading whitespace" + }), + ] } )) ); @@ -732,9 +743,7 @@ mod tests { }, contents: Some(Body { elements: vec![ - TemplateElement::TESpan(Span { - contents: vec!["hello "] - }), + TemplateElement::TESpan(Span { contents: "hello " }), TemplateElement::TETag(DustTag::DTReference(Reference { path: Path { keys: vec!["name"] }, filters: Vec::new() @@ -759,9 +768,7 @@ mod tests { }, contents: Some(Body { elements: vec![ - TemplateElement::TESpan(Span { - contents: vec!["hello "] - }), + TemplateElement::TESpan(Span { contents: "hello " }), TemplateElement::TETag(DustTag::DTReference(Reference { path: Path { keys: vec!["name"] }, filters: Vec::new() @@ -771,7 +778,7 @@ mod tests { else_contents: Some(Body { elements: vec![ TemplateElement::TESpan(Span { - contents: vec!["goodbye "] + contents: "goodbye " }), TemplateElement::TETag(DustTag::DTReference(Reference { path: Path { keys: vec!["name"] }, @@ -808,9 +815,7 @@ mod tests { name: "foo", contents: Some(Body { elements: vec![ - TemplateElement::TESpan(Span { - contents: vec!["hello "] - }), + TemplateElement::TESpan(Span { contents: "hello " }), TemplateElement::TETag(DustTag::DTReference(Reference { path: Path { keys: vec!["name"] }, filters: Vec::new() @@ -846,9 +851,7 @@ mod tests { name: "foo", contents: Some(Body { elements: vec![ - TemplateElement::TESpan(Span { - contents: vec!["hello "] - }), + TemplateElement::TESpan(Span { contents: "hello " }), TemplateElement::TETag(DustTag::DTReference(Reference { path: Path { keys: vec!["name"] }, filters: Vec::new() @@ -935,15 +938,13 @@ mod tests { contents: Some(Body { elements: vec![ TemplateElement::TESpan(Span { - contents: vec!["Pet the "] + contents: "Pet the " }), TemplateElement::TETag(DustTag::DTReference(Reference { path: Path { keys: vec!["name"] }, filters: Vec::new() })), - TemplateElement::TESpan(Span { - contents: vec!["!"] - }) + TemplateElement::TESpan(Span { contents: "!" }) ] }), else_contents: None @@ -976,4 +977,79 @@ mod tests { )) ); } + + #[test] + fn test_full_document_new_line_equality() { + assert_eq!( + super::template( + "- simple -{~n} +{#names}{.}{/names} +{~n}- new lines -{~n} +{#names} +{.} +{/names}" + ), + Ok::<_, nom::Err<(&str, ErrorKind)>>(( + "", + Template { + contents: Body { + elements: vec![ + TemplateElement::TESpan(Span { + contents: "- simple -" + }), + TemplateElement::TETag(DustTag::DTSpecial(Special::NewLine)), + TemplateElement::TEIgnoredWhitespace(IgnoredWhitespace::StartOfLine( + "\n" + )), + TemplateElement::TETag(DustTag::DTSection(Container { + path: Path { + keys: vec!["names"] + }, + contents: Some(Body { + elements: vec![TemplateElement::TETag(DustTag::DTReference( + Reference { + path: Path { keys: vec![] }, + filters: vec![] + } + ))] + }), + else_contents: None, + })), + TemplateElement::TEIgnoredWhitespace(IgnoredWhitespace::StartOfLine( + "\n" + )), + TemplateElement::TETag(DustTag::DTSpecial(Special::NewLine)), + TemplateElement::TESpan(Span { + contents: "- new lines -" + }), + TemplateElement::TETag(DustTag::DTSpecial(Special::NewLine)), + TemplateElement::TEIgnoredWhitespace(IgnoredWhitespace::StartOfLine( + "\n" + )), + TemplateElement::TETag(DustTag::DTSection(Container { + path: Path { + keys: vec!["names"] + }, + contents: Some(Body { + elements: vec![ + TemplateElement::TEIgnoredWhitespace( + IgnoredWhitespace::StartOfLine("\n") + ), + TemplateElement::TETag(DustTag::DTReference(Reference { + path: Path { keys: vec![] }, + filters: vec![] + })), + TemplateElement::TEIgnoredWhitespace( + IgnoredWhitespace::StartOfLine("\n") + ) + ] + }), + else_contents: None, + })), + ] + } + } + )) + ); + } } diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index 601e391..520516b 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -85,10 +85,8 @@ impl<'a> DustRenderer<'a> { let mut output = String::new(); for elem in &body.elements { match elem { - TemplateElement::TESpan(span) => span - .contents - .iter() - .for_each(|line: &&str| output.push_str(line)), + TemplateElement::TEIgnoredWhitespace(_) => {} + TemplateElement::TESpan(span) => output.push_str(span.contents), TemplateElement::TETag(dt) => { output.push_str(&self.render_tag(dt, context)?); }