2020-04-10 20:27:27 -04:00
|
|
|
use crate::parser::template;
|
2020-05-31 21:18:54 -04:00
|
|
|
use crate::parser::Body;
|
|
|
|
use crate::parser::DustTag;
|
2020-05-31 21:26:31 -04:00
|
|
|
use crate::parser::Filter;
|
2020-05-31 21:18:54 -04:00
|
|
|
use crate::parser::PartialNameElement;
|
2020-05-25 15:40:42 -04:00
|
|
|
use crate::parser::Path;
|
2020-05-31 21:26:31 -04:00
|
|
|
use crate::parser::Special;
|
2020-04-10 20:27:27 -04:00
|
|
|
use crate::parser::Template;
|
2020-05-31 21:18:54 -04:00
|
|
|
use crate::parser::TemplateElement;
|
2020-05-31 18:18:21 -04:00
|
|
|
use crate::renderer::breadcrumb_tree::BreadcrumbTree;
|
|
|
|
use crate::renderer::breadcrumb_tree::BreadcrumbTreeElement;
|
2020-04-28 19:09:02 -04:00
|
|
|
use crate::renderer::context_element::ContextElement;
|
2020-05-30 16:34:32 -04:00
|
|
|
use crate::renderer::context_element::IntoContextElement;
|
2020-04-11 18:25:48 -04:00
|
|
|
use crate::renderer::errors::CompileError;
|
2020-05-31 21:18:54 -04:00
|
|
|
use crate::renderer::errors::RenderError;
|
2020-05-31 21:26:31 -04:00
|
|
|
use crate::renderer::errors::WalkError;
|
2020-05-31 21:18:54 -04:00
|
|
|
use crate::renderer::inline_partial_tree::extract_inline_partials;
|
|
|
|
use crate::renderer::inline_partial_tree::InlinePartialTreeElement;
|
2020-05-31 18:18:21 -04:00
|
|
|
use crate::renderer::tree_walking::walk_path;
|
2020-05-31 21:18:54 -04:00
|
|
|
use std::borrow::Borrow;
|
2020-05-23 17:57:19 -04:00
|
|
|
use std::collections::HashMap;
|
2020-05-31 23:47:20 -04:00
|
|
|
use std::rc::Rc;
|
2020-04-10 20:27:27 -04:00
|
|
|
|
2020-04-10 20:55:44 -04:00
|
|
|
#[derive(Clone, Debug)]
|
2020-04-10 20:58:55 -04:00
|
|
|
pub struct DustRenderer<'a> {
|
2020-04-11 18:25:48 -04:00
|
|
|
templates: HashMap<String, &'a Template<'a>>,
|
2020-04-10 20:55:44 -04:00
|
|
|
}
|
|
|
|
|
2020-05-31 19:01:51 -04:00
|
|
|
pub fn compile_template<'a>(source: &'a str) -> Result<Template<'a>, CompileError> {
|
|
|
|
let (_remaining, parsed_template) = template(source).map_err(|err| CompileError {
|
|
|
|
message: "Failed to compile template".to_owned(),
|
|
|
|
})?;
|
|
|
|
Ok(parsed_template)
|
2020-04-10 20:27:27 -04:00
|
|
|
}
|
2020-04-10 20:55:44 -04:00
|
|
|
|
2020-04-10 20:58:55 -04:00
|
|
|
impl<'a> DustRenderer<'a> {
|
|
|
|
pub fn new() -> DustRenderer<'a> {
|
|
|
|
DustRenderer {
|
2020-04-11 18:25:48 -04:00
|
|
|
templates: HashMap::new(),
|
2020-04-10 20:55:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-31 19:01:51 -04:00
|
|
|
pub fn load_source(&mut self, template: &'a Template, name: String) {
|
|
|
|
self.templates.insert(name, template);
|
2020-04-10 20:55:44 -04:00
|
|
|
}
|
2020-04-10 20:58:55 -04:00
|
|
|
|
2020-05-31 21:18:54 -04:00
|
|
|
pub fn render<C>(&'a self, name: &str, context: Option<&C>) -> Result<String, RenderError>
|
|
|
|
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<String, RenderError> {
|
|
|
|
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<Body>,
|
|
|
|
breadcrumbs: Option<&'a BreadcrumbTree>,
|
|
|
|
blocks: &'a BlockContext<'a>,
|
|
|
|
) -> Result<String, RenderError> {
|
|
|
|
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<String, RenderError> {
|
|
|
|
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<PartialNameElement>,
|
|
|
|
breadcrumbs: Option<&'a BreadcrumbTree>,
|
|
|
|
) -> Result<String, RenderError> {
|
|
|
|
let converted_to_template_elements: Vec<TemplateElement<'a>> =
|
|
|
|
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<String, RenderError> {
|
|
|
|
match tag {
|
2020-05-31 21:26:31 -04:00
|
|
|
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 {
|
2020-05-31 22:17:58 -04:00
|
|
|
Err(WalkError::CantWalk) | Ok(None) => return Ok("".to_owned()),
|
|
|
|
Ok(Some(final_val)) => {
|
2020-05-31 23:47:20 -04:00
|
|
|
return if final_val.get_context_element_reference().is_truthy() {
|
|
|
|
final_val
|
|
|
|
.get_context_element_reference()
|
|
|
|
.render(&Self::preprocess_filters(&reference.filters))
|
2020-05-31 21:26:31 -04:00
|
|
|
} else {
|
|
|
|
Ok("".to_owned())
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-05-31 21:30:36 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-05-31 21:18:54 -04:00
|
|
|
_ => panic!("Unsupported tag"),
|
|
|
|
}
|
|
|
|
Ok("".to_owned())
|
|
|
|
}
|
|
|
|
|
2020-05-31 18:18:21 -04:00
|
|
|
/// Returns a option of a tuple of (parent, new_node_elements)
|
|
|
|
/// which can then be formed into new BreadcrumbTreeNodes
|
2020-05-03 15:29:02 -04:00
|
|
|
///
|
2020-05-31 18:18:21 -04:00
|
|
|
/// 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
|
2020-05-25 22:50:10 -04:00
|
|
|
fn new_breadcrumbs_section<'b>(
|
2020-05-31 21:18:54 -04:00
|
|
|
&'b self,
|
|
|
|
maybe_breadcrumbs: Option<&'b BreadcrumbTree>,
|
2020-05-30 17:50:27 -04:00
|
|
|
index_context: Option<&'b dyn IntoContextElement>,
|
|
|
|
injected_context: Option<&'b dyn IntoContextElement>,
|
2020-05-25 22:50:10 -04:00
|
|
|
explicit_context: &Option<Path<'b>>,
|
|
|
|
new_context_element: Option<&'b dyn ContextElement>,
|
2020-05-31 18:18:21 -04:00
|
|
|
) -> Option<(Option<&'b BreadcrumbTree>, Vec<BreadcrumbTreeElement<'b>>)> {
|
2020-05-25 22:50:10 -04:00
|
|
|
// 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,
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
|
2020-05-31 18:18:21 -04:00
|
|
|
// If there is an explicit context, then drop all the current
|
|
|
|
// context
|
2020-05-31 20:41:27 -04:00
|
|
|
let parent = match explicit_context {
|
2020-05-31 18:18:21 -04:00
|
|
|
Some(_) => None,
|
|
|
|
None => maybe_breadcrumbs,
|
2020-05-25 22:50:10 -04:00
|
|
|
};
|
2020-05-31 18:27:55 -04:00
|
|
|
let mut new_nodes: Vec<BreadcrumbTreeElement> = Vec::new();
|
2020-05-31 18:18:21 -04:00
|
|
|
|
2020-06-06 17:52:46 -04:00
|
|
|
explicit_context.as_ref().map(|path| {
|
2020-05-31 22:17:58 -04:00
|
|
|
walk_path(maybe_breadcrumbs, &path.keys)
|
|
|
|
.map(|ice| ice.into_context_element(self, maybe_breadcrumbs))
|
|
|
|
.ok()
|
|
|
|
.flatten()
|
2020-05-31 18:27:55 -04:00
|
|
|
.map(|val| {
|
2020-05-31 23:47:20 -04:00
|
|
|
if val.get_context_element_reference().is_truthy() {
|
|
|
|
new_nodes.push(BreadcrumbTreeElement::Owned(Rc::new(val)))
|
2020-05-31 18:27:55 -04:00
|
|
|
}
|
|
|
|
});
|
2020-06-06 17:52:46 -04:00
|
|
|
});
|
2020-05-31 18:18:21 -04:00
|
|
|
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))
|
2020-05-05 20:22:25 -04:00
|
|
|
}
|
2020-05-31 20:41:27 -04:00
|
|
|
|
2020-05-31 21:18:54 -04:00
|
|
|
/// 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
|
2020-05-31 20:41:27 -04:00
|
|
|
fn new_breadcrumbs_partial<'b>(
|
2020-05-31 20:43:07 -04:00
|
|
|
&'b self,
|
2020-05-31 20:41:27 -04:00
|
|
|
maybe_breadcrumbs: Option<&'b BreadcrumbTree>,
|
|
|
|
explicit_context_maybe_breadcrumbs: Option<&'b BreadcrumbTree>,
|
|
|
|
injected_context: Option<&'b dyn IntoContextElement>,
|
|
|
|
explicit_context: &Option<Path<'b>>,
|
|
|
|
) -> Option<(Option<&'b BreadcrumbTree>, Vec<BreadcrumbTreeElement<'b>>)> {
|
|
|
|
// 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<BreadcrumbTreeElement> = 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)),
|
|
|
|
}
|
|
|
|
});
|
2020-06-06 17:52:46 -04:00
|
|
|
explicit_context.as_ref().map(|path| {
|
2020-05-31 20:41:27 -04:00
|
|
|
// TODO: should resolving the value here use
|
|
|
|
// explicit_context_maybe_breadcrumbs or
|
|
|
|
// maybe_breadcrumbs?
|
2020-05-31 22:17:58 -04:00
|
|
|
walk_path(maybe_breadcrumbs, &path.keys)
|
|
|
|
.map(|ice| ice.into_context_element(self, maybe_breadcrumbs))
|
|
|
|
.ok()
|
|
|
|
.flatten()
|
2020-05-31 20:41:27 -04:00
|
|
|
.map(|val| {
|
2020-06-06 15:20:43 -04:00
|
|
|
if val.get_context_element_reference().is_truthy() {
|
|
|
|
new_nodes.push(BreadcrumbTreeElement::Owned(Rc::new(val)));
|
2020-05-31 20:41:27 -04:00
|
|
|
}
|
|
|
|
});
|
2020-06-06 17:52:46 -04:00
|
|
|
});
|
2020-05-31 20:41:27 -04:00
|
|
|
|
2020-05-31 20:43:07 -04:00
|
|
|
Some((parent, new_nodes))
|
2020-05-31 20:41:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/// 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-05-31 21:26:31 -04:00
|
|
|
|
|
|
|
fn preprocess_filters(filters: &Vec<Filter>) -> Vec<Filter> {
|
|
|
|
let mut final_filters: Vec<Filter> = 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
|
|
|
|
}
|
2020-04-11 20:31:44 -04:00
|
|
|
}
|
2020-05-31 21:18:54 -04:00
|
|
|
|
|
|
|
struct BlockContext<'a> {
|
|
|
|
/// The breadcrumbs at the time of entering the current partial
|
|
|
|
breadcrumbs: Option<&'a BreadcrumbTree<'a>>,
|
|
|
|
blocks: &'a InlinePartialTreeElement<'a>,
|
|
|
|
}
|