mod forloop;
mod square_brackets;
#[cfg(test)]
mod tests;
use std::collections::HashMap;
use serde_json::to_string_pretty;
use serde_json::value::{to_value, Number, Value};
use serde_json::map::Map as JsonMap;
use self::forloop::{ForLoop, ForLoopState};
use self::square_brackets::pull_out_square_bracket;
use parser::ast::*;
use template::Template;
use tera::Tera;
use errors::{Result, ResultExt};
use context::{get_json_pointer, ValueNumber, ValueRender, ValueTruthy};
use utils::escape_html;
static MAGICAL_DUMP_VAR: &'static str = "__tera_context";
#[derive(Debug)]
pub struct Renderer<'a> {
template: &'a Template,
context: Value,
tera: &'a Tera,
for_loops: Vec<ForLoop>,
macros: Vec<HashMap<String, &'a HashMap<String, MacroDefinition>>>,
macro_context: Vec<(Value, Vec<ForLoop>)>,
macro_namespaces: Vec<String>,
should_escape: bool,
blocks: Vec<(String, usize)>,
}
impl<'a> Renderer<'a> {
pub fn new(template: &'a Template, tera: &'a Tera, context: Value) -> Renderer<'a> {
let should_escape = tera.autoescape_suffixes.iter().any(|ext| {
if let Some(ref p) = template.path {
return p.ends_with(ext);
}
template.name.ends_with(ext)
});
Renderer {
template,
tera,
context,
should_escape,
for_loops: vec![],
macros: vec![],
macro_context: vec![],
macro_namespaces: vec![],
blocks: vec![],
}
}
fn lookup_ident(&mut self, key: &str) -> Result<Value> {
let (context, for_loops) = match self.macro_context.last() {
Some(c) => (&c.0, &c.1),
None => (&self.context, &self.for_loops),
};
if key == MAGICAL_DUMP_VAR {
return Ok(to_value(to_string_pretty(context).unwrap()).unwrap());
}
#[inline]
fn evaluate_sub_variable(context: &Value, key: &str, tpl_name: &str) -> 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 find_variable(context, sub_var.as_ref(), tpl_name) {
Err(e) => {
bail!(format!("Variable {} can not be evaluated because: {}", key, e));
}
Ok(post_var) => {
let post_var_as_str = match post_var {
Value::String(ref s) => s.to_string(),
Value::Number(ref n) => n.to_string(),
_ => bail!(
"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("['", ".")
.replace("[\"", ".")
.replace("[", ".")
.replace("']", "")
.replace("\"]", "")
.replace("]", "")
)
}
#[inline]
fn find_variable(context: &Value, key: &str, tpl_name: &str) -> Result<Value> {
let key_s = if key.contains('[') {
evaluate_sub_variable(context, key, tpl_name)?
} else {
key.into()
};
match context.pointer(&get_json_pointer(key_s.as_ref())) {
Some(v) => Ok(v.clone()),
None => bail!(
"Variable `{}` not found in context while rendering '{}'",
key,
tpl_name
),
}
}
if for_loops.is_empty() {
return find_variable(context, key, &self.template.name);
}
let (real_key, tail) = if let Some(tail_pos) = key.find('.') {
(&key[..tail_pos], &key[tail_pos + 1..])
} else {
(key, "")
};
for for_loop in for_loops.iter().rev() {
if real_key == "loop" {
match tail {
"index" => {
return Ok(to_value(&(for_loop.current + 1))?);
}
"index0" => {
return Ok(to_value(&for_loop.current)?);
}
"first" => {
return Ok(to_value(&(for_loop.current == 0))?);
}
"last" => {
return Ok(to_value(&(for_loop.current == for_loop.len() - 1))?);
}
_ => bail!("Unknown loop built-in variable: {:?}", key),
}
}
if for_loop.is_key(key) {
return Ok(to_value(&for_loop.get_current_key())?);
}
let value = if real_key == for_loop.value_name {
for_loop.get_current_value()
} else {
for_loop.extra_values.get(real_key)
};
if let Some(v) = value {
if tail.is_empty() {
return Ok(v.clone());
}
return find_variable(v, tail, &self.template.name)
.chain_err(|| format!("Variable lookup failed in forloop for `{}`", key));
}
}
find_variable(context, key, &self.template.name)
}
fn current_for_loop_mut(&mut self) -> Option<&mut ForLoop> {
match self.macro_context.last_mut() {
Some(&mut (_, ref mut for_loops)) => for_loops.last_mut(),
None => self.for_loops.last_mut(),
}
}
fn current_for_loop(&self) -> Option<&ForLoop> {
match self.macro_context.last() {
Some(&(_, ref for_loops)) => for_loops.last(),
None => self.for_loops.last(),
}
}
fn eval_set(&mut self, set: &Set) -> Result<()> {
let val = self.safe_eval_expression(&set.value)?;
let context = match self.macro_context.last_mut() {
Some(c) => c.0.as_object_mut().unwrap(),
None => match self.for_loops.last_mut() {
Some(f) => if set.global {
self.context.as_object_mut().unwrap()
} else {
&mut f.extra_values
},
None => self.context.as_object_mut().unwrap(),
},
};
context.insert(set.key.to_string(), val);
Ok(())
}
fn eval_expr_as_number(&mut self, expr: &Expr) -> Result<Option<Number>> {
if !expr.filters.is_empty() {
match self.eval_expression(expr)? {
Value::Number(s) => Ok(Some(s)),
_ => bail!("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: &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 {
bail!(
"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();
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();
Some(Number::from(ll * rr))
} else {
let ll = l.as_f64().unwrap();
let rr = r.as_f64().unwrap();
Some(Number::from_f64(ll * rr).unwrap())
}
},
MathOperator::Div => {
let ll = l.as_f64().unwrap();
let rr = r.as_f64().unwrap();
let res = ll / rr;
if res.is_nan() {
None
} else {
Some(Number::from_f64(res).unwrap())
}
},
MathOperator::Add => {
if l.is_i64() && r.is_i64() {
let ll = l.as_i64().unwrap();
let rr = r.as_i64().unwrap();
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();
Some(Number::from(ll + rr))
} 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();
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();
Some(Number::from(ll - rr))
} 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();
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();
Some(Number::from(ll % rr))
} else {
let ll = l.as_f64().unwrap();
let rr = r.as_f64().unwrap();
Some(Number::from_f64(ll % rr).unwrap())
}
}
}
}
ExprVal::String(ref val) => bail!("Tried to do math with a string: `{}`", val),
ExprVal::Bool(val) => bail!("Tried to do math with a boolean: `{}`", val),
_ => unreachable!("unimplemented"),
};
Ok(result)
}
fn eval_as_bool(&mut self, expr: &Expr) -> Result<bool> {
let res = match 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),
_ => bail!("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 = Value::Number(
Number::from_f64(lhs_val.as_f64().unwrap()).unwrap()
);
rhs_val = 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(ref ident) => {
self.lookup_ident(ident).map(|v| v.is_truthy()).unwrap_or(false)
}
ExprVal::Math(_) | ExprVal::Int(_) | ExprVal::Float(_) => {
match self.eval_as_number(&expr.val) {
Ok(Some(n)) => n.as_f64().unwrap() != 0.0,
Ok(None) => false,
Err(_) => false,
}
}
ExprVal::Test(ref test) => self.eval_test(test).unwrap_or(false),
ExprVal::Bool(val) => val,
ExprVal::String(ref string) => !string.is_empty(),
_ => unreachable!("unimplemented logic operation for {:?}", expr),
};
if expr.negated {
return Ok(!res);
}
Ok(res)
}
fn eval_test(&mut self, test: &Test) -> Result<bool> {
let tester_fn = self.tera.get_tester(&test.name)?;
let mut tester_args = vec![];
for arg in &test.args {
tester_args.push(self.safe_eval_expression(arg)?);
}
Ok(tester_fn(self.lookup_ident(&test.ident).ok(), tester_args)?)
}
fn eval_global_fn_call(&mut self, fn_call: &FunctionCall) -> Result<Value> {
let global_fn = self.tera.get_global_function(&fn_call.name)?;
let mut args = HashMap::new();
for (arg_name, expr) in &fn_call.args {
args.insert(arg_name.to_string(), self.safe_eval_expression(expr)?);
}
global_fn(args)
}
fn eval_macro_call(&mut self, macro_call: &MacroCall) -> Result<String> {
let active_namespace = match macro_call.namespace.as_ref() {
"self" => {
self.macro_namespaces
.last()
.expect("Open an issue with a template sample please (mention `self namespace macro`)!")
.to_string()
}
_ => {
self.macro_namespaces.push(macro_call.namespace.clone());
macro_call.namespace.clone()
}
};
let macro_definition = self.macros
.last()
.and_then(|m| m.get(&active_namespace))
.and_then(|m| m.get(¯o_call.name))
.ok_or_else(|| {
format!(
"Macro `{}` was not found in the namespace `{}`",
macro_call.name, active_namespace,
)
})?;
let mut macro_context = JsonMap::new();
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 => bail!(
"Macro `{}` is missing the argument `{}`",
macro_call.name,
arg_name,
),
}
};
macro_context.insert(arg_name.to_string(), value);
}
self.macro_context.push((macro_context.into(), vec![]));
let output = self.render_body(¯o_definition.body)?;
if macro_call.namespace == active_namespace {
self.macro_namespaces.pop();
}
self.macro_context.pop();
Ok(output)
}
fn eval_filter(&mut self, value: Value, filter: &FunctionCall) -> Result<Value> {
let filter_fn = self.tera.get_filter(&filter.name)?;
let mut args = HashMap::new();
for (arg_name, expr) in &filter.args {
args.insert(arg_name.to_string(), self.safe_eval_expression(expr)?);
}
filter_fn(value, args)
}
fn eval_expression(&mut self, expr: &Expr) -> Result<Value> {
let mut needs_escape = false;
let mut res = match expr.val {
ExprVal::Array(ref arr) => {
let mut vals = vec![];
for v in arr {
vals.push(self.eval_expression(v)?);
}
Value::Array(vals)
},
ExprVal::String(ref val) => {
needs_escape = true;
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::Ident(ref i) => {
match self.lookup_ident(i)? {
Value::String(v) => res.push_str(&v),
_ => bail!("Tried to concat a value that is not a string from ident {}", i)
}
}
_ => unreachable!()
};
}
Value::String(res)
},
ExprVal::Int(val) => Value::Number(val.into()),
ExprVal::Float(val) => Value::Number(Number::from_f64(val).unwrap()),
ExprVal::Bool(val) => Value::Bool(val),
ExprVal::Ident(ref ident) => {
needs_escape = ident != MAGICAL_DUMP_VAR;
match self.lookup_ident(ident) {
Ok(val) => val,
Err(e) => {
if expr.has_default_filter() {
if let Some(default_expr) = expr.filters[0].args.get("value") {
self.eval_expression(default_expr)?
} else {
bail!("The `default` filter requires a `value` argument.");
}
} else {
if !expr.negated {
return Err(e);
}
return Ok(Value::Bool(true));
}
}
}
}
ExprVal::FunctionCall(ref fn_call) => {
needs_escape = true;
self.eval_global_fn_call(fn_call)?
}
ExprVal::MacroCall(ref macro_call) => Value::String(self.eval_macro_call(macro_call)?),
ExprVal::Test(ref test) => Value::Bool(self.eval_test(test)?),
ExprVal::Logic(_) => Value::Bool(self.eval_as_bool(expr)?),
ExprVal::Math(_) => {
match self.eval_as_number(&expr.val) {
Ok(Some(n)) => Value::Number(n),
Ok(None) => Value::String("NaN".to_owned()),
Err(e) => bail!(e.to_string()),
}
}
_ => unreachable!("{:?}", expr),
};
if self.should_escape
&& needs_escape
&& res.is_string()
&& expr.filters.first().map_or(true, |f| f.name != "safe") {
res = to_value(self.tera.get_escape_fn()(res.as_str().unwrap()))?;
}
for filter in &expr.filters {
if filter.name == "safe" || filter.name == "default" {
continue;
}
res = self.eval_filter(res, filter)?;
}
if expr.negated {
return Ok(Value::Bool(!res.is_truthy()));
}
Ok(res)
}
fn safe_eval_expression(&mut self, expr: &Expr) -> Result<Value> {
let should_escape = self.should_escape;
self.should_escape = false;
let res = self.eval_expression(expr);
self.should_escape = should_escape;
res
}
fn render_if(&mut self, node: &If) -> Result<String> {
for &(_, ref expr, ref body) in &node.conditions {
if self.eval_as_bool(expr)? {
return self.render_body(body);
}
}
if let Some((_, ref body)) = node.otherwise {
return self.render_body(body);
}
Ok(String::new())
}
fn render_for(&mut self, node: &Forloop) -> Result<String> {
let container_name = match node.container.val {
ExprVal::Ident(ref ident) => ident,
ExprVal::FunctionCall(FunctionCall { ref name, .. }) => name,
ExprVal::Array(_) => "an array literal",
_ => bail!(
"Forloop containers have to be an ident or a function call (tried to iterate on '{:?}')",
node.container.val,
),
};
let container_val = self.safe_eval_expression(&node.container)?;
let for_loop = match container_val {
Value::Array(_) => {
if node.key.is_some() {
bail!(
"Tried to iterate using key value on variable `{}`, but it isn't an object/map",
container_name,
);
}
ForLoop::new(&node.value, container_val)
}
Value::Object(_) => {
if node.key.is_none() {
bail!(
"Tried to iterate using key value on variable `{}`, but it is missing a key",
container_name,
);
}
ForLoop::new_key_value(
&node.key.clone().unwrap(),
&node.value,
container_val,
)
}
_ => bail!(
"Tried to iterate on a container (`{}`) that has a unsupported type",
container_name,
),
};
let length = for_loop.len();
match self.macro_context.last_mut() {
Some(m) => m.1.push(for_loop),
None => self.for_loops.push(for_loop),
};
let mut output = String::new();
for _ in 0..length {
output.push_str(&self.render_body(&node.body)?);
if self.current_for_loop_mut().unwrap().state == ForLoopState::Break {
break
}
match self.macro_context.last_mut() {
Some(m) => m.1.last_mut().unwrap().increment(),
None => self.for_loops.last_mut().unwrap().increment(),
};
}
match self.macro_context.last_mut() {
Some(m) => m.1.pop(),
None => self.for_loops.pop(),
};
Ok(output)
}
fn import_template_macros(&mut self, tpl_name: &str) -> Result<bool> {
let tpl = self.tera.get_template(tpl_name)?;
if tpl.imported_macro_files.is_empty() {
return Ok(false);
}
fn load_macros<'a>(
tera: &'a Tera,
tpl: &Template,
) -> Result<HashMap<String, &'a HashMap<String, MacroDefinition>>> {
let mut macros = HashMap::new();
for &(ref filename, ref namespace) in &tpl.imported_macro_files {
let macro_tpl = tera.get_template(filename)?;
macros.insert(namespace.to_string(), ¯o_tpl.macros);
if !macro_tpl.imported_macro_files.is_empty() {
macros.extend(load_macros(tera, macro_tpl)?);
}
}
Ok(macros)
}
self.macros.push(load_macros(self.tera, tpl)?);
Ok(true)
}
fn render_block(&mut self, block: &Block, level: usize) -> Result<String> {
let blocks_definitions = match level {
0 => &self.template.blocks_definitions,
_ => {
&self.tera
.get_template(&self.template.parents[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.to_string(), level));
let has_macro = self.import_template_macros(tpl_name)?;
let res = self.render_body(body);
if has_macro {
self.macros.pop();
}
return res;
}
if level < self.template.parents.len() {
return self.render_block(block, level + 1);
}
self.render_body(&block.body)
}
fn do_super(&mut self) -> Result<String> {
let (block_name, level) = self.blocks.pop().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.to_string(), next_level));
let has_macro = self.import_template_macros(tpl_name)?;
let res = self.render_body(body);
if has_macro {
self.macros.pop();
}
if next_level >= self.template.parents.len() {
self.blocks.pop();
}
return res;
} else {
next_level += 1;
}
}
bail!("Tried to use super() in the top level block")
}
fn render_node(&mut self, node: &Node) -> Result<String> {
let output = match *node {
Node::Text(ref s) | Node::Raw(_, ref s, _) => s.to_string(),
Node::VariableBlock(ref expr) => self.eval_expression(expr)?.render(),
Node::Set(_, ref set) => self.eval_set(set).and(Ok(String::new()))?,
Node::FilterSection(_, FilterSection { ref filter, ref body }, _) => {
let output = self.render_body(body)?;
self.eval_filter(Value::String(output), filter)?.render()
}
Node::ImportMacro(_, _, _) => String::new(),
Node::If(ref if_node, _) => self.render_if(if_node)?,
Node::Forloop(_, ref forloop, _) => self.render_for(forloop)?,
Node::Break(_) => {
self.current_for_loop_mut().unwrap().break_loop();
String::new()
},
Node::Continue(_) => {
self.current_for_loop_mut().unwrap().continue_loop();
String::new()
},
Node::Block(_, ref block, _) => self.render_block(block, 0)?,
Node::Super => self.do_super()?,
Node::Include(_, ref tpl_name) => {
let has_macro = self.import_template_macros(tpl_name)?;
let res = self.render_body(&self.tera.get_template(tpl_name)?.ast);
if has_macro {
self.macros.pop();
}
return res;
}
_ => unreachable!("render_node -> unexpected node: {:?}", node),
};
Ok(output)
}
fn render_body(&mut self, body: &[Node]) -> Result<String> {
let mut output = String::new();
for n in body {
output.push_str(&self.render_node(n)?);
if let Some(for_loop) = self.current_for_loop() {
match for_loop.state {
ForLoopState::Continue
| ForLoopState::Break => break,
ForLoopState::Normal => {},
}
}
}
Ok(output)
}
fn get_error_location(&self) -> String {
let mut error_location = format!("Failed to render '{}'", self.template.name);
if let Some(macro_namespace) = self.macro_namespaces.last() {
error_location += &format!(
": error while rendering a macro from the `{}` namespace",
macro_namespace,
);
}
if let Some(&(ref name, ref level)) = self.blocks.last() {
let block_def = self.template
.blocks_definitions
.get(name)
.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) -> Result<String> {
let (tpl_name, ast) = match self.template.parents.last() {
Some(parent_tpl_name) => {
let tpl = self.tera.get_template(parent_tpl_name).unwrap();
(&tpl.name, &tpl.ast)
}
None => (&self.template.name, &self.template.ast),
};
self.import_template_macros(tpl_name)?;
let mut output = String::new();
for node in ast {
output.push_str(
&self.render_node(node).chain_err(|| self.get_error_location())?
);
}
Ok(output)
}
}