diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs index 59df6874f..d904524a2 100644 --- a/askama_shared/src/generator.rs +++ b/askama_shared/src/generator.rs @@ -909,7 +909,9 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { Expr::StrLit(s) => self.visit_str_lit(buf, s), Expr::CharLit(s) => self.visit_char_lit(buf, s), Expr::Var(s) => self.visit_var(buf, s), + Expr::VarCall(var, ref args) => self.visit_var_call(buf, var, args), Expr::Path(ref path) => self.visit_path(buf, path), + Expr::PathCall(ref path, ref args) => self.visit_path_call(buf, path, args), 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), @@ -1028,7 +1030,10 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { } let scoped = match *arg { - Expr::Filter(_, _) | Expr::MethodCall(_, _, _) => true, + Expr::Filter(_, _) + | Expr::MethodCall(_, _, _) + | Expr::VarCall(_, _) + | Expr::PathCall(_, _) => true, _ => false, }; @@ -1160,6 +1165,19 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { DisplayWrap::Unwrapped } + fn visit_path_call(&mut self, buf: &mut Buffer, path: &[&str], args: &[Expr]) -> DisplayWrap { + for (i, part) in path.iter().enumerate() { + if i > 0 { + buf.write("::"); + } + buf.write(part); + } + buf.write("(&"); + self._visit_args(buf, args); + buf.write(")"); + DisplayWrap::Unwrapped + } + fn visit_var(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap { if self.locals.contains(s) || s == "self" { buf.write(s); @@ -1170,6 +1188,20 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { DisplayWrap::Unwrapped } + fn visit_var_call(&mut self, buf: &mut Buffer, s: &str, args: &[Expr]) -> DisplayWrap { + buf.write("("); + if self.locals.contains(s) || s == "self" { + buf.write(s); + } else { + buf.write("self."); + buf.write(s); + } + buf.write(")(&"); + self._visit_args(buf, args); + buf.write(")"); + DisplayWrap::Unwrapped + } + fn visit_bool_lit(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap { buf.write(s); DisplayWrap::Unwrapped diff --git a/askama_shared/src/parser.rs b/askama_shared/src/parser.rs index 9c3358e49..b6404c5dd 100644 --- a/askama_shared/src/parser.rs +++ b/askama_shared/src/parser.rs @@ -10,14 +10,16 @@ use std::str; use crate::Syntax; -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum Expr<'a> { BoolLit(&'a str), NumLit(&'a str), StrLit(&'a str), CharLit(&'a str), Var(&'a str), + VarCall(&'a str, Vec>), Path(Vec<&'a str>), + PathCall(Vec<&'a str>, Vec>), Array(Vec>), Attr(Box>, &'a str), Index(Box>, Box>), @@ -30,7 +32,7 @@ pub enum Expr<'a> { RustMacro(&'a str, &'a str), } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum MatchVariant<'a> { Path(Vec<&'a str>), Name(&'a str), @@ -39,7 +41,7 @@ pub enum MatchVariant<'a> { CharLit(&'a str), } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum MatchParameter<'a> { Name(&'a str), NumLit(&'a str), @@ -47,16 +49,16 @@ pub enum MatchParameter<'a> { CharLit(&'a str), } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum Target<'a> { Name(&'a str), Tuple(Vec<&'a str>), } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct WS(pub bool, pub bool); -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct Macro<'a> { pub ws1: WS, pub args: Vec<&'a str>, @@ -64,7 +66,7 @@ pub struct Macro<'a> { pub ws2: WS, } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum Node<'a> { Lit(&'a str, &'a str, &'a str), Comment(WS), @@ -84,6 +86,7 @@ pub enum Node<'a> { } pub type Cond<'a> = (WS, Option>, Vec>); + pub type When<'a> = ( WS, Option>, @@ -91,7 +94,7 @@ pub type When<'a> = ( Vec>, ); -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum MatchParameters<'a> { Simple(Vec>), Named(Vec<(&'a str, Option>)>), @@ -301,15 +304,30 @@ fn expr_var(i: &[u8]) -> IResult<&[u8], Expr> { map(identifier, |s| Expr::Var(s))(i) } -fn expr_path(i: &[u8]) -> IResult<&[u8], Expr> { +fn expr_var_call(i: &[u8]) -> IResult<&[u8], Expr> { + let (i, (s, args)) = tuple((identifier, arguments))(i)?; + Ok((i, Expr::VarCall(s, args))) +} + +fn path(i: &[u8]) -> IResult<&[u8], Vec<&str>> { let tail = separated_nonempty_list(tag("::"), identifier); let (i, (start, _, rest)) = tuple((identifier, tag("::"), tail))(i)?; let mut path = vec![start]; path.extend(rest); + Ok((i, path)) +} + +fn expr_path(i: &[u8]) -> IResult<&[u8], Expr> { + let (i, path) = path(i)?; Ok((i, Expr::Path(path))) } +fn expr_path_call(i: &[u8]) -> IResult<&[u8], Expr> { + let (i, (path, args)) = tuple((path, arguments))(i)?; + Ok((i, Expr::PathCall(path, args))) +} + fn variant_path(i: &[u8]) -> IResult<&[u8], MatchVariant> { map(separated_nonempty_list(tag("::"), identifier), |path| { MatchVariant::Path(path) @@ -437,9 +455,11 @@ fn expr_single(i: &[u8]) -> IResult<&[u8], Expr> { expr_num_lit, expr_str_lit, expr_char_lit, + expr_path_call, expr_path, expr_rust_macro, expr_array_lit, + expr_var_call, expr_var, expr_group, ))(i) @@ -1078,6 +1098,34 @@ mod tests { super::parse("{{ strvar|e }}", &Syntax::default()); } + #[test] + fn test_parse_var_call() { + assert_eq!( + super::parse("{{ function(\"123\", 3) }}", &Syntax::default()), + vec![super::Node::Expr( + super::WS(false, false), + super::Expr::VarCall( + "function", + vec![super::Expr::StrLit("123"), super::Expr::NumLit("3")] + ), + )], + ); + } + + #[test] + fn test_parse_path_call() { + assert_eq!( + super::parse("{{ self::function(\"123\", 3) }}", &Syntax::default()), + vec![super::Node::Expr( + super::WS(false, false), + super::Expr::PathCall( + vec!["self", "function"], + vec![super::Expr::StrLit("123"), super::Expr::NumLit("3")], + ), + )], + ); + } + #[test] fn change_delimiters_parse_filter() { let syntax = Syntax { diff --git a/testing/tests/simple.rs b/testing/tests/simple.rs index 354459954..7db8cb4f9 100644 --- a/testing/tests/simple.rs +++ b/testing/tests/simple.rs @@ -262,6 +262,45 @@ fn test_slice_literal() { assert_eq!(t.render().unwrap(), "a"); } +#[derive(Template)] +#[template(source = "Hello, {{ world(\"123\", 4) }}!", ext = "txt")] +struct FunctionRefTemplate { + world: fn(s: &str, v: &u8) -> String, +} + +#[test] +fn test_func_ref_call() { + let t = FunctionRefTemplate { + world: |s, r| format!("world({}, {})", s, r), + }; + assert_eq!(t.render().unwrap(), "Hello, world(123, 4)!"); +} + +#[allow(clippy::trivially_copy_pass_by_ref)] +fn world2(s: &str, v: &u8) -> String { + format!("world{}{}", v, s) +} + +#[derive(Template)] +#[template(source = "Hello, {{ self::world2(\"123\", 4) }}!", ext = "txt")] +struct PathFunctionTemplate; + +#[test] +fn test_path_func_call() { + assert_eq!(PathFunctionTemplate.render().unwrap(), "Hello, world4123!"); +} + +#[derive(Template)] +#[template(source = "Hello, {{ Self::world3(self, \"123\", 4) }}!", ext = "txt")] +struct FunctionTemplate; + +impl FunctionTemplate { + #[allow(clippy::trivially_copy_pass_by_ref)] + fn world3(&self, s: &str, v: &u8) -> String { + format!("world{}{}", s, v) + } +} + #[derive(Template)] #[template(source = " {# foo -#} ", ext = "txt")] struct CommentTemplate {}