diff --git a/js/test_cases/helpers_eq/main.dust b/js/test_cases/helpers_eq/main.dust index 3d182d5..aad3f75 100644 --- a/js/test_cases/helpers_eq/main.dust +++ b/js/test_cases/helpers_eq/main.dust @@ -21,3 +21,9 @@ {@eq key=array_of_some_obj value=array_of_some_obj}array_of_some_obj is equal to array_of_some_obj{:else}array_of_some_obj does not equal array_of_some_obj{/eq}{~n} {@eq key=array_of_some_obj value=copy_array_of_some_obj}array_of_some_obj is equal to copy_array_of_some_obj{:else}array_of_some_obj does not equal copy_array_of_some_obj{/eq}{~n} {@eq key=array_of_some_obj value=array_of_other_obj}array_of_some_obj is equal to array_of_other_obj{:else}array_of_some_obj does not equal array_of_other_obj{/eq}{~n} + +Do objects with different paths referencing the same variable match?{~n} +===================================================================={~n} +{#int renamed=some_obj} + {@eq key=some_obj value=renamed}some_obj equals renamed{:else}some_obj does not equal renamed{/eq}{~n} +{/int} diff --git a/js/test_cases/reference_parameters/input1.json b/js/test_cases/reference_parameters/input1.json index 3c704cd..7a597e1 100644 --- a/js/test_cases/reference_parameters/input1.json +++ b/js/test_cases/reference_parameters/input1.json @@ -19,5 +19,14 @@ "bar" ] } - ] + ], + "some_object": { + "foo": "bar" + }, + "some_same_object": { + "foo": "bar" + }, + "some_different_object": { + "foo": "baz" + } } diff --git a/js/test_cases/reference_parameters/main.dust b/js/test_cases/reference_parameters/main.dust index 0b553cb..4308d5a 100644 --- a/js/test_cases/reference_parameters/main.dust +++ b/js/test_cases/reference_parameters/main.dust @@ -53,3 +53,12 @@ Reference Parameters{~n} {#truthy name="chris" pet="{petname}" petname="{deeperpetname}" deeperpetname="fluffy"} Hello {name}, nice {pet}{~n} {/truthy} + +Equality{~n} +========{~n} +{@eq key=some_object value=some_object}some_object equals some_object{:else}some_object does not equal some_object{/eq}{~n} +{@eq key=some_object value=some_same_object}some_object equals some_same_object{:else}some_object does not equal some_same_object{/eq}{~n} +{@eq key=some_object value="{some_object}"}some_object equals reference(some_object){:else}some_object does not equal reference(some_object){/eq}{~n} +{@eq key="{some_object}" value="{some_object}"}reference(some_object) equals reference(some_object){:else}reference(some_object) does not equal reference(some_object){/eq}{~n} +{@eq key="{some_object}" value="{some_same_object}"}reference(some_object) equals reference(some_same_object){:else}reference(some_object) does not equal reference(some_same_object){/eq}{~n} +{@eq key="{some_object}" value="{some_different_object}"}reference(some_object) equals reference(some_different_object){:else}reference(some_object) does not equal reference(some_different_object){/eq}{~n} diff --git a/src/bin.rs b/src/bin.rs index 84fe456..b58c8ef 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -53,13 +53,12 @@ fn main() { compiled_templates.iter().for_each(|(name, template)| { dust_renderer.load_source(template, name.to_owned()); }); - // let breadcrumbs = vec![&context as &dyn IntoContextElement]; - // println!( - // "{}", - // dust_renderer - // .render(main_template_name, &breadcrumbs) - // .expect("Failed to render") - // ); + println!( + "{}", + dust_renderer + .render(main_template_name, Some(&context)) + .expect("Failed to render") + ); } fn template_from_file<'a>( @@ -171,288 +170,288 @@ fn encode_uri_component(inp: &str) -> String { output } -// fn apply_filter( -// json_value: &serde_json::Value, -// filter: &Filter, -// ) -> Result { -// match (json_value, filter) { -// // Html escape filter -// (serde_json::Value::String(string), Filter::HtmlEncode) => { -// Ok(serde_json::Value::String(html_escape(string))) -// } -// (_, Filter::HtmlEncode) => Ok(serde_json::Value::String(html_escape( -// &json_value.render(&Vec::new())?, -// ))), -// // Disable html escape filter -// (_, Filter::DisableHtmlEncode) => panic!("The |s filter is automatically removed by the renderer since it is a no-op during rendering."), -// // Parse JSON filter -// (serde_json::Value::String(string), Filter::JsonParse) => { -// serde_json::from_str(&string).or(Err(RenderError::InvalidJson(string.to_owned()))) -// } -// (_, Filter::JsonParse) => { -// let rendered_value = json_value.render(&Vec::new())?; -// serde_json::from_str(&rendered_value).or(Err(RenderError::InvalidJson(rendered_value))) -// } -// // Json Stringify filter -// (_, Filter::JsonStringify) => { -// Ok(serde_json::Value::String(json_value.to_string())) -// } -// // Javascript escape filter -// (serde_json::Value::String(string), Filter::JavascriptStringEncode) => { -// Ok(serde_json::Value::String(javascript_escape(string))) -// } -// (serde_json::Value::Bool(boolean), Filter::JavascriptStringEncode) => { -// Ok(serde_json::Value::Bool(*boolean)) -// } -// (serde_json::Value::Number(number), Filter::JavascriptStringEncode) => { -// Ok(serde_json::Value::Number(number.clone())) -// } -// (serde_json::Value::Array(arr), Filter::JavascriptStringEncode) => { -// Ok(serde_json::Value::Array(arr.clone())) -// } -// (serde_json::Value::Object(obj), Filter::JavascriptStringEncode) => { -// Ok(serde_json::Value::Object(obj.clone())) -// } -// (_, Filter::JavascriptStringEncode) => Ok(serde_json::Value::String(javascript_escape( -// &json_value.render(&Vec::new())?, -// ))), -// // EncodeURI filter -// (serde_json::Value::String(string), Filter::EncodeUri) => { -// Ok(serde_json::Value::String(encode_uri(string))) -// } -// (_, Filter::EncodeUri) => Ok(serde_json::Value::String(encode_uri( -// &json_value.render(&Vec::new())?, -// ))), -// // EncodeURIComponent filter -// (serde_json::Value::String(string), Filter::EncodeUriComponent) => { -// Ok(serde_json::Value::String(encode_uri_component(string))) -// } -// (_, Filter::EncodeUriComponent) => Ok(serde_json::Value::String(encode_uri_component( -// &json_value.render(&Vec::new())?, -// ))), -// } -// } +fn apply_filter( + json_value: &serde_json::Value, + filter: &Filter, +) -> Result { + match (json_value, filter) { + // Html escape filter + (serde_json::Value::String(string), Filter::HtmlEncode) => { + Ok(serde_json::Value::String(html_escape(string))) + } + (_, Filter::HtmlEncode) => Ok(serde_json::Value::String(html_escape( + &json_value.render(&Vec::new())?, + ))), + // Disable html escape filter + (_, Filter::DisableHtmlEncode) => panic!("The |s filter is automatically removed by the renderer since it is a no-op during rendering."), + // Parse JSON filter + (serde_json::Value::String(string), Filter::JsonParse) => { + serde_json::from_str(&string).or(Err(RenderError::InvalidJson(string.to_owned()))) + } + (_, Filter::JsonParse) => { + let rendered_value = json_value.render(&Vec::new())?; + serde_json::from_str(&rendered_value).or(Err(RenderError::InvalidJson(rendered_value))) + } + // Json Stringify filter + (_, Filter::JsonStringify) => { + Ok(serde_json::Value::String(json_value.to_string())) + } + // Javascript escape filter + (serde_json::Value::String(string), Filter::JavascriptStringEncode) => { + Ok(serde_json::Value::String(javascript_escape(string))) + } + (serde_json::Value::Bool(boolean), Filter::JavascriptStringEncode) => { + Ok(serde_json::Value::Bool(*boolean)) + } + (serde_json::Value::Number(number), Filter::JavascriptStringEncode) => { + Ok(serde_json::Value::Number(number.clone())) + } + (serde_json::Value::Array(arr), Filter::JavascriptStringEncode) => { + Ok(serde_json::Value::Array(arr.clone())) + } + (serde_json::Value::Object(obj), Filter::JavascriptStringEncode) => { + Ok(serde_json::Value::Object(obj.clone())) + } + (_, Filter::JavascriptStringEncode) => Ok(serde_json::Value::String(javascript_escape( + &json_value.render(&Vec::new())?, + ))), + // EncodeURI filter + (serde_json::Value::String(string), Filter::EncodeUri) => { + Ok(serde_json::Value::String(encode_uri(string))) + } + (_, Filter::EncodeUri) => Ok(serde_json::Value::String(encode_uri( + &json_value.render(&Vec::new())?, + ))), + // EncodeURIComponent filter + (serde_json::Value::String(string), Filter::EncodeUriComponent) => { + Ok(serde_json::Value::String(encode_uri_component(string))) + } + (_, Filter::EncodeUriComponent) => Ok(serde_json::Value::String(encode_uri_component( + &json_value.render(&Vec::new())?, + ))), + } +} -// fn apply_filters( -// json_value: &serde_json::Value, -// filters: &[Filter], -// ) -> Result { -// let mut final_value: serde_json::Value = apply_filter(json_value, &filters[0])?; +fn apply_filters( + json_value: &serde_json::Value, + filters: &[Filter], +) -> Result { + let mut final_value: serde_json::Value = apply_filter(json_value, &filters[0])?; -// for filter in &filters[1..] { -// final_value = apply_filter(&final_value, filter)?; -// } + for filter in &filters[1..] { + final_value = apply_filter(&final_value, filter)?; + } -// Ok(final_value) -// } + Ok(final_value) +} -// impl ContextElement for serde_json::Value {} +impl ContextElement for serde_json::Value {} -// impl Truthiness for serde_json::Value { -// fn is_truthy(&self) -> bool { -// match self { -// serde_json::Value::Null => false, -// serde_json::Value::Bool(boolean) => *boolean, -// serde_json::Value::Number(_num) => true, -// serde_json::Value::String(string_value) => !string_value.is_empty(), -// serde_json::Value::Array(array_value) => !array_value.is_empty(), -// serde_json::Value::Object(_obj) => true, -// } -// } -// } +impl Truthiness for serde_json::Value { + fn is_truthy(&self) -> bool { + match self { + serde_json::Value::Null => false, + serde_json::Value::Bool(boolean) => *boolean, + serde_json::Value::Number(_num) => true, + serde_json::Value::String(string_value) => !string_value.is_empty(), + serde_json::Value::Array(array_value) => !array_value.is_empty(), + serde_json::Value::Object(_obj) => true, + } + } +} -// impl Renderable for serde_json::Value { -// fn render(&self, _filters: &Vec) -> Result { -// let after_apply = if _filters.is_empty() { -// None -// } else { -// Some(apply_filters(self, _filters)?) -// }; +impl Renderable for serde_json::Value { + fn render(&self, _filters: &Vec) -> Result { + let after_apply = if _filters.is_empty() { + None + } else { + Some(apply_filters(self, _filters)?) + }; -// match after_apply.as_ref().unwrap_or(self) { -// serde_json::Value::Null => Ok("".to_owned()), -// serde_json::Value::Bool(boolean) => Ok(boolean.to_string()), -// serde_json::Value::Number(num) => Ok(num.to_string()), -// serde_json::Value::String(string) => Ok(string.to_string()), -// serde_json::Value::Array(arr) => { -// // TODO: Handle the filters instead of passing a Vec::new() -// let rendered: Result, RenderError> = -// arr.iter().map(|val| val.render(&Vec::new())).collect(); -// let rendered_slice: &[String] = &rendered?; -// Ok(rendered_slice.join(",")) -// } -// serde_json::Value::Object(_obj) => Ok("[object Object]".to_owned()), -// } -// } -// } + match after_apply.as_ref().unwrap_or(self) { + serde_json::Value::Null => Ok("".to_owned()), + serde_json::Value::Bool(boolean) => Ok(boolean.to_string()), + serde_json::Value::Number(num) => Ok(num.to_string()), + serde_json::Value::String(string) => Ok(string.to_string()), + serde_json::Value::Array(arr) => { + // TODO: Handle the filters instead of passing a Vec::new() + let rendered: Result, RenderError> = + arr.iter().map(|val| val.render(&Vec::new())).collect(); + let rendered_slice: &[String] = &rendered?; + Ok(rendered_slice.join(",")) + } + serde_json::Value::Object(_obj) => Ok("[object Object]".to_owned()), + } + } +} -// impl Walkable for serde_json::Value { -// fn walk(&self, segment: &str) -> Result<&dyn IntoContextElement, WalkError> { -// match 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 Walkable for serde_json::Value { + fn walk(&self, segment: &str) -> Result<&dyn IntoContextElement, WalkError> { + match 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) -> Vec<&dyn ContextElement> { -// match self { -// serde_json::Value::Array(array_value) => array_value.iter().map(|x| x as _).collect(), -// _ => Vec::new(), -// } -// } -// } +impl Loopable for serde_json::Value { + fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { + match self { + serde_json::Value::Array(array_value) => array_value.iter().map(|x| x as _).collect(), + _ => Vec::new(), + } + } +} -// impl CompareContextElement for serde_json::Value { -// fn equals(&self, other: &dyn ContextElement) -> bool { -// // println!("equals json {:?} | {:?}", self, other); -// // Handle other serde_json::Value -// match other.to_any().downcast_ref::() { -// None => (), -// Some(other_json_value) => match (self, other_json_value) { -// // Non-scalar values not caught in the renderer by the -// // identical-path shortcut are always not equal. -// (serde_json::Value::Array(_), _) -// | (_, serde_json::Value::Array(_)) -// | (serde_json::Value::Object(_), _) -// | (_, serde_json::Value::Object(_)) => return false, -// _ => return self == other_json_value, -// }, -// } -// // Handle literals -// match other.to_any().downcast_ref::() { -// None => (), -// Some(OwnedLiteral::LString(other_string)) => { -// return self.as_str().map_or(false, |s| s == other_string) -// } -// Some(OwnedLiteral::LPositiveInteger(other_num)) => { -// return self.as_u64().map_or(false, |n| n == *other_num) -// } -// } -// false -// } +impl CompareContextElement for serde_json::Value { + fn equals(&self, other: &dyn ContextElement) -> bool { + // println!("equals json {:?} | {:?}", self, other); + // Handle other serde_json::Value + match other.to_any().downcast_ref::() { + None => (), + Some(other_json_value) => match (self, other_json_value) { + // Non-scalar values not caught in the renderer by the + // identical-path shortcut are always not equal. + (serde_json::Value::Array(_), _) + | (_, serde_json::Value::Array(_)) + | (serde_json::Value::Object(_), _) + | (_, serde_json::Value::Object(_)) => return false, + _ => return self == other_json_value, + }, + } + // Handle literals + match other.to_any().downcast_ref::() { + None => (), + Some(OwnedLiteral::LString(other_string)) => { + return self.as_str().map_or(false, |s| s == other_string) + } + Some(OwnedLiteral::LPositiveInteger(other_num)) => { + return self.as_u64().map_or(false, |n| n == *other_num) + } + } + false + } -// fn partial_compare(&self, other: &dyn ContextElement) -> Option { -// // println!("partial_compare json {:?} | {:?}", self, other); -// // Handle type coerced objects + fn partial_compare(&self, other: &dyn ContextElement) -> Option { + // println!("partial_compare json {:?} | {:?}", self, other); + // Handle type coerced objects -// // When doing a greater than or less than comparison, -// // javascript coerces objects into "[object Object]". -// if let serde_json::Value::Object(_) = self { -// return OwnedLiteral::LString(self.render(&Vec::new()).unwrap_or("".to_owned())) -// .partial_compare(other); -// } + // When doing a greater than or less than comparison, + // javascript coerces objects into "[object Object]". + if let serde_json::Value::Object(_) = self { + return OwnedLiteral::LString(self.render(&Vec::new()).unwrap_or("".to_owned())) + .partial_compare(other); + } -// // When doing a greater than or less than comparison -// // javascript turns arrays into strings. -// if let serde_json::Value::Array(_) = self { -// return OwnedLiteral::LString(self.render(&Vec::new()).unwrap_or("".to_owned())) -// .partial_compare(other); -// } + // When doing a greater than or less than comparison + // javascript turns arrays into strings. + if let serde_json::Value::Array(_) = self { + return OwnedLiteral::LString(self.render(&Vec::new()).unwrap_or("".to_owned())) + .partial_compare(other); + } -// // Handle other serde_json::Value -// match other.to_any().downcast_ref::() { -// None => (), -// Some(other_json_value) => { -// return match (self, other_json_value) { -// ( -// serde_json::Value::Bool(self_boolean), -// serde_json::Value::Bool(other_boolean), -// ) => self_boolean.partial_cmp(other_boolean), -// ( -// serde_json::Value::Number(self_number), -// serde_json::Value::Number(other_number), -// ) => return compare_json_numbers(self_number, other_number), -// ( -// serde_json::Value::String(self_string), -// serde_json::Value::Number(other_number), -// ) => return compare_json_numbers(self_string, other_number), -// ( -// serde_json::Value::Number(self_number), -// serde_json::Value::String(other_string), -// ) => return compare_json_numbers(self_number, other_string), -// ( -// serde_json::Value::String(self_string), -// serde_json::Value::String(other_string), -// ) => self_string.partial_cmp(other_string), -// ( -// serde_json::Value::Array(self_array), -// serde_json::Value::Array(other_array), -// ) => { -// return self -// .render(&Vec::new()) -// .unwrap_or("".to_owned()) -// .partial_cmp( -// &other_json_value -// .render(&Vec::new()) -// .unwrap_or("".to_owned()), -// ) -// } -// _ => None, -// }; -// } -// } -// // Handle literals -// match other.to_any().downcast_ref::() { -// None => (), -// Some(other_literal) => match (self, other_literal) { -// (serde_json::Value::String(self_string), OwnedLiteral::LString(other_string)) => { -// return self_string.partial_cmp(other_string) -// } -// ( -// serde_json::Value::String(self_string), -// OwnedLiteral::LPositiveInteger(other_num), -// ) => return compare_json_numbers(self_string, other_literal), -// (serde_json::Value::Number(self_num), OwnedLiteral::LString(other_string)) => { -// return compare_json_numbers(self_num, other_string) -// } -// ( -// serde_json::Value::Number(self_num), -// OwnedLiteral::LPositiveInteger(other_num), -// ) => return compare_json_numbers(self_num, other_literal), -// (serde_json::Value::Array(_), _) => { -// // TODO -// todo!() -// } -// (serde_json::Value::Object(_), _) => { -// // TODO -// todo!() -// } -// (serde_json::Value::Bool(_), _) => { -// // TODO -// todo!() -// } -// (serde_json::Value::Null, _) => { -// // TODO -// todo!() -// } -// }, -// } -// None -// } -// } + // Handle other serde_json::Value + match other.to_any().downcast_ref::() { + None => (), + Some(other_json_value) => { + return match (self, other_json_value) { + ( + serde_json::Value::Bool(self_boolean), + serde_json::Value::Bool(other_boolean), + ) => self_boolean.partial_cmp(other_boolean), + ( + serde_json::Value::Number(self_number), + serde_json::Value::Number(other_number), + ) => return compare_json_numbers(self_number, other_number), + ( + serde_json::Value::String(self_string), + serde_json::Value::Number(other_number), + ) => return compare_json_numbers(self_string, other_number), + ( + serde_json::Value::Number(self_number), + serde_json::Value::String(other_string), + ) => return compare_json_numbers(self_number, other_string), + ( + serde_json::Value::String(self_string), + serde_json::Value::String(other_string), + ) => self_string.partial_cmp(other_string), + ( + serde_json::Value::Array(self_array), + serde_json::Value::Array(other_array), + ) => { + return self + .render(&Vec::new()) + .unwrap_or("".to_owned()) + .partial_cmp( + &other_json_value + .render(&Vec::new()) + .unwrap_or("".to_owned()), + ) + } + _ => None, + }; + } + } + // Handle literals + match other.to_any().downcast_ref::() { + None => (), + Some(other_literal) => match (self, other_literal) { + (serde_json::Value::String(self_string), OwnedLiteral::LString(other_string)) => { + return self_string.partial_cmp(other_string) + } + ( + serde_json::Value::String(self_string), + OwnedLiteral::LPositiveInteger(other_num), + ) => return compare_json_numbers(self_string, other_literal), + (serde_json::Value::Number(self_num), OwnedLiteral::LString(other_string)) => { + return compare_json_numbers(self_num, other_string) + } + ( + serde_json::Value::Number(self_num), + OwnedLiteral::LPositiveInteger(other_num), + ) => return compare_json_numbers(self_num, other_literal), + (serde_json::Value::Array(_), _) => { + // TODO + todo!() + } + (serde_json::Value::Object(_), _) => { + // TODO + todo!() + } + (serde_json::Value::Bool(_), _) => { + // TODO + todo!() + } + (serde_json::Value::Null, _) => { + // TODO + todo!() + } + }, + } + None + } +} -// /// Create a new vec by of references to the serde_json::Values as -// /// ContextElement trait objects so we can use its implementation of -// /// PartialOrd. -// /// -// /// You cannot implement a trait you do not define for a type you do -// /// not define, so I cannot implement PartialOrd for -// /// serde_json::value. Instead, I just re-use the PartialOrd -// /// implementation for ContextElement which unfortunately has extra -// /// overhead of downcasting. This would be a good spot for -// /// optimization. -// fn convert_vec_to_context_element(array: &Vec) -> Vec<&dyn ContextElement> { -// array.iter().map(|v| v as _).collect() -// } +/// Create a new vec by of references to the serde_json::Values as +/// ContextElement trait objects so we can use its implementation of +/// PartialOrd. +/// +/// You cannot implement a trait you do not define for a type you do +/// not define, so I cannot implement PartialOrd for +/// serde_json::value. Instead, I just re-use the PartialOrd +/// implementation for ContextElement which unfortunately has extra +/// overhead of downcasting. This would be a good spot for +/// optimization. +fn convert_vec_to_context_element(array: &Vec) -> Vec<&dyn ContextElement> { + array.iter().map(|v| v as _).collect() +} #[derive(Debug)] enum JsonNumber { diff --git a/src/renderer/breadcrumb_tree.rs b/src/renderer/breadcrumb_tree.rs index 6aeba51..fd70bd0 100644 --- a/src/renderer/breadcrumb_tree.rs +++ b/src/renderer/breadcrumb_tree.rs @@ -5,11 +5,6 @@ use crate::renderer::context_element::IntoRcIce; use std::borrow::Borrow; use std::rc::Rc; -pub struct BreadcrumbTree<'a> { - parent: Option<&'a BreadcrumbTree<'a>>, - element: BreadcrumbTreeElement<'a>, -} - #[derive(Clone, Debug)] pub enum BreadcrumbTreeElement<'a> { // Using Rc so that when we need to create BreadcrumbTrees with @@ -54,43 +49,6 @@ impl<'a> From> for BreadcrumbTreeElement<'a> { } } -impl<'a> BreadcrumbTree<'a> { - pub fn new(parent: Option<&'a BreadcrumbTree>, element: BreadcrumbTreeElement<'a>) -> Self { - BreadcrumbTree { - parent: parent, - element: element, - } - } - - pub fn get_ice(&self) -> &dyn IntoContextElement { - self.element.borrow() - } - - pub fn get_parent(&self) -> Option<&BreadcrumbTree> { - self.parent - } - - pub fn get_element(&self) -> &BreadcrumbTreeElement<'a> { - &self.element - } - - pub fn ice_iter(&'a self) -> impl Iterator { - self.breadcrumb_iter().map(|b| b.get_ice()) - } - - pub fn breadcrumb_iter(&'a self) -> BreadcrumbTreeIterator<'a> { - BreadcrumbTreeIterator(Some(self)) - } - - pub fn clone_to_new_parent(&self, parent: Option<&'a BreadcrumbTree>) -> Self { - // TODO: Maybe not needed anymore? - BreadcrumbTree { - parent: parent, - element: self.element.clone(), - } - } -} - impl<'a> Borrow for BreadcrumbTreeElement<'a> { fn borrow(&self) -> &(dyn IntoContextElement + 'a) { match self { @@ -99,24 +57,3 @@ impl<'a> Borrow for BreadcrumbTreeElement<'a> { } } } - -impl<'a> IntoIterator for &'a BreadcrumbTree<'a> { - type Item = &'a BreadcrumbTree<'a>; - type IntoIter = BreadcrumbTreeIterator<'a>; - - fn into_iter(self) -> BreadcrumbTreeIterator<'a> { - self.breadcrumb_iter() - } -} - -pub struct BreadcrumbTreeIterator<'a>(Option<&'a BreadcrumbTree<'a>>); - -impl<'a> Iterator for BreadcrumbTreeIterator<'a> { - type Item = &'a BreadcrumbTree<'a>; - - fn next(&mut self) -> Option { - let ret = self.0; - self.0 = self.0.map(|node| node.get_parent()).flatten(); - ret - } -} diff --git a/src/renderer/context_element.rs b/src/renderer/context_element.rs index 32f005d..8be76b9 100644 --- a/src/renderer/context_element.rs +++ b/src/renderer/context_element.rs @@ -1,5 +1,5 @@ use crate::parser::Filter; -use crate::renderer::breadcrumb_tree::BreadcrumbTree; +use crate::renderer::breadcrumb_tree::BreadcrumbTreeElement; use crate::renderer::errors::RenderError; use crate::renderer::errors::WalkError; use crate::renderer::DustRenderer; @@ -106,7 +106,7 @@ pub trait IntoContextElement: Debug + Walkable /* + CloneIntoBoxedContextElement fn into_context_element<'a>( &'a self, renderer: &DustRenderer, - breadcrumbs: Option<&'a BreadcrumbTree<'a>>, + breadcrumbs: &'a Vec>, ) -> Option>; } @@ -114,7 +114,7 @@ impl IntoContextElement for C { fn into_context_element<'a>( &'a self, renderer: &DustRenderer, - breadcrumbs: Option<&'a BreadcrumbTree<'a>>, + breadcrumbs: &'a Vec>, ) -> Option> { Some(IceResult::from_borrowed(self)) } @@ -161,7 +161,7 @@ impl<'a> IntoContextElement for IceResult<'a> { fn into_context_element<'b>( &'b self, renderer: &DustRenderer, - breadcrumbs: Option<&'b BreadcrumbTree<'b>>, + breadcrumbs: &'b Vec>, ) -> Option> { match self { IceResult::Owned(rc_ce) => Some(IceResult::from_borrowed(rc_ce.as_ref())), diff --git a/src/renderer/iteration_context.rs b/src/renderer/iteration_context.rs index 9830137..687fb71 100644 --- a/src/renderer/iteration_context.rs +++ b/src/renderer/iteration_context.rs @@ -1,6 +1,9 @@ +use crate::renderer::breadcrumb_tree::BreadcrumbTreeElement; use crate::renderer::context_element::CompareContextElement; use crate::renderer::context_element::ContextElement; +use crate::renderer::context_element::IceResult; use crate::renderer::context_element::IntoContextElement; +use crate::renderer::DustRenderer; use crate::renderer::Loopable; use crate::renderer::RenderError; use crate::renderer::Renderable; @@ -16,7 +19,7 @@ use std::cmp::Ordering; /// Functions the same as the injected parameters contexts for /// helpers/partials with parameters but this has no need for storing /// breadcrumbs since its simply storing two integers. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct IterationContext { idx: OwnedLiteral, len: OwnedLiteral, @@ -32,32 +35,13 @@ impl IterationContext { } } -impl ContextElement for IterationContext {} - -impl Truthiness for IterationContext { - fn is_truthy(&self) -> bool { - // 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 `{.}`. - true - } -} - -impl Renderable for IterationContext { - 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 Loopable for IterationContext { - 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::new() +impl IntoContextElement for IterationContext { + fn into_context_element<'b>( + &'b self, + renderer: &DustRenderer, + breadcrumbs: &'b Vec>, + ) -> Option> { + panic!("into_context_element cannot be called on pseudo elements"); } } @@ -74,15 +58,3 @@ impl Walkable for IterationContext { true } } - -impl CompareContextElement for IterationContext { - fn equals(&self, other: &dyn ContextElement) -> bool { - // TODO: Does this ever happen? perhaps I should have a panic here. - false - } - - fn partial_compare(&self, other: &dyn ContextElement) -> Option { - // TODO: Does this ever happen? perhaps I should have a panic here. - None - } -} diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 6baa196..11f75f8 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -4,7 +4,7 @@ mod breadcrumb_tree; mod context_element; mod errors; mod inline_partial_tree; -// mod iteration_context; +mod iteration_context; mod parameters_context; mod renderer; mod tree_walking; diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index f286e68..926def2 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -2,7 +2,6 @@ use crate::parser::Filter; use crate::parser::KVPair; use crate::parser::OwnedLiteral; use crate::parser::RValue; -use crate::renderer::breadcrumb_tree::BreadcrumbTree; use crate::renderer::breadcrumb_tree::BreadcrumbTreeElement; use crate::renderer::context_element::CompareContextElement; use crate::renderer::context_element::ContextElement; @@ -23,13 +22,13 @@ use std::rc::Rc; #[derive(Debug)] pub struct ParametersContext<'a> { - params: HashMap<&'a str, BreadcrumbTreeElement<'a>>, + params: HashMap<&'a str, (&'a RValue<'a>, Option>)>, } impl<'a> ParametersContext<'a> { pub fn new( renderer: &DustRenderer, - breadcrumbs: Option<&'a BreadcrumbTree>, + breadcrumbs: &'a Vec>, params: &'a Vec, ) -> Self { // If the parameter is a Path, then we resolve it immediately @@ -40,39 +39,46 @@ impl<'a> ParametersContext<'a> { // then those are resolved at the time of access rather than // the time of assignment, so we leave them into their // original IntoContextElement state. - let rendered_params: HashMap<&'a str, BreadcrumbTreeElement<'a>> = params - .iter() - .map(|kvpair| { - let k = kvpair.key; - let v: Option> = match &kvpair.value { - RValue::RVLiteral(owned_literal) => { - Some(BreadcrumbTreeElement::from_borrowed(&kvpair.value)) - } - RValue::RVPath(path) => kvpair - .value - .into_context_element(renderer, breadcrumbs) - .map(std::convert::From::from), - RValue::RVTemplate(template) => { - Some(BreadcrumbTreeElement::from_borrowed(&kvpair.value)) - } - }; - v.map(|some_v| (k, some_v)) - }) - // TODO: Should a None value here be the same as a key not existing, or should we store the Nones? - .filter_map(|pair| pair) - .collect(); + let rendered_params: HashMap<&'a str, (&'a RValue<'a>, Option>)> = + params + .iter() + .map(|kvpair| { + let k = kvpair.key; + let v: Option> = match &kvpair.value { + RValue::RVLiteral(owned_literal) => { + Some(BreadcrumbTreeElement::from_borrowed(&kvpair.value)) + } + RValue::RVPath(path) => kvpair + .value + .into_context_element(renderer, breadcrumbs) + .map(std::convert::From::from), + RValue::RVTemplate(template) => { + Some(BreadcrumbTreeElement::from_borrowed(&kvpair.value)) + } + }; + (k, (&kvpair.value, v)) + }) + .collect(); ParametersContext { params: rendered_params, } } + + pub fn get_original_rvalue(&self, segment: &str) -> Option<&'a RValue<'a>> { + self.params.get(segment).map(|(rvalue, _bte)| *rvalue) + } + + pub fn contains_key(&self, segment: &str) -> bool { + self.params.contains_key(segment) + } } impl<'a> IntoContextElement for ParametersContext<'a> { fn into_context_element<'b>( &'b self, renderer: &DustRenderer, - breadcrumbs: Option<&'b BreadcrumbTree<'b>>, + breadcrumbs: &'b Vec>, ) -> Option> { panic!("into_context_element cannot be called on pseudo elements"); } @@ -80,10 +86,10 @@ impl<'a> IntoContextElement for ParametersContext<'a> { impl<'a> Walkable for ParametersContext<'a> { fn walk(&self, segment: &str) -> Result<&dyn IntoContextElement, WalkError> { - self.params - .get(segment) - .map(|bte| bte.borrow()) - .ok_or(WalkError::CantWalk) + match self.params.get(segment).map(|(_rvalue, bte)| bte) { + Some(Some(bte)) => Ok(bte.borrow()), + _ => Err(WalkError::CantWalk), + } } fn is_pseudo_element(&self) -> bool { @@ -95,7 +101,7 @@ impl<'a> IntoContextElement for RValue<'a> { fn into_context_element<'b>( &'b self, renderer: &DustRenderer, - breadcrumbs: Option<&'b BreadcrumbTree<'b>>, + breadcrumbs: &'b Vec>, ) -> Option> { match self { RValue::RVLiteral(owned_literal) => Some(IceResult::from_borrowed(owned_literal)), diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index 428d1c3..6759c53 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -2,20 +2,24 @@ use crate::parser::template; use crate::parser::Body; use crate::parser::DustTag; use crate::parser::Filter; +use crate::parser::KVPair; use crate::parser::PartialNameElement; use crate::parser::Path; +use crate::parser::RValue; use crate::parser::Special; use crate::parser::Template; use crate::parser::TemplateElement; -use crate::renderer::breadcrumb_tree::BreadcrumbTree; use crate::renderer::breadcrumb_tree::BreadcrumbTreeElement; use crate::renderer::context_element::ContextElement; +use crate::renderer::context_element::IceResult; use crate::renderer::context_element::IntoContextElement; +use crate::renderer::context_element::Walkable; 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::iteration_context::IterationContext; use crate::renderer::parameters_context::ParametersContext; use crate::renderer::tree_walking::walk_path; use std::borrow::Borrow; @@ -49,15 +53,16 @@ impl<'a> DustRenderer<'a> { where C: IntoContextElement, { - let breadcrumbs = - context.map(|ctx| BreadcrumbTree::new(None, BreadcrumbTreeElement::from_borrowed(ctx))); + let breadcrumbs = context + .map(|ctx| vec![BreadcrumbTreeElement::from_borrowed(ctx)]) + .unwrap_or(Vec::new()); self.render_template(name, breadcrumbs.as_ref(), None) } pub fn render_template( &'a self, name: &str, - breadcrumbs: Option<&'a BreadcrumbTree>, + breadcrumbs: &'a Vec>, blocks: Option<&'a InlinePartialTreeElement<'a>>, ) -> Result { let main_template = match self.templates.get(name) { @@ -78,7 +83,7 @@ impl<'a> DustRenderer<'a> { fn render_maybe_body( &'a self, body: &'a Option, - breadcrumbs: Option<&'a BreadcrumbTree>, + breadcrumbs: &'a Vec>, blocks: &'a BlockContext<'a>, ) -> Result { match body { @@ -90,7 +95,7 @@ impl<'a> DustRenderer<'a> { fn render_body( &'a self, body: &'a Body, - breadcrumbs: Option<&'a BreadcrumbTree>, + breadcrumbs: &'a Vec>, blocks: &'a BlockContext<'a>, ) -> Result { let mut output = String::new(); @@ -110,7 +115,7 @@ impl<'a> DustRenderer<'a> { pub fn render_partial_name( &'a self, body: &'a Vec, - breadcrumbs: Option<&'a BreadcrumbTree>, + breadcrumbs: &'a Vec>, ) -> Result { let converted_to_template_elements: Vec> = body.into_iter().map(|e| e.into()).collect(); @@ -118,7 +123,7 @@ impl<'a> DustRenderer<'a> { // cannot contain blocks or inline partials, so we use a blank // BlockContext. let empty_block_context = BlockContext { - breadcrumbs: None, + breadcrumbs: &Vec::new(), blocks: &InlinePartialTreeElement::new(None, HashMap::new()), }; self.render_body( @@ -133,7 +138,7 @@ impl<'a> DustRenderer<'a> { fn render_tag( &'a self, tag: &'a DustTag, - breadcrumbs: Option<&'a BreadcrumbTree>, + breadcrumbs: &'a Vec>, blocks: &'a BlockContext<'a>, ) -> Result { match tag { @@ -170,8 +175,7 @@ impl<'a> DustRenderer<'a> { let val = walk_path(breadcrumbs, &container.path.keys) .map(|ice| ice.into_context_element(self, breadcrumbs)); match val { - Err(WalkError::CantWalk) => { - // TODO + Err(WalkError::CantWalk) | Ok(None) => { let new_breadcrumbs = self.new_breadcrumbs_section( breadcrumbs, None, @@ -179,34 +183,485 @@ impl<'a> DustRenderer<'a> { &container.explicit_context, None, ); + return self.render_maybe_body( + &container.else_contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ); } - Ok(final_val) => { - // TODO + Ok(Some(final_val)) => { + let context_element = final_val.get_context_element_reference(); + return if context_element.is_truthy() { + match &container.contents { + None => Ok("".to_owned()), + Some(body) => { + let loop_elements: Vec<&dyn ContextElement> = + context_element.get_loop_elements(); + if loop_elements.is_empty() { + // Scalar value + let new_breadcrumbs = self.new_breadcrumbs_section( + breadcrumbs, + None, + Some(&injected_context), + &container.explicit_context, + Some(context_element), + ); + self.render_body( + body, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ) + } else { + // Array-like value + let total_length = loop_elements.len(); + let rendered_results: Result, RenderError> = + loop_elements + .into_iter() + .enumerate() + .map(|(i, array_elem)| { + let index_context = + IterationContext::new(i, total_length); + 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 + .as_ref() + .unwrap_or(breadcrumbs), + blocks, + ) + }) + .collect(); + let rendered_slice: &[String] = &rendered_results?; + return Ok(rendered_slice.join("")); + } + } + } + } else { + // Oddly enough if the value is falsey (like + // 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_section( + breadcrumbs, + None, + Some(&injected_context), + &container.explicit_context, + None, + ); + self.render_maybe_body( + &container.else_contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ) + }; + } + } + } + DustTag::DTExists(container) => { + let new_breadcrumbs = self.new_breadcrumbs_partial( + breadcrumbs, + breadcrumbs, + None, + &container.explicit_context, + ); + let val = walk_path(breadcrumbs, &container.path.keys) + .map(|ice| ice.into_context_element(self, breadcrumbs)); + return match val { + Ok(Some(v)) if v.get_context_element_reference().is_truthy() => self + .render_maybe_body( + &container.contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ), + _ => self.render_maybe_body( + &container.else_contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ), + }; + } + DustTag::DTNotExists(container) => { + let new_breadcrumbs = self.new_breadcrumbs_partial( + breadcrumbs, + breadcrumbs, + None, + &container.explicit_context, + ); + let val = walk_path(breadcrumbs, &container.path.keys) + .map(|ice| ice.into_context_element(self, breadcrumbs)); + return match val { + Ok(Some(v)) if v.get_context_element_reference().is_truthy() => self + .render_maybe_body( + &container.else_contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ), + _ => self.render_maybe_body( + &container.contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ), + }; + } + DustTag::DTPartial(partial) => { + let partial_name = self.render_partial_name(&partial.name, breadcrumbs)?; + if partial.params.is_empty() { + let new_breadcrumbs = self.new_breadcrumbs_partial( + breadcrumbs, + breadcrumbs, + None, + &partial.explicit_context, + ); + let rendered_content = self.render_template( + &partial_name, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + Some(blocks.blocks), + )?; + return Ok(rendered_content); + } else { + let injected_context = + ParametersContext::new(self, breadcrumbs, &partial.params); + let new_breadcrumbs = self.new_breadcrumbs_partial( + breadcrumbs, + breadcrumbs, + Some(&injected_context), + &partial.explicit_context, + ); + let rendered_content = self.render_template( + &partial_name, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + Some(blocks.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) => { + let new_breadcrumbs = self.new_breadcrumbs_partial( + breadcrumbs, + blocks.breadcrumbs, + None, + &named_block.explicit_context, + ); + 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), + blocks, + ), + Some(inline_partial) => self.render_maybe_body( + inline_partial, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ), + }; + } + DustTag::DTHelperEquals(parameterized_block) => { + let new_breadcrumbs = self.new_breadcrumbs_partial( + breadcrumbs, + breadcrumbs, + None, + ¶meterized_block.explicit_context, + ); + + let param_map = + ParametersContext::new(self, breadcrumbs, ¶meterized_block.params); + + if !param_map.contains_key("key") { + return Ok("".to_owned()); + } + let left_side = param_map + .walk("key") + .map(|ice| ice.into_context_element(self, breadcrumbs)); + let right_side = param_map + .walk("value") + .map(|ice| ice.into_context_element(self, breadcrumbs)); + // Special case: when comparing two RVPaths, if the + // path points to the same value then they are + // equal. This is particularly important for objects + // which compare memory locations rather than contents + // (javascript object equality). + if Self::new_are_paths_identical(&left_side, &right_side) + || left_side.as_ref().map(|maybe_ice| { + maybe_ice + .as_ref() + .map(|ice| ice.get_context_element_reference()) + }) == right_side.as_ref().map(|maybe_ice| { + maybe_ice + .as_ref() + .map(|ice| ice.get_context_element_reference()) + }) + { + return self.render_maybe_body( + ¶meterized_block.contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ); + } else { + return self.render_maybe_body( + ¶meterized_block.else_contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ); + } + } + DustTag::DTHelperNotEquals(parameterized_block) => { + let new_breadcrumbs = self.new_breadcrumbs_partial( + breadcrumbs, + breadcrumbs, + None, + ¶meterized_block.explicit_context, + ); + + let param_map = + ParametersContext::new(self, breadcrumbs, ¶meterized_block.params); + + if !param_map.contains_key("key") { + return Ok("".to_owned()); + } + let left_side = param_map + .walk("key") + .map(|ice| ice.into_context_element(self, breadcrumbs)); + let right_side = param_map + .walk("value") + .map(|ice| ice.into_context_element(self, breadcrumbs)); + // Special case: when comparing two RVPaths, if the + // path points to the same value then they are + // equal. This is particularly important for objects + // which compare memory locations rather than contents + // (javascript object equality). + if Self::new_are_paths_identical(&left_side, &right_side) + || left_side.as_ref().map(|maybe_ice| { + maybe_ice + .as_ref() + .map(|ice| ice.get_context_element_reference()) + }) == right_side.as_ref().map(|maybe_ice| { + maybe_ice + .as_ref() + .map(|ice| ice.get_context_element_reference()) + }) + { + return self.render_maybe_body( + ¶meterized_block.else_contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ); + } else { + return self.render_maybe_body( + ¶meterized_block.contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ); + } + } + DustTag::DTHelperGreaterThan(parameterized_block) => { + let new_breadcrumbs = self.new_breadcrumbs_partial( + breadcrumbs, + breadcrumbs, + None, + ¶meterized_block.explicit_context, + ); + + let param_map = + ParametersContext::new(self, breadcrumbs, ¶meterized_block.params); + if !param_map.contains_key("key") { + return Ok("".to_owned()); + } + let left_side = param_map + .walk("key") + .map(|ice| ice.into_context_element(self, breadcrumbs)); + let right_side = param_map + .walk("value") + .map(|ice| ice.into_context_element(self, breadcrumbs)); + match (left_side, right_side) { + (Ok(Some(left_side_unwrapped)), Ok(Some(right_side_unwrapped))) => { + if left_side_unwrapped.get_context_element_reference() + > right_side_unwrapped.get_context_element_reference() + { + return self.render_maybe_body( + ¶meterized_block.contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ); + } else { + return self.render_maybe_body( + ¶meterized_block.else_contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ); + } + } + _ => { + return self.render_maybe_body( + ¶meterized_block.else_contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ) + } + } + } + DustTag::DTHelperGreaterThanOrEquals(parameterized_block) => { + let new_breadcrumbs = self.new_breadcrumbs_partial( + breadcrumbs, + breadcrumbs, + None, + ¶meterized_block.explicit_context, + ); + + let param_map = + ParametersContext::new(self, breadcrumbs, ¶meterized_block.params); + if !param_map.contains_key("key") { + return Ok("".to_owned()); + } + let left_side = param_map + .walk("key") + .map(|ice| ice.into_context_element(self, breadcrumbs)); + let right_side = param_map + .walk("value") + .map(|ice| ice.into_context_element(self, breadcrumbs)); + match (left_side, right_side) { + (Ok(Some(left_side_unwrapped)), Ok(Some(right_side_unwrapped))) => { + if left_side_unwrapped.get_context_element_reference() + >= right_side_unwrapped.get_context_element_reference() + { + return self.render_maybe_body( + ¶meterized_block.contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ); + } else { + return self.render_maybe_body( + ¶meterized_block.else_contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ); + } + } + _ => { + return self.render_maybe_body( + ¶meterized_block.else_contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ) + } + } + } + DustTag::DTHelperLessThan(parameterized_block) => { + let new_breadcrumbs = self.new_breadcrumbs_partial( + breadcrumbs, + breadcrumbs, + None, + ¶meterized_block.explicit_context, + ); + + let param_map = + ParametersContext::new(self, breadcrumbs, ¶meterized_block.params); + if !param_map.contains_key("key") { + return Ok("".to_owned()); + } + let left_side = param_map + .walk("key") + .map(|ice| ice.into_context_element(self, breadcrumbs)); + let right_side = param_map + .walk("value") + .map(|ice| ice.into_context_element(self, breadcrumbs)); + match (left_side, right_side) { + (Ok(Some(left_side_unwrapped)), Ok(Some(right_side_unwrapped))) => { + if left_side_unwrapped.get_context_element_reference() + < right_side_unwrapped.get_context_element_reference() + { + return self.render_maybe_body( + ¶meterized_block.contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ); + } else { + return self.render_maybe_body( + ¶meterized_block.else_contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ); + } + } + _ => { + return self.render_maybe_body( + ¶meterized_block.else_contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ) + } + } + } + DustTag::DTHelperLessThanOrEquals(parameterized_block) => { + let new_breadcrumbs = self.new_breadcrumbs_partial( + breadcrumbs, + breadcrumbs, + None, + ¶meterized_block.explicit_context, + ); + + let param_map = + ParametersContext::new(self, breadcrumbs, ¶meterized_block.params); + if !param_map.contains_key("key") { + return Ok("".to_owned()); + } + let left_side = param_map + .walk("key") + .map(|ice| ice.into_context_element(self, breadcrumbs)); + let right_side = param_map + .walk("value") + .map(|ice| ice.into_context_element(self, breadcrumbs)); + match (left_side, right_side) { + (Ok(Some(left_side_unwrapped)), Ok(Some(right_side_unwrapped))) => { + if left_side_unwrapped.get_context_element_reference() + <= right_side_unwrapped.get_context_element_reference() + { + return self.render_maybe_body( + ¶meterized_block.contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ); + } else { + return self.render_maybe_body( + ¶meterized_block.else_contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ); + } + } + _ => { + return self.render_maybe_body( + ¶meterized_block.else_contents, + new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), + blocks, + ) } } } _ => panic!("Unsupported tag"), } + Ok("".to_owned()) } - /// Returns a option of a tuple of (parent, new_node_elements) - /// which can then be formed into new BreadcrumbTreeNodes - /// - /// If None is returned, then it is a signal to simply re-use the - /// existing breadcrumbs. - /// - /// Otherwise, the parent (which may be None, especially for - /// explicit contexts) and the additional node elements (which may - /// be empty) should be combined into a final BreadcrumbTreeNode fn new_breadcrumbs_section<'b>( &'b self, - maybe_breadcrumbs: Option<&'b BreadcrumbTree>, + breadcrumbs: &'b Vec>, index_context: Option<&'b dyn IntoContextElement>, injected_context: Option<&'b dyn IntoContextElement>, explicit_context: &Option>, new_context_element: Option<&'b dyn ContextElement>, - ) -> Option<(Option<&'b BreadcrumbTree>, Vec>)> { + ) -> Option>> { // 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. @@ -222,50 +677,40 @@ impl<'a> DustRenderer<'a> { // If there is an explicit context, then drop all the current // context - let parent = match explicit_context { - Some(_) => None, - None => maybe_breadcrumbs, + let mut new_stack = match explicit_context { + Some(_) => Vec::with_capacity(4), + None => breadcrumbs.clone(), }; - let mut new_nodes: Vec = Vec::new(); explicit_context.as_ref().map(|path| { - walk_path(maybe_breadcrumbs, &path.keys) - .map(|ice| ice.into_context_element(self, maybe_breadcrumbs)) + walk_path(breadcrumbs, &path.keys) + .map(|ice| ice.into_context_element(self, breadcrumbs)) .ok() .flatten() .map(|val| { if val.get_context_element_reference().is_truthy() { - new_nodes.push(std::convert::From::from(val)) + new_stack.push(std::convert::From::from(val)) } }); }); - injected_context.map(|ctx| new_nodes.push(BreadcrumbTreeElement::from_borrowed(ctx))); + injected_context.map(|ctx| new_stack.push(BreadcrumbTreeElement::from_borrowed(ctx))); new_context_element.map(|ctx| { - new_nodes.push(BreadcrumbTreeElement::from_borrowed( + new_stack.push(BreadcrumbTreeElement::from_borrowed( ctx.from_context_element(), )) }); - index_context.map(|ctx| new_nodes.push(BreadcrumbTreeElement::from_borrowed(ctx))); + index_context.map(|ctx| new_stack.push(BreadcrumbTreeElement::from_borrowed(ctx))); - Some((parent, new_nodes)) + Some(new_stack) } - /// Returns a option of a tuple of (parent, new_node_elements) - /// which can then be formed into new BreadcrumbTreeNodes - /// - /// If None is returned, then it is a signal to simply re-use the - /// existing breadcrumbs. - /// - /// Otherwise, the parent (which may be None, especially for - /// explicit contexts) and the additional node elements (which may - /// be empty) should be combined into a final BreadcrumbTreeNode fn new_breadcrumbs_partial<'b>( &'b self, - maybe_breadcrumbs: Option<&'b BreadcrumbTree>, - explicit_context_maybe_breadcrumbs: Option<&'b BreadcrumbTree>, + breadcrumbs: &'b Vec>, + explicit_context_breadcrumbs: &'a Vec>, injected_context: Option<&'b dyn IntoContextElement>, explicit_context: &Option>, - ) -> Option<(Option<&'b BreadcrumbTree>, Vec>)> { + ) -> Option>> { // 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. @@ -276,11 +721,10 @@ impl<'a> DustRenderer<'a> { // If there is an explicit context, then drop all the current // context - let mut parent = match explicit_context { - Some(_) => None, - None => maybe_breadcrumbs, + let mut new_stack = match explicit_context { + Some(_) => Vec::with_capacity(3), + None => breadcrumbs.clone(), }; - let mut new_nodes: Vec = Vec::new(); injected_context.map(|ctx| { // Special case: when there is no explicit context, the @@ -289,56 +733,69 @@ impl<'a> DustRenderer<'a> { // added after the current context but before the explicit // context. match explicit_context { - None => { - let (new_parent, passed_nodes) = - Self::split_tree_at_predicate(parent, |b| b.get_ice().is_pseudo_element()); - parent = new_parent; - new_nodes.extend(passed_nodes.iter().map(|b| b.get_element().clone())); - } - _ => new_nodes.push(BreadcrumbTreeElement::from_borrowed(ctx)), + None => new_stack.insert( + Self::get_index_of_first_non_pseudo_element(&new_stack).unwrap_or(0), + BreadcrumbTreeElement::from_borrowed(ctx), + ), + _ => new_stack.push(BreadcrumbTreeElement::from_borrowed(ctx)), } }); + explicit_context.as_ref().map(|path| { // TODO: should resolving the value here use // explicit_context_maybe_breadcrumbs or // maybe_breadcrumbs? - walk_path(explicit_context_maybe_breadcrumbs, &path.keys) - .map(|ice| ice.into_context_element(self, maybe_breadcrumbs)) + walk_path(explicit_context_breadcrumbs, &path.keys) + .map(|ice| ice.into_context_element(self, breadcrumbs)) .ok() .flatten() .map(|val| { if val.get_context_element_reference().is_truthy() { - new_nodes.push(std::convert::From::from(val)); + new_stack.push(std::convert::From::from(val)); } }); }); - Some((parent, new_nodes)) + Some(new_stack) } - /// Returns a Breadcrumb tree where all the bottom nodes that do - /// not match the predicate and the first node that match the - /// predicate are shaved off, and a list of those nodes that are - /// shaved off. - fn split_tree_at_predicate<'b, F>( - maybe_breadcrumbs: Option<&'b BreadcrumbTree>, - f: F, - ) -> (Option<&'b BreadcrumbTree<'b>>, Vec<&'b BreadcrumbTree<'b>>) - where - F: Fn(&'b BreadcrumbTree) -> bool, - { - match maybe_breadcrumbs { - None => return (None, Vec::new()), - Some(breadcrumbs) => { - let mut passed_nodes: Vec<&'b BreadcrumbTree<'b>> = Vec::new(); - for tree_node in breadcrumbs { - passed_nodes.push(tree_node); - if f(tree_node) { - return (tree_node.get_parent(), passed_nodes); - } + fn are_paths_identical<'b>( + param_map: &ParametersContext<'b>, + left_key: &str, + right_key: &str, + ) -> bool { + match ( + param_map.get_original_rvalue(left_key), + param_map.get_original_rvalue(right_key), + ) { + (None, _) => false, + (_, None) => false, + (Some(key_rval), Some(value_rval)) => match (key_rval, value_rval) { + (RValue::RVPath(key_path), RValue::RVPath(value_path)) => { + key_path.keys == value_path.keys } - return (None, passed_nodes); - } + _ => false, + }, + } + } + + fn new_are_paths_identical<'b>( + left_side: &Result>, WalkError>, + right_side: &Result>, WalkError>, + ) -> bool { + let left_resolved = left_side.as_ref().map(|maybe_ice| { + maybe_ice + .as_ref() + .map(|ice| ice.get_context_element_reference()) + }); + let right_resolved = right_side.as_ref().map(|maybe_ice| { + maybe_ice + .as_ref() + .map(|ice| ice.get_context_element_reference()) + }); + match (left_resolved, right_resolved) { + (Ok(Some(lce)), Ok(Some(rce))) => lce as *const _ == rce as *const _, + _ => false, } } @@ -356,10 +813,18 @@ impl<'a> DustRenderer<'a> { } final_filters } + + fn get_index_of_first_non_pseudo_element<'b>( + breadcrumbs: &'b Vec>, + ) -> Option { + breadcrumbs.iter().rposition(|b| { + !std::borrow::Borrow::::borrow(b).is_pseudo_element() + }) + } } struct BlockContext<'a> { /// The breadcrumbs at the time of entering the current partial - breadcrumbs: Option<&'a BreadcrumbTree<'a>>, + breadcrumbs: &'a Vec>, blocks: &'a InlinePartialTreeElement<'a>, } diff --git a/src/renderer/tree_walking.rs b/src/renderer/tree_walking.rs index a37c208..023c8b7 100644 --- a/src/renderer/tree_walking.rs +++ b/src/renderer/tree_walking.rs @@ -1,4 +1,4 @@ -use crate::renderer::breadcrumb_tree::BreadcrumbTree; +use crate::renderer::breadcrumb_tree::BreadcrumbTreeElement; use crate::renderer::context_element::IntoContextElement; use crate::renderer::WalkError; use std::borrow::Borrow; @@ -38,30 +38,33 @@ where } fn get_first_non_pseudo_element<'a>( - breadcrumbs: &'a BreadcrumbTree, -) -> Option<&'a BreadcrumbTree<'a>> { + breadcrumbs: &'a Vec>, +) -> Option<&'a BreadcrumbTreeElement<'a>> { breadcrumbs - .breadcrumb_iter() - .filter(|b| b.get_ice().is_pseudo_element()) + .iter() + .rev() + .filter(|b| { + !std::borrow::Borrow::::borrow(*b).is_pseudo_element() + }) .next() } pub fn walk_path<'a, P>( - maybe_breadcrumbs: Option<&'a BreadcrumbTree>, + breadcrumbs: &'a Vec>, path: &Vec

, ) -> Result<&'a dyn IntoContextElement, WalkError> where P: Borrow, { - match (maybe_breadcrumbs, path.first()) { + match (breadcrumbs.last(), path.first()) { (None, _) => return Err(WalkError::CantWalk), - (Some(breadcrumbs), None) => return Ok(breadcrumbs.get_ice()), - (Some(breadcrumbs), Some(path_first)) if path_first.borrow() == "." => { + (Some(last_elem), None) => return Ok(last_elem.borrow()), + (Some(_), Some(path_first)) if path_first.borrow() == "." => { 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.get_ice(), &path[1..]) { + match walk_path_from_single_level(current_context.borrow(), &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 @@ -71,9 +74,9 @@ where } }; } - (Some(breadcrumbs), Some(path_first)) => { - for context in breadcrumbs.ice_iter() { - match walk_path_from_single_level(context, path) { + (Some(_), Some(path_first)) => { + for context in breadcrumbs.iter().rev() { + match walk_path_from_single_level(context.borrow(), path) { // If no walking was done at all, keep looping WalkResult::NoWalk => {} // If we partially walked then stop trying to find