diff --git a/src/parser/parser.rs b/src/parser/parser.rs index a1ed027..56df718 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -34,6 +34,7 @@ pub enum DustTag<'a> { DTBlock(NamedBlock<'a>), DTInlinePartial(NamedBlock<'a>), DTPartial(Partial<'a>), + DTNewPartial(NewPartial<'a>), DTHelperEquals(ParameterizedBlock<'a>), DTHelperNotEquals(ParameterizedBlock<'a>), DTHelperGreaterThan(ParameterizedBlock<'a>), @@ -112,6 +113,12 @@ pub struct ParameterizedBlock<'a> { pub else_contents: Option>, } +#[derive(Clone, Debug, PartialEq)] +pub struct NewPartial<'a> { + pub name: Vec, + pub params: Vec>, +} + #[derive(Clone, Debug, PartialEq)] pub struct Partial<'a> { pub name: String, @@ -136,6 +143,17 @@ pub struct KVPair<'a> { pub value: RValue<'a>, } +#[derive(Clone, Debug, PartialEq)] +pub enum PartialNameElement { + PNSpan { + contents: String, + }, + PNReference { + path: Vec, + filters: Vec, + }, +} + #[derive(Clone, Debug, PartialEq)] pub struct Body<'a> { pub elements: Vec>, @@ -153,6 +171,28 @@ pub enum TemplateElement<'a> { TEIgnoredWhitespace(IgnoredWhitespace<'a>), } +impl From> for PartialNameElement { + fn from(original: TemplateElement) -> Self { + match original { + TemplateElement::TESpan(span) => PartialNameElement::PNSpan { + contents: span.contents.to_owned(), + }, + TemplateElement::TETag(DustTag::DTReference(reference)) => { + PartialNameElement::PNReference { + path: reference + .path + .keys + .into_iter() + .map(|s| s.to_owned()) + .collect(), + filters: reference.filters, + } + } + _ => panic!("Only spans and references can be used in partial names."), + } + } +} + /// Any element significant to dust that isn't plain text /// /// These elements are always wrapped in curly braces @@ -166,7 +206,7 @@ fn dust_tag(i: &str) -> IResult<&str, DustTag> { conditional("{^", DustTag::DTNotExists), named_block("{+", DustTag::DTBlock), named_block("{<", DustTag::DTInlinePartial), - partial("{>", DustTag::DTPartial), + new_partial("{>", DustTag::DTNewPartial), parameterized_block("{@", "gte", DustTag::DTHelperGreaterThanOrEquals), parameterized_block("{@", "lte", DustTag::DTHelperLessThanOrEquals), parameterized_block("{@", "eq", DustTag::DTHelperEquals), @@ -467,6 +507,91 @@ where } } +fn new_partial_with_plain_tag<'a>( + open_matcher: &'static str, +) -> impl Fn(&'a str) -> IResult<&'a str, NewPartial<'a>> { + move |i: &'a str| { + let (i, (name, params)) = delimited( + tag(open_matcher), + tuple(( + key, + opt(delimited( + space1, + separated_list1(space1, key_value_pair), + space0, + )), + )), + tag("/}"), + )(i)?; + + Ok(( + i, + NewPartial { + name: vec![PartialNameElement::PNSpan { + contents: name.to_owned(), + }], + params: params.unwrap_or(Vec::new()), + }, + )) + } +} + +fn new_partial_quoted_tag(i: &str) -> IResult<&str, Vec> { + // TODO: make all consuming + many1(alt(( + map(span, TemplateElement::TESpan), + map(map(reference, DustTag::DTReference), TemplateElement::TETag), + )))(i) +} + +fn new_partial_with_quoted_tag<'a>( + open_matcher: &'static str, +) -> impl Fn(&'a str) -> IResult<&'a str, NewPartial<'a>> { + move |i: &'a str| { + let (i, (name, params)) = delimited( + tag(open_matcher), + tuple(( + verify(quoted_string, |s: &String| { + new_partial_quoted_tag(s.as_str()).is_ok() + }), + opt(delimited( + space1, + separated_list1(space1, key_value_pair), + space0, + )), + )), + tag("/}"), + )(i)?; + + let (_remaining, template_name_elements) = new_partial_quoted_tag(name.as_str()) + .expect("A successful parse was verified earlier with a call to verify()"); + let partial_name_elements = template_name_elements + .into_iter() + .map(|e| e.into()) + .collect(); + + Ok(( + i, + NewPartial { + name: partial_name_elements, + params: params.unwrap_or(Vec::new()), + }, + )) + } +} + +fn new_partial<'a, F>( + open_matcher: &'static str, + constructor: F, +) -> impl Fn(&'a str) -> IResult<&'a str, DustTag<'a>> +where + F: Fn(NewPartial<'a>) -> DustTag<'a>, +{ + let plain = new_partial_with_plain_tag(open_matcher); + let quoted = new_partial_with_quoted_tag(open_matcher); + move |i: &'a str| map(alt((&plain, "ed)), &constructor)(i) +} + fn partial<'a, F>( open_matcher: &'static str, constructor: F, @@ -911,8 +1036,11 @@ mod tests { dust_tag(r#"{>foo bar=baz animal="cat"/}"#), Ok(( "", - DustTag::DTPartial(Partial { - name: "foo".to_owned(), + DustTag::DTNewPartial(NewPartial { + name: vec![PartialNameElement::PNSpan { + contents: "foo".to_owned() + },], + params: vec![ KVPair { key: "bar", @@ -934,8 +1062,44 @@ mod tests { dust_tag(r#"{>"template name * with * special \" characters" bar=baz animal="cat"/}"#), Ok(( "", - DustTag::DTPartial(Partial { - name: r#"template name * with * special " characters"#.to_owned(), + DustTag::DTNewPartial(NewPartial { + name: vec![PartialNameElement::PNSpan { + contents: 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::RVLiteral(OwnedLiteral::LString("cat".to_owned())) + } + ] + }) + )) + ); + } + + #[test] + fn test_dynamic_partial() { + assert_eq!( + dust_tag(r#"{>"dynamic{ref}template" bar=baz animal="cat"/}"#), + Ok(( + "", + DustTag::DTNewPartial(NewPartial { + name: vec![ + PartialNameElement::PNSpan { + contents: "dynamic".to_owned() + }, + PartialNameElement::PNReference { + path: vec!["ref".to_owned()], + filters: Vec::new() + }, + PartialNameElement::PNSpan { + contents: "template".to_owned() + } + ], params: vec![ KVPair { key: "bar", @@ -957,8 +1121,10 @@ mod tests { dust_tag(r#"{>foo a="foo" b=179/}"#), Ok(( "", - DustTag::DTPartial(Partial { - name: "foo".to_owned(), + DustTag::DTNewPartial(NewPartial { + name: vec![PartialNameElement::PNSpan { + contents: "foo".to_owned() + },], params: vec![ KVPair { key: "a", @@ -1125,9 +1291,11 @@ mod tests { keys: vec!["level3", "level4"] }, contents: Some(Body { - elements: vec![TemplateElement::TETag(DustTag::DTPartial( - Partial { - name: "partialtwo".to_owned(), + elements: vec![TemplateElement::TETag(DustTag::DTNewPartial( + NewPartial { + name: vec![PartialNameElement::PNSpan { + contents: "partialtwo".to_owned() + },], params: vec![ KVPair { key: "v1", diff --git a/src/renderer/inline_partial_tree.rs b/src/renderer/inline_partial_tree.rs index ff7220b..7d992a2 100644 --- a/src/renderer/inline_partial_tree.rs +++ b/src/renderer/inline_partial_tree.rs @@ -95,6 +95,7 @@ fn extract_inline_partials_from_tag<'a, 'b>( }; } DustTag::DTPartial(..) => (), + DustTag::DTNewPartial(..) => (), DustTag::DTInlinePartial(named_block) => { blocks.insert(&named_block.name, &named_block.contents); }