Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Add support for filter chaining in filter blocks #984

Merged
merged 4 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 54 additions & 6 deletions askama_derive/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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_first_filter_argument(&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));
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand All @@ -2030,6 +2038,46 @@ fn median(sizes: &mut [usize]) -> usize {
}
}

/// In `FilterBlock`, we have a recursive `Expr::Filter` entry, where the more you go "down",
/// the sooner you are called in the Rust code. Example:
///
/// ```text
/// {% filter a|b|c %}bla{% endfilter %}
/// ```
///
/// Will be translated as:
///
/// ```text
/// FilterBlock {
/// filters: Filter {
/// name: "c",
/// arguments: vec![
/// Filter {
/// name: "b",
/// arguments: vec![
/// Filter {
/// name: "a",
/// arguments: vec![],
/// }.
/// ],
/// }
/// ],
/// },
/// // ...
/// }
/// ```
///
/// So in here, we want to insert the variable containing the content of the filter block inside
/// the call to `"a"`. To do so, we recursively go through all `Filter` and finally insert our
/// variable as the first argument to the `"a"` call.
fn insert_first_filter_argument(args: &mut Vec<Expr<'_>>, var_name: String) {
if let Some(Expr::Filter(Filter { arguments, .. })) = args.first_mut() {
insert_first_filter_argument(arguments, var_name);
} else {
args.insert(0, Expr::Generated(var_name));
}
}

#[derive(Clone, Copy, PartialEq)]
enum AstLevel {
Top,
Expand Down
38 changes: 19 additions & 19 deletions askama_parser/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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>>),
Expand Down Expand Up @@ -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
},
});
}

Expand Down Expand Up @@ -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>),
Expand Down
12 changes: 11 additions & 1 deletion askama_parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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)))
}
28 changes: 21 additions & 7 deletions askama_parser/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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,
}
Expand All @@ -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),
Expand All @@ -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),
},
Expand Down
Loading