use std::collections::{HashMap, LinkedList};
use pest::prelude::*;
use serde_json::value::{Value, to_value};
use errors::{TeraResult, TeraError};
#[derive(Clone, Debug, PartialEq)]
pub enum Node {
List(LinkedList<Node>),
Text(String),
Int(i32),
Float(f32),
Bool(bool),
Math {lhs: Box<Node>, rhs: Box<Node>, operator: String},
Logic {lhs: Box<Node>, rhs: Box<Node>, operator: String},
If {condition_nodes: LinkedList<Node>, else_node: Option<Box<Node>>},
Conditional {condition: Box<Node>, body: Box<Node>},
For {variable: String, array: String, body: Box<Node>},
Block {name: String, body: Box<Node>},
Test {expression: Box<Node>, name: String, params: LinkedList<Node>},
Filter {name: String, args: HashMap<String, Value>, args_ident: HashMap<String, String>},
Identifier {name: String, filters: Option<LinkedList<Node>>},
Raw(String),
Extends(String),
VariableBlock(Box<Node>),
}
impl Node {
pub fn get_children(&self) -> LinkedList<Node> {
match *self {
Node::List(ref l) => l.clone(),
Node::If {ref condition_nodes, ..} => condition_nodes.clone(),
_ => panic!("tried to get_children on a non-list/if node")
}
}
}
impl_rdp! {
grammar! {
whitespace = _{ ([" "] | ["\t"] | ["\r"] | ["\n"])+ }
op_or = { ["or"] }
op_wrong_or = { ["||"] }
op_and = { ["and"] }
op_wrong_and = { ["&&"] }
op_lte = { ["<="] }
op_gte = { [">="] }
op_lt = { ["<"] }
op_gt = { [">"] }
op_eq = { ["=="] }
op_ineq = { ["!="] }
op_plus = { ["+"] }
op_minus = { ["-"] }
op_times = { ["*"] }
op_slash = { ["/"] }
op_true = { ["true"] }
op_false = { ["false"] }
boolean = _{ op_true | op_false }
op_filter = _{ ["|"] }
int = @{ ["-"]? ~ (["0"] | ['1'..'9'] ~ ['0'..'9']*) }
float = @{
["-"]? ~
["0"] ~ ["."] ~ ['0'..'9']+ |
['1'..'9'] ~ ['0'..'9']* ~ ["."] ~ ['0'..'9']+
}
string = @{ ["\""] ~ (!(["\""]) ~ any )* ~ ["\""]}
simple_ident = @{
(['a'..'z'] | ['A'..'Z'] | ["_"]) ~
(['a'..'z'] | ['A'..'Z'] | ["_"] | ['0'..'9'])*
}
fn_arg_value = @{ boolean | int | float | string | identifier }
fn_arg = @{ simple_ident ~ ["="] ~ fn_arg_value }
fn_args = !@{ fn_arg ~ ([","] ~ fn_arg )* }
fn_call = { simple_ident ~ ["("] ~ fn_args ~ [")"] | simple_ident }
filters = { (op_filter ~ fn_call)+ }
identifier = @{
(['a'..'z'] | ['A'..'Z'] | ["_"]) ~
(['a'..'z'] | ['A'..'Z'] | ["_"] | ["."] | ['0'..'9'])*
}
identifier_with_filter = { identifier ~ filters }
idents = _{ identifier_with_filter | identifier }
test_fn_param = { expression }
test_fn_params = {
test_fn_param
| (["("] ~ test_fn_param ~ ([","] ~ test_fn_param)* ~ [")"])
}
test_fn = !@{ simple_ident ~ test_fn_params? }
test = { ["is"] ~ test_fn }
expression = _{
{ boolean | idents | float | int }
or = { op_or | op_wrong_or }
and = { op_and | op_wrong_and }
comparison = { op_gt | op_lt | op_eq | op_ineq | op_lte | op_gte }
add_sub = { op_plus | op_minus }
mul_div = { op_times | op_slash }
}
variable_start = _{ ["{{"] }
variable_end = _{ ["}}"] }
tag_start = _{ ["{%"] }
tag_end = _{ ["%}"] }
comment_start = _{ ["{#"] }
comment_end = _{ ["#}"] }
block_start = _{ variable_start | tag_start | comment_start }
extends_tag = !@{ tag_start ~ ["extends"] ~ string ~ tag_end }
variable_tag = !@{ variable_start ~ expression ~ variable_end }
comment_tag = !@{ comment_start ~ (!comment_end ~ any )* ~ comment_end }
block_tag = !@{ tag_start ~ ["block"] ~ identifier ~ tag_end }
if_tag = !@{ tag_start ~ ["if"] ~ expression ~ test? ~ tag_end }
elif_tag = !@{ tag_start ~ ["elif"] ~ expression ~ test? ~ tag_end }
else_tag = !@{ tag_start ~ ["else"] ~ tag_end }
for_tag = !@{ tag_start ~ ["for"] ~ identifier ~ ["in"] ~ idents ~ tag_end }
raw_tag = !@{ tag_start ~ ["raw"] ~ tag_end }
endraw_tag = !@{ tag_start ~ ["endraw"] ~ tag_end }
endblock_tag = !@{ tag_start ~ ["endblock"] ~ identifier ~ tag_end }
endif_tag = !@{ tag_start ~ ["endif"] ~ tag_end }
endfor_tag = !@{ tag_start ~ ["endfor"] ~ tag_end }
elif_block = { elif_tag ~ content* }
raw_text = { (!endraw_tag ~ any )* }
text = { (!(block_start) ~ any )+ }
content = @{
variable_tag |
comment_tag |
block_tag ~ content* ~ endblock_tag |
if_tag ~ content* ~ elif_block* ~ (else_tag ~ content*)? ~ endif_tag |
for_tag ~ content* ~ endfor_tag |
raw_tag ~ raw_text ~ endraw_tag |
text
}
template = @{ soi ~ extends_tag? ~ content* ~ eoi }
}
process! {
main(&self) -> TeraResult<Node> {
(_: template, tpl: _template()) => {
match tpl {
Ok(t) => Ok(Node::List(t)),
Err(e) => Err(e)
}
}
}
_template(&self) -> TeraResult<LinkedList<Node>> {
(_: extends_tag, &name: string, tail: _template()) => {
let mut tail2 = try!(tail);
tail2.push_front(Node::Extends(name.replace("\"", "").to_string()));
Ok(tail2)
},
(_: extends_tag, &name: string) => {
let mut body = LinkedList::new();
body.push_front(Node::Extends(name.replace("\"", "").to_string()));
Ok(body)
},
(_: content, node: _content(), tail: _template()) => {
let mut tail2 = try!(tail);
match try!(node) {
Some(n) => { tail2.push_front(n); }
None => ()
};
Ok(tail2)
},
() => Ok(LinkedList::new())
}
_content(&self) -> TeraResult<Option<Node>> {
(&head: text) => {
Ok(Some(Node::Text(head.to_string())))
},
(_: variable_tag, exp: _expression()) => {
Ok(Some(Node::VariableBlock(Box::new(try!(exp)))))
},
(_: raw_tag, &body: raw_text, _: endraw_tag) => {
Ok(Some(Node::Raw(body.to_string())))
},
(_: block_tag, &name: identifier, body: _template(), _: endblock_tag, &end_name: identifier) => {
if name != end_name {
let (line_no, col_no) = self.input().line_col(self.input.pos());
return Err(
TeraError::MismatchingEndBlock(
line_no, col_no, name.to_string(), end_name.to_string()
)
);
}
Ok(Some(Node::Block {
name: name.to_string(),
body: Box::new(Node::List(try!(body)))
}))
},
(_: for_tag, &variable: identifier, &array: identifier, body: _template(), _: endfor_tag) => {
Ok(Some(Node::For {
variable: variable.to_string(),
array: array.to_string(),
body: Box::new(Node::List(try!(body)))
}))
},
(_: if_tag, cond: _condition(), body: _template(), _: endif_tag) => {
let mut condition_nodes = LinkedList::new();
condition_nodes.push_front(Node::Conditional {
condition: Box::new(try!(cond)),
body: Box::new(Node::List(try!(body))),
});
Ok(Some(Node::If {
condition_nodes: condition_nodes,
else_node: None,
}))
},
(_: if_tag, cond: _condition(), body: _template(), elifs: _elifs(), _: else_tag, else_body: _template(), _: endif_tag) => {
let mut condition_nodes = LinkedList::new();
condition_nodes.push_front(Node::Conditional {
condition: Box::new(try!(cond)),
body: Box::new(Node::List(try!(body))),
});
for elif in try!(elifs) {
condition_nodes.push_back(elif)
}
Ok(Some(Node::If {
condition_nodes: condition_nodes,
else_node: Some(Box::new(Node::List(try!(else_body)))),
}))
},
(_: if_tag, cond: _condition(), body: _template(), elifs: _elifs(), _: endif_tag) => {
let mut condition_nodes = LinkedList::new();
condition_nodes.push_front(Node::Conditional {
condition: Box::new(try!(cond)),
body: Box::new(Node::List(try!(body))),
});
for elif in try!(elifs) {
condition_nodes.push_back(elif)
}
Ok(Some(Node::If {
condition_nodes: condition_nodes,
else_node: None,
}))
},
(_: if_tag, cond: _condition(), body: _template(), _: else_tag, else_body: _template(), _: endif_tag) => {
let mut condition_nodes = LinkedList::new();
condition_nodes.push_front(Node::Conditional {
condition: Box::new(try!(cond)),
body: Box::new(Node::List(try!(body))),
});
Ok(Some(Node::If {
condition_nodes: condition_nodes,
else_node: Some(Box::new(Node::List(try!(else_body)))),
}))
},
(_: comment_tag) => {
Ok(None)
}
}
_condition(&self) -> TeraResult<Node> {
(exp: _expression(), _: test, test_args: _test()) => {
let (name, params) = try!(test_args);
Ok(Node::Test {
expression: Box::new(try!(exp)),
name: name,
params: params,
})
},
(exp: _expression()) => {
exp
}
}
_elifs(&self) -> TeraResult<LinkedList<Node>> {
(_: elif_block, node: _if(), tail: _elifs()) => {
let mut tail2 = try!(tail);
tail2.push_front(try!(node));
Ok(tail2)
},
() => Ok(LinkedList::new())
}
_if(&self) -> TeraResult<Node> {
(_: if_tag, cond: _condition(), body: _template()) => {
Ok(Node::Conditional {
condition: Box::new(try!(cond)),
body: Box::new(Node::List(try!(body))),
})
},
(_: elif_tag, cond: _condition(), body: _template()) => {
Ok(Node::Conditional {
condition: Box::new(try!(cond)),
body: Box::new(Node::List(try!(body))),
})
},
}
_fn_arg_value(&self) -> Value {
(&number: int) => {
to_value(&number.parse::<i32>().unwrap())
},
(&number: float) => {
to_value(&number.parse::<f32>().unwrap())
},
(_: op_true) => {
to_value(&true)
},
(_: op_false) => {
to_value(&false)
},
(&text: text) => {
to_value(text)
}
}
_fn_args(&self) -> (HashMap<String, Value>, HashMap<String, String>) {
(_: fn_args, _: fn_arg, &name: simple_ident, _: fn_arg_value, &ident: identifier, mut tail: _fn_args()) => {
tail.1.insert(name.to_string(), ident.to_string());
tail
},
(_: fn_args, _: fn_arg, &name: simple_ident, _: fn_arg_value, arg: _fn_arg_value(), mut tail: _fn_args()) => {
tail.0.insert(name.to_string(), arg);
tail
},
(_: fn_arg, &name: simple_ident, _: fn_arg_value, &ident: identifier, mut tail: _fn_args()) => {
tail.1.insert(name.to_string(), ident.to_string());
tail
},
(_: fn_arg, &name: simple_ident, _: fn_arg_value, arg: _fn_arg_value(), mut tail: _fn_args()) => {
tail.0.insert(name.to_string(), arg);
tail
},
() => (HashMap::new(), HashMap::new())
}
_fn(&self) -> TeraResult<Node> {
(_: fn_call, &name: simple_ident, args: _fn_args()) => {
Ok(Node::Filter{name: name.to_string(), args: args.0, args_ident: args.1})
},
(&name: simple_ident, args: _fn_args()) => {
Ok(Node::Filter{name: name.to_string(), args: args.0, args_ident: args.1})
},
}
_filters(&self) -> TeraResult<LinkedList<Node>> {
(_: filters, filter: _fn(), tail: _filters()) => {
let mut tail2 = try!(tail);
tail2.push_front(try!(filter));
Ok(tail2)
},
(_: fn_call, filter: _fn(), tail: _filters()) => {
let mut tail2 = try!(tail);
tail2.push_front(try!(filter));
Ok(tail2)
},
() => Ok(LinkedList::new())
}
_test_fn_params(&self) -> (TeraResult<LinkedList<Node>>) {
(_: test_fn_params, _: test_fn_param, value: _expression(), tail: _test_fn_params()) => {
let mut tail = try!(tail);
tail.push_front(try!(value));
Ok(tail)
},
(_: test_fn_param, value: _expression(), tail: _test_fn_params()) => {
let mut tail = try!(tail);
tail.push_front(try!(value));
Ok(tail)
},
() => (Ok(LinkedList::new()))
}
_test(&self) -> TeraResult<(String, LinkedList<Node>)> {
(_: test_fn, &name: simple_ident, params: _test_fn_params()) => {
Ok((name.to_string(), try!(params)))
},
}
_expression(&self) -> TeraResult<Node> {
(_: add_sub, left: _expression(), sign, right: _expression()) => {
Ok(Node::Math {
lhs: Box::new(try!(left)),
rhs: Box::new(try!(right)),
operator: match sign.rule {
Rule::op_plus => "+".to_string(),
Rule::op_minus => "-".to_string(),
_ => unreachable!()
}
})
},
(_: mul_div, left: _expression(), sign, right: _expression()) => {
Ok(Node::Math {
lhs: Box::new(try!(left)),
rhs: Box::new(try!(right)),
operator: match sign.rule {
Rule::op_times => "*".to_string(),
Rule::op_slash => "/".to_string(),
_ => unreachable!()
}
})
},
(_: comparison, left: _expression(), sign, right: _expression()) => {
Ok(Node::Logic {
lhs: Box::new(try!(left)),
rhs: Box::new(try!(right)),
operator: match sign.rule {
Rule::op_gt => ">".to_string(),
Rule::op_lt => "<".to_string(),
Rule::op_eq => "==".to_string(),
Rule::op_ineq => "!=".to_string(),
Rule::op_lte => "<=".to_string(),
Rule::op_gte => ">=".to_string(),
_ => unreachable!()
}
})
},
(_: and, left: _expression(), _, right: _expression()) => {
Ok(Node::Logic {
lhs: Box::new(try!(left)),
rhs: Box::new(try!(right)),
operator: "and".to_string()
})
},
(_: or, left: _expression(), _, right: _expression()) => {
Ok(Node::Logic {
lhs: Box::new(try!(left)),
rhs: Box::new(try!(right)),
operator: "or".to_string()
})
},
(_: identifier_with_filter, &ident: identifier, tail: _filters()) => {
Ok(Node::Identifier {
name: ident.to_string(),
filters: Some(try!(tail)),
})
},
(&ident: identifier) => {
Ok(Node::Identifier {name: ident.to_string(), filters: None })
},
(&number: int) => {
Ok(Node::Int(number.parse::<i32>().unwrap()))
},
(&number: float) => {
Ok(Node::Float(number.parse::<f32>().unwrap()))
},
(_: op_true) => {
Ok(Node::Bool(true))
},
(_: op_false) => {
Ok(Node::Bool(false))
},
(&text: text) => {
Ok(Node::Text(text.to_string()))
},
}
}
}
pub fn parse(input: &str) -> TeraResult<Node> {
let mut parser = Rdp::new(StringInput::new(input));
if !parser.template() {
let (_, pos) = parser.expected();
let (line_no, col_no) = parser.input().line_col(pos);
return Err(TeraError::InvalidSyntax(line_no, col_no));
}
for token in parser.queue() {
match token.rule {
Rule::op_wrong_and => {
let (line_no, col_no) = parser.input().line_col(token.start);
return Err(
TeraError::DeprecatedSyntax(
line_no, col_no, "Use `and` instead of `&&`".to_string()
)
);
},
Rule::op_wrong_or => {
let (line_no, col_no) = parser.input().line_col(token.start);
return Err(
TeraError::DeprecatedSyntax(
line_no, col_no, "Use `or` instead of `||`".to_string()
)
);
},
_ => ()
};
}
parser.main()
}
#[cfg(test)]
mod tests {
use std::collections::{LinkedList, HashMap};
use pest::prelude::*;
use serde_json::value::{to_value};
use super::{Rdp, Node, parse};
use errors::TeraError;
#[test]
fn test_int() {
let mut parser = Rdp::new(StringInput::new("123"));
assert!(parser.int());
assert!(parser.end());
}
#[test]
fn test_float() {
let mut parser = Rdp::new(StringInput::new("123.5"));
assert!(parser.float());
assert!(parser.end());
}
#[test]
fn test_identifier() {
let mut parser = Rdp::new(StringInput::new("client.phone_number"));
assert!(parser.identifier());
assert!(parser.end());
}
#[test]
fn test_identifier_with_filter() {
let mut parser = Rdp::new(
StringInput::new("phone_number | phone(format=user.country) | truncate(limit=50)")
);
assert!(parser.identifier_with_filter());
assert!(parser.end());
}
#[test]
fn test_text() {
let mut parser = Rdp::new(StringInput::new("Hello\n 世界"));
assert!(parser.text());
assert!(parser.end());
}
#[test]
fn test_text_with_trailing_space() {
let mut parser = Rdp::new(StringInput::new("Hello\n 世界 "));
assert!(parser.template());
assert!(parser.end());
}
#[test]
fn test_text_with_leading_space() {
let mut parser = Rdp::new(StringInput::new(" Hello\n 世界"));
assert!(parser.text());
assert!(parser.end());
}
#[test]
fn test_string() {
let mut parser = Rdp::new(StringInput::new("\"Blabla\""));
assert!(parser.string());
assert!(parser.end());
}
#[test]
fn test_extends_tag() {
let mut parser = Rdp::new(StringInput::new("{% extends \"base.html\" %}"));
assert!(parser.extends_tag());
assert!(parser.end());
}
#[test]
fn test_comment_tag() {
let mut parser = Rdp::new(StringInput::new("{# some text {{}} #}"));
assert!(parser.comment_tag());
assert!(parser.end());
}
#[test]
fn test_block_tag() {
let mut parser = Rdp::new(StringInput::new("{% block hello %}"));
assert!(parser.block_tag());
assert!(parser.end());
}
#[test]
fn test_endblock_tag() {
let mut parser = Rdp::new(StringInput::new("{% endblock hello %}"));
assert!(parser.endblock_tag());
assert!(parser.end());
}
#[test]
fn test_for_tag() {
let mut parser = Rdp::new(StringInput::new("{% for client in clients %}"));
assert!(parser.for_tag());
assert!(parser.end());
}
#[test]
fn test_for_tag_with_filter() {
let mut parser = Rdp::new(StringInput::new("{% for client in clients | slice(start=1, end=9) %}"));
assert!(parser.for_tag());
assert!(parser.end());
}
#[test]
fn test_endfor_tag() {
let mut parser = Rdp::new(StringInput::new("{% endfor %}"));
assert!(parser.endfor_tag());
assert!(parser.end());
}
#[test]
fn test_expression_math() {
let mut parser = Rdp::new(StringInput::new("1 + 2 + 3 * 9/2 + 2"));
assert!(parser.expression());
assert!(parser.end());
}
#[test]
fn test_expression_identifier_logic_simple() {
let mut parser = Rdp::new(StringInput::new("index + 1 > 1"));
assert!(parser.expression());
assert!(parser.end());
}
#[test]
fn test_expression_identifier_logic_complex() {
let mut parser = Rdp::new(StringInput::new("1 > 2 or 3 == 4 and admin"));
assert!(parser.expression());
assert!(parser.end());
}
#[test]
fn test_if_tag() {
let mut parser = Rdp::new(StringInput::new("{% if true or show == false %}"));
assert!(parser.if_tag());
assert!(parser.end());
}
#[test]
fn test_if_tag_with_filter() {
let mut parser = Rdp::new(StringInput::new("{% if 1 + something | test %}"));
assert!(parser.if_tag());
assert!(parser.end());
}
#[test]
fn test_if_tag_with_test() {
let mut parser = Rdp::new(StringInput::new("{% if value is defined %}"));
assert!(parser.if_tag());
assert!(parser.end());
}
#[test]
fn test_elif_tag_with_test() {
let mut parser = Rdp::new(StringInput::new("{% elif value is defined %}"));
assert!(parser.elif_tag());
assert!(parser.end());
}
#[test]
fn test_variable_tag() {
let mut parser = Rdp::new(StringInput::new("{{loop.index + 1}}"));
assert!(parser.variable_tag());
assert!(parser.end());
}
#[test]
fn test_variable_tag_with_filter() {
let mut parser = Rdp::new(StringInput::new("{{ greeting | i18n(lang=user.lang) | truncate(limit=50) }}"));
assert!(parser.variable_tag());
assert!(parser.end());
}
#[test]
fn test_content() {
let mut parser = Rdp::new(StringInput::new("{% if i18n %}世界{% else %}world{% endif %}"));
assert!(parser.content());
assert!(parser.end());
}
#[test]
fn test_template() {
let mut parser = Rdp::new(StringInput::new("
{# Greeter template #}
Hello {% if i18n %}世界{% else %}world{% endif %}
{% for country in countries %}
{{ loop.index }}.{{ country }}
{% endfor %}
"));
assert!(parser.template());
assert!(parser.end());
}
#[test]
fn test_invalid_syntax() {
let parsed_ast = parse("{% block hey ");
assert!(parsed_ast.is_err());
assert_eq!(
parsed_ast.err().unwrap(),
TeraError::InvalidSyntax(1, 1)
);
}
#[test]
fn test_invalid_extends() {
let parsed_ast = parse("{% extends \"base.html\" %} {% extends \"base.html\" %}");
assert!(parsed_ast.is_err());
assert_eq!(
parsed_ast.err().unwrap(),
TeraError::InvalidSyntax(1, 27)
);
}
#[test]
fn test_ast_basic() {
let parsed_ast = parse(" Hello {{ count + 1 * 2.5 }} {{ true or false and 1 }}");
let mut ast = LinkedList::new();
ast.push_front(Node::VariableBlock(
Box::new(Node::Logic {
lhs: Box::new(Node::Bool(true)),
rhs: Box::new(Node::Logic {
lhs: Box::new(Node::Bool(false)),
rhs: Box::new(Node::Int(1)),
operator: "and".to_string()
}),
operator: "or".to_string()
})
));
ast.push_front(Node::Text(" ".to_string()));
ast.push_front(Node::VariableBlock(
Box::new(Node::Math {
lhs: Box::new(Node::Identifier{name: "count".to_string(), filters: None}),
rhs: Box::new(Node::Math {
lhs: Box::new(Node::Int(1)),
rhs: Box::new(Node::Float(2.5)),
operator: "*".to_string()
}),
operator: "+".to_string()
})
));
ast.push_front(Node::Text(" Hello ".to_string()));
let root = Node::List(ast);
assert_eq!(parsed_ast.unwrap(), root);
}
#[test]
fn test_ast_block() {
let parsed_ast = parse("{% block content %}Hello{% endblock content %}");
let mut ast = LinkedList::new();
let mut inner_content = LinkedList::new();
inner_content.push_front(Node::Text("Hello".to_string()));
ast.push_front(Node::Block {
name: "content".to_string(),
body: Box::new(Node::List(inner_content))
});
let root = Node::List(ast);
assert_eq!(parsed_ast.unwrap(), root);
}
#[test]
fn test_ast_for() {
let parsed_ast = parse("{% for user in users %}{{user.email}}{% endfor %}");
let mut ast = LinkedList::new();
let mut inner_content = LinkedList::new();
inner_content.push_front(Node::VariableBlock(
Box::new(Node::Identifier {name: "user.email".to_string(), filters: None})
));
ast.push_front(Node::For {
variable: "user".to_string(),
array: "users".to_string(),
body: Box::new(Node::List(inner_content))
});
let root = Node::List(ast);
assert_eq!(parsed_ast.unwrap(), root);
}
#[test]
fn test_ast_extends() {
let parsed_ast = parse("{% extends \"base.html\" %}");
let mut ast = LinkedList::new();
ast.push_front(Node::Extends("base.html".to_string()));
let root = Node::List(ast);
assert_eq!(parsed_ast.unwrap(), root);
}
#[test]
fn test_ast_if() {
let parsed_ast = parse("{% if superadmin %}Hey{% endif %}");
let mut ast = LinkedList::new();
let mut body = LinkedList::new();
body.push_front(Node::Text("Hey".to_string()));
let mut condition_nodes = LinkedList::new();
condition_nodes.push_front(Node::Conditional {
condition: Box::new(Node::Identifier {name: "superadmin".to_string(), filters: None}),
body: Box::new(Node::List(body.clone()))
});
ast.push_front(Node::If {
condition_nodes: condition_nodes,
else_node: None,
});
let root = Node::List(ast);
assert_eq!(parsed_ast.unwrap(), root);
}
#[test]
fn test_ast_if_with_test() {
let parsed_ast = parse("{% if number is defined %}Hey{% endif %}");
let mut ast = LinkedList::new();
let mut body = LinkedList::new();
body.push_front(Node::Text("Hey".to_string()));
let mut condition_nodes = LinkedList::new();
condition_nodes.push_front(Node::Conditional {
condition: Box::new(
Node::Test {
expression: Box::new(Node::Identifier {
name: "number".to_string(), filters: None,
}),
name: "defined".to_string(),
params: LinkedList::new()
}
),
body: Box::new(Node::List(body.clone()))
});
ast.push_front(Node::If {
condition_nodes: condition_nodes,
else_node: None,
});
let root = Node::List(ast);
assert_eq!(parsed_ast.unwrap(), root);
}
#[test]
fn test_ast_if_with_test_params() {
let parsed_ast = parse(r#"{% if pi is equalto 3.14 %}Hey{% endif %}"#);
let mut ast = LinkedList::new();
let mut body = LinkedList::new();
body.push_front(Node::Text("Hey".to_string()));
let mut params = LinkedList::new();
params.push_front(Node::Float(3.14));
let mut condition_nodes = LinkedList::new();
condition_nodes.push_front(Node::Conditional {
condition: Box::new(
Node::Test {
expression: Box::new(Node::Identifier {
name: "pi".to_string(), filters: None,
}),
name: "equalto".to_string(),
params: params,
}
),
body: Box::new(Node::List(body.clone()))
});
ast.push_front(Node::If {
condition_nodes: condition_nodes,
else_node: None,
});
let root = Node::List(ast);
assert_eq!(parsed_ast.unwrap(), root);
}
#[test]
fn test_ast_if_elif_with_test() {
let parsed_ast = parse("{% if hi %}Hey{% elif admin is oneof(a, 2, true) %}Hey{% endif %}");
let mut ast = LinkedList::new();
let mut body = LinkedList::new();
body.push_front(Node::Text("Hey".to_string()));
let mut condition_nodes = LinkedList::new();
condition_nodes.push_front(Node::Conditional {
condition: Box::new(Node::Identifier {name: "hi".to_string(), filters: None}),
body: Box::new(Node::List(body.clone()))
});
let mut params = LinkedList::new();
params.push_back(Node::Identifier { name: "a".to_string(), filters: None });
params.push_back(Node::Int(2));
params.push_back(Node::Bool(true));
condition_nodes.push_back(Node::Conditional {
condition: Box::new(
Node::Test {
expression: Box::new(Node::Identifier {
name: "admin".to_string(), filters: None,
}),
name: "oneof".to_string(),
params: params,
}
),
body: Box::new(Node::List(body.clone()))
});
ast.push_front(Node::If {
condition_nodes: condition_nodes,
else_node: None,
});
let root = Node::List(ast);
assert_eq!(parsed_ast.unwrap(), root);
}
#[test]
fn test_ast_if_else() {
let parsed_ast = parse("{% if superadmin %}Hey{% else %}Hey{% endif %}");
let mut ast = LinkedList::new();
let mut body = LinkedList::new();
body.push_front(Node::Text("Hey".to_string()));
let mut condition_nodes = LinkedList::new();
condition_nodes.push_front(Node::Conditional {
condition: Box::new(Node::Identifier {name: "superadmin".to_string(), filters: None}),
body: Box::new(Node::List(body.clone()))
});
ast.push_front(Node::If {
condition_nodes: condition_nodes,
else_node: Some(Box::new(Node::List(body.clone()))),
});
let root = Node::List(ast);
assert_eq!(parsed_ast.unwrap(), root);
}
#[test]
fn test_ast_if_elif() {
let parsed_ast = parse("{% if superadmin %}Hey{% elif admin %}Hey{% endif %}");
let mut ast = LinkedList::new();
let mut body = LinkedList::new();
body.push_front(Node::Text("Hey".to_string()));
let mut condition_nodes = LinkedList::new();
condition_nodes.push_front(Node::Conditional {
condition: Box::new(Node::Identifier {name: "superadmin".to_string(), filters: None}),
body: Box::new(Node::List(body.clone()))
});
condition_nodes.push_back(Node::Conditional {
condition: Box::new(Node::Identifier {name: "admin".to_string(), filters: None}),
body: Box::new(Node::List(body.clone()))
});
ast.push_front(Node::If {
condition_nodes: condition_nodes,
else_node: None,
});
let root = Node::List(ast);
assert_eq!(parsed_ast.unwrap(), root);
}
#[test]
fn test_ast_if_elifs_else() {
let parsed_ast = parse("{% if superadmin %}Hey{% elif admin %}Hey{% else %}Hey{% endif %}");
let mut ast = LinkedList::new();
let mut body = LinkedList::new();
body.push_front(Node::Text("Hey".to_string()));
let mut condition_nodes = LinkedList::new();
condition_nodes.push_back(Node::Conditional {
condition: Box::new(Node::Identifier {name: "admin".to_string(), filters: None}),
body: Box::new(Node::List(body.clone()))
});
condition_nodes.push_front(Node::Conditional {
condition: Box::new(Node::Identifier {name: "superadmin".to_string(), filters: None}),
body: Box::new(Node::List(body.clone()))
});
ast.push_front(Node::If {
condition_nodes: condition_nodes,
else_node: Some(Box::new(Node::List(body.clone()))),
});
let root = Node::List(ast);
assert_eq!(parsed_ast.unwrap(), root);
}
#[test]
fn test_nullary_test() {
let mut parser = Rdp::new(StringInput::new("is defined"));
assert!(parser.test());
assert!(parser.end());
}
#[test]
fn test_unary_test() {
let mut parser = Rdp::new(StringInput::new("is equalto other"));
assert!(parser.test());
assert!(parser.end());
let mut parser = Rdp::new(StringInput::new("is equalto(other)"));
assert!(parser.test());
assert!(parser.end());
}
#[test]
fn test_n_ary_test() {
let mut parser = Rdp::new(StringInput::new("is oneof(a, b, c)"));
assert!(parser.test());
assert!(parser.end());
}
#[test]
fn test_n_ary_test_requires_parens() {
let mut parser = Rdp::new(StringInput::new("is oneof a, b, c"));
assert!(parser.test()); assert!(!parser.end());
}
#[test]
fn test_ast_raw() {
let parsed_ast = parse("Hey {% raw %}Hey {{ name }}{% endraw %} Ho");
let mut ast = LinkedList::new();
ast.push_front(Node::Text(" Ho".to_string()));
ast.push_front(Node::Raw("Hey {{ name }}".to_string()));
ast.push_front(Node::Text("Hey ".to_string()));
let root = Node::List(ast);
assert_eq!(parsed_ast.unwrap(), root);
}
#[test]
fn test_ast_filter() {
let parsed_ast = parse("{{ greeting | i18n(lang=user.lang, units=user.units) | truncate(limit=50, cut_word=true) }}");
let mut filters = LinkedList::new();
let mut args_truncate = HashMap::new();
args_truncate.insert("limit".to_string(), to_value(&50));
args_truncate.insert("cut_word".to_string(), to_value(&true));
let mut args_i18n = HashMap::new();
args_i18n.insert("lang".to_string(), "user.lang".to_string());
args_i18n.insert("units".to_string(), "user.units".to_string());
filters.push_front(Node::Filter {
name: "truncate".to_string(),
args: args_truncate,
args_ident: HashMap::new(),
});
filters.push_front(Node::Filter {
name: "i18n".to_string(),
args: HashMap::new(),
args_ident: args_i18n,
});
let mut ast = LinkedList::new();
ast.push_front(Node::VariableBlock(
Box::new(Node::Identifier {
name: "greeting".to_string(),
filters: Some(filters),
})
));
let root = Node::List(ast);
assert_eq!(parsed_ast.unwrap(), root);
}
#[test]
fn test_ast_error_old_and() {
let parsed_ast = parse("{{ true && 1 }}");
assert!(parsed_ast.is_err());
assert_eq!(
parsed_ast.err().unwrap(),
TeraError::DeprecatedSyntax(1, 9, "Use `and` instead of `&&`".to_string())
);
}
#[test]
fn test_ast_error_old_or() {
let parsed_ast = parse("{{ true || 1 }}");
assert!(parsed_ast.is_err());
assert_eq!(
parsed_ast.err().unwrap(),
TeraError::DeprecatedSyntax(1, 9, "Use `or` instead of `||`".to_string())
);
}
#[test]
fn test_ast_error_mismatch_endblock_name() {
let parsed_ast = parse("{% block hey %}{% endblock ho %}");
assert!(parsed_ast.is_err());
assert_eq!(
parsed_ast.err().unwrap(),
TeraError::MismatchingEndBlock(1, 33, "hey".to_string(), "ho".to_string())
);
}
#[test]
fn test_parse_bench() {
let parsed_ast = parse("
<html>
<head>
<title>{{ product.name }}</title>
</head>
<body>
<h1>{{ product.name }} - {{ product.manufacturer }}</h1>
<p>{{ product.summary }}</p>
<p>£{{ product.price * 1.20 }} (VAT inc.)</p>
<p>Look at reviews from your friends {{ username }}</p>
<button>Buy!</button>
</body>
</html>
");
assert!(parsed_ast.is_ok());
}
}