Merge branch 'explicit_context_priority' into render

This commit is contained in:
Tom Alexander 2020-05-30 14:43:23 -04:00
commit d07ac3dcc9
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
11 changed files with 538 additions and 336 deletions

View File

@ -1,3 +1,11 @@
Priority
--------
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
-------------
@ -6,7 +14,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.
@ -33,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
----------

View File

@ -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."

View File

@ -296,3 +296,120 @@ 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"/}
Exists vs Partial 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"}
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}
{! 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}
{<some_block_override pet="lizard" name="rover"}
{pet}, {name}{~n}
{/some_block_override}
Does exists have parameters{~n}
==========================={~n}
{?some_global pet="snake"}
{pet}{~n}
{/some_global}

View File

@ -0,0 +1 @@
{pet}{~n}

View File

@ -29,11 +29,11 @@ pub enum DustTag<'a> {
DTComment(Comment<'a>),
DTLiteralStringBlock(&'a str),
DTReference(Reference<'a>),
DTSection(Container<'a>),
DTExists(Container<'a>),
DTNotExists(Container<'a>),
DTBlock(NamedBlock<'a>),
DTInlinePartial(NamedBlock<'a>),
DTSection(ParameterizedBlock<'a>),
DTExists(ParameterizedBlock<'a>),
DTNotExists(ParameterizedBlock<'a>),
DTBlock(ParameterizedBlock<'a>),
DTInlinePartial(ParameterizedBlock<'a>),
DTPartial(Partial<'a>),
DTHelperEquals(ParameterizedBlock<'a>),
DTHelperNotEquals(ParameterizedBlock<'a>),
@ -92,24 +92,9 @@ pub struct Span<'a> {
pub contents: &'a str,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Container<'a> {
pub path: Path<'a>,
pub explicit_context: Option<Path<'a>>,
pub contents: Option<Body<'a>>,
pub else_contents: Option<Body<'a>>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct NamedBlock<'a> {
pub name: &'a str,
pub explicit_context: Option<Path<'a>>,
pub contents: Option<Body<'a>>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct ParameterizedBlock<'a> {
pub name: &'a str,
pub path: Path<'a>,
pub explicit_context: Option<Path<'a>>,
pub params: Vec<KVPair<'a>>,
pub contents: Option<Body<'a>>,
@ -218,18 +203,42 @@ 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),
named_block("{+", DustTag::DTBlock),
named_block("{<", DustTag::DTInlinePartial),
map(parameterized_block("{#", path), DustTag::DTSection),
map(parameterized_block("{?", path), DustTag::DTExists),
map(parameterized_block("{^", path), DustTag::DTNotExists),
map(
parameterized_block_without_else("{+", key_to_path),
DustTag::DTBlock,
),
map(
parameterized_block_without_else("{<", key_to_path),
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("{@", &tag_to_path("gte")),
DustTag::DTHelperGreaterThanOrEquals,
),
map(
parameterized_block("{@", &tag_to_path("lte")),
DustTag::DTHelperLessThanOrEquals,
),
map(
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("{@", &tag_to_path("lt")),
DustTag::DTHelperLessThan,
),
))(i)
}
@ -280,6 +289,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(
@ -324,208 +341,125 @@ 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,
) -> 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,
tag_name: &'static str,
constructor: F,
) -> impl FnMut(&'a str) -> IResult<&'a str, DustTag<'a>>
name_matcher: F,
) -> impl FnMut(&'a str) -> IResult<&'a str, ParameterizedBlock<'a>>
where
F: Copy + Fn(ParameterizedBlock<'a>) -> DustTag<'a>,
F: Copy + Fn(&'a str) -> IResult<&'a str, Path<'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, name_matcher),
parameterized_self_closing_block(open_matcher, name_matcher),
))
}
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,
tag_name: &'static str,
constructor: F,
) -> impl Fn(&'a str) -> IResult<&'a str, DustTag<'a>>
name_matcher: F,
) -> impl Fn(&'a str) -> IResult<&'a str, ParameterizedBlock<'a>>
where
F: Fn(ParameterizedBlock<'a>) -> DustTag<'a>,
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,
constructor(ParameterizedBlock {
name: name,
ParameterizedBlock {
path: opening_name,
explicit_context: maybe_explicit_context,
params: params.unwrap_or(Vec::new()),
contents: inner,
else_contents: maybe_else.flatten(),
}),
},
))
}
}
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,
tag_name: &'static str,
constructor: F,
) -> impl Fn(&'a str) -> IResult<&'a str, DustTag<'a>>
name_matcher: F,
) -> impl Fn(&'a str) -> IResult<&'a str, ParameterizedBlock<'a>>
where
F: Fn(ParameterizedBlock<'a>) -> DustTag<'a>,
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,
@ -538,13 +472,13 @@ where
Ok((
i,
constructor(ParameterizedBlock {
name: name,
ParameterizedBlock {
path: opening_name,
explicit_context: maybe_explicit_context,
params: params.unwrap_or(Vec::new()),
contents: None,
else_contents: None,
}),
},
))
}
}
@ -894,11 +828,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,
})
@ -912,11 +847,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,
})
@ -930,11 +866,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 " }),
@ -956,11 +893,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 " }),
@ -992,13 +930,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,
})
@ -1012,11 +951,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,
})
@ -1030,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
})
))
);
@ -1045,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 " }),
@ -1056,7 +999,8 @@ mod tests {
filters: Vec::new()
}))
]
})
}),
else_contents: None
})
))
);
@ -1068,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
})
))
);
@ -1085,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 " }),
@ -1098,7 +1045,8 @@ mod tests {
filters: Vec::new()
}))
]
})
}),
else_contents: None
})
))
);
@ -1110,10 +1058,12 @@ mod tests {
super::dust_tag("{<foo/}"),
Ok((
"",
DustTag::DTInlinePartial(NamedBlock {
name: "foo",
DustTag::DTInlinePartial(ParameterizedBlock {
path: Path { keys: vec!["foo"] },
explicit_context: None,
contents: None
params: Vec::new(),
contents: None,
else_contents: None
})
))
);
@ -1125,9 +1075,10 @@ mod tests {
super::dust_tag("{<foo}hello {name}{/foo}"),
Ok((
"",
DustTag::DTInlinePartial(NamedBlock {
name: "foo",
DustTag::DTInlinePartial(ParameterizedBlock {
path: Path { keys: vec!["foo"] },
explicit_context: None,
params: Vec::new(),
contents: Some(Body {
elements: vec![
TemplateElement::TESpan(Span { contents: "hello " }),
@ -1136,7 +1087,8 @@ mod tests {
filters: Vec::new()
}))
]
})
}),
else_contents: None
})
))
);
@ -1328,7 +1280,7 @@ mod tests {
Ok((
"",
DustTag::DTHelperEquals(ParameterizedBlock {
name: "eq",
path: Path { keys: vec!["eq"] },
explicit_context: None,
params: vec![
KVPair {
@ -1365,7 +1317,7 @@ mod tests {
Ok((
"",
DustTag::DTHelperEquals(ParameterizedBlock {
name: "eq",
path: Path { keys: vec!["eq"] },
explicit_context: None,
params: vec![
KVPair {
@ -1391,7 +1343,7 @@ mod tests {
Ok((
"",
DustTag::DTHelperEquals(ParameterizedBlock {
name: "eq",
path: Path { keys: vec!["eq"] },
explicit_context: Some(Path {
keys: vec!["foo", "bar"]
}),
@ -1430,7 +1382,7 @@ mod tests {
Ok((
"",
DustTag::DTHelperEquals(ParameterizedBlock {
name: "eq",
path: Path { keys: vec!["eq"] },
explicit_context: Some(Path {
keys: vec!["foo", "bar"]
}),
@ -1474,11 +1426,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 {
@ -1500,11 +1453,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(
@ -1538,55 +1492,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
}
))]
}
}
))

View File

@ -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 {

View File

@ -97,7 +97,7 @@ fn extract_inline_partials_from_tag<'a, 'b>(
}
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) => {

View File

@ -68,6 +68,10 @@ impl Walkable for IterationContext {
_ => Err(WalkError::CantWalk),
}
}
fn is_pseudo_element(&self) -> bool {
true
}
}
impl CompareContextElement for IterationContext {

View File

@ -103,6 +103,10 @@ impl Walkable for ParametersContext {
OwnedRValue::RVLiteral(literal) => Ok(literal),
}
}
fn is_pseudo_element(&self) -> bool {
true
}
}
impl Clone for ParametersContext {

View File

@ -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)]
@ -165,13 +166,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 +196,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 +216,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 +245,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 +262,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 +284,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 +308,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 +322,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,14 +341,13 @@ 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) {
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),
@ -363,12 +361,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,
&parameterized_block.explicit_context,
None,
);
let param_map: HashMap<&str, &RValue<'a>> =
Self::get_rval_map(&parameterized_block.params);
@ -414,12 +411,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,
&parameterized_block.explicit_context,
None,
);
let param_map: HashMap<&str, &RValue<'a>> =
Self::get_rval_map(&parameterized_block.params);
@ -464,12 +460,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,
&parameterized_block.explicit_context,
None,
);
let param_map: HashMap<&str, &RValue<'a>> =
Self::get_rval_map(&parameterized_block.params);
@ -507,12 +502,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,
&parameterized_block.explicit_context,
None,
);
let param_map: HashMap<&str, &RValue<'a>> =
Self::get_rval_map(&parameterized_block.params);
@ -550,12 +544,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,
&parameterized_block.explicit_context,
None,
);
let param_map: HashMap<&str, &RValue<'a>> =
Self::get_rval_map(&parameterized_block.params);
@ -593,12 +586,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,
&parameterized_block.explicit_context,
None,
);
let param_map: HashMap<&str, &RValue<'a>> =
Self::get_rval_map(&parameterized_block.params);
@ -744,7 +736,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>,
@ -785,6 +777,92 @@ 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<Path<'b>>,
new_context_element: Option<&'b dyn ContextElement>,
) -> Option<Vec<&'b dyn ContextElement>> {
// 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 get_index_of_first_non_pseudo_element<'b, B>(breadcrumbs: &'b Vec<B>) -> Option<usize>
where
B: Borrow<dyn ContextElement + 'a>,
{
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>,
injected_context: Option<&'b dyn ContextElement>,
explicit_context: &Option<Path<'b>>,
) -> Option<Vec<&'b dyn ContextElement>> {
// 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(
Self::get_index_of_first_non_pseudo_element(&new_stack).unwrap_or(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> {

View File

@ -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<B>) -> Option<&B>
where
B: Borrow<dyn ContextElement + 'a>,
{
breadcrumbs
.iter()
.rev()
.filter(|b| !(*b).borrow().is_pseudo_element())
.next()
}
pub fn walk_path<'a, B, P>(
breadcrumbs: &'a Vec<B>,
path: &Vec<P>,
@ -60,17 +72,18 @@ 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);
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() {