use crate::parser::Filter; use crate::parser::KVPair; use crate::parser::OwnedLiteral; use crate::parser::RValue; 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::tree_walking::walk_path; use crate::renderer::DustRenderer; use crate::renderer::Loopable; use crate::renderer::RenderError; use crate::renderer::Renderable; use crate::renderer::Truthiness; use crate::renderer::WalkError; use crate::renderer::Walkable; use std::borrow::Borrow; use std::cmp::Ordering; use std::collections::HashMap; use std::rc::Rc; #[derive(Debug)] pub struct ParametersContext<'a> { params: HashMap<&'a str, BreadcrumbTreeElement<'a>>, } impl<'a> ParametersContext<'a> { pub fn new( renderer: &DustRenderer, breadcrumbs: &'a Vec>, params: &'a Vec, ) -> Self { // If the parameter is a Path, then we resolve it immediately // to a context element because those are resolved using the // breadcrumbs at the time of assignment. // // If the parameter is a template (for example `foo="{bar}"`) // 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(); ParametersContext { params: rendered_params, } } } impl<'a> IntoContextElement for ParametersContext<'a> { fn into_context_element<'b>( &'b self, renderer: &DustRenderer, breadcrumbs: &'b Vec>, ) -> Option> { panic!("into_context_element cannot be called on pseudo elements"); } } 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) } fn is_pseudo_element(&self) -> bool { true } } impl<'a> IntoContextElement for RValue<'a> { fn into_context_element<'b>( &'b self, renderer: &DustRenderer, breadcrumbs: &'b Vec>, ) -> Option> { match self { RValue::RVLiteral(owned_literal) => Some(IceResult::from_borrowed(owned_literal)), RValue::RVPath(path) => walk_path(breadcrumbs, &path.keys) .map(|ice| ice.into_context_element(renderer, breadcrumbs)) .ok() .flatten(), RValue::RVTemplate(template) => renderer .render_partial_name(template, breadcrumbs) .map(|rendered| OwnedLiteral::LString(rendered)) .ok() .map(|owned_literal| IceResult::from_owned(owned_literal)), } } } impl<'a> Walkable for RValue<'a> { fn walk(&self, segment: &str) -> Result<&dyn IntoContextElement, WalkError> { Err(WalkError::CantWalk) } } impl ContextElement for OwnedLiteral {} impl Truthiness for OwnedLiteral { fn is_truthy(&self) -> bool { match self { OwnedLiteral::LString(text) => !text.is_empty(), OwnedLiteral::LPositiveInteger(num) => true, } } } impl Renderable for OwnedLiteral { fn render(&self, _filters: &Vec) -> Result { match self { OwnedLiteral::LString(text) => Ok(text.clone()), OwnedLiteral::LPositiveInteger(num) => Ok(num.to_string()), } } } impl Loopable for OwnedLiteral { fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { Vec::new() } } impl Walkable for OwnedLiteral { fn walk(&self, segment: &str) -> Result<&dyn IntoContextElement, WalkError> { Err(WalkError::CantWalk) } } impl CompareContextElement for OwnedLiteral { fn equals(&self, other: &dyn ContextElement) -> bool { // println!("equals literal {:?} | {:?}", self, other); // If its an OwnedLiteral then compare them directly, // otherwise defer to the other type's implementation of // CompareContextElement since the end user could add any // type. match other.to_any().downcast_ref::() { None => other.equals(self), Some(other_literal) => match (self, other_literal) { (OwnedLiteral::LString(self_text), OwnedLiteral::LString(other_text)) => { self_text == other_text } (OwnedLiteral::LPositiveInteger(self_num), OwnedLiteral::LString(other_text)) => { &self_num.to_string() == other_text } (OwnedLiteral::LString(self_text), OwnedLiteral::LPositiveInteger(other_num)) => { self_text == &other_num.to_string() } ( OwnedLiteral::LPositiveInteger(self_num), OwnedLiteral::LPositiveInteger(other_num), ) => self_num == other_num, }, } } fn partial_compare(&self, other: &dyn ContextElement) -> Option { // println!("partial_compare literal {:?} | {:?}", self, other); // If its an OwnedLiteral then compare them directly, // otherwise defer to the other type's implementation of // CompareContextElement since the end user could add any // type. match other.to_any().downcast_ref::() { None => match other.partial_compare(self) { None => None, Some(ord) => match ord { Ordering::Equal => Some(Ordering::Equal), Ordering::Greater => Some(Ordering::Less), Ordering::Less => Some(Ordering::Greater), }, }, Some(other_literal) => match (self, other_literal) { (OwnedLiteral::LString(self_text), OwnedLiteral::LString(other_text)) => { self_text.partial_cmp(other_text) } (OwnedLiteral::LPositiveInteger(self_num), OwnedLiteral::LString(other_text)) => { self_num.to_string().partial_cmp(other_text) } (OwnedLiteral::LString(self_text), OwnedLiteral::LPositiveInteger(other_num)) => { self_text.partial_cmp(&other_num.to_string()) } ( OwnedLiteral::LPositiveInteger(self_num), OwnedLiteral::LPositiveInteger(other_num), ) => self_num.partial_cmp(other_num), }, } } }