Merge branch 'partial' into render

This commit is contained in:
Tom Alexander 2020-05-09 15:21:04 -04:00
commit afda9ce22f
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
25 changed files with 436 additions and 190 deletions

View File

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

View File

@ -0,0 +1,3 @@
{#level1.level2}
{>partialone v0="a" v1="a" v2="a" v3="a" v4="a" v5="a"/}
{/level1.level2}

View File

@ -0,0 +1,3 @@
{#level3.level4}
{>partialtwo/}
{/level3.level4}

View File

@ -0,0 +1,8 @@
{#level5}
{v0}{~n}
{v1}{~n}
{v2}{~n}
{v3}{~n}
{v4}{~n}
{v5}{~n}
{/level5}

View File

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

View File

@ -0,0 +1,3 @@
{#level1.level2}
{>partialone v1="a" v2="a" v3="a" v4="a" v5="a"/}
{/level1.level2}

View File

@ -0,0 +1,3 @@
{#level3.level4}
{>partialtwo v1="b" v2="b" v3="b" v4="b" v5="b" /}
{/level3.level4}

View File

@ -0,0 +1,7 @@
{#level5}
{v1}{~n}
{v2}{~n}
{v3}{~n}
{v4}{~n}
{v5}{~n}
{/level5}

View File

@ -0,0 +1 @@
Hello {name}{?item}, nice {item}{/item}!{~n}

View File

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

View File

@ -0,0 +1,3 @@
{#people}
{>greeting item=globals.item/}
{/people}

View File

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

View File

@ -0,0 +1 @@
Hello {name}{?item}, nice {item}{/item}!{~n}

View File

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

View File

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

View File

@ -0,0 +1,3 @@
{#people}
{>greeting item="shoes"/}
{/people}

View File

@ -8,6 +8,7 @@ use renderer::DustRenderer;
use renderer::Loopable; use renderer::Loopable;
use renderer::RenderError; use renderer::RenderError;
use renderer::Renderable; use renderer::Renderable;
use renderer::WalkError;
use renderer::Walkable; use renderer::Walkable;
use std::env; use std::env;
use std::fs; use std::fs;
@ -91,67 +92,48 @@ impl Renderable for serde_json::Value {
} }
impl Walkable 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 { match self {
serde_json::Value::Null => Err(RenderError::CantWalk { serde_json::Value::Null => Err(WalkError::CantWalk),
segment: segment.to_string(), serde_json::Value::Bool(_boolean) => Err(WalkError::CantWalk),
elem: self, serde_json::Value::Number(_num) => Err(WalkError::CantWalk),
}), serde_json::Value::String(_string) => Err(WalkError::CantWalk),
serde_json::Value::Bool(_boolean) => Err(RenderError::CantWalk { serde_json::Value::Array(_arr) => Err(WalkError::CantWalk),
segment: segment.to_string(), serde_json::Value::Object(obj) => obj
elem: self, .get(segment)
}), .map(|val| val as _)
serde_json::Value::Number(_num) => Err(RenderError::CantWalk { .ok_or(WalkError::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,
})
}
} }
} }
} }
impl Loopable for serde_json::Value { impl Loopable for serde_json::Value {
fn get_loop_elements(&self) -> Result<Vec<&dyn ContextElement>, RenderError> { fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
match self { match self {
serde_json::Value::Null => Ok(Vec::new()), serde_json::Value::Null => Vec::new(),
serde_json::Value::Bool(boolean) => { serde_json::Value::Bool(boolean) => {
if *boolean { if *boolean {
Ok(vec![self]) vec![self]
} else { } 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) => { serde_json::Value::String(string_value) => {
if string_value.is_empty() { if string_value.is_empty() {
Ok(Vec::new()) Vec::new()
} else { } else {
Ok(vec![self]) vec![self]
} }
} }
serde_json::Value::Array(array_value) => { serde_json::Value::Array(array_value) => {
if array_value.is_empty() { if array_value.is_empty() {
Ok(Vec::new()) Vec::new()
} else { } 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],
} }
} }
} }

View File

@ -6,6 +6,8 @@ pub use parser::template;
pub use parser::Body; pub use parser::Body;
pub use parser::DustTag; pub use parser::DustTag;
pub use parser::Filter; pub use parser::Filter;
pub use parser::KVPair;
pub use parser::RValue;
pub use parser::Special; pub use parser::Special;
pub use parser::Template; pub use parser::Template;
pub use parser::TemplateElement; pub use parser::TemplateElement;

View File

@ -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::line_ending;
use nom::character::complete::multispace0; use nom::character::complete::multispace0;
use nom::character::complete::one_of; 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::all_consuming;
use nom::combinator::map; use nom::combinator::map;
use nom::combinator::opt; use nom::combinator::opt;
@ -114,20 +114,20 @@ pub struct ParameterizedBlock<'a> {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Partial<'a> { pub struct Partial<'a> {
name: String, pub name: String,
params: Vec<KVPair<'a>>, pub params: Vec<KVPair<'a>>,
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
enum RValue<'a> { pub enum RValue<'a> {
RVPath(Path<'a>), RVPath(Path<'a>),
RVString(String), RVString(String),
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
struct KVPair<'a> { pub struct KVPair<'a> {
key: &'a str, pub key: &'a str,
value: RValue<'a>, pub value: RValue<'a>,
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -387,7 +387,11 @@ where
let (i, (name, params, inner, maybe_else, _closing_name)) = tuple(( let (i, (name, params, inner, maybe_else, _closing_name)) = tuple((
preceded(tag(open_matcher), tag(tag_name)), preceded(tag(open_matcher), tag(tag_name)),
terminated( terminated(
opt(preceded(space1, separated_list1(space1, key_value_pair))), opt(delimited(
space1,
separated_list1(space1, key_value_pair),
space0,
)),
tag("}"), tag("}"),
), ),
opt(body), opt(body),
@ -420,7 +424,11 @@ where
tag(open_matcher), tag(open_matcher),
tuple(( tuple((
tag(tag_name), tag(tag_name),
opt(preceded(space1, separated_list1(space1, key_value_pair))), opt(delimited(
space1,
separated_list1(space1, key_value_pair),
space0,
)),
)), )),
tag("/}"), tag("/}"),
)(i)?; )(i)?;
@ -449,7 +457,11 @@ where
tag(open_matcher), tag(open_matcher),
tuple(( tuple((
alt((map(key, String::from), quoted_string)), 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("/}"), tag("/}"),
)(i)?; )(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
}))]
}
}
))
);
}
} }

View File

@ -1,11 +1,12 @@
use crate::parser::Filter; use crate::parser::Filter;
use crate::renderer::errors::RenderError; use crate::renderer::errors::RenderError;
use crate::renderer::errors::WalkError;
use std::fmt::Debug; use std::fmt::Debug;
pub trait ContextElement: Debug + Walkable + Renderable + Loopable {} pub trait ContextElement: Debug + Walkable + Renderable + Loopable {}
pub trait Walkable { pub trait Walkable {
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError>; fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError>;
} }
pub trait Renderable { pub trait Renderable {
@ -22,5 +23,5 @@ pub trait Loopable {
/// once with the context being the element at that path. Finally, /// once with the context being the element at that path. Finally,
/// if its an array-like value then it will render n-times, once /// if its an array-like value then it will render n-times, once
/// for each element of the array. /// for each element of the array.
fn get_loop_elements(&self) -> Result<Vec<&dyn ContextElement>, RenderError>; fn get_loop_elements(&self) -> Vec<&dyn ContextElement>;
} }

View File

@ -1,27 +1,16 @@
use crate::renderer::context_element::ContextElement;
use std::error; use std::error;
use std::fmt; use std::fmt;
pub enum RenderError<'a> { /// Fatal errors while rendering.
///
/// A RenderError will halt rendering.
pub enum RenderError {
Generic(String), Generic(String),
/// For when walking is absolutely impossible TemplateNotFound(String),
CantWalk { }
segment: String,
elem: &'a dyn ContextElement, pub enum WalkError {
}, CantWalk,
/// 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,
},
} }
#[derive(Clone)] #[derive(Clone)]
@ -29,43 +18,51 @@ pub struct CompileError {
pub message: String, pub message: String,
} }
impl fmt::Display for RenderError<'_> { impl fmt::Display for RenderError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
RenderError::Generic(msg) => write!(f, "{}", msg), RenderError::Generic(msg) => write!(f, "{}", msg),
RenderError::CantWalk { segment, elem } => { RenderError::TemplateNotFound(name) => {
write!(f, "Tried to walk to {} from {:?}", segment, elem) write!(f, "No template named {} in context", name)
}
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)
} }
} }
} }
} }
impl fmt::Debug for RenderError<'_> { impl fmt::Debug for RenderError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
RenderError::Generic(msg) => write!(f, "{}", msg), RenderError::Generic(msg) => write!(f, "{}", msg),
RenderError::CantWalk { segment, elem } => { RenderError::TemplateNotFound(name) => {
write!(f, "Tried to walk to {} from {:?}", segment, elem) write!(f, "No template named {} in context", name)
}
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)
} }
} }
} }
} }
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)> { fn source(&self) -> Option<&(dyn error::Error + 'static)> {
None None
} }

View File

@ -2,7 +2,9 @@
mod context_element; mod context_element;
mod errors; mod errors;
mod parameters_context;
mod renderer; mod renderer;
mod walking;
pub use context_element::ContextElement; pub use context_element::ContextElement;
pub use context_element::Loopable; pub use context_element::Loopable;
@ -10,6 +12,7 @@ pub use context_element::Renderable;
pub use context_element::Walkable; pub use context_element::Walkable;
pub use errors::CompileError; pub use errors::CompileError;
pub use errors::RenderError; pub use errors::RenderError;
pub use errors::WalkError;
pub use renderer::compile_template; pub use renderer::compile_template;
pub use renderer::CompiledTemplate; pub use renderer::CompiledTemplate;
pub use renderer::DustRenderer; pub use renderer::DustRenderer;

View File

@ -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<KVPair<'a>>,
) -> 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<Filter>) -> Result<String, RenderError> {
// 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<Filter>) -> Result<String, RenderError> {
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)
}
}

View File

@ -7,6 +7,9 @@ use crate::parser::TemplateElement;
use crate::renderer::context_element::ContextElement; use crate::renderer::context_element::ContextElement;
use crate::renderer::errors::CompileError; use crate::renderer::errors::CompileError;
use crate::renderer::errors::RenderError; 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; use std::collections::HashMap;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -49,14 +52,11 @@ impl<'a> DustRenderer<'a> {
&'a self, &'a self,
name: &str, name: &str,
breadcrumbs: &Vec<&'a dyn ContextElement>, breadcrumbs: &Vec<&'a dyn ContextElement>,
) -> Result<String, RenderError<'a>> { ) -> Result<String, RenderError> {
let main_template = match self.templates.get(name) { let main_template = match self.templates.get(name) {
Some(tmpl) => tmpl, Some(tmpl) => tmpl,
None => { None => {
return Err(RenderError::Generic(format!( return Err(RenderError::TemplateNotFound(name.to_owned()));
"No template named {} in context",
name
)));
} }
}; };
self.render_body(&main_template.contents, breadcrumbs) self.render_body(&main_template.contents, breadcrumbs)
@ -66,7 +66,7 @@ impl<'a> DustRenderer<'a> {
&'a self, &'a self,
body: &'a Body, body: &'a Body,
breadcrumbs: &Vec<&'a dyn ContextElement>, breadcrumbs: &Vec<&'a dyn ContextElement>,
) -> Result<String, RenderError<'a>> { ) -> Result<String, RenderError> {
let mut output = String::new(); let mut output = String::new();
for elem in &body.elements { for elem in &body.elements {
match elem { match elem {
@ -84,7 +84,7 @@ impl<'a> DustRenderer<'a> {
&'a self, &'a self,
tag: &'a DustTag, tag: &'a DustTag,
breadcrumbs: &Vec<&'a dyn ContextElement>, breadcrumbs: &Vec<&'a dyn ContextElement>,
) -> Result<String, RenderError<'a>> { ) -> Result<String, RenderError> {
match tag { match tag {
DustTag::DTComment(_comment) => (), DustTag::DTComment(_comment) => (),
DustTag::DTSpecial(special) => { DustTag::DTSpecial(special) => {
@ -100,21 +100,20 @@ impl<'a> DustRenderer<'a> {
DustTag::DTReference(reference) => { DustTag::DTReference(reference) => {
let val = walk_path(breadcrumbs, &reference.path.keys); let val = walk_path(breadcrumbs, &reference.path.keys);
match val { match val {
Err(RenderError::NotFound { .. }) => return Ok("".to_owned()), Err(WalkError::CantWalk) => return Ok("".to_owned()),
Ok(final_val) => { Ok(final_val) => {
let loop_elements = final_val.get_loop_elements()?; let loop_elements = final_val.get_loop_elements();
if loop_elements.is_empty() { if loop_elements.is_empty() {
return Ok("".to_owned()); return Ok("".to_owned());
} else { } else {
return final_val.render(&reference.filters); return final_val.render(&reference.filters);
} }
} }
Err(render_error) => return Err(render_error),
} }
} }
DustTag::DTSection(container) => { DustTag::DTSection(container) => {
let val = walk_path(breadcrumbs, &container.path.keys); 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() { if loop_elements.is_empty() {
// Oddly enough if the value is falsey (like // Oddly enough if the value is falsey (like
// an empty array or null), Dust uses the // an empty array or null), Dust uses the
@ -144,7 +143,7 @@ impl<'a> DustRenderer<'a> {
} }
DustTag::DTExists(container) => { DustTag::DTExists(container) => {
let val = walk_path(breadcrumbs, &container.path.keys); 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() { if loop_elements.is_empty() {
return match &container.else_contents { return match &container.else_contents {
Some(body) => self.render_body(&body, breadcrumbs), Some(body) => self.render_body(&body, breadcrumbs),
@ -159,7 +158,7 @@ impl<'a> DustRenderer<'a> {
} }
DustTag::DTNotExists(container) => { DustTag::DTNotExists(container) => {
let val = walk_path(breadcrumbs, &container.path.keys); 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() { if !loop_elements.is_empty() {
return match &container.else_contents { return match &container.else_contents {
Some(body) => self.render_body(&body, breadcrumbs), 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 _ => (), // TODO: Implement the rest
} }
Ok("".to_owned()) Ok("".to_owned())
@ -183,72 +194,15 @@ impl<'a> DustRenderer<'a> {
/// block, this will return an empty vector. /// block, this will return an empty vector.
fn get_loop_elements<'b>( fn get_loop_elements<'b>(
&'a self, &'a self,
walk_result: Result<&'b dyn ContextElement, RenderError<'b>>, walk_result: Result<&'b dyn ContextElement, WalkError>,
) -> Result<Vec<&'b dyn ContextElement>, RenderError<'b>> { ) -> Vec<&'b dyn ContextElement> {
if let Err(RenderError::NotFound { .. }) = walk_result { match walk_result {
// If reference does not exist in the context, render the else block Err(WalkError::CantWalk) => Vec::new(),
Ok(vec![]) Ok(walk_target) => walk_target.get_loop_elements(),
} else {
Ok(walk_result?.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<WalkResult<'a>, 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -278,57 +232,48 @@ mod tests {
impl<I: ContextElement> Renderable for HashMap<&str, I> { impl<I: ContextElement> Renderable for HashMap<&str, I> {
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> { fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
// TODO: handle the filters // TODO: handle the filters
Err(RenderError::CantRender { elem: self }) Ok("[object Object]".to_owned())
} }
} }
impl<I: ContextElement> Walkable for HashMap<&str, I> { impl<I: ContextElement> Walkable for HashMap<&str, I> {
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError> { fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
let child = self.get(segment).ok_or(RenderError::WontWalk { let child = self.get(segment).ok_or(WalkError::CantWalk)?;
segment: segment.to_string(),
elem: self,
})?;
Ok(child) Ok(child)
} }
} }
impl Walkable for &str { impl Walkable for &str {
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError> { fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
Err(RenderError::CantWalk { Err(WalkError::CantWalk)
segment: segment.to_string(),
elem: self,
})
} }
} }
impl Walkable for u32 { impl Walkable for u32 {
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError> { fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
Err(RenderError::CantWalk { Err(WalkError::CantWalk)
segment: segment.to_string(),
elem: self,
})
} }
} }
impl Loopable for &str { impl Loopable for &str {
fn get_loop_elements(&self) -> Result<Vec<&dyn ContextElement>, RenderError> { fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
if self.is_empty() { if self.is_empty() {
Ok(Vec::new()) Vec::new()
} else { } else {
Ok(vec![self]) vec![self]
} }
} }
} }
impl Loopable for u32 { impl Loopable for u32 {
fn get_loop_elements(&self) -> Result<Vec<&dyn ContextElement>, RenderError> { fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
Ok(vec![self]) vec![self]
} }
} }
impl<I: ContextElement> Loopable for HashMap<&str, I> { impl<I: ContextElement> Loopable for HashMap<&str, I> {
fn get_loop_elements(&self) -> Result<Vec<&dyn ContextElement>, RenderError> { fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
Ok(vec![self]) vec![self]
} }
} }

53
src/renderer/walking.rs Normal file
View File

@ -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)
}