use std::borrow::Cow;
use std::collections::HashMap;
use std::io::Write;
use serde_json::{to_string_pretty, to_value, Number, Value};
use crate::context::{ValueRender, ValueTruthy};
use crate::errors::{Error, Result};
use crate::parser::ast::*;
use crate::renderer::call_stack::CallStack;
use crate::renderer::for_loop::ForLoop;
use crate::renderer::macros::MacroCollection;
use crate::renderer::square_brackets::pull_out_square_bracket;
use crate::renderer::stack_frame::{FrameContext, FrameType, Val};
use crate::template::Template;
use crate::tera::Tera;
use crate::utils::render_to_string;
use crate::Context;
static MAGICAL_DUMP_VAR: &str = "__tera_context";
fn evaluate_sub_variables<'a>(key: &str, call_stack: &CallStack<'a>) -> Result<String> {
let sub_vars_to_calc = pull_out_square_bracket(key);
let mut new_key = key.to_string();
for sub_var in &sub_vars_to_calc {
match process_path(sub_var.as_ref(), call_stack) {
Err(e) => {
return Err(Error::msg(format!(
"Variable {} can not be evaluated because: {}",
key, e
)));
}
Ok(post_var) => {
let post_var_as_str = match *post_var {
Value::String(ref s) => format!(r#""{}""#, s),
Value::Number(ref n) => n.to_string(),
_ => {
return Err(Error::msg(format!(
"Only variables evaluating to String or Number can be used as \
index (`{}` of `{}`)",
sub_var, key,
)));
}
};
let nk = new_key.clone();
let divider = "[".to_string() + sub_var + "]";
let mut the_parts = nk.splitn(2, divider.as_str());
new_key = the_parts.next().unwrap().to_string()
+ "."
+ post_var_as_str.as_ref()
+ the_parts.next().unwrap_or("");
}
}
}
Ok(new_key
.replace("/", "~1") .replace("['", ".\"")
.replace("[\"", ".\"")
.replace("[", ".")
.replace("']", "\"")
.replace("\"]", "\"")
.replace("]", ""))
}
fn process_path<'a>(path: &str, call_stack: &CallStack<'a>) -> Result<Val<'a>> {
if !path.contains('[') {
match call_stack.lookup(path) {
Some(v) => Ok(v),
None => Err(Error::msg(format!(
"Variable `{}` not found in context while rendering '{}'",
path,
call_stack.active_template().name
))),
}
} else {
let full_path = evaluate_sub_variables(path, call_stack)?;
match call_stack.lookup(full_path.as_ref()) {
Some(v) => Ok(v),
None => Err(Error::msg(format!(
"Variable `{}` not found in context while rendering '{}': \
the evaluated version was `{}`. Maybe the index is out of bounds?",
path,
call_stack.active_template().name,
full_path,
))),
}
}
}
pub struct Processor<'a> {
template: &'a Template,
template_root: &'a Template,
tera: &'a Tera,
call_stack: CallStack<'a>,
macros: MacroCollection<'a>,
should_escape: bool,
blocks: Vec<(&'a str, &'a str, usize)>,
}
impl<'a> Processor<'a> {
pub fn new(
template: &'a Template,
tera: &'a Tera,
context: &'a Context,
should_escape: bool,
) -> Self {
let template_root = template
.parents
.last()
.map(|parent| tera.get_template(parent).unwrap())
.unwrap_or(template);
let call_stack = CallStack::new(&context, template);
Processor {
template,
template_root,
tera,
call_stack,
macros: MacroCollection::from_original_template(&template, &tera),
should_escape,
blocks: Vec::new(),
}
}
fn render_body(&mut self, body: &'a [Node], write: &mut impl Write) -> Result<()> {
for n in body {
self.render_node(n, write)?;
if self.call_stack.should_break_body() {
break;
}
}
Ok(())
}
fn render_for_loop(&mut self, for_loop: &'a Forloop, write: &mut impl Write) -> Result<()> {
let container_name = match for_loop.container.val {
ExprVal::Ident(ref ident) => ident,
ExprVal::FunctionCall(FunctionCall { ref name, .. }) => name,
ExprVal::Array(_) => "an array literal",
_ => return Err(Error::msg(format!(
"Forloop containers have to be an ident or a function call (tried to iterate on '{:?}')",
for_loop.container.val,
))),
};
let for_loop_name = &for_loop.value;
let for_loop_body = &for_loop.body;
let for_loop_empty_body = &for_loop.empty_body;
let container_val = self.safe_eval_expression(&for_loop.container)?;
let for_loop = match *container_val {
Value::Array(_) => {
if for_loop.key.is_some() {
return Err(Error::msg(format!(
"Tried to iterate using key value on variable `{}`, but it isn't an object/map",
container_name,
)));
}
ForLoop::from_array(&for_loop.value, container_val)
}
Value::String(_) => {
if for_loop.key.is_some() {
return Err(Error::msg(format!(
"Tried to iterate using key value on variable `{}`, but it isn't an object/map",
container_name,
)));
}
ForLoop::from_string(&for_loop.value, container_val)
}
Value::Object(_) => {
if for_loop.key.is_none() {
return Err(Error::msg(format!(
"Tried to iterate using key value on variable `{}`, but it is missing a key",
container_name,
)));
}
match container_val {
Cow::Borrowed(c) => {
ForLoop::from_object(&for_loop.key.as_ref().unwrap(), &for_loop.value, c)
}
Cow::Owned(c) => ForLoop::from_object_owned(
&for_loop.key.as_ref().unwrap(),
&for_loop.value,
c,
),
}
}
_ => {
return Err(Error::msg(format!(
"Tried to iterate on a container (`{}`) that has a unsupported type",
container_name,
)));
}
};
let len = for_loop.len();
match (len, for_loop_empty_body) {
(0, Some(empty_body)) => self.render_body(&empty_body, write),
(0, _) => Ok(()),
(_, _) => {
self.call_stack.push_for_loop_frame(for_loop_name, for_loop);
for _ in 0..len {
self.render_body(&for_loop_body, write)?;
if self.call_stack.should_break_for_loop() {
break;
}
self.call_stack.increment_for_loop()?;
}
self.call_stack.pop();
Ok(())
}
}
}
fn render_if_node(&mut self, if_node: &'a If, write: &mut impl Write) -> Result<()> {
for &(_, ref expr, ref body) in &if_node.conditions {
if self.eval_as_bool(expr)? {
return self.render_body(body, write);
}
}
if let Some((_, ref body)) = if_node.otherwise {
return self.render_body(body, write);
}
Ok(())
}
fn render_block(
&mut self,
block: &'a Block,
level: usize,
write: &mut impl Write,
) -> Result<()> {
let level_template = match level {
0 => self.call_stack.active_template(),
_ => self
.tera
.get_template(&self.call_stack.active_template().parents[level - 1])
.unwrap(),
};
let blocks_definitions = &level_template.blocks_definitions;
if let Some(block_def) = blocks_definitions.get(&block.name) {
let (_, Block { ref body, .. }) = block_def[0];
self.blocks.push((&block.name[..], &level_template.name[..], level));
return self.render_body(body, write);
}
if level < self.call_stack.active_template().parents.len() {
return self.render_block(block, level + 1, write);
}
self.render_body(&block.body, write)
}
fn get_default_value(&mut self, expr: &'a Expr) -> Result<Val<'a>> {
if let Some(default_expr) = expr.filters[0].args.get("value") {
self.eval_expression(default_expr)
} else {
Err(Error::msg("The `default` filter requires a `value` argument."))
}
}
fn eval_in_condition(&mut self, in_cond: &'a In) -> Result<bool> {
let lhs = self.safe_eval_expression(&in_cond.lhs)?;
let rhs = self.safe_eval_expression(&in_cond.rhs)?;
let present = match *rhs {
Value::Array(ref v) => v.contains(&lhs),
Value::String(ref s) => match *lhs {
Value::String(ref s2) => s.contains(s2),
_ => {
return Err(Error::msg(format!(
"Tried to check if {:?} is in a string, but it isn't a string",
lhs
)))
}
},
Value::Object(ref map) => match *lhs {
Value::String(ref s2) => map.contains_key(s2),
_ => {
return Err(Error::msg(format!(
"Tried to check if {:?} is in a object, but it isn't a string",
lhs
)))
}
},
_ => {
return Err(Error::msg(
"The `in` operator only supports strings, arrays and objects.",
))
}
};
Ok(if in_cond.negated { !present } else { present })
}
fn eval_expression(&mut self, expr: &'a Expr) -> Result<Val<'a>> {
let mut needs_escape = false;
let mut res = match expr.val {
ExprVal::Array(ref arr) => {
let mut values = vec![];
for v in arr {
values.push(self.eval_expression(v)?.into_owned());
}
Cow::Owned(Value::Array(values))
}
ExprVal::In(ref in_cond) => Cow::Owned(Value::Bool(self.eval_in_condition(in_cond)?)),
ExprVal::String(ref val) => {
needs_escape = true;
Cow::Owned(Value::String(val.to_string()))
}
ExprVal::StringConcat(ref str_concat) => {
let mut res = String::new();
for s in &str_concat.values {
match *s {
ExprVal::String(ref v) => res.push_str(&v),
ExprVal::Int(ref v) => res.push_str(&format!("{}", v)),
ExprVal::Float(ref v) => res.push_str(&format!("{}", v)),
ExprVal::Ident(ref i) => match *self.lookup_ident(i)? {
Value::String(ref v) => res.push_str(&v),
Value::Number(ref v) => res.push_str(&v.to_string()),
_ => return Err(Error::msg(format!(
"Tried to concat a value that is not a string or a number from ident {}",
i
))),
},
ExprVal::FunctionCall(ref fn_call) => match *self.eval_tera_fn_call(fn_call, &mut needs_escape)? {
Value::String(ref v) => res.push_str(&v),
Value::Number(ref v) => res.push_str(&v.to_string()),
_ => return Err(Error::msg(format!(
"Tried to concat a value that is not a string or a number from function call {}",
fn_call.name
))),
},
_ => unreachable!(),
};
}
Cow::Owned(Value::String(res))
}
ExprVal::Int(val) => Cow::Owned(Value::Number(val.into())),
ExprVal::Float(val) => Cow::Owned(Value::Number(Number::from_f64(val).unwrap())),
ExprVal::Bool(val) => Cow::Owned(Value::Bool(val)),
ExprVal::Ident(ref ident) => {
needs_escape = ident != MAGICAL_DUMP_VAR;
match self.lookup_ident(ident) {
Ok(val) => {
if val.is_null() && expr.has_default_filter() {
self.get_default_value(expr)?
} else {
val
}
}
Err(e) => {
if expr.has_default_filter() {
self.get_default_value(expr)?
} else {
if !expr.negated {
return Err(e);
}
return Ok(Cow::Owned(Value::Bool(true)));
}
}
}
}
ExprVal::FunctionCall(ref fn_call) => {
self.eval_tera_fn_call(fn_call, &mut needs_escape)?
}
ExprVal::MacroCall(ref macro_call) => {
let val = render_to_string(
|| format!("macro {}", macro_call.name),
|w| self.eval_macro_call(macro_call, w),
)?;
Cow::Owned(Value::String(val))
}
ExprVal::Test(ref test) => Cow::Owned(Value::Bool(self.eval_test(test)?)),
ExprVal::Logic(_) => Cow::Owned(Value::Bool(self.eval_as_bool(expr)?)),
ExprVal::Math(_) => match self.eval_as_number(&expr.val) {
Ok(Some(n)) => Cow::Owned(Value::Number(n)),
Ok(None) => Cow::Owned(Value::String("NaN".to_owned())),
Err(e) => return Err(Error::msg(e)),
},
};
for filter in &expr.filters {
if filter.name == "safe" || filter.name == "default" {
continue;
}
res = self.eval_filter(&res, filter, &mut needs_escape)?;
}
if expr.negated {
return Ok(Cow::Owned(Value::Bool(!res.is_truthy())));
}
if self.should_escape && needs_escape && res.is_string() && !expr.is_marked_safe() {
res = Cow::Owned(
to_value(self.tera.get_escape_fn()(res.as_str().unwrap())).map_err(Error::json)?,
);
}
Ok(res)
}
fn safe_eval_expression(&mut self, expr: &'a Expr) -> Result<Val<'a>> {
let should_escape = self.should_escape;
self.should_escape = false;
let res = self.eval_expression(expr);
self.should_escape = should_escape;
res
}
fn eval_set(&mut self, set: &'a Set) -> Result<()> {
let assigned_value = self.safe_eval_expression(&set.value)?;
self.call_stack.add_assignment(&set.key[..], set.global, assigned_value);
Ok(())
}
fn eval_test(&mut self, test: &'a Test) -> Result<bool> {
let tester_fn = self.tera.get_tester(&test.name)?;
let err_wrap = |e| Error::call_test(&test.name, e);
let mut tester_args = vec![];
for arg in &test.args {
tester_args
.push(self.safe_eval_expression(arg).map_err(err_wrap)?.clone().into_owned());
}
let found = self.lookup_ident(&test.ident).map(|found| found.clone().into_owned()).ok();
let result = tester_fn.test(found.as_ref(), &tester_args).map_err(err_wrap)?;
if test.negated {
Ok(!result)
} else {
Ok(result)
}
}
fn eval_tera_fn_call(
&mut self,
function_call: &'a FunctionCall,
needs_escape: &mut bool,
) -> Result<Val<'a>> {
let tera_fn = self.tera.get_function(&function_call.name)?;
*needs_escape = !tera_fn.is_safe();
let err_wrap = |e| Error::call_function(&function_call.name, e);
let mut args = HashMap::new();
for (arg_name, expr) in &function_call.args {
args.insert(
arg_name.to_string(),
self.safe_eval_expression(expr).map_err(err_wrap)?.clone().into_owned(),
);
}
Ok(Cow::Owned(tera_fn.call(&args).map_err(err_wrap)?))
}
fn eval_macro_call(&mut self, macro_call: &'a MacroCall, write: &mut impl Write) -> Result<()> {
let active_template_name = if let Some(block) = self.blocks.last() {
block.1
} else if self.template.name != self.template_root.name {
&self.template_root.name
} else {
&self.call_stack.active_template().name
};
let (macro_template_name, macro_definition) = self.macros.lookup_macro(
active_template_name,
¯o_call.namespace[..],
¯o_call.name[..],
)?;
let mut frame_context = FrameContext::with_capacity(macro_definition.args.len());
for (arg_name, default_value) in ¯o_definition.args {
let value = match macro_call.args.get(arg_name) {
Some(val) => self.safe_eval_expression(val)?,
None => match *default_value {
Some(ref val) => self.safe_eval_expression(val)?,
None => {
return Err(Error::msg(format!(
"Macro `{}` is missing the argument `{}`",
macro_call.name, arg_name
)));
}
},
};
frame_context.insert(&arg_name, value);
}
self.call_stack.push_macro_frame(
¯o_call.namespace,
¯o_call.name,
frame_context,
self.tera.get_template(macro_template_name)?,
);
self.render_body(¯o_definition.body, write)?;
self.call_stack.pop();
Ok(())
}
fn eval_filter(
&mut self,
value: &Val<'a>,
fn_call: &'a FunctionCall,
needs_escape: &mut bool,
) -> Result<Val<'a>> {
let filter_fn = self.tera.get_filter(&fn_call.name)?;
*needs_escape = !filter_fn.is_safe();
let err_wrap = |e| Error::call_filter(&fn_call.name, e);
let mut args = HashMap::new();
for (arg_name, expr) in &fn_call.args {
args.insert(
arg_name.to_string(),
self.safe_eval_expression(expr).map_err(err_wrap)?.clone().into_owned(),
);
}
Ok(Cow::Owned(filter_fn.filter(&value, &args).map_err(err_wrap)?))
}
fn eval_as_bool(&mut self, bool_expr: &'a Expr) -> Result<bool> {
let res = match bool_expr.val {
ExprVal::Logic(LogicExpr { ref lhs, ref rhs, ref operator }) => {
match *operator {
LogicOperator::Or => self.eval_as_bool(lhs)? || self.eval_as_bool(rhs)?,
LogicOperator::And => self.eval_as_bool(lhs)? && self.eval_as_bool(rhs)?,
LogicOperator::Gt
| LogicOperator::Gte
| LogicOperator::Lt
| LogicOperator::Lte => {
let l = self.eval_expr_as_number(lhs)?;
let r = self.eval_expr_as_number(rhs)?;
let (ll, rr) = match (l, r) {
(Some(nl), Some(nr)) => (nl, nr),
_ => return Err(Error::msg("Comparison to NaN")),
};
match *operator {
LogicOperator::Gte => ll.as_f64().unwrap() >= rr.as_f64().unwrap(),
LogicOperator::Gt => ll.as_f64().unwrap() > rr.as_f64().unwrap(),
LogicOperator::Lte => ll.as_f64().unwrap() <= rr.as_f64().unwrap(),
LogicOperator::Lt => ll.as_f64().unwrap() < rr.as_f64().unwrap(),
_ => unreachable!(),
}
}
LogicOperator::Eq | LogicOperator::NotEq => {
let mut lhs_val = self.eval_expression(lhs)?;
let mut rhs_val = self.eval_expression(rhs)?;
if lhs_val.is_number() || rhs_val.is_number() {
if !lhs_val.is_number() || !rhs_val.is_number() {
return Ok(false);
}
lhs_val = Cow::Owned(Value::Number(
Number::from_f64(lhs_val.as_f64().unwrap()).unwrap(),
));
rhs_val = Cow::Owned(Value::Number(
Number::from_f64(rhs_val.as_f64().unwrap()).unwrap(),
));
}
match *operator {
LogicOperator::Eq => *lhs_val == *rhs_val,
LogicOperator::NotEq => *lhs_val != *rhs_val,
_ => unreachable!(),
}
}
}
}
ExprVal::Ident(_) => {
let mut res = self
.eval_expression(&bool_expr)
.unwrap_or(Cow::Owned(Value::Bool(false)))
.is_truthy();
if bool_expr.negated {
res = !res;
}
res
}
ExprVal::Math(_) | ExprVal::Int(_) | ExprVal::Float(_) => {
match self.eval_as_number(&bool_expr.val)? {
Some(n) => n.as_f64().unwrap() != 0.0,
None => false,
}
}
ExprVal::In(ref in_cond) => self.eval_in_condition(&in_cond)?,
ExprVal::Test(ref test) => self.eval_test(test)?,
ExprVal::Bool(val) => val,
ExprVal::String(ref string) => !string.is_empty(),
ExprVal::FunctionCall(ref fn_call) => {
let v = self.eval_tera_fn_call(fn_call, &mut false)?;
match v.as_bool() {
Some(val) => val,
None => {
return Err(Error::msg(format!(
"Function `{}` was used in a logic operation but is not returning a bool",
fn_call.name,
)));
}
}
}
ExprVal::StringConcat(_) => {
let res = self.eval_expression(bool_expr)?;
!res.as_str().unwrap().is_empty()
}
ExprVal::MacroCall(ref macro_call) => {
let mut buf = Vec::new();
self.eval_macro_call(¯o_call, &mut buf)?;
!buf.is_empty()
}
_ => unreachable!("unimplemented logic operation for {:?}", bool_expr),
};
if bool_expr.negated {
return Ok(!res);
}
Ok(res)
}
fn eval_expr_as_number(&mut self, expr: &'a Expr) -> Result<Option<Number>> {
if !expr.filters.is_empty() {
match *self.eval_expression(expr)? {
Value::Number(ref s) => Ok(Some(s.clone())),
_ => {
Err(Error::msg("Tried to do math with an expression not resulting in a number"))
}
}
} else {
self.eval_as_number(&expr.val)
}
}
fn eval_as_number(&mut self, expr: &'a ExprVal) -> Result<Option<Number>> {
let result = match *expr {
ExprVal::Ident(ref ident) => {
let v = &*self.lookup_ident(ident)?;
if v.is_i64() {
Some(Number::from(v.as_i64().unwrap()))
} else if v.is_u64() {
Some(Number::from(v.as_u64().unwrap()))
} else if v.is_f64() {
Some(Number::from_f64(v.as_f64().unwrap()).unwrap())
} else {
return Err(Error::msg(format!(
"Variable `{}` was used in a math operation but is not a number",
ident
)));
}
}
ExprVal::Int(val) => Some(Number::from(val)),
ExprVal::Float(val) => Some(Number::from_f64(val).unwrap()),
ExprVal::Math(MathExpr { ref lhs, ref rhs, ref operator }) => {
let (l, r) = match (self.eval_expr_as_number(lhs)?, self.eval_expr_as_number(rhs)?)
{
(Some(l), Some(r)) => (l, r),
_ => return Ok(None),
};
match *operator {
MathOperator::Mul => {
if l.is_i64() && r.is_i64() {
let ll = l.as_i64().unwrap();
let rr = r.as_i64().unwrap();
let res = match ll.checked_mul(rr) {
Some(s) => s,
None => {
return Err(Error::msg(format!(
"{} x {} results in an out of bounds i64",
ll, rr
)));
}
};
Some(Number::from(res))
} else if l.is_u64() && r.is_u64() {
let ll = l.as_u64().unwrap();
let rr = r.as_u64().unwrap();
let res = match ll.checked_mul(rr) {
Some(s) => s,
None => {
return Err(Error::msg(format!(
"{} x {} results in an out of bounds u64",
ll, rr
)));
}
};
Some(Number::from(res))
} else {
let ll = l.as_f64().unwrap();
let rr = r.as_f64().unwrap();
Number::from_f64(ll * rr)
}
}
MathOperator::Div => {
let ll = l.as_f64().unwrap();
let rr = r.as_f64().unwrap();
let res = ll / rr;
if res.is_nan() {
None
} else {
if res.round() == res && res.is_finite() {
Some(Number::from(res as i64))
} else {
Number::from_f64(res)
}
}
}
MathOperator::Add => {
if l.is_i64() && r.is_i64() {
let ll = l.as_i64().unwrap();
let rr = r.as_i64().unwrap();
let res = match ll.checked_add(rr) {
Some(s) => s,
None => {
return Err(Error::msg(format!(
"{} + {} results in an out of bounds i64",
ll, rr
)));
}
};
Some(Number::from(res))
} else if l.is_u64() && r.is_u64() {
let ll = l.as_u64().unwrap();
let rr = r.as_u64().unwrap();
let res = match ll.checked_add(rr) {
Some(s) => s,
None => {
return Err(Error::msg(format!(
"{} + {} results in an out of bounds u64",
ll, rr
)));
}
};
Some(Number::from(res))
} else {
let ll = l.as_f64().unwrap();
let rr = r.as_f64().unwrap();
Some(Number::from_f64(ll + rr).unwrap())
}
}
MathOperator::Sub => {
if l.is_i64() && r.is_i64() {
let ll = l.as_i64().unwrap();
let rr = r.as_i64().unwrap();
let res = match ll.checked_sub(rr) {
Some(s) => s,
None => {
return Err(Error::msg(format!(
"{} - {} results in an out of bounds i64",
ll, rr
)));
}
};
Some(Number::from(res))
} else if l.is_u64() && r.is_u64() {
let ll = l.as_u64().unwrap();
let rr = r.as_u64().unwrap();
let res = match ll.checked_sub(rr) {
Some(s) => s,
None => {
return Err(Error::msg(format!(
"{} - {} results in an out of bounds u64",
ll, rr
)));
}
};
Some(Number::from(res))
} else {
let ll = l.as_f64().unwrap();
let rr = r.as_f64().unwrap();
Some(Number::from_f64(ll - rr).unwrap())
}
}
MathOperator::Modulo => {
if l.is_i64() && r.is_i64() {
let ll = l.as_i64().unwrap();
let rr = r.as_i64().unwrap();
if rr == 0 {
return Err(Error::msg(format!(
"Tried to do a modulo by zero: {:?}/{:?}",
lhs, rhs
)));
}
Some(Number::from(ll % rr))
} else if l.is_u64() && r.is_u64() {
let ll = l.as_u64().unwrap();
let rr = r.as_u64().unwrap();
if rr == 0 {
return Err(Error::msg(format!(
"Tried to do a modulo by zero: {:?}/{:?}",
lhs, rhs
)));
}
Some(Number::from(ll % rr))
} else {
let ll = l.as_f64().unwrap();
let rr = r.as_f64().unwrap();
Number::from_f64(ll % rr)
}
}
}
}
ExprVal::FunctionCall(ref fn_call) => {
let v = self.eval_tera_fn_call(fn_call, &mut false)?;
if v.is_i64() {
Some(Number::from(v.as_i64().unwrap()))
} else if v.is_u64() {
Some(Number::from(v.as_u64().unwrap()))
} else if v.is_f64() {
Some(Number::from_f64(v.as_f64().unwrap()).unwrap())
} else {
return Err(Error::msg(format!(
"Function `{}` was used in a math operation but is not returning a number",
fn_call.name
)));
}
}
ExprVal::String(ref val) => {
return Err(Error::msg(format!("Tried to do math with a string: `{}`", val)));
}
ExprVal::Bool(val) => {
return Err(Error::msg(format!("Tried to do math with a boolean: `{}`", val)));
}
ExprVal::StringConcat(ref val) => {
return Err(Error::msg(format!(
"Tried to do math with a string concatenation: {}",
val.to_template_string()
)));
}
ExprVal::Test(ref test) => {
return Err(Error::msg(format!("Tried to do math with a test: {}", test.name)));
}
_ => unreachable!("unimplemented math expression for {:?}", expr),
};
Ok(result)
}
fn do_super(&mut self, write: &mut impl Write) -> Result<()> {
let &(block_name, _, level) = self.blocks.last().unwrap();
let mut next_level = level + 1;
while next_level <= self.template.parents.len() {
let blocks_definitions = &self
.tera
.get_template(&self.template.parents[next_level - 1])
.unwrap()
.blocks_definitions;
if let Some(block_def) = blocks_definitions.get(block_name) {
let (ref tpl_name, Block { ref body, .. }) = block_def[0];
self.blocks.push((block_name, tpl_name, next_level));
self.render_body(body, write)?;
self.blocks.pop();
if next_level >= self.template.parents.len() {
self.blocks.pop();
}
return Ok(());
} else {
next_level += 1;
}
}
Err(Error::msg("Tried to use super() in the top level block"))
}
fn lookup_ident(&self, key: &str) -> Result<Val<'a>> {
if key == MAGICAL_DUMP_VAR {
return Ok(Cow::Owned(
to_value(
to_string_pretty(&self.call_stack.current_context_cloned().take()).unwrap(),
)
.unwrap(),
));
}
process_path(key, &self.call_stack)
}
fn render_node(&mut self, node: &'a Node, write: &mut impl Write) -> Result<()> {
match *node {
Node::Comment(_, _) => (),
Node::Text(ref s) | Node::Raw(_, ref s, _) => write!(write, "{}", s)?,
Node::VariableBlock(_, ref expr) => self.eval_expression(expr)?.render(write)?,
Node::Set(_, ref set) => self.eval_set(set)?,
Node::FilterSection(_, FilterSection { ref filter, ref body }, _) => {
let body = render_to_string(
|| format!("filter {}", filter.name),
|w| self.render_body(body, w),
)?;
if filter.name == "safe" {
write!(write, "{}", body)?;
} else {
self.eval_filter(&Cow::Owned(Value::String(body)), filter, &mut false)?
.render(write)?;
}
}
Node::ImportMacro(_, _, _) => (),
Node::If(ref if_node, _) => self.render_if_node(if_node, write)?,
Node::Forloop(_, ref forloop, _) => self.render_for_loop(forloop, write)?,
Node::Break(_) => {
self.call_stack.break_for_loop()?;
}
Node::Continue(_) => {
self.call_stack.continue_for_loop()?;
}
Node::Block(_, ref block, _) => self.render_block(block, 0, write)?,
Node::Super => self.do_super(write)?,
Node::Include(_, ref tpl_names, ignore_missing) => {
let mut found = false;
for tpl_name in tpl_names {
let template = self.tera.get_template(tpl_name);
if template.is_err() {
continue;
}
let template = template.unwrap();
self.macros.add_macros_from_template(&self.tera, template)?;
self.call_stack.push_include_frame(tpl_name, template);
self.render_body(&template.ast, write)?;
self.call_stack.pop();
found = true;
break;
}
if !found && !ignore_missing {
return Err(Error::template_not_found(
vec!["[", &tpl_names.join(", "), "]"].join(""),
));
}
}
Node::Extends(_, ref name) => {
return Err(Error::msg(format!(
"Inheritance in included templates is currently not supported: extended `{}`",
name
)));
}
Node::MacroDefinition(_, ref def, _) => {
return Err(Error::invalid_macro_def(&def.name));
}
};
Ok(())
}
fn get_error_location(&self) -> String {
let mut error_location = format!("Failed to render '{}'", self.template.name);
if self.call_stack.current_frame().kind == FrameType::Macro {
let frame = self.call_stack.current_frame();
error_location += &format!(
": error while rendering macro `{}::{}`",
frame.macro_namespace.expect("Macro namespace"),
frame.name,
);
}
if let Some(&(ref name, ref _template, ref level)) = self.blocks.last() {
let block_def = self
.template
.blocks_definitions
.get(&(*name).to_string())
.and_then(|b| b.get(*level));
if let Some(&(ref tpl_name, _)) = block_def {
if tpl_name != &self.template.name {
error_location += &format!(" (error happened in '{}').", tpl_name);
}
} else {
error_location += " (error happened in a parent template)";
}
} else if let Some(parent) = self.template.parents.last() {
error_location += &format!(" (error happened in '{}').", parent);
}
error_location
}
pub fn render(&mut self, write: &mut impl Write) -> Result<()> {
for node in &self.template_root.ast {
self.render_node(node, write)
.map_err(|e| Error::chain(self.get_error_location(), e))?;
}
Ok(())
}
}