From 0ca17e0885acadbf6671c278b3536ef5fdbe5c5e Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 25 May 2020 20:23:10 -0400 Subject: [PATCH 01/11] Add tests that prove that the priority between explicit contexts and parameters varies across sections and partials. --- .../explicit_context_setting/README.md | 8 ++++- .../explicit_context_setting/input1.json | 8 +++++ .../explicit_context_setting/main.dust | 30 +++++++++++++++++++ .../explicit_context_setting/priority.dust | 1 + 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 js/test_cases/explicit_context_setting/priority.dust diff --git a/js/test_cases/explicit_context_setting/README.md b/js/test_cases/explicit_context_setting/README.md index ffb7a36..33ab331 100644 --- a/js/test_cases/explicit_context_setting/README.md +++ b/js/test_cases/explicit_context_setting/README.md @@ -1,3 +1,9 @@ +Priority +-------- +Partials: Explicit context takes priority over parameters + +Sections: New context takes priority, then parameters, then explicit + $idx and $len ------------- @@ -6,7 +12,7 @@ $idx and $len do not survive through an explicit context setting, which will wor You can use $idx and $len as your explicit context, but as scalar values I do not think there is a way to access them anyway. Exists and Not-Exists -===================== +--------------------- Looks like you can exlicitly set a context with exists and not-exists tags too. This works out well in the parser because I am using the same code for those blocks. diff --git a/js/test_cases/explicit_context_setting/input1.json b/js/test_cases/explicit_context_setting/input1.json index 48c86c4..7a4dc34 100644 --- a/js/test_cases/explicit_context_setting/input1.json +++ b/js/test_cases/explicit_context_setting/input1.json @@ -1,4 +1,9 @@ { + "another_idx": { + "$idx": 21, + "$len": 22, + "other": 23 + }, "block": { "message": { "contents": "Explicit contexts are evaluated in the context of the block." @@ -14,6 +19,9 @@ "explicit": { "pet": "cat" }, + "has_idx": { + "$idx": 14 + }, "inline_partial": { "message": { "contents": "Explicit contexts are evaluated in the context of the inline partial." diff --git a/js/test_cases/explicit_context_setting/main.dust b/js/test_cases/explicit_context_setting/main.dust index bc4e329..eb92a10 100644 --- a/js/test_cases/explicit_context_setting/main.dust +++ b/js/test_cases/explicit_context_setting/main.dust @@ -296,3 +296,33 @@ Explicit Evaluation Time Split Partial Context OVerride{~n} {>explicit_evaluation_time_split_override/} {/inline_partial} {/partial_context} + +{! What happens with sections with explicit context and parameters !} +Section set $idx as a parameter{~n} +==============================={~n} +{#explicit $idx="7"} + $idx is {$idx}{~n} + $len is {$len}{~n} +{/explicit} + +Section set $idx as a parameter and new context{~n} +==============================================={~n} +{#has_idx $idx="7"} + $idx is {$idx}{~n} + $len is {$len}{~n} +{/has_idx} + +Section set $idx as a parameter, new context, and explicit context{~n} +=================================================================={~n} +{#has_idx:another_idx $idx="7" $len="8"} + $idx is {$idx}{~n} + $len is {$len}{~n} + other is {other}{~n} +{/has_idx} + +Section vs Partial priority{~n} +==========================={~n} +{#some_global:explicit pet="snake"} + {pet}{~n} +{/some_global} +{>priority:explicit pet="snake"/} diff --git a/js/test_cases/explicit_context_setting/priority.dust b/js/test_cases/explicit_context_setting/priority.dust new file mode 100644 index 0000000..4e7a114 --- /dev/null +++ b/js/test_cases/explicit_context_setting/priority.dust @@ -0,0 +1 @@ +{pet}{~n} From 2527baeff4f46ae8fcd9505752d5920d889a9220 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 25 May 2020 20:40:58 -0400 Subject: [PATCH 02/11] Pull the calls for the DustTag constructor up out of parameterized_block. --- src/parser/parser.rs | 57 +++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 9e70e8c..32da594 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -224,12 +224,21 @@ fn dust_tag(i: &str) -> IResult<&str, DustTag> { named_block("{+", DustTag::DTBlock), named_block("{<", DustTag::DTInlinePartial), partial("{>", DustTag::DTPartial), - parameterized_block("{@", "gte", DustTag::DTHelperGreaterThanOrEquals), - parameterized_block("{@", "lte", DustTag::DTHelperLessThanOrEquals), - parameterized_block("{@", "eq", DustTag::DTHelperEquals), - parameterized_block("{@", "ne", DustTag::DTHelperNotEquals), - parameterized_block("{@", "gt", DustTag::DTHelperGreaterThan), - parameterized_block("{@", "lt", DustTag::DTHelperLessThan), + map( + parameterized_block("{@", "gte"), + DustTag::DTHelperGreaterThanOrEquals, + ), + map( + parameterized_block("{@", "lte"), + DustTag::DTHelperLessThanOrEquals, + ), + map(parameterized_block("{@", "eq"), DustTag::DTHelperEquals), + map(parameterized_block("{@", "ne"), DustTag::DTHelperNotEquals), + map( + parameterized_block("{@", "gt"), + DustTag::DTHelperGreaterThan, + ), + map(parameterized_block("{@", "lt"), DustTag::DTHelperLessThan), ))(i) } @@ -460,28 +469,20 @@ where } } -fn parameterized_block<'a, F>( +fn parameterized_block<'a>( open_matcher: &'static str, tag_name: &'static str, - constructor: F, -) -> impl FnMut(&'a str) -> IResult<&'a str, DustTag<'a>> -where - F: Copy + Fn(ParameterizedBlock<'a>) -> DustTag<'a>, -{ +) -> impl FnMut(&'a str) -> IResult<&'a str, ParameterizedBlock<'a>> { alt(( - parameterized_block_with_body(open_matcher, tag_name, constructor), - parameterized_self_closing_block(open_matcher, tag_name, constructor), + parameterized_block_with_body(open_matcher, tag_name), + parameterized_self_closing_block(open_matcher, tag_name), )) } -fn parameterized_block_with_body<'a, F>( +fn parameterized_block_with_body<'a>( open_matcher: &'static str, tag_name: &'static str, - constructor: F, -) -> impl Fn(&'a str) -> IResult<&'a str, DustTag<'a>> -where - F: Fn(ParameterizedBlock<'a>) -> DustTag<'a>, -{ +) -> impl Fn(&'a str) -> IResult<&'a str, ParameterizedBlock<'a>> { move |i: &'a str| { let (i, (name, maybe_explicit_context, params, inner, maybe_else, _closing_name)) = tuple(( @@ -502,25 +503,21 @@ where Ok(( i, - constructor(ParameterizedBlock { + ParameterizedBlock { name: name, explicit_context: maybe_explicit_context, params: params.unwrap_or(Vec::new()), contents: inner, else_contents: maybe_else.flatten(), - }), + }, )) } } -fn parameterized_self_closing_block<'a, F>( +fn parameterized_self_closing_block<'a>( open_matcher: &'static str, tag_name: &'static str, - constructor: F, -) -> impl Fn(&'a str) -> IResult<&'a str, DustTag<'a>> -where - F: Fn(ParameterizedBlock<'a>) -> DustTag<'a>, -{ +) -> impl Fn(&'a str) -> IResult<&'a str, ParameterizedBlock<'a>> { move |i: &'a str| { let (i, (name, maybe_explicit_context, params)) = delimited( tag(open_matcher), @@ -538,13 +535,13 @@ where Ok(( i, - constructor(ParameterizedBlock { + ParameterizedBlock { name: name, explicit_context: maybe_explicit_context, params: params.unwrap_or(Vec::new()), contents: None, else_contents: None, - }), + }, )) } } From dc929733134ffaa50f0d0e7ff72a5218b16aa53b Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 25 May 2020 21:17:58 -0400 Subject: [PATCH 03/11] Switch parameterized blocks to having a Path for a name and taking a parser for a name matcher in the goal of merging with the conditional blocks. --- src/parser/parser.rs | 113 +++++++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 42 deletions(-) diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 32da594..bdf6f8c 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -109,7 +109,7 @@ pub struct NamedBlock<'a> { #[derive(Clone, Debug, PartialEq)] pub struct ParameterizedBlock<'a> { - pub name: &'a str, + pub path: Path<'a>, pub explicit_context: Option>, pub params: Vec>, pub contents: Option>, @@ -225,20 +225,29 @@ fn dust_tag(i: &str) -> IResult<&str, DustTag> { named_block("{<", DustTag::DTInlinePartial), partial("{>", DustTag::DTPartial), map( - parameterized_block("{@", "gte"), + parameterized_block("{@", &tag_to_path("gte")), DustTag::DTHelperGreaterThanOrEquals, ), map( - parameterized_block("{@", "lte"), + parameterized_block("{@", &tag_to_path("lte")), DustTag::DTHelperLessThanOrEquals, ), - map(parameterized_block("{@", "eq"), DustTag::DTHelperEquals), - map(parameterized_block("{@", "ne"), DustTag::DTHelperNotEquals), map( - parameterized_block("{@", "gt"), + parameterized_block("{@", &tag_to_path("eq")), + DustTag::DTHelperEquals, + ), + map( + parameterized_block("{@", &tag_to_path("ne")), + DustTag::DTHelperNotEquals, + ), + map( + parameterized_block("{@", &tag_to_path("gt")), DustTag::DTHelperGreaterThan, ), - map(parameterized_block("{@", "lt"), DustTag::DTHelperLessThan), + map( + parameterized_block("{@", &tag_to_path("lt")), + DustTag::DTHelperLessThan, + ), ))(i) } @@ -289,6 +298,14 @@ fn path(i: &str) -> IResult<&str, Path> { ))(i) } +fn tag_to_path<'a>(text: &'static str) -> impl Fn(&'a str) -> IResult<&str, Path<'a>> { + move |i: &'a str| map(tag(text), |t| Path { keys: vec![t] })(i) +} + +fn key_to_path<'a>(i: &'a str) -> IResult<&str, Path<'a>> { + map(key, |k| Path { keys: vec![k] })(i) +} + /// Just digits, no signs or decimals fn postitive_integer_literal(i: &str) -> IResult<&str, u64> { map( @@ -469,42 +486,51 @@ where } } -fn parameterized_block<'a>( +fn parameterized_block<'a, F>( open_matcher: &'static str, - tag_name: &'static str, -) -> impl FnMut(&'a str) -> IResult<&'a str, ParameterizedBlock<'a>> { + name_matcher: F, +) -> impl FnMut(&'a str) -> IResult<&'a str, ParameterizedBlock<'a>> +where + F: Copy + Fn(&'a str) -> IResult<&'a str, Path<'a>>, +{ alt(( - parameterized_block_with_body(open_matcher, tag_name), - parameterized_self_closing_block(open_matcher, tag_name), + parameterized_block_with_body(open_matcher, name_matcher), + parameterized_self_closing_block(open_matcher, name_matcher), )) } -fn parameterized_block_with_body<'a>( +fn parameterized_block_with_body<'a, F>( open_matcher: &'static str, - tag_name: &'static str, -) -> impl Fn(&'a str) -> IResult<&'a str, ParameterizedBlock<'a>> { + name_matcher: F, +) -> impl Fn(&'a str) -> IResult<&'a str, ParameterizedBlock<'a>> +where + F: Copy + Fn(&'a str) -> IResult<&'a str, Path<'a>>, +{ move |i: &'a str| { - let (i, (name, maybe_explicit_context, params, inner, maybe_else, _closing_name)) = - tuple(( - preceded(tag(open_matcher), tag(tag_name)), - opt(preceded(tag(":"), path)), - terminated( - opt(delimited( - space1, - separated_list1(space1, key_value_pair), - space0, - )), - tag("}"), - ), - opt(body), - opt(preceded(tag("{:else}"), opt(body))), - delimited(tag("{/"), tag(tag_name), tag("}")), - ))(i)?; + let (i, (opening_name, maybe_explicit_context, params, inner, maybe_else, _closing_name)) = + verify( + tuple(( + preceded(tag(open_matcher), name_matcher), + opt(preceded(tag(":"), path)), + terminated( + opt(delimited( + space1, + separated_list1(space1, key_value_pair), + space0, + )), + tag("}"), + ), + opt(body), + opt(preceded(tag("{:else}"), opt(body))), + delimited(tag("{/"), name_matcher, tag("}")), + )), + |(open, _maybe_explicit, _params, _inn, _maybe_else, close)| open == close, + )(i)?; Ok(( i, ParameterizedBlock { - name: name, + path: opening_name, explicit_context: maybe_explicit_context, params: params.unwrap_or(Vec::new()), contents: inner, @@ -514,15 +540,18 @@ fn parameterized_block_with_body<'a>( } } -fn parameterized_self_closing_block<'a>( +fn parameterized_self_closing_block<'a, F>( open_matcher: &'static str, - tag_name: &'static str, -) -> impl Fn(&'a str) -> IResult<&'a str, ParameterizedBlock<'a>> { + name_matcher: F, +) -> impl Fn(&'a str) -> IResult<&'a str, ParameterizedBlock<'a>> +where + F: Copy + Fn(&'a str) -> IResult<&'a str, Path<'a>>, +{ move |i: &'a str| { - let (i, (name, maybe_explicit_context, params)) = delimited( + let (i, (opening_name, maybe_explicit_context, params)) = delimited( tag(open_matcher), tuple(( - tag(tag_name), + name_matcher, opt(preceded(tag(":"), path)), opt(delimited( space1, @@ -536,7 +565,7 @@ fn parameterized_self_closing_block<'a>( Ok(( i, ParameterizedBlock { - name: name, + path: opening_name, explicit_context: maybe_explicit_context, params: params.unwrap_or(Vec::new()), contents: None, @@ -1325,7 +1354,7 @@ mod tests { Ok(( "", DustTag::DTHelperEquals(ParameterizedBlock { - name: "eq", + path: Path { keys: vec!["eq"] }, explicit_context: None, params: vec![ KVPair { @@ -1362,7 +1391,7 @@ mod tests { Ok(( "", DustTag::DTHelperEquals(ParameterizedBlock { - name: "eq", + path: Path { keys: vec!["eq"] }, explicit_context: None, params: vec![ KVPair { @@ -1388,7 +1417,7 @@ mod tests { Ok(( "", DustTag::DTHelperEquals(ParameterizedBlock { - name: "eq", + path: Path { keys: vec!["eq"] }, explicit_context: Some(Path { keys: vec!["foo", "bar"] }), @@ -1427,7 +1456,7 @@ mod tests { Ok(( "", DustTag::DTHelperEquals(ParameterizedBlock { - name: "eq", + path: Path { keys: vec!["eq"] }, explicit_context: Some(Path { keys: vec!["foo", "bar"] }), From 108cffb7715d53ea105b04567d0a01554a5227aa Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 25 May 2020 21:26:16 -0400 Subject: [PATCH 04/11] Fully replaced conditional's Container type with ParameterizedBlock. --- src/parser/parser.rs | 215 +++++++++++++++---------------------------- 1 file changed, 74 insertions(+), 141 deletions(-) diff --git a/src/parser/parser.rs b/src/parser/parser.rs index bdf6f8c..9451372 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -29,9 +29,9 @@ pub enum DustTag<'a> { DTComment(Comment<'a>), DTLiteralStringBlock(&'a str), DTReference(Reference<'a>), - DTSection(Container<'a>), - DTExists(Container<'a>), - DTNotExists(Container<'a>), + DTSection(ParameterizedBlock<'a>), + DTExists(ParameterizedBlock<'a>), + DTNotExists(ParameterizedBlock<'a>), DTBlock(NamedBlock<'a>), DTInlinePartial(NamedBlock<'a>), DTPartial(Partial<'a>), @@ -92,14 +92,6 @@ pub struct Span<'a> { pub contents: &'a str, } -#[derive(Clone, Debug, PartialEq)] -pub struct Container<'a> { - pub path: Path<'a>, - pub explicit_context: Option>, - pub contents: Option>, - pub else_contents: Option>, -} - #[derive(Clone, Debug, PartialEq)] pub struct NamedBlock<'a> { pub name: &'a str, @@ -218,9 +210,9 @@ fn dust_tag(i: &str) -> IResult<&str, DustTag> { map(comment, DustTag::DTComment), map(literal_string_block, DustTag::DTLiteralStringBlock), map(reference, DustTag::DTReference), - conditional("{#", DustTag::DTSection), - conditional("{?", DustTag::DTExists), - conditional("{^", DustTag::DTNotExists), + map(parameterized_block("{#", path), DustTag::DTSection), + map(parameterized_block("{?", path), DustTag::DTExists), + map(parameterized_block("{^", path), DustTag::DTNotExists), named_block("{+", DustTag::DTBlock), named_block("{<", DustTag::DTInlinePartial), partial("{>", DustTag::DTPartial), @@ -350,76 +342,6 @@ fn reference(i: &str) -> IResult<&str, Reference> { )) } -fn conditional<'a, F>( - open_matcher: &'static str, - constructor: F, -) -> impl FnMut(&'a str) -> IResult<&'a str, DustTag<'a>> -where - F: Copy + Fn(Container<'a>) -> DustTag<'a>, -{ - alt(( - conditional_with_body(open_matcher, constructor), - self_closing_conditional(open_matcher, constructor), - )) -} - -fn conditional_with_body<'a, F>( - open_matcher: &'static str, - constructor: F, -) -> impl Fn(&'a str) -> IResult<&'a str, DustTag<'a>> -where - F: Fn(Container<'a>) -> DustTag<'a>, -{ - move |i: &'a str| { - let (i, (opening_name, maybe_explicit_context, inner, maybe_else, _closing_name)) = - verify( - tuple(( - preceded(tag(open_matcher), path), - terminated(opt(preceded(tag(":"), path)), tag("}")), - opt(body), - opt(preceded(tag("{:else}"), opt(body))), - delimited(tag("{/"), path, tag("}")), - )), - |(open, _maybe_explicit, _inn, _maybe_else, close)| open == close, - )(i)?; - - Ok(( - i, - constructor(Container { - path: opening_name, - explicit_context: maybe_explicit_context, - contents: inner, - else_contents: maybe_else.flatten(), - }), - )) - } -} - -fn self_closing_conditional<'a, F>( - open_matcher: &'static str, - constructor: F, -) -> impl Fn(&'a str) -> IResult<&'a str, DustTag<'a>> -where - F: Fn(Container<'a>) -> DustTag<'a>, -{ - move |i: &'a str| { - let (i, (opening_name, maybe_explicit_context)) = tuple(( - preceded(tag(open_matcher), path), - terminated(opt(preceded(tag(":"), path)), tag("/}")), - ))(i)?; - - Ok(( - i, - constructor(Container { - path: opening_name, - explicit_context: maybe_explicit_context, - contents: None, - else_contents: None, - }), - )) - } -} - fn named_block<'a, F>( open_matcher: &'static str, constructor: F, @@ -920,11 +842,12 @@ mod tests { super::dust_tag("{#foo.bar}{/foo.bar}"), Ok(( "", - DustTag::DTSection(Container { + DustTag::DTSection(ParameterizedBlock { path: Path { keys: vec!["foo", "bar"] }, explicit_context: None, + params: Vec::new(), contents: None, else_contents: None, }) @@ -938,11 +861,12 @@ mod tests { super::dust_tag("{#foo.bar/}"), Ok(( "", - DustTag::DTSection(Container { + DustTag::DTSection(ParameterizedBlock { path: Path { keys: vec!["foo", "bar"] }, explicit_context: None, + params: Vec::new(), contents: None, else_contents: None, }) @@ -956,11 +880,12 @@ mod tests { super::dust_tag("{#foo.bar}hello {name}{/foo.bar}"), Ok(( "", - DustTag::DTSection(Container { + DustTag::DTSection(ParameterizedBlock { path: Path { keys: vec!["foo", "bar"] }, explicit_context: None, + params: Vec::new(), contents: Some(Body { elements: vec![ TemplateElement::TESpan(Span { contents: "hello " }), @@ -982,11 +907,12 @@ mod tests { super::dust_tag("{#greeting}hello {name}{:else}goodbye {name}{/greeting}"), Ok(( "", - DustTag::DTSection(Container { + DustTag::DTSection(ParameterizedBlock { path: Path { keys: vec!["greeting"] }, explicit_context: None, + params: Vec::new(), contents: Some(Body { elements: vec![ TemplateElement::TESpan(Span { contents: "hello " }), @@ -1018,13 +944,14 @@ mod tests { super::dust_tag("{#foo.bar:baz.ipsum}{/foo.bar}"), Ok(( "", - DustTag::DTSection(Container { + DustTag::DTSection(ParameterizedBlock { path: Path { keys: vec!["foo", "bar"] }, explicit_context: Some(Path { keys: vec!["baz", "ipsum"] }), + params: Vec::new(), contents: None, else_contents: None, }) @@ -1038,11 +965,12 @@ mod tests { super::dust_tag("{#foo.bar:$idx/}"), Ok(( "", - DustTag::DTSection(Container { + DustTag::DTSection(ParameterizedBlock { path: Path { keys: vec!["foo", "bar"] }, explicit_context: Some(Path { keys: vec!["$idx"] }), + params: Vec::new(), contents: None, else_contents: None, }) @@ -1500,11 +1428,12 @@ mod tests { TemplateElement::TEIgnoredWhitespace(IgnoredWhitespace::StartOfLine( "\n" )), - TemplateElement::TETag(DustTag::DTSection(Container { + TemplateElement::TETag(DustTag::DTSection(ParameterizedBlock { path: Path { keys: vec!["names"] }, explicit_context: None, + params: Vec::new(), contents: Some(Body { elements: vec![TemplateElement::TETag(DustTag::DTReference( Reference { @@ -1526,11 +1455,12 @@ mod tests { TemplateElement::TEIgnoredWhitespace(IgnoredWhitespace::StartOfLine( "\n" )), - TemplateElement::TETag(DustTag::DTSection(Container { + TemplateElement::TETag(DustTag::DTSection(ParameterizedBlock { path: Path { keys: vec!["names"] }, explicit_context: None, + params: Vec::new(), contents: Some(Body { elements: vec![ TemplateElement::TEIgnoredWhitespace( @@ -1564,55 +1494,58 @@ mod tests { "", Template { contents: Body { - elements: vec![TemplateElement::TETag(DustTag::DTSection(Container { - path: Path { - keys: vec!["level3", "level4"] - }, - explicit_context: None, - contents: Some(Body { - elements: vec![TemplateElement::TETag(DustTag::DTPartial( - Partial { - name: vec![PartialNameElement::PNSpan { - contents: "partialtwo".to_owned() - },], - explicit_context: None, - params: vec![ - KVPair { - key: "v1", - value: RValue::RVLiteral(OwnedLiteral::LString( - "b".to_owned() - )) - }, - KVPair { - key: "v2", - value: RValue::RVLiteral(OwnedLiteral::LString( - "b".to_owned() - )) - }, - KVPair { - key: "v3", - value: RValue::RVLiteral(OwnedLiteral::LString( - "b".to_owned() - )) - }, - KVPair { - key: "v4", - value: RValue::RVLiteral(OwnedLiteral::LString( - "b".to_owned() - )) - }, - KVPair { - key: "v5", - value: RValue::RVLiteral(OwnedLiteral::LString( - "b".to_owned() - )) - } - ] - } - ))] - }), - else_contents: None - }))] + elements: vec![TemplateElement::TETag(DustTag::DTSection( + ParameterizedBlock { + path: Path { + keys: vec!["level3", "level4"] + }, + explicit_context: None, + params: Vec::new(), + contents: Some(Body { + elements: vec![TemplateElement::TETag(DustTag::DTPartial( + Partial { + name: vec![PartialNameElement::PNSpan { + contents: "partialtwo".to_owned() + },], + explicit_context: None, + params: vec![ + KVPair { + key: "v1", + value: RValue::RVLiteral( + OwnedLiteral::LString("b".to_owned()) + ) + }, + KVPair { + key: "v2", + value: RValue::RVLiteral( + OwnedLiteral::LString("b".to_owned()) + ) + }, + KVPair { + key: "v3", + value: RValue::RVLiteral( + OwnedLiteral::LString("b".to_owned()) + ) + }, + KVPair { + key: "v4", + value: RValue::RVLiteral( + OwnedLiteral::LString("b".to_owned()) + ) + }, + KVPair { + key: "v5", + value: RValue::RVLiteral( + OwnedLiteral::LString("b".to_owned()) + ) + } + ] + } + ))] + }), + else_contents: None + } + ))] } } )) From 79f52ecdeeb865a39d8c6754c13af0763e6f69c3 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 25 May 2020 22:50:10 -0400 Subject: [PATCH 05/11] Add test for priorities while looping and split the new_breadcrumbs function into two separate new_breadcrumbs functions because sections and partials have different orderings. --- .../explicit_context_setting/README.md | 2 + .../explicit_context_setting/main.dust | 31 ++++++++ src/renderer/renderer.rs | 74 +++++++++++++++++++ 3 files changed, 107 insertions(+) diff --git a/js/test_cases/explicit_context_setting/README.md b/js/test_cases/explicit_context_setting/README.md index 33ab331..99751c6 100644 --- a/js/test_cases/explicit_context_setting/README.md +++ b/js/test_cases/explicit_context_setting/README.md @@ -4,6 +4,8 @@ Partials: Explicit context takes priority over parameters Sections: New context takes priority, then parameters, then explicit +Sections with loops: Loop variables ($idx and $len) take priority over parameters and explicit context + $idx and $len ------------- diff --git a/js/test_cases/explicit_context_setting/main.dust b/js/test_cases/explicit_context_setting/main.dust index eb92a10..2997d29 100644 --- a/js/test_cases/explicit_context_setting/main.dust +++ b/js/test_cases/explicit_context_setting/main.dust @@ -326,3 +326,34 @@ Section vs Partial priority{~n} {pet}{~n} {/some_global} {>priority:explicit pet="snake"/} + +Section vs Partial priority Failed Walk{~n} +======================================={~n} +{#doesnotexist:explicit pet="snake"} + MAIN {pet}{~n} +{:else} + ELSE {pet}{~n} +{/doesnotexist} +{>priority:explicit pet="snake"/} + +Section Loop set $idx as a parameter{~n} +===================================={~n} +{#loop $idx="7"} + $idx is {$idx}{~n} + $len is {$len}{~n} +{/loop} + +Section Loop set $idx in explicit context{~n} +========================================={~n} +{#loop:has_idx} + $idx is {$idx}{~n} + $len is {$len}{~n} +{/loop} + +Section Loop set $idx in explicit context and parameter{~n} +======================================================={~n} +{#loop:has_idx $idx="7"} + $idx is {$idx}{~n} + $len is {$len}{~n} +{/loop} + diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index 3c32be7..d73330c 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -785,6 +785,80 @@ impl<'a> DustRenderer<'a> { new_context_element.map(|ctx| new_stack.push(ctx)); Some(new_stack) } + + fn new_breadcrumbs_section<'b>( + breadcrumbs: &'b Vec<&'b dyn ContextElement>, + index_context: Option<&'b dyn ContextElement>, + injected_context: Option<&'b dyn ContextElement>, + explicit_context: &Option>, + new_context_element: Option<&'b dyn ContextElement>, + ) -> Option> { + // If none of the additional contexts are present, return None + // to signal that the original breadcrumbs should be used + // rather than incurring a copy here. + match ( + index_context, + injected_context, + explicit_context, + new_context_element, + ) { + (None, None, None, None) => return None, + _ => (), + } + let mut new_stack = match explicit_context { + Some(_) => Vec::with_capacity(4), + None => breadcrumbs.clone(), + }; + explicit_context.as_ref().map(|path| { + walk_path(breadcrumbs, &path.keys).map(|val| { + if val.is_truthy() { + new_stack.push(val) + } + }); + }); + injected_context.map(|ctx| new_stack.push(ctx)); + new_context_element.map(|ctx| new_stack.push(ctx)); + index_context.map(|ctx| new_stack.push(ctx)); + Some(new_stack) + } + + fn new_breadcrumbs_partial<'b>( + breadcrumbs: &'b Vec<&'b dyn ContextElement>, + explicit_context_breadcrumbs: &'b Vec<&'b dyn ContextElement>, + injected_context: Option<&'b dyn ContextElement>, + explicit_context: &Option>, + ) -> Option> { + // If none of the additional contexts are present, return None + // to signal that the original breadcrumbs should be used + // rather than incurring a copy here. + match (injected_context, explicit_context) { + (None, None) => return None, + _ => (), + }; + let mut new_stack = match explicit_context { + Some(_) => Vec::with_capacity(3), + None => breadcrumbs.clone(), + }; + injected_context.map(|ctx| { + // Special case: when there is no explicit context, the + // injected context gets inserted 1 spot behind the + // current context. Otherwise, the injected context gets + // added after the current context but before the explicit + // context. + match explicit_context { + None => new_stack.insert(std::cmp::max(new_stack.len() - 1, 0), ctx), + _ => new_stack.push(ctx), + } + }); + explicit_context.as_ref().map(|path| { + walk_path(explicit_context_breadcrumbs, &path.keys).map(|val| { + if val.is_truthy() { + new_stack.push(val) + } + }); + }); + Some(new_stack) + } } struct BlockContext<'a> { From f04e84dc3188e4b34850d4797cc6e1e1303fc95a Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 25 May 2020 23:28:49 -0400 Subject: [PATCH 06/11] Attempted to move section over to the section-specific new breadcrumbs function. I think the problem is, the index context needs to be a higher priority than the new context element, but when referencing data with `{.}` the new context element needs to take priority. I could either combine entries in the context tree so that variables like $idx and $len live side-by-side with real context elements, or try to implement a way to skip over index context elements when walking up the tree. --- .../explicit_context_setting/main.dust | 16 +++++ src/renderer/renderer.rs | 65 ++++++++----------- 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/js/test_cases/explicit_context_setting/main.dust b/js/test_cases/explicit_context_setting/main.dust index 2997d29..7313042 100644 --- a/js/test_cases/explicit_context_setting/main.dust +++ b/js/test_cases/explicit_context_setting/main.dust @@ -327,6 +327,22 @@ Section vs Partial priority{~n} {/some_global} {>priority:explicit pet="snake"/} +Section vs Exists priority{~n} +=========================={~n} +{?some_global:explicit pet="snake"} + {pet}{~n} +{/some_global} +{>priority:explicit pet="snake"/} + +Section vs NotExists priority{~n} +=========================={~n} +{^some_global:explicit pet="snake"} + MAIN {pet}{~n} +{:else} + ELSE {pet}{~n} +{/some_global} +{>priority:explicit pet="snake"/} + Section vs Partial priority Failed Walk{~n} ======================================={~n} {#doesnotexist:explicit pet="snake"} diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index d73330c..0de9263 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -165,13 +165,14 @@ impl<'a> DustRenderer<'a> { } } DustTag::DTSection(container) => { + let injected_context = ParametersContext::new(breadcrumbs, &container.params); let val = walk_path(breadcrumbs, &container.path.keys); match val { Err(WalkError::CantWalk) => { - let new_breadcrumbs = Self::new_breadcrumbs( - breadcrumbs, + let new_breadcrumbs = Self::new_breadcrumbs_section( breadcrumbs, None, + Some(&injected_context), &container.explicit_context, None, ); @@ -194,10 +195,10 @@ impl<'a> DustRenderer<'a> { final_val.get_loop_elements(); if loop_elements.is_empty() { // Scalar value - let new_breadcrumbs = Self::new_breadcrumbs( - breadcrumbs, + let new_breadcrumbs = Self::new_breadcrumbs_section( breadcrumbs, None, + Some(&injected_context), &container.explicit_context, Some(final_val), ); @@ -214,15 +215,16 @@ impl<'a> DustRenderer<'a> { .into_iter() .enumerate() .map(|(i, array_elem)| { - let injected_context = + let index_context = IterationContext::new(i, total_length); - let new_breadcrumbs = Self::new_breadcrumbs( - breadcrumbs, - breadcrumbs, - Some(&injected_context), - &container.explicit_context, - Some(array_elem), - ); + let new_breadcrumbs = + Self::new_breadcrumbs_section( + breadcrumbs, + Some(&index_context), + Some(&injected_context), + &container.explicit_context, + Some(array_elem), + ); self.render_body( &body, new_breadcrumbs @@ -242,10 +244,10 @@ impl<'a> DustRenderer<'a> { // an empty array or null), Dust uses the // original context before walking the path as // the context for rendering the else block - let new_breadcrumbs = Self::new_breadcrumbs( - breadcrumbs, + let new_breadcrumbs = Self::new_breadcrumbs_section( breadcrumbs, None, + Some(&injected_context), &container.explicit_context, None, ); @@ -259,12 +261,11 @@ impl<'a> DustRenderer<'a> { } } DustTag::DTExists(container) => { - let new_breadcrumbs = Self::new_breadcrumbs( + let new_breadcrumbs = Self::new_breadcrumbs_partial( breadcrumbs, breadcrumbs, None, &container.explicit_context, - None, ); let val = walk_path(breadcrumbs, &container.path.keys); return if val.map(|v| v.is_truthy()).unwrap_or(false) { @@ -282,12 +283,11 @@ impl<'a> DustRenderer<'a> { }; } DustTag::DTNotExists(container) => { - let new_breadcrumbs = Self::new_breadcrumbs( + let new_breadcrumbs = Self::new_breadcrumbs_partial( breadcrumbs, breadcrumbs, None, &container.explicit_context, - None, ); let val = walk_path(breadcrumbs, &container.path.keys); return if !val.map(|v| v.is_truthy()).unwrap_or(false) { @@ -307,12 +307,11 @@ impl<'a> DustRenderer<'a> { DustTag::DTPartial(partial) => { let partial_name = self.render_partial_name(&partial.name, breadcrumbs, blocks)?; if partial.params.is_empty() { - let new_breadcrumbs = Self::new_breadcrumbs( + let new_breadcrumbs = Self::new_breadcrumbs_partial( breadcrumbs, breadcrumbs, None, &partial.explicit_context, - None, ); let rendered_content = self.render_template( &partial_name, @@ -322,12 +321,11 @@ impl<'a> DustRenderer<'a> { return Ok(rendered_content); } else { let injected_context = ParametersContext::new(breadcrumbs, &partial.params); - let new_breadcrumbs = Self::new_breadcrumbs( + let new_breadcrumbs = Self::new_breadcrumbs_partial( breadcrumbs, breadcrumbs, Some(&injected_context), &partial.explicit_context, - None, ); let rendered_content = self.render_template( &partial_name, @@ -342,12 +340,11 @@ impl<'a> DustRenderer<'a> { return Ok("".to_owned()); } DustTag::DTBlock(named_block) => { - let new_breadcrumbs = Self::new_breadcrumbs( + let new_breadcrumbs = Self::new_breadcrumbs_partial( breadcrumbs, blocks.breadcrumbs, None, &named_block.explicit_context, - None, ); return match blocks.blocks.get_block(named_block.name) { None => self.render_maybe_body( @@ -363,12 +360,11 @@ impl<'a> DustRenderer<'a> { }; } DustTag::DTHelperEquals(parameterized_block) => { - let new_breadcrumbs = Self::new_breadcrumbs( + let new_breadcrumbs = Self::new_breadcrumbs_partial( breadcrumbs, breadcrumbs, None, ¶meterized_block.explicit_context, - None, ); let param_map: HashMap<&str, &RValue<'a>> = Self::get_rval_map(¶meterized_block.params); @@ -414,12 +410,11 @@ impl<'a> DustRenderer<'a> { } } DustTag::DTHelperNotEquals(parameterized_block) => { - let new_breadcrumbs = Self::new_breadcrumbs( + let new_breadcrumbs = Self::new_breadcrumbs_partial( breadcrumbs, breadcrumbs, None, ¶meterized_block.explicit_context, - None, ); let param_map: HashMap<&str, &RValue<'a>> = Self::get_rval_map(¶meterized_block.params); @@ -464,12 +459,11 @@ impl<'a> DustRenderer<'a> { } } DustTag::DTHelperGreaterThan(parameterized_block) => { - let new_breadcrumbs = Self::new_breadcrumbs( + let new_breadcrumbs = Self::new_breadcrumbs_partial( breadcrumbs, breadcrumbs, None, ¶meterized_block.explicit_context, - None, ); let param_map: HashMap<&str, &RValue<'a>> = Self::get_rval_map(¶meterized_block.params); @@ -507,12 +501,11 @@ impl<'a> DustRenderer<'a> { } } DustTag::DTHelperGreaterThanOrEquals(parameterized_block) => { - let new_breadcrumbs = Self::new_breadcrumbs( + let new_breadcrumbs = Self::new_breadcrumbs_partial( breadcrumbs, breadcrumbs, None, ¶meterized_block.explicit_context, - None, ); let param_map: HashMap<&str, &RValue<'a>> = Self::get_rval_map(¶meterized_block.params); @@ -550,12 +543,11 @@ impl<'a> DustRenderer<'a> { } } DustTag::DTHelperLessThan(parameterized_block) => { - let new_breadcrumbs = Self::new_breadcrumbs( + let new_breadcrumbs = Self::new_breadcrumbs_partial( breadcrumbs, breadcrumbs, None, ¶meterized_block.explicit_context, - None, ); let param_map: HashMap<&str, &RValue<'a>> = Self::get_rval_map(¶meterized_block.params); @@ -593,12 +585,11 @@ impl<'a> DustRenderer<'a> { } } DustTag::DTHelperLessThanOrEquals(parameterized_block) => { - let new_breadcrumbs = Self::new_breadcrumbs( + let new_breadcrumbs = Self::new_breadcrumbs_partial( breadcrumbs, breadcrumbs, None, ¶meterized_block.explicit_context, - None, ); let param_map: HashMap<&str, &RValue<'a>> = Self::get_rval_map(¶meterized_block.params); @@ -744,7 +735,7 @@ impl<'a> DustRenderer<'a> { /// explicit_context /// new_context_element /// ``` - fn new_breadcrumbs<'b>( + fn new_breadcrumbs_deprecated<'b>( breadcrumbs: &'b Vec<&'b dyn ContextElement>, explicit_context_breadcrumbs: &'b Vec<&'b dyn ContextElement>, injected_context: Option<&'b dyn ContextElement>, From 3c9a369908860b0c9c8d62442eaf4ea495334f08 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 30 May 2020 11:50:55 -0400 Subject: [PATCH 07/11] Skipping over pseudo contexts has fixed most of the tests. --- src/renderer/context_element.rs | 10 ++++++++ src/renderer/iteration_context.rs | 4 +++ src/renderer/parameters_context.rs | 4 +++ src/renderer/walking.rs | 39 +++++++++++++++++++++--------- 4 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/renderer/context_element.rs b/src/renderer/context_element.rs index 992fa6b..ec2e5a0 100644 --- a/src/renderer/context_element.rs +++ b/src/renderer/context_element.rs @@ -21,6 +21,16 @@ pub trait Truthiness { pub trait Walkable { fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError>; + + /// If an element contains meta information and should not be + /// returned as the final result of a walk, this function should + /// return true. + /// + /// For example, the iteration context contains $idx and $len but + /// it should not be the result of a dot-reference like `{.}`. + fn is_pseudo_element(&self) -> bool { + false + } } pub trait Renderable { diff --git a/src/renderer/iteration_context.rs b/src/renderer/iteration_context.rs index acdd9a1..f6a3998 100644 --- a/src/renderer/iteration_context.rs +++ b/src/renderer/iteration_context.rs @@ -68,6 +68,10 @@ impl Walkable for IterationContext { _ => Err(WalkError::CantWalk), } } + + fn is_pseudo_element(&self) -> bool { + true + } } impl CompareContextElement for IterationContext { diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index 7a3e6b1..1d4dd8b 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -103,6 +103,10 @@ impl Walkable for ParametersContext { OwnedRValue::RVLiteral(literal) => Ok(literal), } } + + fn is_pseudo_element(&self) -> bool { + true + } } impl Clone for ParametersContext { diff --git a/src/renderer/walking.rs b/src/renderer/walking.rs index 49dff7f..58cd0b4 100644 --- a/src/renderer/walking.rs +++ b/src/renderer/walking.rs @@ -1,4 +1,5 @@ use crate::renderer::context_element::ContextElement; +use crate::renderer::context_element::Walkable; use crate::renderer::WalkError; use std::borrow::Borrow; @@ -34,6 +35,17 @@ where WalkResult::FullyWalked(output) } +pub fn get_first_non_pseudo_element<'a, B>(breadcrumbs: &'a Vec) -> Option<&B> +where + B: Borrow, +{ + breadcrumbs + .iter() + .rev() + .filter(|b| !(*b).borrow().is_pseudo_element()) + .next() +} + pub fn walk_path<'a, B, P>( breadcrumbs: &'a Vec, path: &Vec

, @@ -60,17 +72,22 @@ where .borrow() == "." { - return match walk_path_from_single_level( - breadcrumbs - .last() - .expect("Breadcrumbs should never be empty"), - &path[1..], - ) { - // If no walking was done at all or we partially walked - // then stop trying to find anything because '.' restricts - // us to the current scope - WalkResult::NoWalk | WalkResult::PartialWalk => Err(WalkError::CantWalk), - WalkResult::FullyWalked(new_context) => Ok(new_context), + let first_non_pseudo_element = get_first_non_pseudo_element(breadcrumbs); + // println!( + // "First non-pseudo element: {:?}", + // first_non_pseudo_element.map(|b| b.borrow()) + // ); + return match first_non_pseudo_element { + None => Err(WalkError::CantWalk), + Some(current_context) => { + match walk_path_from_single_level(current_context, &path[1..]) { + // If no walking was done at all or we partially walked + // then stop trying to find anything because '.' restricts + // us to the current scope + WalkResult::NoWalk | WalkResult::PartialWalk => Err(WalkError::CantWalk), + WalkResult::FullyWalked(new_context) => Ok(new_context), + } + } }; } for context in breadcrumbs.iter().rev() { From a0e2ba2b82470b0dacfb406ba87992be12e4c66d Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 30 May 2020 12:24:50 -0400 Subject: [PATCH 08/11] Had to update the 1-behind insertion for partial parameters to account for pseudo elements. --- src/renderer/renderer.rs | 15 ++++++++++++++- src/renderer/walking.rs | 4 ---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index 0de9263..88617db 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -17,6 +17,7 @@ use crate::renderer::inline_partial_tree::InlinePartialTreeElement; use crate::renderer::iteration_context::IterationContext; use crate::renderer::parameters_context::ParametersContext; use crate::renderer::walking::walk_path; +use std::borrow::Borrow; use std::collections::HashMap; #[derive(Clone, Debug)] @@ -813,6 +814,15 @@ impl<'a> DustRenderer<'a> { Some(new_stack) } + fn get_index_of_first_non_pseudo_element<'b, B>(breadcrumbs: &'b Vec) -> Option + where + B: Borrow, + { + breadcrumbs + .iter() + .rposition(|b| !(*b).borrow().is_pseudo_element()) + } + fn new_breadcrumbs_partial<'b>( breadcrumbs: &'b Vec<&'b dyn ContextElement>, explicit_context_breadcrumbs: &'b Vec<&'b dyn ContextElement>, @@ -837,7 +847,10 @@ impl<'a> DustRenderer<'a> { // added after the current context but before the explicit // context. match explicit_context { - None => new_stack.insert(std::cmp::max(new_stack.len() - 1, 0), ctx), + None => new_stack.insert( + Self::get_index_of_first_non_pseudo_element(&new_stack).unwrap_or(0), + ctx, + ), _ => new_stack.push(ctx), } }); diff --git a/src/renderer/walking.rs b/src/renderer/walking.rs index 58cd0b4..c0c2365 100644 --- a/src/renderer/walking.rs +++ b/src/renderer/walking.rs @@ -73,10 +73,6 @@ where == "." { let first_non_pseudo_element = get_first_non_pseudo_element(breadcrumbs); - // println!( - // "First non-pseudo element: {:?}", - // first_non_pseudo_element.map(|b| b.borrow()) - // ); return match first_non_pseudo_element { None => Err(WalkError::CantWalk), Some(current_context) => { From eb0eb8d4ca44fd53139a87af3b04cc8009c620bd Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 30 May 2020 13:47:13 -0400 Subject: [PATCH 09/11] Add priority tests for the other block types except helpers since I do not yet have a helper implemented that sets a value. --- .../explicit_context_setting/README.md | 2 ++ .../explicit_context_setting/main.dust | 35 ++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/js/test_cases/explicit_context_setting/README.md b/js/test_cases/explicit_context_setting/README.md index 99751c6..8c7c820 100644 --- a/js/test_cases/explicit_context_setting/README.md +++ b/js/test_cases/explicit_context_setting/README.md @@ -41,6 +41,8 @@ Explicitly setting a context on a block does work. The explicit context for blocks is evaluated in the context for that template file containing the block, not the inline partial (so, whatever the context is when we invoke the partial containing the block). +Parameters on blocks and inline partials appear to not be read but they do not error out. + References ---------- diff --git a/js/test_cases/explicit_context_setting/main.dust b/js/test_cases/explicit_context_setting/main.dust index 7313042..e9b92d0 100644 --- a/js/test_cases/explicit_context_setting/main.dust +++ b/js/test_cases/explicit_context_setting/main.dust @@ -327,7 +327,7 @@ Section vs Partial priority{~n} {/some_global} {>priority:explicit pet="snake"/} -Section vs Exists priority{~n} +Exists vs Partial priority{~n} =========================={~n} {?some_global:explicit pet="snake"} {pet}{~n} @@ -373,3 +373,36 @@ Section Loop set $idx in explicit context and parameter{~n} $len is {$len}{~n} {/loop} +{! Lets check the priority order for all the tag types now that we know that sections and partials have a different order !} +Section vs Partial priority{~n} +==========================={~n} +{#some_global:explicit pet="snake"} + {pet}{~n} +{/some_global} +{>priority:explicit pet="snake"/} + +Exists vs Section priority{~n} +=========================={~n} +{?some_global:explicit pet="snake"} + {pet}{~n} +{/some_global} +{>priority:explicit pet="snake"/} + +Not Exists vs Section priority{~n} +=============================={~n} +{^empty_array:explicit pet="snake"} + {pet}{~n} +{/empty_array} +{>priority:explicit pet="snake"/} + +{! TODO: Add helpers once we have a helper that sets a variable !} + +{! Do blocks or inline partials have parameters? !} +Do blocks or inline partials have parameters{~n} +============================================{~n} +{+some_block_override pet="snake" name="cuddlebunny"} + {pet}, {name}{~n} +{/some_block_override} +{ Date: Sat, 30 May 2020 14:39:31 -0400 Subject: [PATCH 10/11] Get rid of the concept of NamedBlock and replace it with a ParameterizedBlock special case that does not support an else block. --- src/parser/parser.rs | 188 ++++++++++++++-------------- src/renderer/inline_partial_tree.rs | 2 +- src/renderer/renderer.rs | 2 +- 3 files changed, 95 insertions(+), 97 deletions(-) diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 9451372..3e0696a 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -32,8 +32,8 @@ pub enum DustTag<'a> { DTSection(ParameterizedBlock<'a>), DTExists(ParameterizedBlock<'a>), DTNotExists(ParameterizedBlock<'a>), - DTBlock(NamedBlock<'a>), - DTInlinePartial(NamedBlock<'a>), + DTBlock(ParameterizedBlock<'a>), + DTInlinePartial(ParameterizedBlock<'a>), DTPartial(Partial<'a>), DTHelperEquals(ParameterizedBlock<'a>), DTHelperNotEquals(ParameterizedBlock<'a>), @@ -92,13 +92,6 @@ pub struct Span<'a> { pub contents: &'a str, } -#[derive(Clone, Debug, PartialEq)] -pub struct NamedBlock<'a> { - pub name: &'a str, - pub explicit_context: Option>, - pub contents: Option>, -} - #[derive(Clone, Debug, PartialEq)] pub struct ParameterizedBlock<'a> { pub path: Path<'a>, @@ -213,8 +206,14 @@ fn dust_tag(i: &str) -> IResult<&str, DustTag> { map(parameterized_block("{#", path), DustTag::DTSection), map(parameterized_block("{?", path), DustTag::DTExists), map(parameterized_block("{^", path), DustTag::DTNotExists), - named_block("{+", DustTag::DTBlock), - named_block("{<", DustTag::DTInlinePartial), + map( + parameterized_block_without_else("{+", key_to_path), + DustTag::DTBlock, + ), + map( + parameterized_block_without_else("{<", key_to_path), + DustTag::DTInlinePartial, + ), partial("{>", DustTag::DTPartial), map( parameterized_block("{@", &tag_to_path("gte")), @@ -342,72 +341,6 @@ fn reference(i: &str) -> IResult<&str, Reference> { )) } -fn named_block<'a, F>( - open_matcher: &'static str, - constructor: F, -) -> impl FnMut(&'a str) -> IResult<&'a str, DustTag<'a>> -where - F: Copy + Fn(NamedBlock<'a>) -> DustTag<'a>, -{ - alt(( - named_block_with_body(open_matcher, constructor), - self_closing_named_block(open_matcher, constructor), - )) -} - -fn named_block_with_body<'a, F>( - open_matcher: &'static str, - constructor: F, -) -> impl Fn(&'a str) -> IResult<&'a str, DustTag<'a>> -where - F: Fn(NamedBlock<'a>) -> DustTag<'a>, -{ - move |i: &'a str| { - let (i, (opening_name, maybe_explicit_context, inner, _closing_name)) = verify( - tuple(( - preceded(tag(open_matcher), key), - terminated(opt(preceded(tag(":"), path)), tag("}")), - opt(body), - delimited(tag("{/"), key, tag("}")), - )), - |(open, _maybe_explicit, _inn, close)| open == close, - )(i)?; - - Ok(( - i, - constructor(NamedBlock { - name: opening_name, - explicit_context: maybe_explicit_context, - contents: inner, - }), - )) - } -} - -fn self_closing_named_block<'a, F>( - open_matcher: &'static str, - constructor: F, -) -> impl Fn(&'a str) -> IResult<&'a str, DustTag<'a>> -where - F: Fn(NamedBlock<'a>) -> DustTag<'a>, -{ - move |i: &'a str| { - let (i, (opening_name, maybe_explicit_context)) = tuple(( - preceded(tag(open_matcher), key), - terminated(opt(preceded(tag(":"), path)), tag("/}")), - ))(i)?; - - Ok(( - i, - constructor(NamedBlock { - name: opening_name, - explicit_context: maybe_explicit_context, - contents: None, - }), - )) - } -} - fn parameterized_block<'a, F>( open_matcher: &'static str, name_matcher: F, @@ -421,6 +354,19 @@ where )) } +fn parameterized_block_without_else<'a, F>( + open_matcher: &'static str, + name_matcher: F, +) -> impl FnMut(&'a str) -> IResult<&'a str, ParameterizedBlock<'a>> +where + F: Copy + Fn(&'a str) -> IResult<&'a str, Path<'a>>, +{ + alt(( + parameterized_block_with_body_without_else(open_matcher, name_matcher), + parameterized_self_closing_block(open_matcher, name_matcher), + )) +} + fn parameterized_block_with_body<'a, F>( open_matcher: &'static str, name_matcher: F, @@ -462,6 +408,46 @@ where } } +fn parameterized_block_with_body_without_else<'a, F>( + open_matcher: &'static str, + name_matcher: F, +) -> impl Fn(&'a str) -> IResult<&'a str, ParameterizedBlock<'a>> +where + F: Copy + Fn(&'a str) -> IResult<&'a str, Path<'a>>, +{ + move |i: &'a str| { + let (i, (opening_name, maybe_explicit_context, params, inner, _closing_name)) = + verify( + tuple(( + preceded(tag(open_matcher), name_matcher), + opt(preceded(tag(":"), path)), + terminated( + opt(delimited( + space1, + separated_list1(space1, key_value_pair), + space0, + )), + tag("}"), + ), + opt(body), + delimited(tag("{/"), name_matcher, tag("}")), + )), + |(open, _maybe_explicit, _params, _inn, close)| open == close, + )(i)?; + + Ok(( + i, + ParameterizedBlock { + path: opening_name, + explicit_context: maybe_explicit_context, + params: params.unwrap_or(Vec::new()), + contents: inner, + else_contents: None, + }, + )) + } +} + fn parameterized_self_closing_block<'a, F>( open_matcher: &'static str, name_matcher: F, @@ -984,10 +970,12 @@ mod tests { super::dust_tag("{+foo/}"), Ok(( "", - DustTag::DTBlock(NamedBlock { - name: "foo", + DustTag::DTBlock(ParameterizedBlock { + path: Path { keys: vec!["foo"] }, explicit_context: None, - contents: None + params: Vec::new(), + contents: None, + else_contents: None }) )) ); @@ -999,9 +987,10 @@ mod tests { super::dust_tag("{+foo}hello {name}{/foo}"), Ok(( "", - DustTag::DTBlock(NamedBlock { - name: "foo", + DustTag::DTBlock(ParameterizedBlock { + path: Path { keys: vec!["foo"] }, explicit_context: None, + params: Vec::new(), contents: Some(Body { elements: vec![ TemplateElement::TESpan(Span { contents: "hello " }), @@ -1010,7 +999,8 @@ mod tests { filters: Vec::new() })) ] - }) + }), + else_contents: None }) )) ); @@ -1022,12 +1012,14 @@ mod tests { super::dust_tag("{+foo:bar.baz/}"), Ok(( "", - DustTag::DTBlock(NamedBlock { - name: "foo", + DustTag::DTBlock(ParameterizedBlock { + path: Path { keys: vec!["foo"] }, explicit_context: Some(Path { keys: vec!["bar", "baz"] }), - contents: None + params: Vec::new(), + contents: None, + else_contents: None }) )) ); @@ -1039,11 +1031,12 @@ mod tests { super::dust_tag("{+foo:bar.baz}hello {name}{/foo}"), Ok(( "", - DustTag::DTBlock(NamedBlock { - name: "foo", + DustTag::DTBlock(ParameterizedBlock { + path: Path { keys: vec!["foo"] }, explicit_context: Some(Path { keys: vec!["bar", "baz"] }), + params: Vec::new(), contents: Some(Body { elements: vec![ TemplateElement::TESpan(Span { contents: "hello " }), @@ -1052,7 +1045,8 @@ mod tests { filters: Vec::new() })) ] - }) + }), + else_contents: None }) )) ); @@ -1064,10 +1058,12 @@ mod tests { super::dust_tag("{( } DustTag::DTPartial(..) => (), DustTag::DTInlinePartial(named_block) => { - blocks.insert(&named_block.name, &named_block.contents); + blocks.insert(&named_block.path.keys[0], &named_block.contents); } DustTag::DTBlock(..) => (), DustTag::DTHelperEquals(parameterized_block) => { diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index 88617db..8898405 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -347,7 +347,7 @@ impl<'a> DustRenderer<'a> { None, &named_block.explicit_context, ); - return match blocks.blocks.get_block(named_block.name) { + return match blocks.blocks.get_block(named_block.path.keys[0]) { None => self.render_maybe_body( &named_block.contents, new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), From e7c05f597ffadc07b5c2d2ee0d621313a415b0ad Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 30 May 2020 14:42:34 -0400 Subject: [PATCH 11/11] Add a test proving exists does not support parameters. --- js/test_cases/explicit_context_setting/main.dust | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/js/test_cases/explicit_context_setting/main.dust b/js/test_cases/explicit_context_setting/main.dust index e9b92d0..65bdddb 100644 --- a/js/test_cases/explicit_context_setting/main.dust +++ b/js/test_cases/explicit_context_setting/main.dust @@ -406,3 +406,10 @@ Do blocks or inline partials have parameters{~n} {