diff --git a/js/test_cases/partial_jump_around_one/input1.json b/js/test_cases/partial_jump_around_one/input1.json new file mode 100644 index 0000000..4170048 --- /dev/null +++ b/js/test_cases/partial_jump_around_one/input1.json @@ -0,0 +1,28 @@ +{ + "v0": "0", + "level3": { + "v3": "3", + "v4": "3", + "v5": "3", + "level4": { + "v4": "4", + "v5": "4", + "level5": { + "v5": "5" + } + } + }, + "level1": { + "v1": "1", + "v2": "1", + "v3": "1", + "v4": "1", + "v5": "1", + "level2": { + "v2": "2", + "v3": "2", + "v4": "2", + "v5": "2" + } + } +} diff --git a/js/test_cases/partial_jump_around_one/main.dust b/js/test_cases/partial_jump_around_one/main.dust new file mode 100644 index 0000000..004474e --- /dev/null +++ b/js/test_cases/partial_jump_around_one/main.dust @@ -0,0 +1,3 @@ +{#level1.level2} +{>partialone v0="a" v1="a" v2="a" v3="a" v4="a" v5="a"/} +{/level1.level2} diff --git a/js/test_cases/partial_jump_around_one/partialone.dust b/js/test_cases/partial_jump_around_one/partialone.dust new file mode 100644 index 0000000..a53b2eb --- /dev/null +++ b/js/test_cases/partial_jump_around_one/partialone.dust @@ -0,0 +1,3 @@ +{#level3.level4} + {>partialtwo/} +{/level3.level4} diff --git a/js/test_cases/partial_jump_around_one/partialtwo.dust b/js/test_cases/partial_jump_around_one/partialtwo.dust new file mode 100644 index 0000000..bb751b3 --- /dev/null +++ b/js/test_cases/partial_jump_around_one/partialtwo.dust @@ -0,0 +1,8 @@ +{#level5} + {v0}{~n} + {v1}{~n} + {v2}{~n} + {v3}{~n} + {v4}{~n} + {v5}{~n} +{/level5} diff --git a/js/test_cases/partial_jump_around_two/input1.json b/js/test_cases/partial_jump_around_two/input1.json new file mode 100644 index 0000000..5b9dd75 --- /dev/null +++ b/js/test_cases/partial_jump_around_two/input1.json @@ -0,0 +1,27 @@ +{ + "level3": { + "v3": "3", + "v4": "3", + "v5": "3", + "level4": { + "v4": "4", + "v5": "4", + "level5": { + "v5": "5" + } + } + }, + "level1": { + "v1": "1", + "v2": "1", + "v3": "1", + "v4": "1", + "v5": "1", + "level2": { + "v2": "2", + "v3": "2", + "v4": "2", + "v5": "2" + } + } +} diff --git a/js/test_cases/partial_jump_around_two/main.dust b/js/test_cases/partial_jump_around_two/main.dust new file mode 100644 index 0000000..e6ad4e4 --- /dev/null +++ b/js/test_cases/partial_jump_around_two/main.dust @@ -0,0 +1,3 @@ +{#level1.level2} +{>partialone v1="a" v2="a" v3="a" v4="a" v5="a"/} +{/level1.level2} diff --git a/js/test_cases/partial_jump_around_two/partialone.dust b/js/test_cases/partial_jump_around_two/partialone.dust new file mode 100644 index 0000000..0642002 --- /dev/null +++ b/js/test_cases/partial_jump_around_two/partialone.dust @@ -0,0 +1,3 @@ +{#level3.level4} + {>partialtwo v1="b" v2="b" v3="b" v4="b" v5="b" /} +{/level3.level4} diff --git a/js/test_cases/partial_jump_around_two/partialtwo.dust b/js/test_cases/partial_jump_around_two/partialtwo.dust new file mode 100644 index 0000000..d8f7866 --- /dev/null +++ b/js/test_cases/partial_jump_around_two/partialtwo.dust @@ -0,0 +1,7 @@ +{#level5} + {v1}{~n} + {v2}{~n} + {v3}{~n} + {v4}{~n} + {v5}{~n} +{/level5} diff --git a/js/test_cases/partial_path/greeting.dust b/js/test_cases/partial_path/greeting.dust new file mode 100644 index 0000000..f5a4adb --- /dev/null +++ b/js/test_cases/partial_path/greeting.dust @@ -0,0 +1 @@ +Hello {name}{?item}, nice {item}{/item}!{~n} diff --git a/js/test_cases/partial_path/input1.json b/js/test_cases/partial_path/input1.json new file mode 100644 index 0000000..232ddbe --- /dev/null +++ b/js/test_cases/partial_path/input1.json @@ -0,0 +1,14 @@ +{ + "people": [ + { + "name": "Alice", + "item": "cat" + }, + { + "name": "Bob" + } + ], + "globals": { + "item": "couch" + } +} diff --git a/js/test_cases/partial_path/main.dust b/js/test_cases/partial_path/main.dust new file mode 100644 index 0000000..87518e2 --- /dev/null +++ b/js/test_cases/partial_path/main.dust @@ -0,0 +1,3 @@ +{#people} +{>greeting item=globals.item/} +{/people} diff --git a/js/test_cases/partial_simple/README.md b/js/test_cases/partial_simple/README.md new file mode 100644 index 0000000..d8908e5 --- /dev/null +++ b/js/test_cases/partial_simple/README.md @@ -0,0 +1 @@ +Partial parameters do not take highest priority but also do not seem to take lowest priority. Current theory is parameters are inserted 1 level above the current context (so parameters would be references before walking up). diff --git a/js/test_cases/partial_simple/greeting.dust b/js/test_cases/partial_simple/greeting.dust new file mode 100644 index 0000000..f5a4adb --- /dev/null +++ b/js/test_cases/partial_simple/greeting.dust @@ -0,0 +1 @@ +Hello {name}{?item}, nice {item}{/item}!{~n} diff --git a/js/test_cases/partial_simple/input1.json b/js/test_cases/partial_simple/input1.json new file mode 100644 index 0000000..272dfa5 --- /dev/null +++ b/js/test_cases/partial_simple/input1.json @@ -0,0 +1,4 @@ +{"people": [ + {"name": "Alice", "item": "cat"}, + {"name": "Bob"} +]} diff --git a/js/test_cases/partial_simple/input2.json b/js/test_cases/partial_simple/input2.json new file mode 100644 index 0000000..02913d0 --- /dev/null +++ b/js/test_cases/partial_simple/input2.json @@ -0,0 +1,5 @@ +{"people": [ + {"name": "Alice"}, + {"name": "Bob"} +], + "item": "cat"} diff --git a/js/test_cases/partial_simple/main.dust b/js/test_cases/partial_simple/main.dust new file mode 100644 index 0000000..5961053 --- /dev/null +++ b/js/test_cases/partial_simple/main.dust @@ -0,0 +1,3 @@ +{#people} +{>greeting item="shoes"/} +{/people} diff --git a/src/bin.rs b/src/bin.rs index bb1c1f1..12e1bd3 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -8,6 +8,7 @@ use renderer::DustRenderer; use renderer::Loopable; use renderer::RenderError; use renderer::Renderable; +use renderer::WalkError; use renderer::Walkable; use std::env; use std::fs; @@ -91,67 +92,48 @@ impl Renderable for serde_json::Value { } impl Walkable for serde_json::Value { - fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError> { + fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> { match self { - serde_json::Value::Null => Err(RenderError::CantWalk { - segment: segment.to_string(), - elem: self, - }), - serde_json::Value::Bool(_boolean) => Err(RenderError::CantWalk { - segment: segment.to_string(), - elem: self, - }), - serde_json::Value::Number(_num) => Err(RenderError::CantWalk { - segment: segment.to_string(), - elem: self, - }), - serde_json::Value::String(_string) => Err(RenderError::CantWalk { - segment: segment.to_string(), - elem: self, - }), - serde_json::Value::Array(_arr) => Err(RenderError::CantWalk { - segment: segment.to_string(), - elem: self, - }), - serde_json::Value::Object(obj) => { - obj.get(segment) - .map(|val| val as _) - .ok_or(RenderError::WontWalk { - segment: segment.to_string(), - elem: self, - }) - } + serde_json::Value::Null => Err(WalkError::CantWalk), + serde_json::Value::Bool(_boolean) => Err(WalkError::CantWalk), + serde_json::Value::Number(_num) => Err(WalkError::CantWalk), + serde_json::Value::String(_string) => Err(WalkError::CantWalk), + serde_json::Value::Array(_arr) => Err(WalkError::CantWalk), + serde_json::Value::Object(obj) => obj + .get(segment) + .map(|val| val as _) + .ok_or(WalkError::CantWalk), } } } impl Loopable for serde_json::Value { - fn get_loop_elements(&self) -> Result, RenderError> { + fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { match self { - serde_json::Value::Null => Ok(Vec::new()), + serde_json::Value::Null => Vec::new(), serde_json::Value::Bool(boolean) => { if *boolean { - Ok(vec![self]) + vec![self] } else { - Ok(Vec::new()) + Vec::new() } } - serde_json::Value::Number(_num) => Ok(vec![self]), + serde_json::Value::Number(_num) => vec![self], serde_json::Value::String(string_value) => { if string_value.is_empty() { - Ok(Vec::new()) + Vec::new() } else { - Ok(vec![self]) + vec![self] } } serde_json::Value::Array(array_value) => { if array_value.is_empty() { - Ok(Vec::new()) + Vec::new() } else { - Ok(array_value.iter().map(|x| x as _).collect()) + array_value.iter().map(|x| x as _).collect() } } - serde_json::Value::Object(_obj) => Ok(vec![self]), + serde_json::Value::Object(_obj) => vec![self], } } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 588c8f9..202309e 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -6,6 +6,8 @@ pub use parser::template; pub use parser::Body; pub use parser::DustTag; pub use parser::Filter; +pub use parser::KVPair; +pub use parser::RValue; pub use parser::Special; pub use parser::Template; pub use parser::TemplateElement; diff --git a/src/parser/parser.rs b/src/parser/parser.rs index b6e19c8..9422a6d 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -6,7 +6,7 @@ use nom::bytes::complete::{tag, take_until, take_until_parser_matches}; use nom::character::complete::line_ending; use nom::character::complete::multispace0; use nom::character::complete::one_of; -use nom::character::complete::space1; +use nom::character::complete::{space0, space1}; use nom::combinator::all_consuming; use nom::combinator::map; use nom::combinator::opt; @@ -114,20 +114,20 @@ pub struct ParameterizedBlock<'a> { #[derive(Clone, Debug, PartialEq)] pub struct Partial<'a> { - name: String, - params: Vec>, + pub name: String, + pub params: Vec>, } #[derive(Clone, Debug, PartialEq)] -enum RValue<'a> { +pub enum RValue<'a> { RVPath(Path<'a>), RVString(String), } #[derive(Clone, Debug, PartialEq)] -struct KVPair<'a> { - key: &'a str, - value: RValue<'a>, +pub struct KVPair<'a> { + pub key: &'a str, + pub value: RValue<'a>, } #[derive(Clone, Debug, PartialEq)] @@ -387,7 +387,11 @@ where let (i, (name, params, inner, maybe_else, _closing_name)) = tuple(( preceded(tag(open_matcher), tag(tag_name)), terminated( - opt(preceded(space1, separated_list1(space1, key_value_pair))), + opt(delimited( + space1, + separated_list1(space1, key_value_pair), + space0, + )), tag("}"), ), opt(body), @@ -420,7 +424,11 @@ where tag(open_matcher), tuple(( tag(tag_name), - opt(preceded(space1, separated_list1(space1, key_value_pair))), + opt(delimited( + space1, + separated_list1(space1, key_value_pair), + space0, + )), )), tag("/}"), )(i)?; @@ -449,7 +457,11 @@ where tag(open_matcher), tuple(( alt((map(key, String::from), quoted_string)), - opt(preceded(space1, separated_list1(space1, key_value_pair))), + opt(delimited( + space1, + separated_list1(space1, key_value_pair), + space0, + )), )), tag("/}"), )(i)?; @@ -1052,4 +1064,55 @@ mod tests { )) ); } + + #[test] + fn test_full_document_parameterized_partial() { + assert_eq!( + super::template( + r#"{#level3.level4}{>partialtwo v1="b" v2="b" v3="b" v4="b" v5="b" /}{/level3.level4}"# + ), + Ok::<_, nom::Err<(&str, ErrorKind)>>(( + "", + Template { + contents: Body { + elements: vec![TemplateElement::TETag(DustTag::DTSection(Container { + path: Path { + keys: vec!["level3", "level4"] + }, + contents: Some(Body { + elements: vec![TemplateElement::TETag(DustTag::DTPartial( + Partial { + name: "partialtwo".to_owned(), + params: vec![ + KVPair { + key: "v1", + value: RValue::RVString("b".to_owned()) + }, + KVPair { + key: "v2", + value: RValue::RVString("b".to_owned()) + }, + KVPair { + key: "v3", + value: RValue::RVString("b".to_owned()) + }, + KVPair { + key: "v4", + value: RValue::RVString("b".to_owned()) + }, + KVPair { + key: "v5", + value: RValue::RVString("b".to_owned()) + } + ] + } + ))] + }), + else_contents: None + }))] + } + } + )) + ); + } } diff --git a/src/renderer/context_element.rs b/src/renderer/context_element.rs index 14f1aae..dee6680 100644 --- a/src/renderer/context_element.rs +++ b/src/renderer/context_element.rs @@ -1,11 +1,12 @@ use crate::parser::Filter; use crate::renderer::errors::RenderError; +use crate::renderer::errors::WalkError; use std::fmt::Debug; pub trait ContextElement: Debug + Walkable + Renderable + Loopable {} pub trait Walkable { - fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError>; + fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError>; } pub trait Renderable { @@ -22,5 +23,5 @@ pub trait Loopable { /// once with the context being the element at that path. Finally, /// if its an array-like value then it will render n-times, once /// for each element of the array. - fn get_loop_elements(&self) -> Result, RenderError>; + fn get_loop_elements(&self) -> Vec<&dyn ContextElement>; } diff --git a/src/renderer/errors.rs b/src/renderer/errors.rs index 8af8bb1..5a38068 100644 --- a/src/renderer/errors.rs +++ b/src/renderer/errors.rs @@ -1,27 +1,16 @@ -use crate::renderer::context_element::ContextElement; use std::error; use std::fmt; -pub enum RenderError<'a> { +/// Fatal errors while rendering. +/// +/// A RenderError will halt rendering. +pub enum RenderError { Generic(String), - /// For when walking is absolutely impossible - CantWalk { - segment: String, - elem: &'a dyn ContextElement, - }, - /// For when walking fails (example, a missing key on a map) - WontWalk { - segment: String, - elem: &'a dyn ContextElement, - }, - NotFound { - path: &'a Vec<&'a str>, - breadcrumbs: Vec<&'a dyn ContextElement>, - }, - /// Attempting to render and unrenderable type (for example, an object without any filters) - CantRender { - elem: &'a dyn ContextElement, - }, + TemplateNotFound(String), +} + +pub enum WalkError { + CantWalk, } #[derive(Clone)] @@ -29,43 +18,51 @@ pub struct CompileError { pub message: String, } -impl fmt::Display for RenderError<'_> { +impl fmt::Display for RenderError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { RenderError::Generic(msg) => write!(f, "{}", msg), - RenderError::CantWalk { segment, elem } => { - write!(f, "Tried to walk to {} from {:?}", segment, elem) - } - RenderError::WontWalk { segment, elem } => { - write!(f, "Failed to walk to {} from {:?}", segment, elem) - } - RenderError::CantRender { elem } => write!(f, "Cant render {:?}", elem), - RenderError::NotFound { path, breadcrumbs } => { - write!(f, "Could not find {:?} in {:?}", path, breadcrumbs) + RenderError::TemplateNotFound(name) => { + write!(f, "No template named {} in context", name) } } } } -impl fmt::Debug for RenderError<'_> { +impl fmt::Debug for RenderError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { RenderError::Generic(msg) => write!(f, "{}", msg), - RenderError::CantWalk { segment, elem } => { - write!(f, "Tried to walk to {} from {:?}", segment, elem) - } - RenderError::WontWalk { segment, elem } => { - write!(f, "Failed to walk to {} from {:?}", segment, elem) - } - RenderError::CantRender { elem } => write!(f, "Cant render {:?}", elem), - RenderError::NotFound { path, breadcrumbs } => { - write!(f, "Could not find {:?} in {:?}", path, breadcrumbs) + RenderError::TemplateNotFound(name) => { + write!(f, "No template named {} in context", name) } } } } -impl error::Error for RenderError<'_> { +impl error::Error for RenderError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + None + } +} + +impl fmt::Display for WalkError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + WalkError::CantWalk => write!(f, "Failed to walk"), + } + } +} + +impl fmt::Debug for WalkError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + WalkError::CantWalk => write!(f, "Failed to walk"), + } + } +} + +impl error::Error for WalkError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { None } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index ce43f4d..13d2ede 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -2,7 +2,9 @@ mod context_element; mod errors; +mod parameters_context; mod renderer; +mod walking; pub use context_element::ContextElement; pub use context_element::Loopable; @@ -10,6 +12,7 @@ pub use context_element::Renderable; pub use context_element::Walkable; pub use errors::CompileError; pub use errors::RenderError; +pub use errors::WalkError; pub use renderer::compile_template; pub use renderer::CompiledTemplate; pub use renderer::DustRenderer; diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs new file mode 100644 index 0000000..c2aa6d0 --- /dev/null +++ b/src/renderer/parameters_context.rs @@ -0,0 +1,86 @@ +use crate::parser::KVPair; +use crate::parser::{Filter, RValue}; +use crate::renderer::context_element::ContextElement; +use crate::renderer::walking::walk_path; +use crate::renderer::Loopable; +use crate::renderer::RenderError; +use crate::renderer::Renderable; +use crate::renderer::WalkError; +use crate::renderer::Walkable; +use std::collections::HashMap; + +#[derive(Clone, Debug)] +pub struct ParametersContext<'a> { + params: HashMap<&'a str, &'a RValue<'a>>, + breadcrumbs: &'a Vec<&'a dyn ContextElement>, +} + +impl<'a> ParametersContext<'a> { + pub fn new( + breadcrumbs: &'a Vec<&'a dyn ContextElement>, + params: &'a Vec>, + ) -> ParametersContext<'a> { + let param_map = params + .iter() + .map(|pair: &KVPair<'a>| (pair.key, &pair.value)) + .collect(); + ParametersContext { + params: param_map, + breadcrumbs: breadcrumbs, + } + } +} + +impl<'a> ContextElement for ParametersContext<'a> {} + +impl<'a> Renderable for ParametersContext<'a> { + fn render(&self, _filters: &Vec) -> Result { + // TODO: Would this even ever be called? Won't matter, but I'd + // like to know. Since it is injected 1 above the current + // context, we wouldn't be able to access it with `{.}`. + Ok("[object Object]".to_owned()) + } +} + +impl<'a> Loopable for ParametersContext<'a> { + fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { + // TODO: Would this even ever be called? Won't matter, but I'd + // like to know. Since it is injected 1 above the current + // context, we wouldn't be able to access it with `{.}`. + vec![self] + } +} + +impl<'a> Walkable for ParametersContext<'a> { + fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> { + let rval = self.params.get(segment).ok_or(WalkError::CantWalk)?; + match rval { + RValue::RVPath(path) => walk_path(self.breadcrumbs, &path.keys), + RValue::RVString(text) => Ok(text), + } + } +} + +impl ContextElement for String {} + +impl Renderable for String { + fn render(&self, _filters: &Vec) -> Result { + Ok(self.clone()) + } +} + +impl Loopable for String { + fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { + if self.is_empty() { + Vec::new() + } else { + vec![self] + } + } +} + +impl Walkable for String { + fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> { + Err(WalkError::CantWalk) + } +} diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index f4b3edd..5a4f18e 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -7,6 +7,9 @@ use crate::parser::TemplateElement; use crate::renderer::context_element::ContextElement; use crate::renderer::errors::CompileError; use crate::renderer::errors::RenderError; +use crate::renderer::errors::WalkError; +use crate::renderer::parameters_context::ParametersContext; +use crate::renderer::walking::walk_path; use std::collections::HashMap; #[derive(Clone, Debug)] @@ -49,14 +52,11 @@ impl<'a> DustRenderer<'a> { &'a self, name: &str, breadcrumbs: &Vec<&'a dyn ContextElement>, - ) -> Result> { + ) -> Result { let main_template = match self.templates.get(name) { Some(tmpl) => tmpl, None => { - return Err(RenderError::Generic(format!( - "No template named {} in context", - name - ))); + return Err(RenderError::TemplateNotFound(name.to_owned())); } }; self.render_body(&main_template.contents, breadcrumbs) @@ -66,7 +66,7 @@ impl<'a> DustRenderer<'a> { &'a self, body: &'a Body, breadcrumbs: &Vec<&'a dyn ContextElement>, - ) -> Result> { + ) -> Result { let mut output = String::new(); for elem in &body.elements { match elem { @@ -84,7 +84,7 @@ impl<'a> DustRenderer<'a> { &'a self, tag: &'a DustTag, breadcrumbs: &Vec<&'a dyn ContextElement>, - ) -> Result> { + ) -> Result { match tag { DustTag::DTComment(_comment) => (), DustTag::DTSpecial(special) => { @@ -100,21 +100,20 @@ impl<'a> DustRenderer<'a> { DustTag::DTReference(reference) => { let val = walk_path(breadcrumbs, &reference.path.keys); match val { - Err(RenderError::NotFound { .. }) => return Ok("".to_owned()), + Err(WalkError::CantWalk) => return Ok("".to_owned()), Ok(final_val) => { - let loop_elements = final_val.get_loop_elements()?; + let loop_elements = final_val.get_loop_elements(); if loop_elements.is_empty() { return Ok("".to_owned()); } else { return final_val.render(&reference.filters); } } - Err(render_error) => return Err(render_error), } } DustTag::DTSection(container) => { let val = walk_path(breadcrumbs, &container.path.keys); - let loop_elements: Vec<&dyn ContextElement> = self.get_loop_elements(val)?; + let loop_elements: Vec<&dyn ContextElement> = self.get_loop_elements(val); if loop_elements.is_empty() { // Oddly enough if the value is falsey (like // an empty array or null), Dust uses the @@ -144,7 +143,7 @@ impl<'a> DustRenderer<'a> { } DustTag::DTExists(container) => { let val = walk_path(breadcrumbs, &container.path.keys); - let loop_elements: Vec<&dyn ContextElement> = self.get_loop_elements(val)?; + 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), @@ -159,7 +158,7 @@ impl<'a> DustRenderer<'a> { } DustTag::DTNotExists(container) => { let val = walk_path(breadcrumbs, &container.path.keys); - let loop_elements: Vec<&dyn ContextElement> = self.get_loop_elements(val)?; + 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), @@ -172,6 +171,18 @@ impl<'a> DustRenderer<'a> { }; } } + DustTag::DTPartial(partial) => { + if partial.params.is_empty() { + let rendered_content = self.render(&partial.name, breadcrumbs)?; + 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)?; + return Ok(rendered_content); + } + } _ => (), // TODO: Implement the rest } Ok("".to_owned()) @@ -183,72 +194,15 @@ impl<'a> DustRenderer<'a> { /// block, this will return an empty vector. fn get_loop_elements<'b>( &'a self, - walk_result: Result<&'b dyn ContextElement, RenderError<'b>>, - ) -> Result, RenderError<'b>> { - if let Err(RenderError::NotFound { .. }) = walk_result { - // If reference does not exist in the context, render the else block - Ok(vec![]) - } else { - Ok(walk_result?.get_loop_elements()?) + walk_result: Result<&'b dyn ContextElement, WalkError>, + ) -> Vec<&'b dyn ContextElement> { + match walk_result { + Err(WalkError::CantWalk) => Vec::new(), + Ok(walk_target) => walk_target.get_loop_elements(), } } } -enum WalkResult<'a> { - NoWalk, - PartialWalk, - FullyWalked(&'a dyn ContextElement), -} - -fn walk_path_from_single_level<'a>( - context: &'a dyn ContextElement, - path: &Vec<&str>, -) -> Result, RenderError<'a>> { - if path.is_empty() { - return Ok(WalkResult::FullyWalked(context)); - } - - let mut walk_failure = WalkResult::NoWalk; - let mut output = context; - for elem in path.iter() { - let new_val = output.walk(elem); - if let Err(RenderError::WontWalk { .. }) = new_val { - return Ok(walk_failure); - } else if let Err(RenderError::CantWalk { .. }) = new_val { - return Ok(walk_failure); - } - walk_failure = WalkResult::PartialWalk; - output = new_val?; - } - - Ok(WalkResult::FullyWalked(output)) -} - -fn walk_path<'a>( - breadcrumbs: &Vec<&'a dyn ContextElement>, - path: &'a Vec<&str>, -) -> Result<&'a dyn ContextElement, RenderError<'a>> { - for context in breadcrumbs.iter().rev() { - match walk_path_from_single_level(*context, path)? { - // If no walking was done at all, keep looping - WalkResult::NoWalk => {} - // If we partially walked then stop trying to find - // anything - WalkResult::PartialWalk => { - return Err(RenderError::NotFound { - path: path, - breadcrumbs: breadcrumbs.clone(), - }); - } - WalkResult::FullyWalked(new_context) => return Ok(new_context), - } - } - Err(RenderError::NotFound { - path: path, - breadcrumbs: breadcrumbs.clone(), - }) -} - #[cfg(test)] mod tests { use super::*; @@ -278,57 +232,48 @@ mod tests { impl Renderable for HashMap<&str, I> { fn render(&self, _filters: &Vec) -> Result { // TODO: handle the filters - Err(RenderError::CantRender { elem: self }) + Ok("[object Object]".to_owned()) } } impl Walkable for HashMap<&str, I> { - fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError> { - let child = self.get(segment).ok_or(RenderError::WontWalk { - segment: segment.to_string(), - elem: self, - })?; + fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> { + let child = self.get(segment).ok_or(WalkError::CantWalk)?; Ok(child) } } impl Walkable for &str { - fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError> { - Err(RenderError::CantWalk { - segment: segment.to_string(), - elem: self, - }) + fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> { + Err(WalkError::CantWalk) } } impl Walkable for u32 { - fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError> { - Err(RenderError::CantWalk { - segment: segment.to_string(), - elem: self, - }) + fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> { + Err(WalkError::CantWalk) } } impl Loopable for &str { - fn get_loop_elements(&self) -> Result, RenderError> { + fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { if self.is_empty() { - Ok(Vec::new()) + Vec::new() } else { - Ok(vec![self]) + vec![self] } } } impl Loopable for u32 { - fn get_loop_elements(&self) -> Result, RenderError> { - Ok(vec![self]) + fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { + vec![self] } } impl Loopable for HashMap<&str, I> { - fn get_loop_elements(&self) -> Result, RenderError> { - Ok(vec![self]) + fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { + vec![self] } } diff --git a/src/renderer/walking.rs b/src/renderer/walking.rs new file mode 100644 index 0000000..feadce3 --- /dev/null +++ b/src/renderer/walking.rs @@ -0,0 +1,53 @@ +use crate::renderer::context_element::ContextElement; +use crate::renderer::WalkError; + +enum WalkResult<'a> { + NoWalk, + PartialWalk, + FullyWalked(&'a dyn ContextElement), +} + +fn walk_path_from_single_level<'a>( + context: &'a dyn ContextElement, + path: &Vec<&str>, +) -> WalkResult<'a> { + if path.is_empty() { + return WalkResult::FullyWalked(context); + } + + let mut walk_failure = WalkResult::NoWalk; + let mut output = context; + for elem in path.iter() { + let new_val = output.walk(elem); + match output.walk(elem) { + Err(WalkError::CantWalk { .. }) => { + return walk_failure; + } + Ok(new_val) => { + walk_failure = WalkResult::PartialWalk; + output = new_val; + } + } + } + + WalkResult::FullyWalked(output) +} + +pub fn walk_path<'a>( + breadcrumbs: &Vec<&'a dyn ContextElement>, + path: &'a Vec<&str>, +) -> Result<&'a dyn ContextElement, WalkError> { + for context in breadcrumbs.iter().rev() { + match walk_path_from_single_level(*context, path) { + // If no walking was done at all, keep looping + WalkResult::NoWalk => {} + // If we partially walked then stop trying to find + // anything + WalkResult::PartialWalk => { + return Err(WalkError::CantWalk); + } + WalkResult::FullyWalked(new_context) => return Ok(new_context), + } + } + Err(WalkError::CantWalk) +}