diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 866fca764..7c86beac3 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -12,7 +12,7 @@ use parser::node::{ Call, Comment, CondTest, FilterBlock, If, Include, Let, Lit, Loop, Match, Target, Whitespace, Ws, }; -use parser::{Expr, Node}; +use parser::{Expr, Filter, Node}; use quote::quote; pub(crate) struct Generator<'a> { @@ -736,10 +736,15 @@ impl<'a> Generator<'a> { mem::drop(mem::replace(&mut self.buf_writable, current_buf)); let mut filter_buf = Buffer::new(buf.indent); - let mut args = filter.args.clone(); - args.insert(0, Expr::Generated(var_name.clone())); + let Filter { + name: filter_name, + arguments, + } = &filter.filters; + let mut arguments = arguments.clone(); - let wrap = self.visit_filter(&mut filter_buf, filter.filter_name, &args)?; + insert_variable_at_the_end(&mut arguments, var_name.clone()); + + let wrap = self.visit_filter(&mut filter_buf, filter_name, &arguments)?; self.buf_writable .push(Writable::Generated(filter_buf.buf, wrap)); @@ -1140,7 +1145,10 @@ impl<'a> Generator<'a> { Expr::Array(ref elements) => self.visit_array(buf, elements)?, Expr::Attr(ref obj, name) => self.visit_attr(buf, obj, name)?, Expr::Index(ref obj, ref key) => self.visit_index(buf, obj, key)?, - Expr::Filter(name, ref args) => self.visit_filter(buf, name, args)?, + Expr::Filter(Filter { + name, + ref arguments, + }) => self.visit_filter(buf, name, arguments)?, Expr::Unary(op, ref inner) => self.visit_unary(buf, op, inner)?, Expr::BinOp(op, ref left, ref right) => self.visit_binop(buf, op, left, right)?, Expr::Range(op, ref left, ref right) => { @@ -2003,7 +2011,7 @@ pub(crate) fn is_cacheable(expr: &Expr<'_>) -> bool { Expr::Array(args) => args.iter().all(is_cacheable), Expr::Attr(lhs, _) => is_cacheable(lhs), Expr::Index(lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs), - Expr::Filter(_, args) => args.iter().all(is_cacheable), + Expr::Filter(Filter { arguments, .. }) => arguments.iter().all(is_cacheable), Expr::Unary(_, arg) => is_cacheable(arg), Expr::BinOp(_, lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs), Expr::Range(_, lhs, rhs) => { @@ -2030,6 +2038,14 @@ fn median(sizes: &mut [usize]) -> usize { } } +fn insert_variable_at_the_end(args: &mut Vec<Expr<'_>>, var_name: String) { + if let Some(Expr::Filter(Filter { arguments, .. })) = args.first_mut() { + insert_variable_at_the_end(arguments, var_name); + } else { + args.insert(0, Expr::Generated(var_name)); + } +} + #[derive(Clone, Copy, PartialEq)] enum AstLevel { Top, diff --git a/askama_parser/src/expr.rs b/askama_parser/src/expr.rs index be5adfd2f..dddfb52da 100644 --- a/askama_parser/src/expr.rs +++ b/askama_parser/src/expr.rs @@ -12,7 +12,8 @@ use nom::multi::{fold_many0, many0, separated_list0}; use nom::sequence::{pair, preceded, terminated, tuple}; use super::{ - char_lit, identifier, not_ws, num_lit, path_or_identifier, str_lit, ws, Level, PathOrIdentifier, + char_lit, filter, identifier, not_ws, num_lit, path_or_identifier, str_lit, ws, Level, + PathOrIdentifier, }; use crate::{ErrorContext, ParseResult}; @@ -62,7 +63,7 @@ pub enum Expr<'a> { Array(Vec<Expr<'a>>), Attr(Box<Expr<'a>>, &'a str), Index(Box<Expr<'a>>, Box<Expr<'a>>), - Filter(&'a str, Vec<Expr<'a>>), + Filter(Filter<'a>), NamedArgument(&'a str, Box<Expr<'a>>), Unary(&'a str, Box<Expr<'a>>), BinOp(&'a str, Box<Expr<'a>>, Box<Expr<'a>>), @@ -188,28 +189,21 @@ impl<'a> Expr<'a> { fn filtered(i: &'a str, level: Level) -> ParseResult<'a, Self> { let (_, level) = level.nest(i)?; - #[allow(clippy::type_complexity)] - fn filter(i: &str, level: Level) -> ParseResult<'_, (&str, Option<Vec<Expr<'_>>>)> { - let (i, (_, fname, args)) = tuple(( - char('|'), - ws(identifier), - opt(|i| Expr::arguments(i, level, false)), - ))(i)?; - Ok((i, (fname, args))) - } - let (i, (obj, filters)) = tuple((|i| Self::prefix(i, level), many0(|i| filter(i, level))))(i)?; let mut res = obj; for (fname, args) in filters { - res = Self::Filter(fname, { - let mut args = match args { - Some(inner) => inner, - None => Vec::new(), - }; - args.insert(0, res); - args + res = Self::Filter(Filter { + name: fname, + arguments: { + let mut args = match args { + Some(inner) => inner, + None => Vec::new(), + }; + args.insert(0, res); + args + }, }); } @@ -310,6 +304,12 @@ impl<'a> Expr<'a> { } } +#[derive(Clone, Debug, PartialEq)] +pub struct Filter<'a> { + pub name: &'a str, + pub arguments: Vec<Expr<'a>>, +} + enum Suffix<'a> { Attr(&'a str), Index(Expr<'a>), diff --git a/askama_parser/src/lib.rs b/askama_parser/src/lib.rs index 3f93a370e..9d6c9acf2 100644 --- a/askama_parser/src/lib.rs +++ b/askama_parser/src/lib.rs @@ -15,7 +15,7 @@ use nom::sequence::{delimited, pair, preceded, terminated, tuple}; use nom::{error_position, AsChar, InputTakeAtPosition}; pub mod expr; -pub use expr::Expr; +pub use expr::{Expr, Filter}; pub mod node; pub use node::Node; #[cfg(test)] @@ -477,3 +477,13 @@ impl Level { const MAX_DEPTH: u8 = 128; } + +#[allow(clippy::type_complexity)] +fn filter(i: &str, level: Level) -> ParseResult<'_, (&str, Option<Vec<Expr<'_>>>)> { + let (i, (_, fname, args)) = tuple(( + char('|'), + ws(identifier), + opt(|i| Expr::arguments(i, level, false)), + ))(i)?; + Ok((i, (fname, args))) +} diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs index a50c2e7a8..fdfc6e8b2 100644 --- a/askama_parser/src/node.rs +++ b/askama_parser/src/node.rs @@ -15,8 +15,8 @@ use nom::sequence::{delimited, pair, preceded, terminated, tuple}; use crate::{ErrorContext, ParseResult}; use super::{ - bool_lit, char_lit, identifier, is_ws, keyword, num_lit, path_or_identifier, skip_till, - str_lit, ws, Expr, PathOrIdentifier, State, + bool_lit, char_lit, filter, identifier, is_ws, keyword, num_lit, path_or_identifier, skip_till, + str_lit, ws, Expr, Filter, PathOrIdentifier, State, }; #[derive(Debug, PartialEq)] @@ -563,8 +563,7 @@ impl<'a> Macro<'a> { #[derive(Debug, PartialEq)] pub struct FilterBlock<'a> { pub ws1: Ws, - pub filter_name: &'a str, - pub args: Vec<Expr<'a>>, + pub filters: Filter<'a>, pub nodes: Vec<Node<'a>>, pub ws2: Ws, } @@ -577,11 +576,27 @@ impl<'a> FilterBlock<'a> { cut(tuple(( ws(identifier), opt(|i| Expr::arguments(i, s.level.get(), false)), + many0(|i| filter(i, s.level.get())), opt(Whitespace::parse), |i| s.tag_block_end(i), ))), )); - let (i, (pws1, _, (filter_name, params, nws1, _))) = start(i)?; + let (i, (pws1, _, (filter_name, params, extra_filters, nws1, _))) = start(i)?; + + let mut filters = Filter { + name: filter_name, + arguments: params.unwrap_or_default(), + }; + for (filter_name, args) in extra_filters { + filters = Filter { + name: filter_name, + arguments: { + let mut args = args.unwrap_or_default(); + args.insert(0, Expr::Filter(filters)); + args + }, + }; + } let mut end = cut(tuple(( |i| Node::many(i, s), @@ -598,8 +613,7 @@ impl<'a> FilterBlock<'a> { i, Self { ws1: Ws(pws1, nws1), - filter_name, - args: params.unwrap_or_default(), + filters, nodes, ws2: Ws(pws2, nws2), }, diff --git a/askama_parser/src/tests.rs b/askama_parser/src/tests.rs index 742f0f389..669e376d1 100644 --- a/askama_parser/src/tests.rs +++ b/askama_parser/src/tests.rs @@ -1,5 +1,5 @@ use super::node::{Lit, Whitespace, Ws}; -use super::{Ast, Expr, Node, Syntax}; +use super::{Ast, Expr, Filter, Node, Syntax}; fn check_ws_split(s: &str, res: &(&str, &str, &str)) { let Lit { lws, val, rws } = Lit::split_ws_parts(s); @@ -25,33 +25,47 @@ fn test_invalid_block() { #[test] fn test_parse_filter() { - use Expr::*; let syntax = Syntax::default(); assert_eq!( Ast::from_str("{{ strvar|e }}", &syntax).unwrap().nodes, - vec![Node::Expr(Ws(None, None), Filter("e", vec![Var("strvar")]),)], + vec![Node::Expr( + Ws(None, None), + Expr::Filter(Filter { + name: "e", + arguments: vec![Expr::Var("strvar")] + }), + )], ); assert_eq!( Ast::from_str("{{ 2|abs }}", &syntax).unwrap().nodes, - vec![Node::Expr(Ws(None, None), Filter("abs", vec![NumLit("2")]),)], + vec![Node::Expr( + Ws(None, None), + Expr::Filter(Filter { + name: "abs", + arguments: vec![Expr::NumLit("2")] + }), + )], ); assert_eq!( Ast::from_str("{{ -2|abs }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Filter("abs", vec![Unary("-", NumLit("2").into())]), + Expr::Filter(Filter { + name: "abs", + arguments: vec![Expr::Unary("-", Expr::NumLit("2").into())] + }), )], ); assert_eq!( Ast::from_str("{{ (1 - 2)|abs }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Filter( - "abs", - vec![Group( - BinOp("-", NumLit("1").into(), NumLit("2").into()).into() - )] - ), + Expr::Filter(Filter { + name: "abs", + arguments: vec![Expr::Group( + Expr::BinOp("-", Expr::NumLit("1").into(), Expr::NumLit("2").into()).into() + )], + },), )], ); } @@ -283,16 +297,15 @@ fn change_delimiters_parse_filter() { #[test] fn test_precedence() { - use Expr::*; let syntax = Syntax::default(); assert_eq!( Ast::from_str("{{ a + b == c }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - BinOp( + Expr::BinOp( "==", - BinOp("+", Var("a").into(), Var("b").into()).into(), - Var("c").into(), + Expr::BinOp("+", Expr::Var("a").into(), Expr::Var("b").into()).into(), + Expr::Var("c").into(), ) )], ); @@ -302,15 +315,15 @@ fn test_precedence() { .nodes, vec![Node::Expr( Ws(None, None), - BinOp( + Expr::BinOp( "-", - BinOp( + Expr::BinOp( "+", - Var("a").into(), - BinOp("*", Var("b").into(), Var("c").into()).into(), + Expr::Var("a").into(), + Expr::BinOp("*", Expr::Var("b").into(), Expr::Var("c").into()).into(), ) .into(), - BinOp("/", Var("d").into(), Var("e").into()).into(), + Expr::BinOp("/", Expr::Var("d").into(), Expr::Var("e").into()).into(), ) )], ); @@ -320,15 +333,18 @@ fn test_precedence() { .nodes, vec![Node::Expr( Ws(None, None), - BinOp( + Expr::BinOp( "/", - BinOp( + Expr::BinOp( "*", - Var("a").into(), - Group(BinOp("+", Var("b").into(), Var("c").into()).into()).into() + Expr::Var("a").into(), + Expr::Group( + Expr::BinOp("+", Expr::Var("b").into(), Expr::Var("c").into()).into() + ) + .into() ) .into(), - Unary("-", Var("d").into()).into() + Expr::Unary("-", Expr::Var("d").into()).into() ) )], ); @@ -338,15 +354,15 @@ fn test_precedence() { .nodes, vec![Node::Expr( Ws(None, None), - BinOp( + Expr::BinOp( "||", - BinOp( + Expr::BinOp( "||", - Var("a").into(), - BinOp("&&", Var("b").into(), Var("c").into()).into(), + Expr::Var("a").into(), + Expr::BinOp("&&", Expr::Var("b").into(), Expr::Var("c").into()).into(), ) .into(), - BinOp("&&", Var("d").into(), Var("e").into()).into(), + Expr::BinOp("&&", Expr::Var("d").into(), Expr::Var("e").into()).into(), ) )], ); @@ -354,16 +370,15 @@ fn test_precedence() { #[test] fn test_associativity() { - use Expr::*; let syntax = Syntax::default(); assert_eq!( Ast::from_str("{{ a + b + c }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - BinOp( + Expr::BinOp( "+", - BinOp("+", Var("a").into(), Var("b").into()).into(), - Var("c").into() + Expr::BinOp("+", Expr::Var("a").into(), Expr::Var("b").into()).into(), + Expr::Var("c").into() ) )], ); @@ -371,10 +386,10 @@ fn test_associativity() { Ast::from_str("{{ a * b * c }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - BinOp( + Expr::BinOp( "*", - BinOp("*", Var("a").into(), Var("b").into()).into(), - Var("c").into() + Expr::BinOp("*", Expr::Var("a").into(), Expr::Var("b").into()).into(), + Expr::Var("c").into() ) )], ); @@ -382,10 +397,10 @@ fn test_associativity() { Ast::from_str("{{ a && b && c }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - BinOp( + Expr::BinOp( "&&", - BinOp("&&", Var("a").into(), Var("b").into()).into(), - Var("c").into() + Expr::BinOp("&&", Expr::Var("a").into(), Expr::Var("b").into()).into(), + Expr::Var("c").into() ) )], ); @@ -393,15 +408,15 @@ fn test_associativity() { Ast::from_str("{{ a + b - c + d }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - BinOp( + Expr::BinOp( "+", - BinOp( + Expr::BinOp( "-", - BinOp("+", Var("a").into(), Var("b").into()).into(), - Var("c").into() + Expr::BinOp("+", Expr::Var("a").into(), Expr::Var("b").into()).into(), + Expr::Var("c").into() ) .into(), - Var("d").into() + Expr::Var("d").into() ) )], ); @@ -411,25 +426,25 @@ fn test_associativity() { .nodes, vec![Node::Expr( Ws(None, None), - BinOp( + Expr::BinOp( "==", - BinOp( + Expr::BinOp( ">", - BinOp( + Expr::BinOp( ">", - BinOp( + Expr::BinOp( "!=", - BinOp("==", Var("a").into(), Var("b").into()).into(), - Var("c").into() + Expr::BinOp("==", Expr::Var("a").into(), Expr::Var("b").into()).into(), + Expr::Var("c").into() ) .into(), - Var("d").into() + Expr::Var("d").into() ) .into(), - Var("e").into() + Expr::Var("e").into() ) .into(), - Var("f").into() + Expr::Var("f").into() ) )], ); @@ -437,15 +452,17 @@ fn test_associativity() { #[test] fn test_odd_calls() { - use Expr::*; let syntax = Syntax::default(); assert_eq!( Ast::from_str("{{ a[b](c) }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Call( - Box::new(Index(Box::new(Var("a")), Box::new(Var("b")))), - vec![Var("c")], + Expr::Call( + Box::new(Expr::Index( + Box::new(Expr::Var("a")), + Box::new(Expr::Var("b")) + )), + vec![Expr::Var("c")], ), )], ); @@ -453,13 +470,13 @@ fn test_odd_calls() { Ast::from_str("{{ (a + b)(c) }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Call( - Box::new(Group(Box::new(BinOp( + Expr::Call( + Box::new(Expr::Group(Box::new(Expr::BinOp( "+", - Box::new(Var("a")), - Box::new(Var("b")) + Box::new(Expr::Var("a")), + Box::new(Expr::Var("b")) )))), - vec![Var("c")], + vec![Expr::Var("c")], ), )], ); @@ -467,10 +484,10 @@ fn test_odd_calls() { Ast::from_str("{{ a + b(c) }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - BinOp( + Expr::BinOp( "+", - Box::new(Var("a")), - Box::new(Call(Box::new(Var("b")), vec![Var("c")])), + Box::new(Expr::Var("a")), + Box::new(Expr::Call(Box::new(Expr::Var("b")), vec![Expr::Var("c")])), ), )], ); @@ -478,9 +495,12 @@ fn test_odd_calls() { Ast::from_str("{{ (-a)(b) }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Call( - Box::new(Group(Box::new(Unary("-", Box::new(Var("a")))))), - vec![Var("b")], + Expr::Call( + Box::new(Expr::Group(Box::new(Expr::Unary( + "-", + Box::new(Expr::Var("a")) + )))), + vec![Expr::Var("b")], ), )], ); @@ -488,31 +508,40 @@ fn test_odd_calls() { Ast::from_str("{{ -a(b) }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Unary("-", Box::new(Call(Box::new(Var("a")), vec![Var("b")]))), + Expr::Unary( + "-", + Box::new(Expr::Call(Box::new(Expr::Var("a")), vec![Expr::Var("b")])) + ), )], ); assert_eq!( Ast::from_str("{{ a(b)|c }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Filter("c", vec![Call(Box::new(Var("a")), vec![Var("b")])]), + Expr::Filter(Filter { + name: "c", + arguments: vec![Expr::Call(Box::new(Expr::Var("a")), vec![Expr::Var("b")])] + }), )] ); assert_eq!( Ast::from_str("{{ a(b)| c }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Filter("c", vec![Call(Box::new(Var("a")), vec![Var("b")])]), + Expr::Filter(Filter { + name: "c", + arguments: vec![Expr::Call(Box::new(Expr::Var("a")), vec![Expr::Var("b")])] + }), )] ); assert_eq!( Ast::from_str("{{ a(b) |c }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - BinOp( + Expr::BinOp( "|", - Box::new(Call(Box::new(Var("a")), vec![Var("b")])), - Box::new(Var("c")) + Box::new(Expr::Call(Box::new(Expr::Var("a")), vec![Expr::Var("b")])), + Box::new(Expr::Var("c")) ), )] ); @@ -579,82 +608,110 @@ fn test_parse_comments() { #[test] fn test_parse_tuple() { - use super::expr::Expr::*; let syntax = Syntax::default(); assert_eq!( Ast::from_str("{{ () }}", &syntax).unwrap().nodes, - vec![Node::Expr(Ws(None, None), Tuple(vec![]),)], + vec![Node::Expr(Ws(None, None), Expr::Tuple(vec![]),)], ); assert_eq!( Ast::from_str("{{ (1) }}", &syntax).unwrap().nodes, - vec![Node::Expr(Ws(None, None), Group(Box::new(NumLit("1"))),)], + vec![Node::Expr( + Ws(None, None), + Expr::Group(Box::new(Expr::NumLit("1"))), + )], ); assert_eq!( Ast::from_str("{{ (1,) }}", &syntax).unwrap().nodes, - vec![Node::Expr(Ws(None, None), Tuple(vec![NumLit("1")]),)], + vec![Node::Expr( + Ws(None, None), + Expr::Tuple(vec![Expr::NumLit("1")]), + )], ); assert_eq!( Ast::from_str("{{ (1, ) }}", &syntax).unwrap().nodes, - vec![Node::Expr(Ws(None, None), Tuple(vec![NumLit("1")]),)], + vec![Node::Expr( + Ws(None, None), + Expr::Tuple(vec![Expr::NumLit("1")]), + )], ); assert_eq!( Ast::from_str("{{ (1 ,) }}", &syntax).unwrap().nodes, - vec![Node::Expr(Ws(None, None), Tuple(vec![NumLit("1")]),)], + vec![Node::Expr( + Ws(None, None), + Expr::Tuple(vec![Expr::NumLit("1")]), + )], ); assert_eq!( Ast::from_str("{{ (1 , ) }}", &syntax).unwrap().nodes, - vec![Node::Expr(Ws(None, None), Tuple(vec![NumLit("1")]),)], + vec![Node::Expr( + Ws(None, None), + Expr::Tuple(vec![Expr::NumLit("1")]), + )], ); assert_eq!( Ast::from_str("{{ (1, 2) }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Tuple(vec![NumLit("1"), NumLit("2")]), + Expr::Tuple(vec![Expr::NumLit("1"), Expr::NumLit("2")]), )], ); assert_eq!( Ast::from_str("{{ (1, 2,) }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Tuple(vec![NumLit("1"), NumLit("2")]), + Expr::Tuple(vec![Expr::NumLit("1"), Expr::NumLit("2")]), )], ); assert_eq!( Ast::from_str("{{ (1, 2, 3) }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Tuple(vec![NumLit("1"), NumLit("2"), NumLit("3")]), + Expr::Tuple(vec![ + Expr::NumLit("1"), + Expr::NumLit("2"), + Expr::NumLit("3") + ]), )], ); assert_eq!( Ast::from_str("{{ ()|abs }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Filter("abs", vec![Tuple(vec![])]), + Expr::Filter(Filter { + name: "abs", + arguments: vec![Expr::Tuple(vec![])] + }), )], ); assert_eq!( Ast::from_str("{{ () | abs }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - BinOp("|", Box::new(Tuple(vec![])), Box::new(Var("abs"))), + Expr::BinOp( + "|", + Box::new(Expr::Tuple(vec![])), + Box::new(Expr::Var("abs")) + ), )], ); assert_eq!( Ast::from_str("{{ (1)|abs }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Filter("abs", vec![Group(Box::new(NumLit("1")))]), + Expr::Filter(Filter { + name: "abs", + arguments: vec![Expr::Group(Box::new(Expr::NumLit("1")))] + }), )], ); assert_eq!( Ast::from_str("{{ (1) | abs }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - BinOp( + Expr::BinOp( "|", - Box::new(Group(Box::new(NumLit("1")))), - Box::new(Var("abs")) + Box::new(Expr::Group(Box::new(Expr::NumLit("1")))), + Box::new(Expr::Var("abs")) ), )], ); @@ -662,17 +719,20 @@ fn test_parse_tuple() { Ast::from_str("{{ (1,)|abs }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Filter("abs", vec![Tuple(vec![NumLit("1")])]), + Expr::Filter(Filter { + name: "abs", + arguments: vec![Expr::Tuple(vec![Expr::NumLit("1")])] + }), )], ); assert_eq!( Ast::from_str("{{ (1,) | abs }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - BinOp( + Expr::BinOp( "|", - Box::new(Tuple(vec![NumLit("1")])), - Box::new(Var("abs")) + Box::new(Expr::Tuple(vec![Expr::NumLit("1")])), + Box::new(Expr::Var("abs")) ), )], ); @@ -680,17 +740,20 @@ fn test_parse_tuple() { Ast::from_str("{{ (1, 2)|abs }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Filter("abs", vec![Tuple(vec![NumLit("1"), NumLit("2")])]), + Expr::Filter(Filter { + name: "abs", + arguments: vec![Expr::Tuple(vec![Expr::NumLit("1"), Expr::NumLit("2")])] + }), )], ); assert_eq!( Ast::from_str("{{ (1, 2) | abs }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - BinOp( + Expr::BinOp( "|", - Box::new(Tuple(vec![NumLit("1"), NumLit("2")])), - Box::new(Var("abs")) + Box::new(Expr::Tuple(vec![Expr::NumLit("1"), Expr::NumLit("2")])), + Box::new(Expr::Var("abs")) ), )], ); @@ -766,14 +829,20 @@ fn test_parse_array() { Ast::from_str("{{ []|foo }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Filter("foo", vec![Expr::Array(vec![])]) + Expr::Filter(Filter { + name: "foo", + arguments: vec![Expr::Array(vec![])] + }) )], ); assert_eq!( Ast::from_str("{{ []| foo }}", &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Filter("foo", vec![Expr::Array(vec![])]) + Expr::Filter(Filter { + name: "foo", + arguments: vec![Expr::Array(vec![])] + }) )], ); assert_eq!( diff --git a/book/src/template_syntax.md b/book/src/template_syntax.md index 6117f4306..e9d9aed0a 100644 --- a/book/src/template_syntax.md +++ b/book/src/template_syntax.md @@ -98,6 +98,17 @@ blocks**: The `lower` filter will be applied on the whole content. +Just like filters, you can combine them: + +```text +{% filter lower|capitalize %} + {{ t }} / HELLO / {{ u }} +{% endfilter %} +``` + +In this case, `lower` will be called and then `capitalize` will be +called on what `lower` returned. + ## Whitespace control Askama considers all tabs, spaces, newlines and carriage returns to be diff --git a/testing/tests/filter_block.rs b/testing/tests/filter_block.rs index 029ce1374..55f7e1bad 100644 --- a/testing/tests/filter_block.rs +++ b/testing/tests/filter_block.rs @@ -128,3 +128,22 @@ fn filter_block_not_html_escape() { let template = E; assert_eq!(template.render().unwrap(), r#"<block>"#); } + +// This test checks that the filter chaining is working as expected. +#[derive(Template)] +#[template( + source = r#"{% filter lower|indent(2)|capitalize -%} +HELLO +{{v}} +{%- endfilter %}"#, + ext = "txt" +)] +struct F { + v: &'static str, +} + +#[test] +fn filter_block_chaining() { + let template = F { v: "pIKA" }; + assert_eq!(template.render().unwrap(), "Hello\n pika"); +}