use crate::parser::template; use crate::parser::Body; use crate::parser::DustTag; use crate::parser::Filter; use crate::parser::PartialNameElement; use crate::parser::Path; 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::IntoContextElement; 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::tree_walking::walk_path; use std::borrow::Borrow; use std::collections::HashMap; use std::rc::Rc; #[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| BreadcrumbTree::new(None, BreadcrumbTreeElement::Borrowed(ctx))); self.render_template(name, breadcrumbs.as_ref(), None) } pub fn render_template( &'a self, name: &str, breadcrumbs: Option<&'a BreadcrumbTree>, 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) } fn render_maybe_body( &'a self, body: &'a Option, breadcrumbs: Option<&'a BreadcrumbTree>, blocks: &'a BlockContext<'a>, ) -> Result { match body { None => Ok("".to_owned()), Some(body) => Ok(self.render_body(body, breadcrumbs, blocks)?), } } fn render_body( &'a self, body: &'a Body, breadcrumbs: Option<&'a BreadcrumbTree>, blocks: &'a BlockContext<'a>, ) -> 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)?); } } } Ok(output) } /// For rendering a dynamic partial's name or an rvalue template pub fn render_partial_name( &'a self, body: &'a Vec, breadcrumbs: Option<&'a BreadcrumbTree>, ) -> 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: None, blocks: &InlinePartialTreeElement::new(None, HashMap::new()), }; self.render_body( &Body { elements: converted_to_template_elements, }, breadcrumbs, &empty_block_context, ) } fn render_tag( &'a self, tag: &'a DustTag, breadcrumbs: Option<&'a BreadcrumbTree>, blocks: &'a BlockContext<'a>, ) -> 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(breadcrumbs, &container.params); let val = walk_path(breadcrumbs, &container.path.keys) .map(|ice| ice.into_context_element(self, breadcrumbs)); match val { Err(WalkError::CantWalk) => { // TODO } Ok(final_val) => { // TODO } } } _ => 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>, 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>)> { // 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 parent = match explicit_context { Some(_) => None, None => maybe_breadcrumbs, }; 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)) .ok() .flatten() .map(|val| { if val.get_context_element_reference().is_truthy() { new_nodes.push(BreadcrumbTreeElement::Owned(Rc::new(val))) } }); }); injected_context.map(|ctx| new_nodes.push(BreadcrumbTreeElement::Borrowed(ctx))); new_context_element .map(|ctx| new_nodes.push(BreadcrumbTreeElement::Borrowed(ctx.from_context_element()))); index_context.map(|ctx| new_nodes.push(BreadcrumbTreeElement::Borrowed(ctx))); Some((parent, new_nodes)) } /// 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>, injected_context: Option<&'b dyn IntoContextElement>, explicit_context: &Option>, ) -> Option<(Option<&'b BreadcrumbTree>, Vec>)> { // 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 parent = match explicit_context { Some(_) => None, None => maybe_breadcrumbs, }; let mut new_nodes: Vec = Vec::new(); 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 => { 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::Borrowed(ctx)), } }); explicit_context.as_ref().map(|path| { // TODO: should resolving the value here use // explicit_context_maybe_breadcrumbs or // maybe_breadcrumbs? walk_path(maybe_breadcrumbs, &path.keys) .map(|ice| ice.into_context_element(self, maybe_breadcrumbs)) .ok() .flatten() .map(|val| { if val.get_context_element_reference().is_truthy() { new_nodes.push(BreadcrumbTreeElement::Owned(Rc::new(val))); } }); }); Some((parent, new_nodes)) } /// 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); } } return (None, passed_nodes); } } } 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 } } struct BlockContext<'a> { /// The breadcrumbs at the time of entering the current partial breadcrumbs: Option<&'a BreadcrumbTree<'a>>, blocks: &'a InlinePartialTreeElement<'a>, }