Merge branch 'blocks' into render

This commit is contained in:
Tom Alexander 2020-05-09 22:49:49 -04:00
commit f9212d1fe4
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
26 changed files with 248 additions and 14 deletions

View File

@ -33,12 +33,12 @@ while read -r test_group; do
test_case_name=${test_case_file_name%.*}
set +e
(
if cmp -s <(xargs -a <(find "$test_group" -maxdepth 1 -mindepth 1 -type f -name 'main.dust'; find "$test_group" -maxdepth 1 -mindepth 1 -type f -name '*.dust' ! -name 'main.dust') node "$DIR/dustjs_shim.js" < "$test_case") <(xargs -a <(find "$test_group" -maxdepth 1 -mindepth 1 -type f -name 'main.dust'; find "$test_group" -maxdepth 1 -mindepth 1 -type f -name '*.dust' ! -name 'main.dust') "$DIR/../target/debug/duster-cli" < "$test_case"); then
if cmp -s <(xargs -a <(find "$test_group" -maxdepth 1 -mindepth 1 -type f -name 'main.dust'; find "$test_group" -maxdepth 1 -mindepth 1 -type f -name '*.dust' ! -name 'main.dust' | sort) node "$DIR/dustjs_shim.js" < "$test_case") <(xargs -a <(find "$test_group" -maxdepth 1 -mindepth 1 -type f -name 'main.dust'; find "$test_group" -maxdepth 1 -mindepth 1 -type f -name '*.dust' ! -name 'main.dust' | sort) "$DIR/../target/debug/duster-cli" < "$test_case"); then
echo "$test_group_name::$test_case_name PASSED"
else
echo "$test_group_name::$test_case_name FAILED"
if [ $show_diff -eq 1 ]; then
diff --label "dustjs-linked" --label "duster" <(xargs -a <(find "$test_group" -maxdepth 1 -mindepth 1 -type f -name 'main.dust'; find "$test_group" -maxdepth 1 -mindepth 1 -type f -name '*.dust' ! -name 'main.dust') node "$DIR/dustjs_shim.js" < "$test_case") <(xargs -a <(find "$test_group" -maxdepth 1 -mindepth 1 -type f -name 'main.dust'; find "$test_group" -maxdepth 1 -mindepth 1 -type f -name '*.dust' ! -name 'main.dust') "$DIR/../target/debug/duster-cli" < "$test_case")
diff --label "dustjs-linked" --label "duster" <(xargs -a <(find "$test_group" -maxdepth 1 -mindepth 1 -type f -name 'main.dust'; find "$test_group" -maxdepth 1 -mindepth 1 -type f -name '*.dust' ! -name 'main.dust' | sort) node "$DIR/dustjs_shim.js" < "$test_case" 2>/dev/null ) <(xargs -a <(find "$test_group" -maxdepth 1 -mindepth 1 -type f -name 'main.dust'; find "$test_group" -maxdepth 1 -mindepth 1 -type f -name '*.dust' ! -name 'main.dust' | sort) "$DIR/../target/debug/duster-cli" < "$test_case" 2>/dev/null )
fi
exit 1
fi

View File

@ -0,0 +1 @@
The block content is from {+inject/}

View File

@ -0,0 +1,4 @@
{"people": [
{"name": "Alice", "item": "cat"},
{"name": "Bob"}
]}

View File

@ -0,0 +1,2 @@
{<inject}Before Partial{/inject}
{>"base"/}

View File

@ -0,0 +1 @@
The block content is from {+inject/}

View File

@ -0,0 +1,14 @@
{
"people": [
{
"name": "Alice",
"item": "cat"
},
{
"name": "Bob"
}
],
"pet": {
"name": "fluffy"
}
}

View File

@ -0,0 +1,6 @@
{#pet}
{>"base"/}
{/pet}
{#people}
{<inject}by {name}{/inject}
{/people}

View File

@ -0,0 +1 @@
The block content is from {+inject/}{~n}

View File

@ -0,0 +1,2 @@
{<inject}beta{/inject}
{>"alpha"/}

View File

@ -0,0 +1,4 @@
{"people": [
{"name": "Alice", "item": "cat"},
{"name": "Bob"}
]}

View File

@ -0,0 +1,3 @@
{>"beta"/}
{>"alpha"/}
{<inject}main{/inject}

View File

@ -0,0 +1 @@
{<inject}alpha{/inject}

View File

@ -0,0 +1,2 @@
{<inject}beta{/inject}
{>"alpha"/}

View File

@ -0,0 +1,4 @@
{"people": [
{"name": "Alice", "item": "cat"},
{"name": "Bob"}
]}

View File

@ -0,0 +1,3 @@
The block content is from {+inject/}{~n}
{>"beta"/}
The block content is from {+inject/}{~n}

View File

@ -0,0 +1,9 @@
Blocks seem to be rendered with the last inline partial of the same name.
Blocks appear to be able to be registered in a loop, however, their context is defined by the context during the call to the block `{+}` as opposed to when their override is defined by an inline partial `{<}`.
Even if the surrounding dust would prevent a section from being rendered, the inline context is still registered, which makes me think inline contexts are parsed out in a single pass regardless of the context.
Inline partials in sub-templates do not seem to bubble up to blocks in the higher templates.
After the inverse register order test it seems that it takes the last inline partial with that name at that level, walking up the tree of partials. So each file will have exactly one value for each block name, consisting of the final inline partial with that name. Then when rendering the block, it will walk up the tree just like the context tree.

View File

@ -0,0 +1 @@
The block content is from {+inject/}

View File

@ -0,0 +1,4 @@
{"people": [
{"name": "Alice", "item": "cat"},
{"name": "Bob"}
]}

View File

@ -0,0 +1,3 @@
{<inject}Before Partial{/inject}
{>"base"/}
{<inject}After Partial{/inject}

View File

@ -0,0 +1 @@
The block content is from {+inject/}

View File

@ -0,0 +1,14 @@
{
"people": [
{
"name": "Alice",
"item": "cat"
},
{
"name": "Bob"
}
],
"pet": {
"name": "fluffy"
}
}

View File

@ -0,0 +1,10 @@
{#pet}
{>"base"/}
{/pet}
{#people}
{<inject}by {name}{/inject}
{/people}
{#cake}
OMG CAKE
{<inject}with cake{/inject}
{/cake}

View File

@ -100,8 +100,8 @@ pub struct Container<'a> {
#[derive(Clone, Debug, PartialEq)]
pub struct NamedBlock<'a> {
name: &'a str,
contents: Option<Body<'a>>,
pub name: &'a str,
pub contents: Option<Body<'a>>,
}
#[derive(Clone, Debug, PartialEq)]

View File

@ -0,0 +1,104 @@
use crate::parser::Body;
use crate::parser::DustTag;
use crate::parser::Template;
use crate::parser::TemplateElement;
use std::collections::HashMap;
pub struct InlinePartialTreeElement<'a> {
parent: Option<&'a InlinePartialTreeElement<'a>>,
blocks: HashMap<&'a str, &'a Option<Body<'a>>>,
}
impl<'a> InlinePartialTreeElement<'a> {
pub fn new(
parent: Option<&'a InlinePartialTreeElement<'a>>,
blocks: HashMap<&'a str, &'a Option<Body<'a>>>,
) -> InlinePartialTreeElement<'a> {
InlinePartialTreeElement {
parent: parent,
blocks: blocks,
}
}
pub fn get_block(&self, name: &str) -> Option<&'a Option<Body<'a>>> {
match self.blocks.get(name) {
None => match self.parent {
None => None,
Some(parent_tree_element) => parent_tree_element.get_block(name),
},
Some(interior) => Some(interior),
}
}
}
pub fn extract_inline_partials<'a>(
template: &'a Template<'a>,
) -> HashMap<&'a str, &'a Option<Body<'a>>> {
let mut blocks: HashMap<&'a str, &'a Option<Body<'a>>> = HashMap::new();
extract_inline_partials_from_body(&mut blocks, &template.contents);
blocks
}
fn extract_inline_partials_from_body<'a, 'b>(
blocks: &'b mut HashMap<&'a str, &'a Option<Body<'a>>>,
body: &'a Body<'a>,
) {
for elem in &body.elements {
match elem {
TemplateElement::TEIgnoredWhitespace(_) => (),
TemplateElement::TESpan(span) => (),
TemplateElement::TETag(dt) => {
extract_inline_partials_from_tag(blocks, dt);
}
}
}
}
fn extract_inline_partials_from_tag<'a, 'b>(
blocks: &'b mut HashMap<&'a str, &'a Option<Body<'a>>>,
tag: &'a DustTag,
) {
match tag {
DustTag::DTComment(..) => (),
DustTag::DTSpecial(..) => (),
DustTag::DTReference(..) => (),
DustTag::DTSection(container) => {
match &container.contents {
None => (),
Some(body) => extract_inline_partials_from_body(blocks, &body),
};
match &container.else_contents {
None => (),
Some(body) => extract_inline_partials_from_body(blocks, &body),
};
}
DustTag::DTExists(container) => {
match &container.contents {
None => (),
Some(body) => extract_inline_partials_from_body(blocks, &body),
};
match &container.else_contents {
None => (),
Some(body) => extract_inline_partials_from_body(blocks, &body),
};
}
DustTag::DTNotExists(container) => {
match &container.contents {
None => (),
Some(body) => extract_inline_partials_from_body(blocks, &body),
};
match &container.else_contents {
None => (),
Some(body) => extract_inline_partials_from_body(blocks, &body),
};
}
DustTag::DTPartial(..) => (),
DustTag::DTInlinePartial(named_block) => {
blocks.insert(&named_block.name, &named_block.contents);
}
DustTag::DTBlock(..) => (),
_ => (), // TODO: Implement the rest
}
}

View File

@ -2,6 +2,7 @@
mod context_element;
mod errors;
mod inline_partial_tree;
mod parameters_context;
mod renderer;
mod walking;

View File

@ -8,6 +8,8 @@ use crate::renderer::context_element::ContextElement;
use crate::renderer::errors::CompileError;
use crate::renderer::errors::RenderError;
use crate::renderer::errors::WalkError;
use crate::renderer::inline_partial_tree::extract_inline_partials;
use crate::renderer::inline_partial_tree::InlinePartialTreeElement;
use crate::renderer::parameters_context::ParametersContext;
use crate::renderer::walking::walk_path;
use std::collections::HashMap;
@ -52,6 +54,15 @@ impl<'a> DustRenderer<'a> {
&'a self,
name: &str,
breadcrumbs: &Vec<&'a dyn ContextElement>,
) -> Result<String, RenderError> {
self.render_template(name, breadcrumbs, None)
}
fn render_template(
&'a self,
name: &str,
breadcrumbs: &Vec<&'a dyn ContextElement>,
blocks: Option<&'a InlinePartialTreeElement<'a>>,
) -> Result<String, RenderError> {
let main_template = match self.templates.get(name) {
Some(tmpl) => tmpl,
@ -59,13 +70,16 @@ impl<'a> DustRenderer<'a> {
return Err(RenderError::TemplateNotFound(name.to_owned()));
}
};
self.render_body(&main_template.contents, breadcrumbs)
let extracted_inline_partials = extract_inline_partials(main_template);
let new_blocks = InlinePartialTreeElement::new(blocks, extracted_inline_partials);
self.render_body(&main_template.contents, breadcrumbs, &new_blocks)
}
fn render_body(
&'a self,
body: &'a Body,
breadcrumbs: &Vec<&'a dyn ContextElement>,
blocks: &'a InlinePartialTreeElement<'a>,
) -> Result<String, RenderError> {
let mut output = String::new();
for elem in &body.elements {
@ -73,7 +87,7 @@ impl<'a> DustRenderer<'a> {
TemplateElement::TEIgnoredWhitespace(_) => {}
TemplateElement::TESpan(span) => output.push_str(span.contents),
TemplateElement::TETag(dt) => {
output.push_str(&self.render_tag(dt, breadcrumbs)?);
output.push_str(&self.render_tag(dt, breadcrumbs, blocks)?);
}
}
}
@ -84,6 +98,7 @@ impl<'a> DustRenderer<'a> {
&'a self,
tag: &'a DustTag,
breadcrumbs: &Vec<&'a dyn ContextElement>,
blocks: &'a InlinePartialTreeElement<'a>,
) -> Result<String, RenderError> {
match tag {
DustTag::DTComment(_comment) => (),
@ -120,7 +135,7 @@ impl<'a> DustRenderer<'a> {
// original context before walking the path as
// the context for rendering the else block
return match &container.else_contents {
Some(body) => self.render_body(&body, breadcrumbs),
Some(body) => self.render_body(&body, breadcrumbs, blocks),
None => Ok("".to_owned()),
};
} else {
@ -132,7 +147,7 @@ impl<'a> DustRenderer<'a> {
.map(|array_elem| {
let mut new_breadcumbs = breadcrumbs.clone();
new_breadcumbs.push(array_elem);
self.render_body(&body, &new_breadcumbs)
self.render_body(&body, &new_breadcumbs, blocks)
})
.collect();
let rendered_slice: &[String] = &rendered_results?;
@ -146,13 +161,13 @@ impl<'a> DustRenderer<'a> {
let loop_elements: Vec<&dyn ContextElement> = self.get_loop_elements(val);
if loop_elements.is_empty() {
return match &container.else_contents {
Some(body) => self.render_body(&body, breadcrumbs),
Some(body) => self.render_body(&body, breadcrumbs, blocks),
None => Ok("".to_owned()),
};
} else {
return match &container.contents {
None => Ok("".to_owned()),
Some(body) => self.render_body(&body, breadcrumbs),
Some(body) => self.render_body(&body, breadcrumbs, blocks),
};
}
}
@ -161,28 +176,52 @@ impl<'a> DustRenderer<'a> {
let loop_elements: Vec<&dyn ContextElement> = self.get_loop_elements(val);
if !loop_elements.is_empty() {
return match &container.else_contents {
Some(body) => self.render_body(&body, breadcrumbs),
Some(body) => self.render_body(&body, breadcrumbs, blocks),
None => Ok("".to_owned()),
};
} else {
return match &container.contents {
None => Ok("".to_owned()),
Some(body) => self.render_body(&body, breadcrumbs),
Some(body) => self.render_body(&body, breadcrumbs, blocks),
};
}
}
DustTag::DTPartial(partial) => {
if partial.params.is_empty() {
let rendered_content = self.render(&partial.name, breadcrumbs)?;
let rendered_content =
self.render_template(&partial.name, breadcrumbs, Some(blocks))?;
return Ok(rendered_content);
} else {
let injected_context = ParametersContext::new(breadcrumbs, &partial.params);
let mut new_breadcrumbs = breadcrumbs.clone();
new_breadcrumbs.insert(new_breadcrumbs.len() - 1, &injected_context);
let rendered_content = self.render(&partial.name, &new_breadcrumbs)?;
let rendered_content =
self.render_template(&partial.name, &new_breadcrumbs, Some(blocks))?;
return Ok(rendered_content);
}
}
DustTag::DTInlinePartial(named_block) => {
// Inline partials are blank during rendering (they get injected into blocks)
return Ok("".to_owned());
}
DustTag::DTBlock(named_block) => {
match blocks.get_block(named_block.name) {
None => match &named_block.contents {
None => return Ok("".to_owned()),
Some(body) => {
let rendered_content = self.render_body(body, breadcrumbs, blocks)?;
return Ok(rendered_content);
}
},
Some(interior) => match interior {
None => return Ok("".to_owned()),
Some(body) => {
let rendered_content = self.render_body(body, breadcrumbs, blocks)?;
return Ok(rendered_content);
}
},
};
}
_ => (), // TODO: Implement the rest
}
Ok("".to_owned())