diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 49ffe64..e9e2683 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -1,11 +1,11 @@ use nom::branch::alt; -use nom::bytes::complete::escaped; 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::character::complete::one_of; +use nom::character::complete::space1; use nom::combinator::map; use nom::combinator::opt; use nom::combinator::recognize; @@ -15,8 +15,10 @@ use nom::combinator::verify; use nom::multi::many0; use nom::multi::many1; use nom::multi::separated_list; +use nom::multi::separated_nonempty_list; use nom::sequence::delimited; use nom::sequence::preceded; +use nom::sequence::separated_pair; use nom::sequence::tuple; use nom::IResult; @@ -30,6 +32,7 @@ enum DustTag<'a> { DTNotExists(Container<'a>), DTBlock(NamedBlock<'a>), DTInlinePartial(NamedBlock<'a>), + DTPartial(ParameterizedBlock<'a>), } #[derive(Clone, Debug, PartialEq)] @@ -86,6 +89,24 @@ struct NamedBlock<'a> { contents: Option>, } +#[derive(Clone, Debug, PartialEq)] +struct ParameterizedBlock<'a> { + name: String, + params: Vec>, +} + +#[derive(Clone, Debug, PartialEq)] +enum RValue<'a> { + RVPath(Path<'a>), + RVString(String), +} + +#[derive(Clone, Debug, PartialEq)] +struct KVPair<'a> { + key: &'a str, + value: RValue<'a>, +} + #[derive(Clone, Debug, PartialEq)] struct Body<'a> { elements: Vec>, @@ -115,6 +136,7 @@ fn dust_tag(i: &str) -> IResult<&str, DustTag> { conditional("{^", DustTag::DTNotExists), named_block("{+", DustTag::DTBlock), named_block("{<", DustTag::DTInlinePartial), + parameterized_self_closing_block("{>", DustTag::DTPartial), ))(i) } @@ -152,7 +174,25 @@ fn key(i: &str) -> IResult<&str, &str> { /// A series of keys separated by '.' to reference a variable in the context fn path(i: &str) -> IResult<&str, Path> { - map(separated_list(tag("."), key), |body| Path { keys: body })(i) + map(separated_nonempty_list(tag("."), key), |body| Path { + keys: body, + })(i) +} + +/// Either a literal or a path to a value +fn rvalue(i: &str) -> IResult<&str, RValue> { + alt(( + map(path, RValue::RVPath), + map(quoted_string, RValue::RVString), + ))(i) +} + +/// Parameters for a partial +fn key_value_pair(i: &str) -> IResult<&str, KVPair> { + map(separated_pair(key, tag("="), rvalue), |(k, v)| KVPair { + key: k, + value: v, + })(i) } /// Display a value from the context @@ -290,6 +330,33 @@ where } } +fn parameterized_self_closing_block<'a, F>( + open_matcher: &'static str, + constructor: F, +) -> impl Fn(&'a str) -> IResult<&'a str, DustTag<'a>> +where + F: Fn(ParameterizedBlock<'a>) -> DustTag<'a>, +{ + move |i: &'a str| { + let (i, (name, params)) = delimited( + tag(open_matcher), + tuple(( + alt((map(key, String::from), quoted_string)), + opt(preceded(space1, separated_list(space1, key_value_pair))), + )), + tag("/}"), + )(i)?; + + Ok(( + i, + constructor(ParameterizedBlock { + name: name, + params: params.unwrap_or(Vec::new()), + }), + )) + } +} + fn filter(i: &str) -> IResult<&str, Filter> { preceded( tag("|"), @@ -620,4 +687,50 @@ mod tests { Ok(("", r#"foo"bar\baz"#.to_owned())) ); } + + #[test] + fn test_unquoted_partial() { + assert_eq!( + dust_tag(r#"{>foo bar=baz animal="cat"/}"#), + Ok(( + "", + DustTag::DTPartial(ParameterizedBlock { + name: "foo".to_owned(), + params: vec![ + KVPair { + key: "bar", + value: RValue::RVPath(Path { keys: vec!["baz"] }) + }, + KVPair { + key: "animal", + value: RValue::RVString("cat".to_owned()) + } + ] + }) + )) + ); + } + + #[test] + fn test_quoted_partial() { + assert_eq!( + dust_tag(r#"{>"template name * with * special \" characters" bar=baz animal="cat"/}"#), + Ok(( + "", + DustTag::DTPartial(ParameterizedBlock { + name: r#"template name * with * special " characters"#.to_owned(), + params: vec![ + KVPair { + key: "bar", + value: RValue::RVPath(Path { keys: vec!["baz"] }) + }, + KVPair { + key: "animal", + value: RValue::RVString("cat".to_owned()) + } + ] + }) + )) + ); + } }