diff --git a/js/run_compliance_suite.bash b/js/run_compliance_suite.bash index 7788521..4423242 100755 --- a/js/run_compliance_suite.bash +++ b/js/run_compliance_suite.bash @@ -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 diff --git a/js/test_cases/block_before/base.dust b/js/test_cases/block_before/base.dust new file mode 100644 index 0000000..bf568c4 --- /dev/null +++ b/js/test_cases/block_before/base.dust @@ -0,0 +1 @@ +The block content is from {+inject/} diff --git a/js/test_cases/block_before/input1.json b/js/test_cases/block_before/input1.json new file mode 100644 index 0000000..272dfa5 --- /dev/null +++ b/js/test_cases/block_before/input1.json @@ -0,0 +1,4 @@ +{"people": [ + {"name": "Alice", "item": "cat"}, + {"name": "Bob"} +]} diff --git a/js/test_cases/block_before/main.dust b/js/test_cases/block_before/main.dust new file mode 100644 index 0000000..2d9efd5 --- /dev/null +++ b/js/test_cases/block_before/main.dust @@ -0,0 +1,2 @@ +{"base"/} diff --git a/js/test_cases/block_context/base.dust b/js/test_cases/block_context/base.dust new file mode 100644 index 0000000..bf568c4 --- /dev/null +++ b/js/test_cases/block_context/base.dust @@ -0,0 +1 @@ +The block content is from {+inject/} diff --git a/js/test_cases/block_context/input1.json b/js/test_cases/block_context/input1.json new file mode 100644 index 0000000..09c9919 --- /dev/null +++ b/js/test_cases/block_context/input1.json @@ -0,0 +1,14 @@ +{ + "people": [ + { + "name": "Alice", + "item": "cat" + }, + { + "name": "Bob" + } + ], + "pet": { + "name": "fluffy" + } +} diff --git a/js/test_cases/block_context/main.dust b/js/test_cases/block_context/main.dust new file mode 100644 index 0000000..4a63112 --- /dev/null +++ b/js/test_cases/block_context/main.dust @@ -0,0 +1,6 @@ +{#pet} + {>"base"/} +{/pet} +{#people} + {"alpha"/} diff --git a/js/test_cases/block_inverse_register_order/input1.json b/js/test_cases/block_inverse_register_order/input1.json new file mode 100644 index 0000000..272dfa5 --- /dev/null +++ b/js/test_cases/block_inverse_register_order/input1.json @@ -0,0 +1,4 @@ +{"people": [ + {"name": "Alice", "item": "cat"}, + {"name": "Bob"} +]} diff --git a/js/test_cases/block_inverse_register_order/main.dust b/js/test_cases/block_inverse_register_order/main.dust new file mode 100644 index 0000000..e224313 --- /dev/null +++ b/js/test_cases/block_inverse_register_order/main.dust @@ -0,0 +1,3 @@ +{>"beta"/} +{>"alpha"/} +{"alpha"/} diff --git a/js/test_cases/block_register_order/input1.json b/js/test_cases/block_register_order/input1.json new file mode 100644 index 0000000..272dfa5 --- /dev/null +++ b/js/test_cases/block_register_order/input1.json @@ -0,0 +1,4 @@ +{"people": [ + {"name": "Alice", "item": "cat"}, + {"name": "Bob"} +]} diff --git a/js/test_cases/block_register_order/main.dust b/js/test_cases/block_register_order/main.dust new file mode 100644 index 0000000..03b48c7 --- /dev/null +++ b/js/test_cases/block_register_order/main.dust @@ -0,0 +1,3 @@ +The block content is from {+inject/}{~n} +{>"beta"/} +The block content is from {+inject/}{~n} diff --git a/js/test_cases/block_simple/README.md b/js/test_cases/block_simple/README.md new file mode 100644 index 0000000..4b48992 --- /dev/null +++ b/js/test_cases/block_simple/README.md @@ -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. diff --git a/js/test_cases/block_simple/base.dust b/js/test_cases/block_simple/base.dust new file mode 100644 index 0000000..bf568c4 --- /dev/null +++ b/js/test_cases/block_simple/base.dust @@ -0,0 +1 @@ +The block content is from {+inject/} diff --git a/js/test_cases/block_simple/input1.json b/js/test_cases/block_simple/input1.json new file mode 100644 index 0000000..272dfa5 --- /dev/null +++ b/js/test_cases/block_simple/input1.json @@ -0,0 +1,4 @@ +{"people": [ + {"name": "Alice", "item": "cat"}, + {"name": "Bob"} +]} diff --git a/js/test_cases/block_simple/main.dust b/js/test_cases/block_simple/main.dust new file mode 100644 index 0000000..d269fb1 --- /dev/null +++ b/js/test_cases/block_simple/main.dust @@ -0,0 +1,3 @@ +{"base"/} +{"base"/} +{/pet} +{#people} + { { #[derive(Clone, Debug, PartialEq)] pub struct NamedBlock<'a> { - name: &'a str, - contents: Option>, + pub name: &'a str, + pub contents: Option>, } #[derive(Clone, Debug, PartialEq)] diff --git a/src/renderer/inline_partial_tree.rs b/src/renderer/inline_partial_tree.rs new file mode 100644 index 0000000..c58a5bb --- /dev/null +++ b/src/renderer/inline_partial_tree.rs @@ -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>>, +} + +impl<'a> InlinePartialTreeElement<'a> { + pub fn new( + parent: Option<&'a InlinePartialTreeElement<'a>>, + blocks: HashMap<&'a str, &'a Option>>, + ) -> InlinePartialTreeElement<'a> { + InlinePartialTreeElement { + parent: parent, + blocks: blocks, + } + } + + pub fn get_block(&self, name: &str) -> Option<&'a Option>> { + 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>> { + let mut blocks: HashMap<&'a str, &'a Option>> = 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>, +) { + 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>>, + 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 + } +} diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 13d2ede..70f423b 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -2,6 +2,7 @@ mod context_element; mod errors; +mod inline_partial_tree; mod parameters_context; mod renderer; mod walking; diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index 5a4f18e..e6b7bd4 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -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 { + self.render_template(name, breadcrumbs, None) + } + + fn render_template( + &'a self, + name: &str, + breadcrumbs: &Vec<&'a dyn ContextElement>, + blocks: Option<&'a InlinePartialTreeElement<'a>>, ) -> Result { 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 { 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 { 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())