1139 lines
50 KiB
Rust
1139 lines
50 KiB
Rust
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<String, &'a Template<'a>>,
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
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<C>(&'a self, name: &str, context: Option<&C>) -> Result<String, RenderError>
|
|
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<BreadcrumbTreeElement<'a>>,
|
|
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,
|
|
&mut None,
|
|
)
|
|
}
|
|
|
|
fn render_maybe_body(
|
|
&'a self,
|
|
body: &'a Option<Body>,
|
|
breadcrumbs: &'a Vec<BreadcrumbTreeElement<'a>>,
|
|
blocks: &'a BlockContext<'a>,
|
|
select_context: &mut Option<SelectContext<'a>>,
|
|
) -> Result<String, RenderError> {
|
|
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<BreadcrumbTreeElement<'a>>,
|
|
blocks: &'a BlockContext<'a>,
|
|
select_context: &mut Option<SelectContext<'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, 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<PartialNameElement>,
|
|
breadcrumbs: &'a Vec<BreadcrumbTreeElement<'a>>,
|
|
) -> 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: &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<BreadcrumbTreeElement<'a>>,
|
|
blocks: &'a BlockContext<'a>,
|
|
select_context: &mut Option<SelectContext<'a>>,
|
|
) -> Result<String, RenderError> {
|
|
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<Vec<String>, 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::<OwnedLiteral>();
|
|
let len_cast = len_resolved
|
|
.get_context_element_reference()
|
|
.to_any()
|
|
.downcast_ref::<OwnedLiteral>();
|
|
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::<OwnedLiteral>();
|
|
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::<OwnedLiteral>();
|
|
let len_cast = len_resolved
|
|
.get_context_element_reference()
|
|
.to_any()
|
|
.downcast_ref::<OwnedLiteral>();
|
|
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<BreadcrumbTreeElement<'_>>),
|
|
> = 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())
|
|
.map(|ce| ce.get_size())
|
|
});
|
|
match value_ce {
|
|
// If we found the key but could not get the size from it, render nothing.
|
|
Some(Ok(None)) => return Ok("".to_owned()),
|
|
// 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(Some(ce_size))) => {
|
|
return ce_size.get_context_element_reference().render(&Vec::new())
|
|
}
|
|
}
|
|
}
|
|
DustTag::DTHelperContextDump(parameterized_block) => {
|
|
//todo
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
Ok("".to_owned())
|
|
}
|
|
|
|
/// Read a value from the context tree
|
|
pub fn get<P>(
|
|
&'a self,
|
|
breadcrumbs: &'a Vec<BreadcrumbTreeElement<'a>>,
|
|
name: &Vec<P>,
|
|
) -> Result<IceResult<'a>, WalkError>
|
|
where
|
|
P: Borrow<str>,
|
|
{
|
|
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<P>(
|
|
&'a self,
|
|
breadcrumbs: &'a Vec<BreadcrumbTreeElement<'a>>,
|
|
parameters: &'a ParametersContext<'a>,
|
|
name: &P,
|
|
) -> Option<Result<IceResult<'a>, WalkError>>
|
|
where
|
|
P: AsRef<str> + ?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<BreadcrumbTreeElement<'b>>,
|
|
index_context: Option<&'b dyn IntoContextElement>,
|
|
injected_context: Option<&'b dyn IntoContextElement>,
|
|
explicit_context: &Option<Path<'b>>,
|
|
new_context_element: Option<&'b dyn ContextElement>,
|
|
) -> Option<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 (
|
|
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<BreadcrumbTreeElement<'b>>,
|
|
explicit_context_breadcrumbs: &'a Vec<BreadcrumbTreeElement<'a>>,
|
|
injected_context: Option<&'b dyn IntoContextElement>,
|
|
explicit_context: &Option<Path<'b>>,
|
|
) -> Option<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 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<Result<&dyn ContextElement, &WalkError>>,
|
|
right_side: &Option<Result<&dyn ContextElement, &WalkError>>,
|
|
) -> bool {
|
|
match (left_side, right_side) {
|
|
(Some(Ok(lce)), Some(Ok(rce))) => (*lce) as *const _ == (*rce) as *const _,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
fn get_index_of_first_non_pseudo_element<'b>(
|
|
breadcrumbs: &'b Vec<BreadcrumbTreeElement<'b>>,
|
|
) -> Option<usize> {
|
|
breadcrumbs.iter().rposition(|b| {
|
|
!std::borrow::Borrow::<dyn IntoContextElement + 'b>::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<BreadcrumbTreeElement<'a>>,
|
|
select_parameters: Option<&'a ParametersContext<'a>>,
|
|
) -> Option<bool> {
|
|
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 left_side_ce = left_side.as_ref().map(|maybe_ice| {
|
|
maybe_ice
|
|
.as_ref()
|
|
.map(|ice| ice.get_context_element_reference())
|
|
});
|
|
let right_side_ce = right_side.as_ref().map(|maybe_ice| {
|
|
maybe_ice
|
|
.as_ref()
|
|
.map(|ice| ice.get_context_element_reference())
|
|
});
|
|
if left_side_ce.is_none() {
|
|
// If key did not exist at all, return None
|
|
return 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<BreadcrumbTreeElement<'a>>,
|
|
math_parameters: &'a ParametersContext<'a>,
|
|
) -> Option<IceResult<'a>> {
|
|
// 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<BreadcrumbTreeElement<'a>>,
|
|
blocks: &'a InlinePartialTreeElement<'a>,
|
|
}
|