use crate::parser::template; use crate::parser::Body; use crate::parser::DustTag; use crate::parser::Filter; use crate::parser::OwnedLiteral; 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::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::select_context::SelectContext; use crate::renderer::walking::walk_path; use std::borrow::Borrow; use std::collections::HashMap; #[derive(Clone, Debug)] pub struct DustRenderer<'a> { templates: HashMap>, } pub fn compile_template<'a>(source: &'a str) -> Result, CompileError> { let (_remaining, parsed_template) = template(source).map_err(|_err| CompileError { message: "Failed to compile template".to_owned(), })?; Ok(parsed_template) } impl<'a> DustRenderer<'a> { pub fn new() -> DustRenderer<'a> { DustRenderer { templates: HashMap::new(), } } pub fn load_source(&mut self, template: &'a Template, name: String) { self.templates.insert(name, template); } pub fn render(&'a self, name: &str, context: Option<&C>) -> Result where C: IntoContextElement, { 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: &'a Vec>, blocks: Option<&'a InlinePartialTreeElement<'a>>, ) -> Result { let main_template = match self.templates.get(name) { Some(tmpl) => tmpl, None => { return Err(RenderError::TemplateNotFound(name.to_owned())); } }; let extracted_inline_partials = extract_inline_partials(main_template); let new_blocks = InlinePartialTreeElement::new(blocks, extracted_inline_partials); let new_block_context = BlockContext { breadcrumbs: breadcrumbs, blocks: &new_blocks, }; self.render_body( &main_template.contents, breadcrumbs, &new_block_context, &mut None, ) } fn render_maybe_body( &'a self, body: &'a Option, breadcrumbs: &'a Vec>, blocks: &'a BlockContext<'a>, select_context: &mut Option>, ) -> Result { match body { None => Ok("".to_owned()), Some(body) => Ok(self.render_body(body, breadcrumbs, blocks, select_context)?), } } fn render_body( &'a self, body: &'a Body, breadcrumbs: &'a Vec>, blocks: &'a BlockContext<'a>, select_context: &mut Option>, ) -> Result { let mut output = String::new(); for elem in &body.elements { match elem { TemplateElement::TEIgnoredWhitespace(_) => {} TemplateElement::TESpan(span) => output.push_str(span.contents), TemplateElement::TETag(dt) => { output.push_str(&self.render_tag(dt, breadcrumbs, blocks, select_context)?); } } } Ok(output) } /// For rendering a dynamic partial's name or an rvalue template pub fn render_partial_name( &'a self, body: &'a Vec, breadcrumbs: &'a Vec>, ) -> Result { let converted_to_template_elements: Vec> = body.into_iter().map(|e| e.into()).collect(); // Simple templates like partial names and reference rvalues // cannot contain blocks or inline partials, so we use a blank // BlockContext. let empty_block_context = BlockContext { breadcrumbs: &Vec::new(), blocks: &InlinePartialTreeElement::new(None, HashMap::new()), }; self.render_body( &Body { elements: converted_to_template_elements, }, breadcrumbs, &empty_block_context, &mut None, ) } fn render_tag( &'a self, tag: &'a DustTag, breadcrumbs: &'a Vec>, blocks: &'a BlockContext<'a>, select_context: &mut Option>, ) -> Result { match tag { DustTag::DTComment(_comment) => (), DustTag::DTSpecial(special) => { return Ok(match special { Special::Space => " ", Special::NewLine => "\n", Special::CarriageReturn => "\r", Special::LeftCurlyBrace => "{", Special::RightCurlyBrace => "}", } .to_owned()) } DustTag::DTLiteralStringBlock(literal) => return Ok((*literal).to_owned()), DustTag::DTReference(reference) => { let val = walk_path(breadcrumbs, &reference.path.keys) .map(|ice| ice.into_context_element(self, breadcrumbs)); match val { Err(WalkError::CantWalk) | Ok(None) => return Ok("".to_owned()), Ok(Some(final_val)) => { return if final_val.get_context_element_reference().is_truthy() { final_val .get_context_element_reference() .render(&Self::preprocess_filters(&reference.filters)) } else { Ok("".to_owned()) }; } } } DustTag::DTSection(container) => { let injected_context = ParametersContext::new(self, breadcrumbs, &container.params, None); let val = walk_path(breadcrumbs, &container.path.keys) .map(|ice| ice.into_context_element(self, breadcrumbs)); match val { Err(WalkError::CantWalk) | Ok(None) => { let new_breadcrumbs = self.new_breadcrumbs_section( breadcrumbs, None, Some(&injected_context), &container.explicit_context, None, ); return self.render_maybe_body( &container.else_contents, new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), blocks, &mut None, ); } 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, &mut None, ) } 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, &mut None, ) }) .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, &mut None, ) }; } } } 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, &mut None, ), _ => self.render_maybe_body( &container.else_contents, new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), blocks, &mut None, ), }; } 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, &mut None, ), _ => self.render_maybe_body( &container.contents, new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), blocks, &mut None, ), }; } 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, None); 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, &mut None, ), Some(inline_partial) => self.render_maybe_body( inline_partial, new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), blocks, &mut None, ), }; } DustTag::DTHelperEquals(parameterized_block) | DustTag::DTHelperNotEquals(parameterized_block) | DustTag::DTHelperGreaterThan(parameterized_block) | DustTag::DTHelperLessThan(parameterized_block) | DustTag::DTHelperGreaterThanOrEquals(parameterized_block) | DustTag::DTHelperLessThanOrEquals(parameterized_block) => { match select_context { Some(sc) if sc.allowed_to_render_any_more_conditionals == false => { return Ok("".to_owned()) } _ => (), } let new_breadcrumbs = self.new_breadcrumbs_partial( breadcrumbs, breadcrumbs, None, ¶meterized_block.explicit_context, ); return self .perform_comparison_check( tag, breadcrumbs, select_context.as_ref().map(|sc| sc.select_parameters), ) .map(|check_result| { if check_result { select_context.as_mut().map(|sc| { sc.allowed_to_render_any_more_conditionals = false; }); self.render_maybe_body( ¶meterized_block.contents, new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), blocks, &mut None, ) } else { self.render_maybe_body( ¶meterized_block.else_contents, new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), blocks, &mut None, ) } }) .unwrap_or(Ok("".to_owned())); } DustTag::DTHelperSep(parameterized_block) => { let new_breadcrumbs = self.new_breadcrumbs_partial( breadcrumbs, breadcrumbs, None, ¶meterized_block.explicit_context, ); let index = self.get(breadcrumbs, &vec!["$idx"]); let len = self.get(breadcrumbs, &vec!["$len"]); match (index, len) { (Err(_), _) | (_, Err(_)) => { return self.render_maybe_body( ¶meterized_block.contents, new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), blocks, &mut None, ) } (Ok(index_resolved), Ok(len_resolved)) => { // Iteration contexts use OwnedLiteral::LPositiveinteger let index_cast = index_resolved .get_context_element_reference() .to_any() .downcast_ref::(); let len_cast = len_resolved .get_context_element_reference() .to_any() .downcast_ref::(); match (index_cast, len_cast) { ( Some(OwnedLiteral::LPositiveInteger(index_number)), Some(OwnedLiteral::LPositiveInteger(len_number)), ) => { if *index_number == len_number - 1 { return Ok("".to_owned()); } else { return self.render_maybe_body( ¶meterized_block.contents, new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), blocks, &mut None, ); } } _ => return Ok("".to_owned()), } } } } DustTag::DTHelperFirst(parameterized_block) => { let new_breadcrumbs = self.new_breadcrumbs_partial( breadcrumbs, breadcrumbs, None, ¶meterized_block.explicit_context, ); let index = self.get(breadcrumbs, &vec!["$idx"]); match index { Err(_) => return Ok("".to_owned()), Ok(index_resolved) => { // Iteration contexts use OwnedLiteral::LPositiveinteger let index_cast = index_resolved .get_context_element_reference() .to_any() .downcast_ref::(); match index_cast { Some(OwnedLiteral::LPositiveInteger(index_number)) => { if *index_number != 0 { return Ok("".to_owned()); } else { return self.render_maybe_body( ¶meterized_block.contents, new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), blocks, &mut None, ); } } _ => return Ok("".to_owned()), } } } } DustTag::DTHelperLast(parameterized_block) => { let new_breadcrumbs = self.new_breadcrumbs_partial( breadcrumbs, breadcrumbs, None, ¶meterized_block.explicit_context, ); let index = self.get(breadcrumbs, &vec!["$idx"]); let len = self.get(breadcrumbs, &vec!["$len"]); match (index, len) { (Err(_), _) | (_, Err(_)) => return Ok("".to_owned()), (Ok(index_resolved), Ok(len_resolved)) => { // Iteration contexts use OwnedLiteral::LPositiveinteger let index_cast = index_resolved .get_context_element_reference() .to_any() .downcast_ref::(); let len_cast = len_resolved .get_context_element_reference() .to_any() .downcast_ref::(); match (index_cast, len_cast) { ( Some(OwnedLiteral::LPositiveInteger(index_number)), Some(OwnedLiteral::LPositiveInteger(len_number)), ) => { if *index_number != len_number - 1 { return Ok("".to_owned()); } else { return self.render_maybe_body( ¶meterized_block.contents, new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), blocks, &mut None, ); } } _ => return Ok("".to_owned()), } } } } DustTag::DTHelperSelect(parameterized_block) => { let new_breadcrumbs = self.new_breadcrumbs_partial( breadcrumbs, breadcrumbs, None, ¶meterized_block.explicit_context, ); let new_breadcrumbs_ref = new_breadcrumbs.as_ref().unwrap_or(breadcrumbs); match ¶meterized_block.contents { None => return Ok("".to_owned()), Some(body) => { let param_map = ParametersContext::new( self, breadcrumbs, ¶meterized_block.params, None, ); let are_any_checks_true = body .elements .iter() .filter_map(|te| match te { TemplateElement::TETag(dt) => match dt { DustTag::DTHelperEquals(_) | DustTag::DTHelperNotEquals(_) | DustTag::DTHelperGreaterThan(_) | DustTag::DTHelperLessThan(_) | DustTag::DTHelperGreaterThanOrEquals(_) | DustTag::DTHelperLessThanOrEquals(_) => Some(dt), _ => None, }, _ => None, }) .map(|dt| { self.perform_comparison_check( dt, new_breadcrumbs_ref, Some(¶m_map), ) }) .any(|check_result| check_result.unwrap_or(false)); let select_context = SelectContext::new(¶m_map, are_any_checks_true); return self.render_maybe_body( ¶meterized_block.contents, new_breadcrumbs_ref, blocks, &mut Some(select_context), ); } } } DustTag::DTHelperAny(parameterized_block) => match select_context { Some(sc) if sc.were_any_true => { let new_breadcrumbs = self.new_breadcrumbs_partial( breadcrumbs, breadcrumbs, None, ¶meterized_block.explicit_context, ); return self.render_maybe_body( ¶meterized_block.contents, new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), blocks, &mut None, ); } _ => return Ok("".to_owned()), }, DustTag::DTHelperNone(parameterized_block) => match select_context { Some(sc) if !sc.were_any_true => { let new_breadcrumbs = self.new_breadcrumbs_partial( breadcrumbs, breadcrumbs, None, ¶meterized_block.explicit_context, ); return self.render_maybe_body( ¶meterized_block.contents, new_breadcrumbs.as_ref().unwrap_or(breadcrumbs), blocks, &mut None, ); } _ => return Ok("".to_owned()), }, DustTag::DTHelperMath(parameterized_block) => { let new_breadcrumbs = self.new_breadcrumbs_partial( breadcrumbs, breadcrumbs, None, ¶meterized_block.explicit_context, ); let new_breadcrumbs_ref = new_breadcrumbs.as_ref().unwrap_or(breadcrumbs); let param_map = ParametersContext::new(self, breadcrumbs, ¶meterized_block.params, None); match ¶meterized_block.contents { None => { return self .perform_math_operation(breadcrumbs, ¶m_map) .map(|final_val| { final_val .get_context_element_reference() .render(&Vec::new()) }) .unwrap_or(Ok("".to_owned())); } Some(body) => { // Calculate the value let calculated_value = match self.perform_math_operation(breadcrumbs, ¶m_map) { None => return Ok("".to_owned()), Some(val) => val, }; // Generate a ParametersContext with the result of the math operation as key let converted_value: BreadcrumbTreeElement<'_> = calculated_value.into(); let dummy_rvalue = RValue::RVLiteral(OwnedLiteral::LPositiveInteger(0)); let calculated_param_map: HashMap< &str, (&RValue<'_>, Option>), > = vec![("key", (&dummy_rvalue, Some(converted_value)))] .into_iter() .collect(); let calculated_context = ParametersContext::from_values(None, calculated_param_map); // calculate are_any_checks_true let are_any_checks_true = body .elements .iter() .filter_map(|te| match te { TemplateElement::TETag(dt) => match dt { DustTag::DTHelperEquals(_) | DustTag::DTHelperNotEquals(_) | DustTag::DTHelperGreaterThan(_) | DustTag::DTHelperLessThan(_) | DustTag::DTHelperGreaterThanOrEquals(_) | DustTag::DTHelperLessThanOrEquals(_) => Some(dt), _ => None, }, _ => None, }) .map(|dt| { self.perform_comparison_check( dt, new_breadcrumbs_ref, Some(&calculated_context), ) }) .any(|check_result| check_result.unwrap_or(false)); // Generate a SelectContext let select_context = SelectContext::new(&calculated_context, are_any_checks_true); // render_maybe_body return self.render_maybe_body( ¶meterized_block.contents, new_breadcrumbs_ref, blocks, &mut Some(select_context), ); } } } DustTag::DTHelperSize(parameterized_block) => { let param_map = ParametersContext::new(self, breadcrumbs, ¶meterized_block.params, None); let value = self.tap(breadcrumbs, ¶m_map, "key"); let value_ce = value.as_ref().map(|maybe_ice| { maybe_ice .as_ref() .map(|ice| ice.get_context_element_reference()) }); match value_ce { // If "key" is not on the @size tag at all, render 0. None => return Ok("0".to_owned()), // If the key value could not be found in the context, render 0. Some(Err(_)) => return Ok("0".to_owned()), Some(Ok(ce)) => { // The @size helper attempts to cast values to // numbers, and if that succeeds it uses the // number, otherwise we'll get the size of the // original type. match (ce.cast_to_type("number"), ce.is_castable()) { (Some(ice), true) => { return ice .get_context_element_reference() .get_size() .map(|ce_size| { ce_size.get_context_element_reference().render(&Vec::new()) }) .unwrap_or(Ok("".to_owned())) } (Some(_), false) => { return ce .get_size() .map(|ce_size| { ce_size.get_context_element_reference().render(&Vec::new()) }) .unwrap_or(Ok("".to_owned())) } (None, _) => { return ce .get_size() .map(|ce_size| { ce_size.get_context_element_reference().render(&Vec::new()) }) .unwrap_or(Ok("".to_owned())) } } } } } DustTag::DTHelperContextDump(parameterized_block) => { let param_map = ParametersContext::new(self, breadcrumbs, ¶meterized_block.params, None); let value = self.tap(breadcrumbs, ¶m_map, "key"); let destination = self.tap(breadcrumbs, ¶m_map, "to"); let value_rendered = match value.as_ref().map(|maybe_ice| { maybe_ice .as_ref() .map(|ice| ice.get_context_element_reference()) .map(|ce| ce.render(&Vec::new())) }) { Some(Ok(Ok(val))) => val, _ => "current".to_owned(), }; let destination_rendered = match destination.as_ref().map(|maybe_ice| { maybe_ice .as_ref() .map(|ice| ice.get_context_element_reference()) .map(|ce| ce.render(&Vec::new())) }) { Some(Ok(Ok(val))) => val, _ => "output".to_owned(), }; match ( value_rendered.as_str(), destination_rendered.as_str(), breadcrumbs.last(), ) { ("current", "output", None) => return Ok("{}".to_owned()), ("current", "console", None) => println!("{{}}"), ("current", "output", Some(bc)) => return Ok(format!("{:?}", bc)), ("current", "console", Some(bc)) => println!("{:?}", bc), ("full", "output", _) => return Ok(format!("{:?}", breadcrumbs)), ("full", "console", _) => println!("{:?}", breadcrumbs), _ => panic!("Unhandled contextDump parameters."), } } } Ok("".to_owned()) } /// Read a value from the context tree pub fn get

( &'a self, breadcrumbs: &'a Vec>, name: &Vec

, ) -> Result, WalkError> where P: Borrow, { let val = walk_path(breadcrumbs, name).map(|ice| ice.into_context_element(self, breadcrumbs)); match val { Ok(Some(ice_result)) => Ok(ice_result), Ok(None) => Err(WalkError::CantWalk), Err(walk_error) => Err(walk_error), } } /// Read a value from the parameters context /// /// Returns None if the key is not present at all pub fn tap

( &'a self, breadcrumbs: &'a Vec>, parameters: &'a ParametersContext<'a>, name: &P, ) -> Option, WalkError>> where P: AsRef + ?Sized, { if !parameters.contains_key(name.as_ref()) { return None; } let val = parameters .walk(name.as_ref()) .map(|ice| ice.into_context_element(self, breadcrumbs)); match val { Ok(Some(ice_result)) => Some(Ok(ice_result)), Ok(None) => Some(Err(WalkError::CantWalk)), Err(walk_error) => Some(Err(walk_error)), } } fn new_breadcrumbs_section<'b>( &'b self, 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>> { // 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. match ( index_context, injected_context, explicit_context, new_context_element, ) { (None, None, None, None) => return None, _ => (), } // If there is an explicit context, then drop all the current // context let mut new_stack = match explicit_context { Some(_) => Vec::with_capacity(4), None => breadcrumbs.clone(), }; explicit_context.as_ref().map(|path| { 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_stack.push(std::convert::From::from(val)) } }); }); injected_context.map(|ctx| new_stack.push(BreadcrumbTreeElement::from_borrowed(ctx))); new_context_element.map(|ctx| { new_stack.push(BreadcrumbTreeElement::from_borrowed( ctx.from_context_element(), )) }); index_context.map(|ctx| new_stack.push(BreadcrumbTreeElement::from_borrowed(ctx))); Some(new_stack) } fn new_breadcrumbs_partial<'b>( &'b self, breadcrumbs: &'b Vec>, explicit_context_breadcrumbs: &'a Vec>, injected_context: Option<&'b dyn IntoContextElement>, explicit_context: &Option>, ) -> 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. match (injected_context, explicit_context) { (None, None) => return None, _ => (), }; // If there is an explicit context, then drop all the current // context let mut new_stack = match explicit_context { Some(_) => Vec::with_capacity(3), None => breadcrumbs.clone(), }; injected_context.map(|ctx| { // Special case: when there is no explicit context, the // injected context gets inserted 1 spot behind the // current context. Otherwise, the injected context gets // added after the current context but before the explicit // context. match explicit_context { 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_breadcrumbs, &path.keys) .map(|ice| ice.into_context_element(self, breadcrumbs)) .ok() .flatten() .map(|val| { if val.get_context_element_reference().is_truthy() { new_stack.push(std::convert::From::from(val)); } }); }); Some(new_stack) } fn are_paths_identical<'b>( left_side: &Option>, right_side: &Option>, ) -> bool { match (left_side, right_side) { (Some(Ok(lce)), Some(Ok(rce))) => (*lce) as *const _ == (*rce) as *const _, _ => false, } } fn preprocess_filters(filters: &Vec) -> Vec { let mut final_filters: Vec = filters .into_iter() .filter(|f| f != &&Filter::DisableHtmlEncode) .map(|f| f.clone()) .collect(); // If the user has not specified any escaping filter (|s or // |h), automatically add an html escape filter if !filters.iter().any(|f| f == &Filter::DisableHtmlEncode) { final_filters.push(Filter::HtmlEncode); } 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() }) } /// Performs a comparison (eq, ne, gt, gte, lt, lte) and returns /// whether or not the comparison was true. /// /// Returns None in the special case that the "key" parameter did /// not exist at all. This means that key was not in the original /// dust template source, which is not the same as key referencing /// a value which does not exist. fn perform_comparison_check( &'a self, tag: &'a DustTag, breadcrumbs: &'a Vec>, select_parameters: Option<&'a ParametersContext<'a>>, ) -> Option { let param_map = match tag { DustTag::DTHelperEquals(parameterized_block) | DustTag::DTHelperNotEquals(parameterized_block) | DustTag::DTHelperGreaterThan(parameterized_block) | DustTag::DTHelperLessThan(parameterized_block) | DustTag::DTHelperGreaterThanOrEquals(parameterized_block) | DustTag::DTHelperLessThanOrEquals(parameterized_block) => ParametersContext::new(self, breadcrumbs, ¶meterized_block.params, select_parameters), _ => panic!("perform_comparison_check only implemented for comparison helpers (eq, ne, gt, gte, lt, lte)") }; let left_side = self.tap(breadcrumbs, ¶m_map, "key"); let right_side = self.tap(breadcrumbs, ¶m_map, "value"); let type_cast = self.tap(breadcrumbs, ¶m_map, "type"); let left_side_ce = left_side.as_ref().map(|maybe_ice| { maybe_ice .as_ref() .map(|ice| ice.get_context_element_reference()) }); let mut right_side_ce = right_side.as_ref().map(|maybe_ice| { maybe_ice .as_ref() .map(|ice| ice.get_context_element_reference()) }); let type_rendered = match type_cast.as_ref().map(|maybe_ice| { maybe_ice .as_ref() .map(|ice| ice.get_context_element_reference()) .map(|ce| ce.render(&Vec::new())) }) { Some(Ok(Ok(val))) => Some(val), _ => None, }; if left_side_ce.is_none() { // If key did not exist at all, return None return None; } let right_side_cast = match (right_side_ce, type_rendered) { (Some(Ok(ce)), Some(target_type)) => ce.cast_to_type(&target_type), _ => None, }; match right_side_cast.as_ref() { Some(ice) => { right_side_ce = Some(Ok(ice.get_context_element_reference())); } None => (), } match tag { // 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). DustTag::DTHelperEquals(_) => Some(Self::are_paths_identical(&left_side_ce, &right_side_ce) || left_side_ce.unwrap_or(Err(&WalkError::CantWalk)) == right_side_ce.unwrap_or(Err(&WalkError::CantWalk))), DustTag::DTHelperNotEquals(_) => Some(!Self::are_paths_identical(&left_side_ce, &right_side_ce) && left_side_ce.unwrap_or(Err(&WalkError::CantWalk)) != right_side_ce.unwrap_or(Err(&WalkError::CantWalk))), DustTag::DTHelperGreaterThan(_) => Some(match (left_side_ce, right_side_ce) { (Some(Ok(left_value)), Some(Ok(right_value))) if left_value > right_value => true, _ => false }), DustTag::DTHelperLessThan(_) => Some(match (left_side_ce, right_side_ce) { (Some(Ok(left_value)), Some(Ok(right_value))) if left_value < right_value => true, _ => false }), DustTag::DTHelperGreaterThanOrEquals(_) => Some(match (left_side_ce, right_side_ce) { (Some(Ok(left_value)), Some(Ok(right_value))) if left_value >= right_value => true, _ => false }), DustTag::DTHelperLessThanOrEquals(_) => Some(match (left_side_ce, right_side_ce) { (Some(Ok(left_value)), Some(Ok(right_value))) if left_value <= right_value => true, _ => false }), _ => panic!("perform_comparison_check only implemented for comparison helpers (eq, ne, gt, gte, lt, lte)") } } /// Performs a math operation (add, subtract, multiply, divide, /// mod, abs, floor, ceil) and returns the result or None if /// nothing should be rendered. fn perform_math_operation( &'a self, breadcrumbs: &'a Vec>, math_parameters: &'a ParametersContext<'a>, ) -> Option> { // Special case: if method is a template then do not render // anything. This is to match the behavior of dustjs even // though it works fine. match math_parameters.get_original_rvalue("method") { Some(RValue::RVTemplate(template)) => { if template.iter().any(|pne| match pne { PartialNameElement::PNReference { .. } => true, PartialNameElement::PNSpan { .. } => false, }) { return None; } } _ => (), } let method = match self.tap(breadcrumbs, math_parameters, "method") { None | Some(Err(_)) => return None, Some(Ok(ice_result)) => ice_result, }; let method_rendered = match method.get_context_element_reference().render(&Vec::new()) { Ok(text) => text, Err(_) => return None, }; let left_side = self.tap(breadcrumbs, math_parameters, "key"); let right_side = self.tap(breadcrumbs, math_parameters, "operand"); let left_side_ce = left_side.as_ref().map(|maybe_ice| { maybe_ice .as_ref() .map(|ice| ice.get_context_element_reference()) .map(|ce| ce.cast_to_type("number")) }); let right_side_ce = right_side.as_ref().map(|maybe_ice| { maybe_ice .as_ref() .map(|ice| ice.get_context_element_reference()) .map(|ce| ce.cast_to_type("number")) }); // println!( // "Doing {:?} {:?} {:?}", // left_side, method_rendered, right_side // ); return match method_rendered.as_str() { "add" => match (left_side_ce, right_side_ce) { (None, _) | (Some(Err(_)), _) | (_, None) | (_, Some(Err(_))) => None, (Some(Ok(None)), _) | (_, Some(Ok(None))) => Some(IceResult::from_owned( OwnedLiteral::LString("NaN".to_owned()), )), (Some(Ok(Some(l))), Some(Ok(Some(r)))) => l .get_context_element_reference() .math_add(r.get_context_element_reference()), }, "subtract" => match (left_side_ce, right_side_ce) { (None, _) | (Some(Err(_)), _) | (_, None) | (_, Some(Err(_))) => None, (Some(Ok(None)), _) | (_, Some(Ok(None))) => Some(IceResult::from_owned( OwnedLiteral::LString("NaN".to_owned()), )), (Some(Ok(Some(l))), Some(Ok(Some(r)))) => l .get_context_element_reference() .math_subtract(r.get_context_element_reference()), }, "multiply" => match (left_side_ce, right_side_ce) { (None, _) | (Some(Err(_)), _) | (_, None) | (_, Some(Err(_))) => None, (Some(Ok(None)), _) | (_, Some(Ok(None))) => Some(IceResult::from_owned( OwnedLiteral::LString("NaN".to_owned()), )), (Some(Ok(Some(l))), Some(Ok(Some(r)))) => l .get_context_element_reference() .math_multiply(r.get_context_element_reference()), }, "divide" => match (left_side_ce, right_side_ce) { (None, _) | (Some(Err(_)), _) | (_, None) | (_, Some(Err(_))) => None, (Some(Ok(None)), _) | (_, Some(Ok(None))) => Some(IceResult::from_owned( OwnedLiteral::LString("NaN".to_owned()), )), (Some(Ok(Some(l))), Some(Ok(Some(r)))) => l .get_context_element_reference() .math_divide(r.get_context_element_reference()), }, "mod" => match (left_side_ce, right_side_ce) { (None, _) | (Some(Err(_)), _) | (_, None) | (_, Some(Err(_))) => None, (Some(Ok(None)), _) | (_, Some(Ok(None))) => Some(IceResult::from_owned( OwnedLiteral::LString("NaN".to_owned()), )), (Some(Ok(Some(l))), Some(Ok(Some(r)))) => l .get_context_element_reference() .math_modulus(r.get_context_element_reference()), }, "abs" => match left_side_ce { None | Some(Err(_)) => None, Some(Ok(None)) => Some(IceResult::from_owned(OwnedLiteral::LString( "NaN".to_owned(), ))), Some(Ok(Some(num))) => num.get_context_element_reference().math_abs(), }, "floor" => match left_side_ce { None | Some(Err(_)) => None, Some(Ok(None)) => Some(IceResult::from_owned(OwnedLiteral::LString( "NaN".to_owned(), ))), Some(Ok(Some(num))) => num.get_context_element_reference().math_floor(), }, "ceil" => match left_side_ce { None | Some(Err(_)) => None, Some(Ok(None)) => Some(IceResult::from_owned(OwnedLiteral::LString( "NaN".to_owned(), ))), Some(Ok(Some(num))) => num.get_context_element_reference().math_ceil(), }, _ => None, }; } } struct BlockContext<'a> { /// The breadcrumbs at the time of entering the current partial breadcrumbs: &'a Vec>, blocks: &'a InlinePartialTreeElement<'a>, }