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())
+ }
+ ]
+ })
+ ))
+ );
+ }
}