From 43e92aa3b6b9cd967a70bd0fd54d1f087d6ed76b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Sep 2023 04:24:10 +0000 Subject: [PATCH 01/44] build(deps): update mendes requirement from 0.3.0 to 0.5.0 Updates the requirements on [mendes](https://github.com/djc/mendes) to permit the latest version. - [Commits](https://github.com/djc/mendes/commits) --- updated-dependencies: - dependency-name: mendes dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- askama_mendes/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/askama_mendes/Cargo.toml b/askama_mendes/Cargo.toml index a815a4a05..3811d24f1 100644 --- a/askama_mendes/Cargo.toml +++ b/askama_mendes/Cargo.toml @@ -15,7 +15,7 @@ rust-version = "1.58" [dependencies] askama = { version = "0.12", path = "../askama", default-features = false, features = ["with-mendes", "mime", "mime_guess"] } -mendes = "0.3.0" +mendes = "0.5.0" [dev-dependencies] async-trait = "0.1.51" From dd9635ace66089bda485d4da30cfc6fe131d25f7 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 11 Sep 2023 11:37:46 +0200 Subject: [PATCH 02/44] Rename some variables --- askama_parser/src/expr.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/askama_parser/src/expr.rs b/askama_parser/src/expr.rs index ca1dc9b4b..3e63d9306 100644 --- a/askama_parser/src/expr.rs +++ b/askama_parser/src/expr.rs @@ -248,15 +248,15 @@ impl<'a> Suffix<'a> { } fn r#macro(i: &'a str) -> IResult<&'a str, Self> { - fn nested_parenthesis(i: &str) -> IResult<&str, ()> { + fn nested_parenthesis(input: &str) -> IResult<&str, ()> { let mut nested = 0; let mut last = 0; let mut in_str = false; let mut escaped = false; - for (i, b) in i.chars().enumerate() { - if !(b == '(' || b == ')') || !in_str { - match b { + for (i, c) in input.chars().enumerate() { + if !(c == '(' || c == ')') || !in_str { + match c { '(' => nested += 1, ')' => { if nested == 0 { @@ -281,16 +281,16 @@ impl<'a> Suffix<'a> { } } - if escaped && b != '\\' { + if escaped && c != '\\' { escaped = false; } } if nested == 0 { - Ok((&i[last..], ())) + Ok((&input[last..], ())) } else { Err(nom::Err::Error(error_position!( - i, + input, ErrorKind::SeparatedNonEmptyList ))) } From 2e8e42af457732f25d00b2c5d42695353d301804 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 11 Sep 2023 11:41:24 +0200 Subject: [PATCH 03/44] Use char_indices() to get byte indices for characters --- askama_parser/src/expr.rs | 2 +- askama_parser/src/tests.rs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/askama_parser/src/expr.rs b/askama_parser/src/expr.rs index 3e63d9306..7f822668b 100644 --- a/askama_parser/src/expr.rs +++ b/askama_parser/src/expr.rs @@ -254,7 +254,7 @@ impl<'a> Suffix<'a> { let mut in_str = false; let mut escaped = false; - for (i, c) in input.chars().enumerate() { + for (i, c) in input.char_indices() { if !(c == '(' || c == ')') || !in_str { match c { '(' => nested += 1, diff --git a/askama_parser/src/tests.rs b/askama_parser/src/tests.rs index ce300d36a..c23d13f69 100644 --- a/askama_parser/src/tests.rs +++ b/askama_parser/src/tests.rs @@ -788,3 +788,10 @@ fn test_parse_array() { )], ); } + +#[test] +fn fuzzed_unicode_slice() { + let d = "{eeuuu{b&{!!&{!!11{{ + 0!(!1q҄א!)!!!!!!n!"; + assert!(Ast::from_str(d, &Syntax::default()).is_err()); +} From 8f3140a33fa76be248883539d8ecb51b7a7700e2 Mon Sep 17 00:00:00 2001 From: manunio Date: Mon, 11 Sep 2023 18:21:59 +0530 Subject: [PATCH 04/44] fuzz: Add fuzz for askama_parser --- askama_parser/fuzz/.gitignore | 4 +++ askama_parser/fuzz/Cargo.toml | 27 +++++++++++++++++++ askama_parser/fuzz/README.md | 14 ++++++++++ .../fuzz/fuzz_targets/fuzz_parser.rs | 13 +++++++++ 4 files changed, 58 insertions(+) create mode 100644 askama_parser/fuzz/.gitignore create mode 100644 askama_parser/fuzz/Cargo.toml create mode 100644 askama_parser/fuzz/README.md create mode 100644 askama_parser/fuzz/fuzz_targets/fuzz_parser.rs diff --git a/askama_parser/fuzz/.gitignore b/askama_parser/fuzz/.gitignore new file mode 100644 index 000000000..1a45eee77 --- /dev/null +++ b/askama_parser/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/askama_parser/fuzz/Cargo.toml b/askama_parser/fuzz/Cargo.toml new file mode 100644 index 000000000..3190e5a9f --- /dev/null +++ b/askama_parser/fuzz/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "askama_parser-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" + +[dependencies.askama_parser] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "fuzz_parser" +path = "fuzz_targets/fuzz_parser.rs" +test = false +doc = false diff --git a/askama_parser/fuzz/README.md b/askama_parser/fuzz/README.md new file mode 100644 index 000000000..6f6c2a1fe --- /dev/null +++ b/askama_parser/fuzz/README.md @@ -0,0 +1,14 @@ +# Fuzzing + +Install `cargo-fuzz`: + +```sh +cargo install -f cargo-fuzz +``` + +Run any available target where `$target` is the name of the target. + +```sh +cargo fuzz list # get list of targets +cargo +nightly fuzz run $target +``` \ No newline at end of file diff --git a/askama_parser/fuzz/fuzz_targets/fuzz_parser.rs b/askama_parser/fuzz/fuzz_targets/fuzz_parser.rs new file mode 100644 index 000000000..374936c4c --- /dev/null +++ b/askama_parser/fuzz/fuzz_targets/fuzz_parser.rs @@ -0,0 +1,13 @@ +#![no_main] +use askama_parser::*; +use libfuzzer_sys::fuzz_target; +use std::str; + +fuzz_target!(|data: &[u8]| { + // fuzzed code goes here + if data.len() < 500 { + if let Ok(data) = str::from_utf8(data) { + if let Ok(_) = Ast::from_str(data, &Syntax::default()) {} + } + } +}); From 2ef2b9db9e7dfdb57e6c8b59db5493864aaacf3a Mon Sep 17 00:00:00 2001 From: manunio Date: Mon, 18 Sep 2023 18:38:48 +0530 Subject: [PATCH 05/44] fuzz: remove input limit While working on https://github.com/djc/askama/pull/862 fuzz_parser was crashing and failing oss-fuzz build_checks. so a limit of 500 was placed, this pr removes that. --- askama_parser/fuzz/fuzz_targets/fuzz_parser.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/askama_parser/fuzz/fuzz_targets/fuzz_parser.rs b/askama_parser/fuzz/fuzz_targets/fuzz_parser.rs index 374936c4c..1be1f94d8 100644 --- a/askama_parser/fuzz/fuzz_targets/fuzz_parser.rs +++ b/askama_parser/fuzz/fuzz_targets/fuzz_parser.rs @@ -5,9 +5,7 @@ use std::str; fuzz_target!(|data: &[u8]| { // fuzzed code goes here - if data.len() < 500 { - if let Ok(data) = str::from_utf8(data) { - if let Ok(_) = Ast::from_str(data, &Syntax::default()) {} - } + if let Ok(data) = str::from_utf8(data) { + if let Ok(_) = Ast::from_str(data, &Syntax::default()) {} } }); From c8530493a1b6e773a4d2a16d01e7ed69ae160621 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 28 Sep 2023 11:06:29 +0200 Subject: [PATCH 06/44] Limit expression nesting level to avoid stack overflows --- askama_parser/src/expr.rs | 114 +++++++++++++++++++++++++++----------- askama_parser/src/node.rs | 3 +- 2 files changed, 83 insertions(+), 34 deletions(-) diff --git a/askama_parser/src/expr.rs b/askama_parser/src/expr.rs index 7f822668b..ad0c19ed3 100644 --- a/askama_parser/src/expr.rs +++ b/askama_parser/src/expr.rs @@ -15,11 +15,12 @@ use super::{ macro_rules! expr_prec_layer { ( $name:ident, $inner:ident, $op:expr ) => { - fn $name(i: &'a str) -> IResult<&'a str, Self> { - let (i, left) = Self::$inner(i)?; + fn $name(i: &'a str, level: Level) -> IResult<&'a str, Self> { + let level = level.nest(i)?; + let (i, left) = Self::$inner(i, level)?; let (i, right) = many0(pair( ws(tag($op)), - Self::$inner, + |i| Self::$inner(i, level), ))(i)?; Ok(( i, @@ -30,11 +31,12 @@ macro_rules! expr_prec_layer { } }; ( $name:ident, $inner:ident, $( $op:expr ),+ ) => { - fn $name(i: &'a str) -> IResult<&'a str, Self> { - let (i, left) = Self::$inner(i)?; + fn $name(i: &'a str, level: Level) -> IResult<&'a str, Self> { + let level = level.nest(i)?; + let (i, left) = Self::$inner(i, level)?; let (i, right) = many0(pair( ws(alt(($( tag($op) ),+,))), - Self::$inner, + |i| Self::$inner(i, level), ))(i)?; Ok(( i, @@ -69,24 +71,35 @@ pub enum Expr<'a> { } impl<'a> Expr<'a> { - pub(super) fn arguments(i: &'a str) -> IResult<&'a str, Vec> { + pub(super) fn arguments(i: &'a str, level: Level) -> IResult<&'a str, Vec> { + let level = level.nest(i)?; preceded( ws(char('(')), cut(terminated( - separated_list0(char(','), ws(Self::parse)), + separated_list0(char(','), ws(move |i| Self::nested(i, level))), char(')'), )), )(i) } pub(super) fn parse(i: &'a str) -> IResult<&'a str, Self> { - let range_right = |i| pair(ws(alt((tag("..="), tag("..")))), opt(Self::or))(i); + Self::nested(i, Level::default()) + } + + fn nested(i: &'a str, level: Level) -> IResult<&'a str, Self> { + let level = level.nest(i)?; + let range_right = move |i| { + pair( + ws(alt((tag("..="), tag("..")))), + opt(move |i| Self::or(i, level)), + )(i) + }; alt(( map(range_right, |(op, right)| { Self::Range(op, None, right.map(Box::new)) }), map( - pair(Self::or, opt(range_right)), + pair(move |i| Self::or(i, level), opt(range_right)), |(left, right)| match right { Some((op, right)) => Self::Range(op, Some(Box::new(left)), right.map(Box::new)), None => left, @@ -105,14 +118,19 @@ impl<'a> Expr<'a> { expr_prec_layer!(addsub, muldivmod, "+", "-"); expr_prec_layer!(muldivmod, filtered, "*", "/", "%"); - fn filtered(i: &'a str) -> IResult<&'a str, Self> { - fn filter(i: &str) -> IResult<&str, (&str, Option>>)> { - let (i, (_, fname, args)) = - tuple((char('|'), ws(identifier), opt(Expr::arguments)))(i)?; + fn filtered(i: &'a str, level: Level) -> IResult<&'a str, Self> { + let level = level.nest(i)?; + fn filter(i: &str, level: Level) -> IResult<&str, (&str, Option>>)> { + let (i, (_, fname, args)) = tuple(( + char('|'), + ws(identifier), + opt(|i| Expr::arguments(i, level)), + ))(i)?; Ok((i, (fname, args))) } - let (i, (obj, filters)) = tuple((Self::prefix, many0(filter)))(i)?; + 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 { @@ -129,27 +147,32 @@ impl<'a> Expr<'a> { Ok((i, res)) } - fn prefix(i: &'a str) -> IResult<&'a str, Self> { - let (i, (ops, mut expr)) = pair(many0(ws(alt((tag("!"), tag("-"))))), Suffix::parse)(i)?; + fn prefix(i: &'a str, level: Level) -> IResult<&'a str, Self> { + let level = level.nest(i)?; + let (i, (ops, mut expr)) = pair(many0(ws(alt((tag("!"), tag("-"))))), |i| { + Suffix::parse(i, level) + })(i)?; for op in ops.iter().rev() { expr = Self::Unary(op, Box::new(expr)); } Ok((i, expr)) } - fn single(i: &'a str) -> IResult<&'a str, Self> { + fn single(i: &'a str, level: Level) -> IResult<&'a str, Self> { + let level = level.nest(i)?; alt(( Self::num, Self::str, Self::char, Self::path_var_bool, - Self::array, - Self::group, + move |i| Self::array(i, level), + move |i| Self::group(i, level), ))(i) } - fn group(i: &'a str) -> IResult<&'a str, Self> { - let (i, expr) = preceded(ws(char('(')), opt(Self::parse))(i)?; + fn group(i: &'a str, level: Level) -> IResult<&'a str, Self> { + let level = level.nest(i)?; + let (i, expr) = preceded(ws(char('(')), opt(|i| Self::nested(i, level)))(i)?; let expr = match expr { Some(expr) => expr, None => { @@ -166,7 +189,7 @@ impl<'a> Expr<'a> { let mut exprs = vec![expr]; let (i, _) = fold_many0( - preceded(char(','), ws(Self::parse)), + preceded(char(','), ws(|i| Self::nested(i, level))), || (), |_, expr| { exprs.push(expr); @@ -176,11 +199,15 @@ impl<'a> Expr<'a> { Ok((i, Self::Tuple(exprs))) } - fn array(i: &'a str) -> IResult<&'a str, Self> { + fn array(i: &'a str, level: Level) -> IResult<&'a str, Self> { + let level = level.nest(i)?; preceded( ws(char('[')), cut(terminated( - map(separated_list0(char(','), ws(Self::parse)), Self::Array), + map( + separated_list0(char(','), ws(move |i| Self::nested(i, level))), + Self::Array, + ), char(']'), )), )(i) @@ -218,13 +245,14 @@ enum Suffix<'a> { } impl<'a> Suffix<'a> { - fn parse(i: &'a str) -> IResult<&'a str, Expr<'a>> { - let (mut i, mut expr) = Expr::single(i)?; + fn parse(i: &'a str, level: Level) -> IResult<&'a str, Expr<'a>> { + let level = level.nest(i)?; + let (mut i, mut expr) = Expr::single(i, level)?; loop { let (j, suffix) = opt(alt(( Self::attr, - Self::index, - Self::call, + |i| Self::index(i, level), + |i| Self::call(i, level), Self::r#try, Self::r#macro, )))(i)?; @@ -315,18 +343,38 @@ impl<'a> Suffix<'a> { )(i) } - fn index(i: &'a str) -> IResult<&'a str, Self> { + fn index(i: &'a str, level: Level) -> IResult<&'a str, Self> { + let level = level.nest(i)?; map( - preceded(ws(char('[')), cut(terminated(ws(Expr::parse), char(']')))), + preceded( + ws(char('[')), + cut(terminated(ws(move |i| Expr::nested(i, level)), char(']'))), + ), Self::Index, )(i) } - fn call(i: &'a str) -> IResult<&'a str, Self> { - map(Expr::arguments, Self::Call)(i) + fn call(i: &'a str, level: Level) -> IResult<&'a str, Self> { + let level = level.nest(i)?; + map(move |i| Expr::arguments(i, level), Self::Call)(i) } fn r#try(i: &'a str) -> IResult<&'a str, Self> { map(preceded(take_till(not_ws), char('?')), |_| Self::Try)(i) } } + +#[derive(Clone, Copy, Default)] +pub(crate) struct Level(u8); + +impl Level { + fn nest(self, i: &str) -> Result>> { + if self.0 >= Self::MAX_EXPR_DEPTH { + return Err(nom::Err::Failure(error_position!(i, ErrorKind::TooLarge))); + } + + Ok(Level(self.0 + 1)) + } + + const MAX_EXPR_DEPTH: u8 = 64; +} diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs index a1aa7e2da..1ed4e40a1 100644 --- a/askama_parser/src/node.rs +++ b/askama_parser/src/node.rs @@ -15,6 +15,7 @@ use super::{ bool_lit, char_lit, identifier, is_ws, keyword, num_lit, path_or_identifier, skip_till, str_lit, ws, Expr, PathOrIdentifier, State, }; +use crate::expr::Level; #[derive(Debug, PartialEq)] pub enum Node<'a> { @@ -536,7 +537,7 @@ impl<'a> Call<'a> { cut(tuple(( opt(tuple((ws(identifier), ws(tag("::"))))), ws(identifier), - opt(ws(Expr::arguments)), + opt(ws(|nested| Expr::arguments(nested, Level::default()))), opt(Whitespace::parse), ))), )); From 5ab8813fe29cfbc034b6f99fc36064dcd1a5bd3e Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 28 Sep 2023 15:15:45 +0200 Subject: [PATCH 07/44] Yield a parser error when defining a macro named 'super' --- askama_parser/src/node.rs | 9 +++++---- askama_parser/src/tests.rs | 6 ++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs index 1ed4e40a1..a72c2fbef 100644 --- a/askama_parser/src/node.rs +++ b/askama_parser/src/node.rs @@ -474,16 +474,17 @@ impl<'a> Macro<'a> { ))); let (i, (contents, (_, pws2, _, nws2))) = end(i)?; - assert_ne!(name, "super", "invalid macro name 'super'"); - - let params = params.unwrap_or_default(); + if name == "super" { + // TODO: yield a a better error message here + return Err(nom::Err::Failure(Error::new(i, ErrorKind::Fail))); + } Ok(( i, Self { ws1: Ws(pws1, nws1), name, - args: params, + args: params.unwrap_or_default(), nodes: contents, ws2: Ws(pws2, nws2), }, diff --git a/askama_parser/src/tests.rs b/askama_parser/src/tests.rs index c23d13f69..3a7b4523c 100644 --- a/askama_parser/src/tests.rs +++ b/askama_parser/src/tests.rs @@ -795,3 +795,9 @@ fn fuzzed_unicode_slice() { 0!(!1q҄א!)!!!!!!n!"; assert!(Ast::from_str(d, &Syntax::default()).is_err()); } + +#[test] +fn fuzzed_macro_no_end() { + let s = "{%macro super%}{%endmacro"; + assert!(Ast::from_str(s, &Syntax::default()).is_err()); +} From 57181752917c67a007c86fdbe133d2c64db7c749 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 28 Sep 2023 15:20:04 +0200 Subject: [PATCH 08/44] Move Level into the crate root --- askama_parser/src/expr.rs | 17 +---------------- askama_parser/src/lib.rs | 15 +++++++++++++++ askama_parser/src/node.rs | 3 +-- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/askama_parser/src/expr.rs b/askama_parser/src/expr.rs index ad0c19ed3..d676e85fa 100644 --- a/askama_parser/src/expr.rs +++ b/askama_parser/src/expr.rs @@ -10,7 +10,7 @@ use nom::sequence::{pair, preceded, terminated, tuple}; use nom::{error_position, IResult}; use super::{ - char_lit, identifier, not_ws, num_lit, path_or_identifier, str_lit, ws, PathOrIdentifier, + char_lit, identifier, not_ws, num_lit, path_or_identifier, str_lit, ws, Level, PathOrIdentifier, }; macro_rules! expr_prec_layer { @@ -363,18 +363,3 @@ impl<'a> Suffix<'a> { map(preceded(take_till(not_ws), char('?')), |_| Self::Try)(i) } } - -#[derive(Clone, Copy, Default)] -pub(crate) struct Level(u8); - -impl Level { - fn nest(self, i: &str) -> Result>> { - if self.0 >= Self::MAX_EXPR_DEPTH { - return Err(nom::Err::Failure(error_position!(i, ErrorKind::TooLarge))); - } - - Ok(Level(self.0 + 1)) - } - - const MAX_EXPR_DEPTH: u8 = 64; -} diff --git a/askama_parser/src/lib.rs b/askama_parser/src/lib.rs index 1e48262b3..de88a2019 100644 --- a/askama_parser/src/lib.rs +++ b/askama_parser/src/lib.rs @@ -324,3 +324,18 @@ impl Default for Syntax<'static> { } } } + +#[derive(Clone, Copy, Default)] +pub(crate) struct Level(u8); + +impl Level { + fn nest(self, i: &str) -> Result>> { + if self.0 >= Self::MAX_DEPTH { + return Err(nom::Err::Failure(error_position!(i, ErrorKind::TooLarge))); + } + + Ok(Level(self.0 + 1)) + } + + const MAX_DEPTH: u8 = 64; +} diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs index a72c2fbef..c3a59c675 100644 --- a/askama_parser/src/node.rs +++ b/askama_parser/src/node.rs @@ -13,9 +13,8 @@ use nom::{error_position, IResult}; use super::{ bool_lit, char_lit, identifier, is_ws, keyword, num_lit, path_or_identifier, skip_till, - str_lit, ws, Expr, PathOrIdentifier, State, + str_lit, ws, Expr, Level, PathOrIdentifier, State, }; -use crate::expr::Level; #[derive(Debug, PartialEq)] pub enum Node<'a> { From 36f4442978674a79aaebefcd4f04c7bfe6fe54c4 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 28 Sep 2023 15:22:58 +0200 Subject: [PATCH 09/44] Require Expr::parse() callers to supply Level --- askama_parser/src/expr.rs | 16 ++++++---------- askama_parser/src/node.rs | 18 ++++++++++++------ 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/askama_parser/src/expr.rs b/askama_parser/src/expr.rs index d676e85fa..3b6bced37 100644 --- a/askama_parser/src/expr.rs +++ b/askama_parser/src/expr.rs @@ -76,17 +76,13 @@ impl<'a> Expr<'a> { preceded( ws(char('(')), cut(terminated( - separated_list0(char(','), ws(move |i| Self::nested(i, level))), + separated_list0(char(','), ws(move |i| Self::parse(i, level))), char(')'), )), )(i) } - pub(super) fn parse(i: &'a str) -> IResult<&'a str, Self> { - Self::nested(i, Level::default()) - } - - fn nested(i: &'a str, level: Level) -> IResult<&'a str, Self> { + pub(super) fn parse(i: &'a str, level: Level) -> IResult<&'a str, Self> { let level = level.nest(i)?; let range_right = move |i| { pair( @@ -172,7 +168,7 @@ impl<'a> Expr<'a> { fn group(i: &'a str, level: Level) -> IResult<&'a str, Self> { let level = level.nest(i)?; - let (i, expr) = preceded(ws(char('(')), opt(|i| Self::nested(i, level)))(i)?; + let (i, expr) = preceded(ws(char('(')), opt(|i| Self::parse(i, level)))(i)?; let expr = match expr { Some(expr) => expr, None => { @@ -189,7 +185,7 @@ impl<'a> Expr<'a> { let mut exprs = vec![expr]; let (i, _) = fold_many0( - preceded(char(','), ws(|i| Self::nested(i, level))), + preceded(char(','), ws(|i| Self::parse(i, level))), || (), |_, expr| { exprs.push(expr); @@ -205,7 +201,7 @@ impl<'a> Expr<'a> { ws(char('[')), cut(terminated( map( - separated_list0(char(','), ws(move |i| Self::nested(i, level))), + separated_list0(char(','), ws(move |i| Self::parse(i, level))), Self::Array, ), char(']'), @@ -348,7 +344,7 @@ impl<'a> Suffix<'a> { map( preceded( ws(char('[')), - cut(terminated(ws(move |i| Expr::nested(i, level)), char(']'))), + cut(terminated(ws(move |i| Expr::parse(i, level)), char(']'))), ), Self::Index, )(i) diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs index c3a59c675..f25f25ad5 100644 --- a/askama_parser/src/node.rs +++ b/askama_parser/src/node.rs @@ -101,7 +101,7 @@ impl<'a> Node<'a> { |i| s.tag_expr_start(i), cut(tuple(( opt(Whitespace::parse), - ws(Expr::parse), + ws(|i| Expr::parse(i, Level::default())), opt(Whitespace::parse), |i| s.tag_expr_end(i), ))), @@ -325,7 +325,7 @@ impl<'a> CondTest<'a> { ws(Target::parse), ws(char('=')), )), - ws(Expr::parse), + ws(|i| Expr::parse(i, Level::default())), ))), ); let (i, (target, expr)) = p(i)?; @@ -371,7 +371,10 @@ impl<'a> Loop<'a> { result } - let if_cond = preceded(ws(keyword("if")), cut(ws(Expr::parse))); + let if_cond = preceded( + ws(keyword("if")), + cut(ws(|i| Expr::parse(i, Level::default()))), + ); let else_block = |i| { let mut p = preceded( ws(keyword("else")), @@ -395,7 +398,7 @@ impl<'a> Loop<'a> { ws(Target::parse), ws(keyword("in")), cut(tuple(( - ws(Expr::parse), + ws(|i| Expr::parse(i, Level::default())), opt(if_cond), opt(Whitespace::parse), |i| s.tag_block_end(i), @@ -570,7 +573,7 @@ impl<'a> Match<'a> { opt(Whitespace::parse), ws(keyword("match")), cut(tuple(( - ws(Expr::parse), + ws(|i| Expr::parse(i, Level::default())), opt(Whitespace::parse), |i| s.tag_block_end(i), cut(tuple(( @@ -738,7 +741,10 @@ impl<'a> Let<'a> { ws(alt((keyword("let"), keyword("set")))), cut(tuple(( ws(Target::parse), - opt(preceded(ws(char('=')), ws(Expr::parse))), + opt(preceded( + ws(char('=')), + ws(|i| Expr::parse(i, Level::default())), + )), opt(Whitespace::parse), ))), )); From 238e4bbad7712ccc3a3ea4a5b09c63bd147c692e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Thu, 28 Sep 2023 17:09:29 +0200 Subject: [PATCH 10/44] Limit nesting in parser nodes, too --- askama_parser/src/lib.rs | 15 +++ askama_parser/src/node.rs | 12 ++- testing/tests/ui/excessive_nesting.rs | 121 ++++++++++++++++++++++ testing/tests/ui/excessive_nesting.stderr | 8 ++ 4 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 testing/tests/ui/excessive_nesting.rs create mode 100644 testing/tests/ui/excessive_nesting.stderr diff --git a/askama_parser/src/lib.rs b/askama_parser/src/lib.rs index de88a2019..3428735bc 100644 --- a/askama_parser/src/lib.rs +++ b/askama_parser/src/lib.rs @@ -255,6 +255,7 @@ fn path_or_identifier(i: &str) -> IResult<&str, PathOrIdentifier<'_>> { struct State<'a> { syntax: &'a Syntax<'a>, loop_depth: Cell, + level: Cell, } impl<'a> State<'a> { @@ -262,9 +263,19 @@ impl<'a> State<'a> { State { syntax, loop_depth: Cell::new(0), + level: Cell::new(Level::default()), } } + fn nest<'b>(&self, i: &'b str) -> Result<(), nom::Err>> { + self.level.set(self.level.get().nest(i)?); + Ok(()) + } + + fn leave(&self) { + self.level.set(self.level.get().leave()); + } + fn tag_block_start<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str> { tag(self.syntax.block_start)(i) } @@ -337,5 +348,9 @@ impl Level { Ok(Level(self.0 + 1)) } + fn leave(&self) -> Self { + Level(self.0 - 1) + } + const MAX_DEPTH: u8 = 64; } diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs index f25f25ad5..1909f6c73 100644 --- a/askama_parser/src/node.rs +++ b/askama_parser/src/node.rs @@ -47,7 +47,7 @@ impl<'a> Node<'a> { } fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { - let mut p = tuple(( + let mut p = delimited( |i| s.tag_block_start(i), alt(( map(Call::parse, Self::Call), @@ -65,9 +65,13 @@ impl<'a> Node<'a> { |i| Self::r#continue(i, s), )), cut(|i| s.tag_block_end(i)), - )); - let (i, (_, contents, _)) = p(i)?; - Ok((i, contents)) + ); + + s.nest(i)?; + let result = p(i); + s.leave(); + + result } fn r#break(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { diff --git a/testing/tests/ui/excessive_nesting.rs b/testing/tests/ui/excessive_nesting.rs new file mode 100644 index 000000000..bc72b7de4 --- /dev/null +++ b/testing/tests/ui/excessive_nesting.rs @@ -0,0 +1,121 @@ +use askama::Template; + +#[derive(Template)] +#[template( + source = " + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} 100 + + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} 200 + + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} 300 + + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} 400 + + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} 500 + + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} 600 + + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} 700 + + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} 800 + + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} 900 + + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} + {%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%} 1000 + ", + ext = "txt" +)] +struct IfTimes1000; + +fn main() { +} diff --git a/testing/tests/ui/excessive_nesting.stderr b/testing/tests/ui/excessive_nesting.stderr new file mode 100644 index 000000000..d83cd3b24 --- /dev/null +++ b/testing/tests/ui/excessive_nesting.stderr @@ -0,0 +1,8 @@ +error: problems parsing template source at row 8, column 36 near: + "{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}"... + --> tests/ui/excessive_nesting.rs:3:10 + | +3 | #[derive(Template)] + | ^^^^^^^^ + | + = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info) From eef38cea67d98249b3e0720961ac119f5c7858b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Thu, 28 Sep 2023 17:14:31 +0200 Subject: [PATCH 11/44] Pass `Node` parsing level to expressions --- askama_parser/src/lib.rs | 2 +- askama_parser/src/node.rs | 30 +++++++++++------------ testing/tests/ui/excessive_nesting.stderr | 4 +-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/askama_parser/src/lib.rs b/askama_parser/src/lib.rs index 3428735bc..78b080741 100644 --- a/askama_parser/src/lib.rs +++ b/askama_parser/src/lib.rs @@ -352,5 +352,5 @@ impl Level { Level(self.0 - 1) } - const MAX_DEPTH: u8 = 64; + const MAX_DEPTH: u8 = 128; } diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs index 1909f6c73..ba4d09edd 100644 --- a/askama_parser/src/node.rs +++ b/askama_parser/src/node.rs @@ -13,7 +13,7 @@ use nom::{error_position, IResult}; use super::{ bool_lit, char_lit, identifier, is_ws, keyword, num_lit, path_or_identifier, skip_till, - str_lit, ws, Expr, Level, PathOrIdentifier, State, + str_lit, ws, Expr, PathOrIdentifier, State, }; #[derive(Debug, PartialEq)] @@ -50,8 +50,8 @@ impl<'a> Node<'a> { let mut p = delimited( |i| s.tag_block_start(i), alt(( - map(Call::parse, Self::Call), - map(Let::parse, Self::Let), + map(|i| Call::parse(i, s), Self::Call), + map(|i| Let::parse(i, s), Self::Let), map(|i| If::parse(i, s), Self::If), map(|i| Loop::parse(i, s), |l| Self::Loop(Box::new(l))), map(|i| Match::parse(i, s), Self::Match), @@ -105,7 +105,7 @@ impl<'a> Node<'a> { |i| s.tag_expr_start(i), cut(tuple(( opt(Whitespace::parse), - ws(|i| Expr::parse(i, Level::default())), + ws(|i| Expr::parse(i, s.level.get())), opt(Whitespace::parse), |i| s.tag_expr_end(i), ))), @@ -295,7 +295,7 @@ impl<'a> Cond<'a> { opt(Whitespace::parse), ws(keyword("else")), cut(tuple(( - opt(CondTest::parse), + opt(|i| CondTest::parse(i, s)), opt(Whitespace::parse), |i| s.tag_block_end(i), cut(|i| Node::many(i, s)), @@ -320,7 +320,7 @@ pub struct CondTest<'a> { } impl<'a> CondTest<'a> { - fn parse(i: &'a str) -> IResult<&'a str, Self> { + fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { let mut p = preceded( ws(keyword("if")), cut(tuple(( @@ -329,7 +329,7 @@ impl<'a> CondTest<'a> { ws(Target::parse), ws(char('=')), )), - ws(|i| Expr::parse(i, Level::default())), + ws(|i| Expr::parse(i, s.level.get())), ))), ); let (i, (target, expr)) = p(i)?; @@ -377,7 +377,7 @@ impl<'a> Loop<'a> { let if_cond = preceded( ws(keyword("if")), - cut(ws(|i| Expr::parse(i, Level::default()))), + cut(ws(|i| Expr::parse(i, s.level.get()))), ); let else_block = |i| { let mut p = preceded( @@ -402,7 +402,7 @@ impl<'a> Loop<'a> { ws(Target::parse), ws(keyword("in")), cut(tuple(( - ws(|i| Expr::parse(i, Level::default())), + ws(|i| Expr::parse(i, s.level.get())), opt(if_cond), opt(Whitespace::parse), |i| s.tag_block_end(i), @@ -537,14 +537,14 @@ pub struct Call<'a> { } impl<'a> Call<'a> { - fn parse(i: &'a str) -> IResult<&'a str, Self> { + fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { let mut p = tuple(( opt(Whitespace::parse), ws(keyword("call")), cut(tuple(( opt(tuple((ws(identifier), ws(tag("::"))))), ws(identifier), - opt(ws(|nested| Expr::arguments(nested, Level::default()))), + opt(ws(|nested| Expr::arguments(nested, s.level.get()))), opt(Whitespace::parse), ))), )); @@ -577,7 +577,7 @@ impl<'a> Match<'a> { opt(Whitespace::parse), ws(keyword("match")), cut(tuple(( - ws(|i| Expr::parse(i, Level::default())), + ws(|i| Expr::parse(i, s.level.get())), opt(Whitespace::parse), |i| s.tag_block_end(i), cut(tuple(( @@ -739,7 +739,7 @@ pub struct Let<'a> { } impl<'a> Let<'a> { - fn parse(i: &'a str) -> IResult<&'a str, Self> { + fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { let mut p = tuple(( opt(Whitespace::parse), ws(alt((keyword("let"), keyword("set")))), @@ -747,7 +747,7 @@ impl<'a> Let<'a> { ws(Target::parse), opt(preceded( ws(char('=')), - ws(|i| Expr::parse(i, Level::default())), + ws(|i| Expr::parse(i, s.level.get())), )), opt(Whitespace::parse), ))), @@ -775,7 +775,7 @@ impl<'a> If<'a> { fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { let mut p = tuple(( opt(Whitespace::parse), - CondTest::parse, + |i| CondTest::parse(i, s), cut(tuple(( opt(Whitespace::parse), |i| s.tag_block_end(i), diff --git a/testing/tests/ui/excessive_nesting.stderr b/testing/tests/ui/excessive_nesting.stderr index d83cd3b24..f8f48ec7e 100644 --- a/testing/tests/ui/excessive_nesting.stderr +++ b/testing/tests/ui/excessive_nesting.stderr @@ -1,5 +1,5 @@ -error: problems parsing template source at row 8, column 36 near: - "{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}"... +error: problems parsing template source at row 14, column 34 near: + "%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1"... --> tests/ui/excessive_nesting.rs:3:10 | 3 | #[derive(Template)] From c2b9192dfc0aa2ff40ecd19c2a034e06cea2124e Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 29 Sep 2023 13:56:31 +0200 Subject: [PATCH 12/44] Use externally maintained Actions --- .github/actions/setup/action.yml | 35 -------------------------------- .github/workflows/rust.yml | 13 +++++------- 2 files changed, 5 insertions(+), 43 deletions(-) delete mode 100644 .github/actions/setup/action.yml diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml deleted file mode 100644 index c2d9be54a..000000000 --- a/.github/actions/setup/action.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Setup Rust Environment - -inputs: - key: - description: Cache key - required: true - toolchain: - description: Pass-through to toolchain on actions-rs - default: stable - required: false - components: - description: Pass-through to components on actions-rs - required: false - -runs: - using: composite - steps: - - uses: actions-rs/toolchain@v1 - id: toolchain-install - with: - profile: minimal - override: true - toolchain: ${{ inputs.toolchain }} - components: ${{ inputs.components }} - - uses: actions/cache@v3 - with: - path: | - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ inputs.key }}-${{ runner.os }}-${{ inputs.toolchain }}-${{ steps.toolchain-install.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.lock', '**/Cargo.toml') }} - restore-keys: | - ${{ inputs.key }}-${{ runner.os }}-${{ inputs.toolchain }}-${{ steps.toolchain-install.outputs.rustc_hash }}- - ${{ inputs.key }}-${{ runner.os }}-${{ inputs.toolchain }}- diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b4ba2bb0f..6e4ed4e16 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -21,10 +21,10 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v1 - - uses: ./.github/actions/setup + - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - key: test-${{ matrix.os }}-${{ matrix.rust }} + - uses: Swatinem/rust-cache@v2 - run: cargo build --all-targets - run: cargo test @@ -39,10 +39,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - - uses: ./.github/actions/setup - with: - key: ${{ matrix.package }} - components: clippy + - uses: Swatinem/rust-cache@v2 - run: cd ${{ matrix.package }} && cargo test --all-targets - run: cd ${{ matrix.package }} && cargo clippy --all-targets -- -D warnings @@ -50,9 +47,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - - uses: ./.github/actions/setup + - uses: dtolnay/rust-toolchain@stable with: - key: lint components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 - run: cargo fmt --all -- --check - run: cargo clippy --all-targets -- -D warnings From dcece252a2a5b98bbb69ff6d3509d0673c8ca64c Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 29 Sep 2023 13:58:20 +0200 Subject: [PATCH 13/44] Upgrade to actions/checkout@v4 --- .github/workflows/rust.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6e4ed4e16..aca9e9f34 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -20,7 +20,7 @@ jobs: rust: beta runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} @@ -38,7 +38,7 @@ jobs: ] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - run: cd ${{ matrix.package }} && cargo test --all-targets - run: cd ${{ matrix.package }} && cargo clippy --all-targets -- -D warnings @@ -46,7 +46,7 @@ jobs: Lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt, clippy From e527f5f065bc384515929ccec497d66ad85830d1 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 29 Sep 2023 14:00:42 +0200 Subject: [PATCH 14/44] Add audit job in CI workflow --- .github/workflows/rust.yml | 6 ++++++ deny.toml | 10 ++++++++++ 2 files changed, 16 insertions(+) create mode 100644 deny.toml diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index aca9e9f34..77eb07dcf 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -53,3 +53,9 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: cargo fmt --all -- --check - run: cargo clippy --all-targets -- -D warnings + + Audit: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - uses: EmbarkStudios/cargo-deny-action@v1 diff --git a/deny.toml b/deny.toml new file mode 100644 index 000000000..c5d2843d3 --- /dev/null +++ b/deny.toml @@ -0,0 +1,10 @@ +[licenses] +allow-osi-fsf-free = "either" +copyleft = "warn" +exceptions = [{ allow = ["ISC", "MIT", "OpenSSL"], name = "ring" }] +private = { ignore = true } + +[[licenses.clarify]] +name = "ring" +expression = "ISC AND MIT AND OpenSSL" +license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] From daa8d89b579f9a319e2bd9c59576e1d8ac76365e Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 29 Sep 2023 14:02:11 +0200 Subject: [PATCH 15/44] Add MSRV checking in CI Bump MSRV to 1.65 for the use of let .. else. --- .github/workflows/rust.yml | 9 +++++++++ askama/Cargo.toml | 2 +- askama_actix/Cargo.toml | 2 +- askama_axum/Cargo.toml | 2 +- askama_derive/Cargo.toml | 2 +- askama_derive/src/config.rs | 9 ++------- askama_escape/Cargo.toml | 2 +- askama_gotham/Cargo.toml | 2 +- askama_hyper/Cargo.toml | 2 +- askama_mendes/Cargo.toml | 2 +- askama_parser/Cargo.toml | 2 +- askama_rocket/Cargo.toml | 2 +- askama_tide/Cargo.toml | 2 +- askama_warp/Cargo.toml | 2 +- testing/Cargo.toml | 2 +- 15 files changed, 24 insertions(+), 20 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 77eb07dcf..759edb37a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -54,6 +54,15 @@ jobs: - run: cargo fmt --all -- --check - run: cargo clippy --all-targets -- -D warnings + MSRV: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: "1.65.0" + - run: cargo check --lib -p askama --all-features + Audit: runs-on: ubuntu-22.04 steps: diff --git a/askama/Cargo.toml b/askama/Cargo.toml index f70902b34..438abcfef 100644 --- a/askama/Cargo.toml +++ b/askama/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT OR Apache-2.0" workspace = ".." readme = "../README.md" edition = "2021" -rust-version = "1.58" +rust-version = "1.65" [badges] maintenance = { status = "actively-developed" } diff --git a/askama_actix/Cargo.toml b/askama_actix/Cargo.toml index 4c5dff15e..a46dbffaa 100644 --- a/askama_actix/Cargo.toml +++ b/askama_actix/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT OR Apache-2.0" workspace = ".." readme = "README.md" edition = "2021" -rust-version = "1.58" +rust-version = "1.65" [dependencies] actix-web = { version = "4", default-features = false } diff --git a/askama_axum/Cargo.toml b/askama_axum/Cargo.toml index 7c7d85ef4..034d235fc 100644 --- a/askama_axum/Cargo.toml +++ b/askama_axum/Cargo.toml @@ -2,7 +2,7 @@ name = "askama_axum" version = "0.3.0" edition = "2021" -rust-version = "1.58" +rust-version = "1.65" description = "Axum integration for Askama templates" keywords = ["markup", "template", "jinja2", "html", "axum"] categories = ["template-engine"] diff --git a/askama_derive/Cargo.toml b/askama_derive/Cargo.toml index cb4635f06..5c3d8fa67 100644 --- a/askama_derive/Cargo.toml +++ b/askama_derive/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT/Apache-2.0" workspace = ".." readme = "README.md" edition = "2021" -rust-version = "1.58" +rust-version = "1.65" [lib] proc-macro = true diff --git a/askama_derive/src/config.rs b/askama_derive/src/config.rs index a4eec2826..b2a13aa82 100644 --- a/askama_derive/src/config.rs +++ b/askama_derive/src/config.rs @@ -203,11 +203,12 @@ impl RawConfig<'_> { } } -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)] #[cfg_attr(feature = "serde", derive(Deserialize))] #[cfg_attr(feature = "serde", serde(field_identifier, rename_all = "lowercase"))] pub(crate) enum WhitespaceHandling { /// The default behaviour. It will leave the whitespace characters "as is". + #[default] Preserve, /// It'll remove all the whitespace characters before and after the jinja block. Suppress, @@ -227,12 +228,6 @@ impl From for Whitespace { } } -impl Default for WhitespaceHandling { - fn default() -> Self { - WhitespaceHandling::Preserve - } -} - #[cfg_attr(feature = "serde", derive(Deserialize))] struct General<'a> { #[cfg_attr(feature = "serde", serde(borrow))] diff --git a/askama_escape/Cargo.toml b/askama_escape/Cargo.toml index 8214ea886..654f49ad4 100644 --- a/askama_escape/Cargo.toml +++ b/askama_escape/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT OR Apache-2.0" workspace = ".." readme = "README.md" edition = "2021" -rust-version = "1.58" +rust-version = "1.65" [badges] maintenance = { status = "actively-developed" } diff --git a/askama_gotham/Cargo.toml b/askama_gotham/Cargo.toml index 1e3c352b8..15ea50755 100644 --- a/askama_gotham/Cargo.toml +++ b/askama_gotham/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT OR Apache-2.0" workspace = ".." readme = "README.md" edition = "2021" -rust-version = "1.58" +rust-version = "1.65" [dependencies] askama = { version = "0.12", path = "../askama", default-features = false, features = ["with-gotham", "mime", "mime_guess"] } diff --git a/askama_hyper/Cargo.toml b/askama_hyper/Cargo.toml index 113ca02c2..02fcc58fe 100644 --- a/askama_hyper/Cargo.toml +++ b/askama_hyper/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT OR Apache-2.0" workspace = ".." readme = "README.md" edition = "2021" -rust-version = "1.58" +rust-version = "1.65" [dependencies] askama = { version = "0.12", path = "../askama", default-features = false, features = ["with-hyper"] } diff --git a/askama_mendes/Cargo.toml b/askama_mendes/Cargo.toml index 3811d24f1..ae5a0a412 100644 --- a/askama_mendes/Cargo.toml +++ b/askama_mendes/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT OR Apache-2.0" workspace = ".." readme = "README.md" edition = "2021" -rust-version = "1.58" +rust-version = "1.65" [dependencies] askama = { version = "0.12", path = "../askama", default-features = false, features = ["with-mendes", "mime", "mime_guess"] } diff --git a/askama_parser/Cargo.toml b/askama_parser/Cargo.toml index 8af58dfe3..577e43540 100644 --- a/askama_parser/Cargo.toml +++ b/askama_parser/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT OR Apache-2.0" workspace = ".." readme = "README.md" edition = "2021" -rust-version = "1.58" +rust-version = "1.65" [dependencies] nom = { version = "7", default-features = false, features = ["alloc"] } diff --git a/askama_rocket/Cargo.toml b/askama_rocket/Cargo.toml index e9f5afe3d..0496a340d 100644 --- a/askama_rocket/Cargo.toml +++ b/askama_rocket/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT OR Apache-2.0" workspace = ".." readme = "README.md" edition = "2021" -rust-version = "1.58" +rust-version = "1.65" [dependencies] askama = { version = "0.12", path = "../askama", default-features = false, features = ["with-rocket", "mime", "mime_guess"] } diff --git a/askama_tide/Cargo.toml b/askama_tide/Cargo.toml index eff0073e1..42762accd 100644 --- a/askama_tide/Cargo.toml +++ b/askama_tide/Cargo.toml @@ -2,7 +2,7 @@ name = "askama_tide" version = "0.15.1" edition = "2021" -rust-version = "1.58" +rust-version = "1.65" description = "Tide integration for Askama templates" keywords = ["markup", "template", "html", "tide", "http-types"] homepage = "https://github.com/djc/askama" diff --git a/askama_warp/Cargo.toml b/askama_warp/Cargo.toml index e1176daa4..b0efe4886 100644 --- a/askama_warp/Cargo.toml +++ b/askama_warp/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT OR Apache-2.0" workspace = ".." readme = "README.md" edition = "2021" -rust-version = "1.58" +rust-version = "1.65" [dependencies] askama = { version = "0.12", path = "../askama", default-features = false, features = ["with-warp", "mime", "mime_guess"] } diff --git a/testing/Cargo.toml b/testing/Cargo.toml index 89fd864cf..23b5743ee 100644 --- a/testing/Cargo.toml +++ b/testing/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Dirkjan Ochtman "] workspace = ".." edition = "2021" -rust-version = "1.58" +rust-version = "1.65" publish = false [features] From b82e6c3ee13285a97743098fd8d2f812df466430 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 29 Sep 2023 14:31:34 +0200 Subject: [PATCH 16/44] Bump askama and askama_derive versions --- askama/Cargo.toml | 2 +- askama_derive/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/askama/Cargo.toml b/askama/Cargo.toml index 438abcfef..1fe1afac3 100644 --- a/askama/Cargo.toml +++ b/askama/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "askama" -version = "0.12.0" +version = "0.12.1" description = "Type-safe, compiled Jinja-like templates for Rust" documentation = "https://docs.rs/askama" keywords = ["markup", "template", "jinja2", "html"] diff --git a/askama_derive/Cargo.toml b/askama_derive/Cargo.toml index 5c3d8fa67..53cdc8b97 100644 --- a/askama_derive/Cargo.toml +++ b/askama_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "askama_derive" -version = "0.12.1" +version = "0.12.2" description = "Procedural macro package for Askama" homepage = "https://github.com/djc/askama" repository = "https://github.com/djc/askama" From 1ce939a729b0512752d2fa78369797f10d6ff5e6 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 29 Sep 2023 16:23:27 +0200 Subject: [PATCH 17/44] parser: add rudimentary README --- askama_parser/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 askama_parser/README.md diff --git a/askama_parser/README.md b/askama_parser/README.md new file mode 100644 index 000000000..4223a1106 --- /dev/null +++ b/askama_parser/README.md @@ -0,0 +1,9 @@ +# askama_parser: template parser for the Askama templating engine + +[![Documentation](https://docs.rs/askama_parser/badge.svg)](https://docs.rs/askama_parser/) +[![Latest version](https://img.shields.io/crates/v/askama_parser.svg)](https://crates.io/crates/askama_parser) +[![Build Status](https://github.com/djc/askama/workflows/CI/badge.svg)](https://github.com/djc/askama/actions?query=workflow%3ACI) +[![Chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/djc/askama) + +This crate contains the procedural macros used by the +[Askama](https://github.com/djc/askama) templating engine. From 22ec6c95a19b682379d84dc22b6e1f190b6f8766 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 29 Sep 2023 16:23:36 +0200 Subject: [PATCH 18/44] parser: version bump to 0.1.1 --- askama_parser/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/askama_parser/Cargo.toml b/askama_parser/Cargo.toml index 577e43540..e2fb11c2c 100644 --- a/askama_parser/Cargo.toml +++ b/askama_parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "askama_parser" -version = "0.1.0" +version = "0.1.1" description = "Parser for Askama templates" documentation = "https://docs.rs/askama" keywords = ["markup", "template", "jinja2", "html"] From a6bc14c8ca5a4e5c6ac297a7fa95f86c722b799c Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 3 Oct 2023 10:39:56 +0200 Subject: [PATCH 19/44] Advertise Discord channel instead of Gitter --- README.md | 2 +- askama_actix/README.md | 2 +- askama_axum/README.md | 2 +- askama_derive/README.md | 2 +- askama_escape/README.md | 2 +- askama_gotham/README.md | 2 +- askama_hyper/README.md | 2 +- askama_mendes/README.md | 2 +- askama_parser/README.md | 2 +- askama_rocket/README.md | 2 +- askama_tide/README.md | 2 +- askama_warp/README.md | 2 +- book/src/askama.md | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 76b549136..f394dfb31 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Documentation](https://docs.rs/askama/badge.svg)](https://docs.rs/askama/) [![Latest version](https://img.shields.io/crates/v/askama.svg)](https://crates.io/crates/askama) [![Build Status](https://github.com/djc/askama/workflows/CI/badge.svg)](https://github.com/djc/askama/actions?query=workflow%3ACI) -[![Chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/djc/askama) +[![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/ZucwjE6bmT) Askama implements a template rendering engine based on [Jinja](https://jinja.palletsprojects.com/). It generates Rust code from your templates at compile time diff --git a/askama_actix/README.md b/askama_actix/README.md index d8948f6b2..d026e99b4 100644 --- a/askama_actix/README.md +++ b/askama_actix/README.md @@ -3,7 +3,7 @@ [![Documentation](https://docs.rs/askama_actix/badge.svg)](https://docs.rs/askama_actix/) [![Latest version](https://img.shields.io/crates/v/askama_actix.svg)](https://crates.io/crates/askama_actix) [![Build Status](https://github.com/djc/askama/workflows/CI/badge.svg)](https://github.com/djc/askama/actions?query=workflow%3ACI) -[![Chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/djc/askama) +[![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/ZucwjE6bmT) Integration of the [Askama](https://github.com/djc/askama) templating engine in code building on the Actix-web framework. diff --git a/askama_axum/README.md b/askama_axum/README.md index 584dbd967..6d085be36 100644 --- a/askama_axum/README.md +++ b/askama_axum/README.md @@ -3,7 +3,7 @@ [![Documentation](https://docs.rs/askama_axum/badge.svg)](https://docs.rs/askama_axum/) [![Latest version](https://img.shields.io/crates/v/askama_axum.svg)](https://crates.io/crates/askama_axum) [![Build Status](https://github.com/djc/askama/workflows/CI/badge.svg)](https://github.com/djc/askama/actions?query=workflow%3ACI) -[![Chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/djc/askama) +[![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/ZucwjE6bmT) Integration of the [Askama](https://github.com/djc/askama) templating engine in code building on the Axum web framework. diff --git a/askama_derive/README.md b/askama_derive/README.md index e27f1107d..3b79aa35d 100644 --- a/askama_derive/README.md +++ b/askama_derive/README.md @@ -3,7 +3,7 @@ [![Documentation](https://docs.rs/askama_derive/badge.svg)](https://docs.rs/askama_derive/) [![Latest version](https://img.shields.io/crates/v/askama_derive.svg)](https://crates.io/crates/askama_derive) [![Build Status](https://github.com/djc/askama/workflows/CI/badge.svg)](https://github.com/djc/askama/actions?query=workflow%3ACI) -[![Chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/djc/askama) +[![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/ZucwjE6bmT) This crate contains the procedural macros used by the [Askama](https://github.com/djc/askama) templating engine. diff --git a/askama_escape/README.md b/askama_escape/README.md index 8c6796d83..6eab613c7 100644 --- a/askama_escape/README.md +++ b/askama_escape/README.md @@ -3,7 +3,7 @@ [![Documentation](https://docs.rs/askama_escape/badge.svg)](https://docs.rs/askama_escape/) [![Latest version](https://img.shields.io/crates/v/askama_escape.svg)](https://crates.io/crates/askama_escape) [![Build Status](https://github.com/djc/askama/workflows/CI/badge.svg)](https://github.com/djc/askama/actions?query=workflow%3ACI) -[![Chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/djc/askama) +[![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/ZucwjE6bmT) This crate contains helper code for HTML escaping used by the [Askama](https://github.com/djc/askama) templating engine. diff --git a/askama_gotham/README.md b/askama_gotham/README.md index ade4bebc0..dd7a2515c 100644 --- a/askama_gotham/README.md +++ b/askama_gotham/README.md @@ -3,7 +3,7 @@ [![Documentation](https://docs.rs/askama_gotham/badge.svg)](https://docs.rs/askama_gotham/) [![Latest version](https://img.shields.io/crates/v/askama_gotham.svg)](https://crates.io/crates/askama_gotham) [![Build Status](https://github.com/djc/askama/workflows/CI/badge.svg)](https://github.com/djc/askama/actions?query=workflow%3ACI) -[![Chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/djc/askama) +[![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/ZucwjE6bmT) Integration of the [Askama](https://github.com/djc/askama) templating engine in code building on the Gotham web framework. diff --git a/askama_hyper/README.md b/askama_hyper/README.md index 024665ab0..c1754c9b9 100644 --- a/askama_hyper/README.md +++ b/askama_hyper/README.md @@ -3,7 +3,7 @@ [![Documentation](https://docs.rs/askama_hyper/badge.svg)](https://docs.rs/askama_hyper/) [![Latest version](https://img.shields.io/crates/v/askama_hyper.svg)](https://crates.io/crates/askama_hyper) [![Build Status](https://github.com/djc/askama/workflows/CI/badge.svg)](https://github.com/djc/askama/actions?query=workflow%3ACI) -[![Chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/djc/askama) +[![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/ZucwjE6bmT) Integration of the [Askama](https://github.com/djc/askama) templating engine in code building on the [hyper](https://crates.io/crates/hyper)-based web servers. diff --git a/askama_mendes/README.md b/askama_mendes/README.md index f91ffbd66..94a296d73 100644 --- a/askama_mendes/README.md +++ b/askama_mendes/README.md @@ -3,7 +3,7 @@ [![Documentation](https://docs.rs/askama_mendes/badge.svg)](https://docs.rs/askama_mendes/) [![Latest version](https://img.shields.io/crates/v/askama_mendes.svg)](https://crates.io/crates/askama_mendes) [![Build Status](https://github.com/djc/askama/workflows/CI/badge.svg)](https://github.com/djc/askama/actions?query=workflow%3ACI) -[![Chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/djc/askama) +[![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/ZucwjE6bmT) Integration of the [Askama](https://github.com/djc/askama) templating engine in code building on the [Mendes](https://github.com/djc/mendes) framework. diff --git a/askama_parser/README.md b/askama_parser/README.md index 4223a1106..7a5cd9eea 100644 --- a/askama_parser/README.md +++ b/askama_parser/README.md @@ -3,7 +3,7 @@ [![Documentation](https://docs.rs/askama_parser/badge.svg)](https://docs.rs/askama_parser/) [![Latest version](https://img.shields.io/crates/v/askama_parser.svg)](https://crates.io/crates/askama_parser) [![Build Status](https://github.com/djc/askama/workflows/CI/badge.svg)](https://github.com/djc/askama/actions?query=workflow%3ACI) -[![Chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/djc/askama) +[![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/ZucwjE6bmT) This crate contains the procedural macros used by the [Askama](https://github.com/djc/askama) templating engine. diff --git a/askama_rocket/README.md b/askama_rocket/README.md index 655fdd402..58faa9cdb 100644 --- a/askama_rocket/README.md +++ b/askama_rocket/README.md @@ -3,7 +3,7 @@ [![Documentation](https://docs.rs/askama_rocket/badge.svg)](https://docs.rs/askama_rocket/) [![Latest version](https://img.shields.io/crates/v/askama_rocket.svg)](https://crates.io/crates/askama_rocket) [![Build Status](https://github.com/djc/askama/workflows/CI/badge.svg)](https://github.com/djc/askama/actions?query=workflow%3ACI) -[![Chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/djc/askama) +[![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/ZucwjE6bmT) Integration of the [Askama](https://github.com/djc/askama) templating engine in code building on the Rocket web framework. diff --git a/askama_tide/README.md b/askama_tide/README.md index 05aa275f2..af900b39c 100644 --- a/askama_tide/README.md +++ b/askama_tide/README.md @@ -3,7 +3,7 @@ [![Documentation](https://docs.rs/askama_tide/badge.svg)](https://docs.rs/askama_tide/) [![Latest version](https://img.shields.io/crates/v/askama_tide.svg)](https://crates.io/crates/askama_tide) [![Build Status](https://github.com/djc/askama/workflows/CI/badge.svg)](https://github.com/djc/askama/actions?query=workflow%3ACI) -[![Chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/djc/askama) +[![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/ZucwjE6bmT) Integration of the [Askama](https://github.com/djc/askama) templating engine in code building on the tide web framework. diff --git a/askama_warp/README.md b/askama_warp/README.md index 68059d35f..9879d001d 100644 --- a/askama_warp/README.md +++ b/askama_warp/README.md @@ -3,7 +3,7 @@ [![Documentation](https://docs.rs/askama_warp/badge.svg)](https://docs.rs/askama_warp/) [![Latest version](https://img.shields.io/crates/v/askama_warp.svg)](https://crates.io/crates/askama_warp) [![Build Status](https://github.com/djc/askama/workflows/CI/badge.svg)](https://github.com/djc/askama/actions?query=workflow%3ACI) -[![Chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/djc/askama) +[![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/ZucwjE6bmT) Integration of the [Askama](https://github.com/djc/askama) templating engine in code building on the warp web framework. diff --git a/book/src/askama.md b/book/src/askama.md index f7c613e00..2eb366cd0 100644 --- a/book/src/askama.md +++ b/book/src/askama.md @@ -3,7 +3,7 @@ [![Documentation](https://docs.rs/askama/badge.svg)](https://docs.rs/askama/) [![Latest version](https://img.shields.io/crates/v/askama.svg)](https://crates.io/crates/askama) [![Build Status](https://github.com/djc/askama/workflows/CI/badge.svg)](https://github.com/djc/askama/actions?query=workflow%3ACI) -[![Chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/djc/askama) +[![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/ZucwjE6bmT) Askama implements a template rendering engine based on [Jinja](https://jinja.palletsprojects.com/). It generates Rust code from your templates at compile time From ab281c82365a3cbf4f564e9245b008dbe04e59a7 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 6 Oct 2023 11:28:22 +0200 Subject: [PATCH 20/44] Apply clippy suggestions from Rust 1.73 --- askama_derive/src/heritage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/askama_derive/src/heritage.rs b/askama_derive/src/heritage.rs index ce85ac769..ddf9b9949 100644 --- a/askama_derive/src/heritage.rs +++ b/askama_derive/src/heritage.rs @@ -26,7 +26,7 @@ impl Heritage<'_> { while let Some(ref path) = ctx.extends { ctx = &contexts[path.as_path()]; for (name, def) in &ctx.blocks { - blocks.entry(name).or_insert_with(Vec::new).push((ctx, def)); + blocks.entry(name).or_default().push((ctx, def)); } } From ac73f1d535eb3c1fa5dcfbdbd4f8f10ca6ca0139 Mon Sep 17 00:00:00 2001 From: PizzasBear <43722034+PizzasBear@users.noreply.github.com> Date: Thu, 12 Oct 2023 13:23:24 +0300 Subject: [PATCH 21/44] Allow macros to take `self` as an argument --- askama_derive/src/generator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 76b775b8b..6f561f83b 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -952,7 +952,7 @@ impl<'a> Generator<'a> { // If `expr` is already a form of variable then // don't reintroduce a new variable. This is // to avoid moving non-copyable values. - Expr::Var(name) => { + &Expr::Var(name) if name != "self" => { let var = self.locals.resolve_or_self(name); self.locals.insert(arg, LocalMeta::with_ref(var)); } From 5ee2dfbe6bf901c7c731e9b94ba92b82f09e9101 Mon Sep 17 00:00:00 2001 From: max Date: Thu, 12 Oct 2023 14:47:49 +0300 Subject: [PATCH 22/44] Add test for macro self argument Signed-off-by: max --- testing/templates/macro-self-arg.html | 5 +++++ testing/tests/macro.rs | 12 ++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 testing/templates/macro-self-arg.html diff --git a/testing/templates/macro-self-arg.html b/testing/templates/macro-self-arg.html new file mode 100644 index 000000000..c7c6d5b5b --- /dev/null +++ b/testing/templates/macro-self-arg.html @@ -0,0 +1,5 @@ +{%- macro my_s(slf) -%} + {{ slf.s }} +{%- endmacro -%} + +{%- call my_s(self) -%} diff --git a/testing/tests/macro.rs b/testing/tests/macro.rs index 7e910a921..faada370c 100644 --- a/testing/tests/macro.rs +++ b/testing/tests/macro.rs @@ -83,3 +83,15 @@ fn str_cmp() { let t = StrCmpTemplate; assert_eq!(t.render().unwrap(), "AfooBotherCneitherD"); } + +#[derive(Template)] +#[template(path = "macro-self-arg.html")] +struct MacroSelfArgTemplate<'a> { + s: &'a str, +} + +#[test] +fn test_macro_self_arg() { + let t = MacroSelfArgTemplate { s: "foo" }; + assert_eq!(t.render().unwrap(), "foo"); +} From a7f5186bf49ef20010c2eae7717b3a738a3b548e Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 23 Oct 2023 13:32:37 +0200 Subject: [PATCH 23/44] Add test specifically for named blocks, and named macros --- testing/templates/named-end.html | 9 +++++++++ testing/tests/inheritance.rs | 12 ++++++++++++ testing/tests/ui/name_mismatch_endblock.rs | 8 ++++++++ testing/tests/ui/name_mismatch_endblock.stderr | 8 ++++++++ testing/tests/ui/name_mismatch_endmacro.rs | 8 ++++++++ testing/tests/ui/name_mismatch_endmacro.stderr | 8 ++++++++ 6 files changed, 53 insertions(+) create mode 100644 testing/templates/named-end.html create mode 100644 testing/tests/ui/name_mismatch_endblock.rs create mode 100644 testing/tests/ui/name_mismatch_endblock.stderr create mode 100644 testing/tests/ui/name_mismatch_endmacro.rs create mode 100644 testing/tests/ui/name_mismatch_endmacro.stderr diff --git a/testing/templates/named-end.html b/testing/templates/named-end.html new file mode 100644 index 000000000..35eb0bbb3 --- /dev/null +++ b/testing/templates/named-end.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} +{# Testing named "endmacro" #} +{% macro foo(b) -%} + {% if b %}t{% else %}f{% endif -%} +{% endmacro foo -%} +{# Testing named endblock declaration #} +{% block what %}{% endblock what %} +{# Testing named endblock call #} +{% block foo %}tadam{% endblock foo %} diff --git a/testing/tests/inheritance.rs b/testing/tests/inheritance.rs index 5d8aefbd4..906be046d 100644 --- a/testing/tests/inheritance.rs +++ b/testing/tests/inheritance.rs @@ -340,3 +340,15 @@ fn test_let_block() { let t = LetChild {}; assert_eq!(t.render().unwrap(), "1"); } + +#[derive(Template)] +#[template(path = "named-end.html")] +struct NamedBlocks<'a> { + title: &'a str, +} + +#[test] +fn test_named_end() { + let n = NamedBlocks { title: "title" }; + assert_eq!(n.render().unwrap(), "title\n\ntadam\nCopyright 2017"); +} diff --git a/testing/tests/ui/name_mismatch_endblock.rs b/testing/tests/ui/name_mismatch_endblock.rs new file mode 100644 index 000000000..9fbfbe45b --- /dev/null +++ b/testing/tests/ui/name_mismatch_endblock.rs @@ -0,0 +1,8 @@ +use askama::Template; + +#[derive(Template)] +#[template(source = "{% block foo %}{% endblock not_foo %}", ext = "html")] +struct NameMismatchEndBlock; + +fn main() { +} diff --git a/testing/tests/ui/name_mismatch_endblock.stderr b/testing/tests/ui/name_mismatch_endblock.stderr new file mode 100644 index 000000000..2cd7aafe1 --- /dev/null +++ b/testing/tests/ui/name_mismatch_endblock.stderr @@ -0,0 +1,8 @@ +error: problems parsing template source at row 1, column 27 near: + "not_foo %}" + --> tests/ui/name_mismatch_endblock.rs:3:10 + | +3 | #[derive(Template)] + | ^^^^^^^^ + | + = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/testing/tests/ui/name_mismatch_endmacro.rs b/testing/tests/ui/name_mismatch_endmacro.rs new file mode 100644 index 000000000..10ea31f57 --- /dev/null +++ b/testing/tests/ui/name_mismatch_endmacro.rs @@ -0,0 +1,8 @@ +use askama::Template; + +#[derive(Template)] +#[template(source = "{% macro foo(arg) %} {{arg}} {% endmacro not_foo %}", ext = "html")] +struct NameMismatchEndMacro; + +fn main() { +} diff --git a/testing/tests/ui/name_mismatch_endmacro.stderr b/testing/tests/ui/name_mismatch_endmacro.stderr new file mode 100644 index 000000000..ef389a2c0 --- /dev/null +++ b/testing/tests/ui/name_mismatch_endmacro.stderr @@ -0,0 +1,8 @@ +error: problems parsing template source at row 1, column 41 near: + "not_foo %}" + --> tests/ui/name_mismatch_endmacro.rs:3:10 + | +3 | #[derive(Template)] + | ^^^^^^^^ + | + = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info) From e0574d3092dc71336ef6a1b6ef2db242aa19732d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 23 Oct 2023 13:36:28 +0200 Subject: [PATCH 24/44] Extend documentation about using block/macro name when "ending" it --- book/src/template_syntax.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/book/src/template_syntax.md b/book/src/template_syntax.md index 4483562c4..0b76b22b2 100644 --- a/book/src/template_syntax.md +++ b/book/src/template_syntax.md @@ -176,6 +176,13 @@ inheritance. Blocks can only be specified at the top level of a template or inside other blocks, not inside `if`/`else` branches or in `for`-loop bodies. +It is also possible to use the name of the `block` in `endblock` (both in +declaration and use): + +```html +{% block content %}

Placeholder content

{% endblock content %} +``` + ### Child template Here's an example child template: @@ -458,3 +465,10 @@ You can place templates in a separate file and use it in your templates by using {% call scope::heading(s) %} ``` + +It is also possible to use the name of the `macro` in `endmacro` in the +declaration: + +```html +{% macro heading(arg) %}

{{arg}}

{% endmacro heading %} +``` From 6dae920a451407913ab188fd507a2cfc2cd03018 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 23 Oct 2023 20:43:45 +0200 Subject: [PATCH 25/44] Create `ErrorContext` type --- askama_parser/src/expr.rs | 74 ++++++++++++---------- askama_parser/src/lib.rs | 128 +++++++++++++++++++++++++++++--------- askama_parser/src/node.rs | 76 +++++++++++++--------- 3 files changed, 183 insertions(+), 95 deletions(-) diff --git a/askama_parser/src/expr.rs b/askama_parser/src/expr.rs index 3b6bced37..803681e2d 100644 --- a/askama_parser/src/expr.rs +++ b/askama_parser/src/expr.rs @@ -12,11 +12,12 @@ use nom::{error_position, IResult}; use super::{ char_lit, identifier, not_ws, num_lit, path_or_identifier, str_lit, ws, Level, PathOrIdentifier, }; +use crate::ErrorContext; macro_rules! expr_prec_layer { ( $name:ident, $inner:ident, $op:expr ) => { - fn $name(i: &'a str, level: Level) -> IResult<&'a str, Self> { - let level = level.nest(i)?; + fn $name(i: &'a str, level: Level) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + let (_, level) = level.nest(i)?; let (i, left) = Self::$inner(i, level)?; let (i, right) = many0(pair( ws(tag($op)), @@ -31,8 +32,8 @@ macro_rules! expr_prec_layer { } }; ( $name:ident, $inner:ident, $( $op:expr ),+ ) => { - fn $name(i: &'a str, level: Level) -> IResult<&'a str, Self> { - let level = level.nest(i)?; + fn $name(i: &'a str, level: Level) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + let (_, level) = level.nest(i)?; let (i, left) = Self::$inner(i, level)?; let (i, right) = many0(pair( ws(alt(($( tag($op) ),+,))), @@ -71,8 +72,11 @@ pub enum Expr<'a> { } impl<'a> Expr<'a> { - pub(super) fn arguments(i: &'a str, level: Level) -> IResult<&'a str, Vec> { - let level = level.nest(i)?; + pub(super) fn arguments( + i: &'a str, + level: Level, + ) -> IResult<&'a str, Vec, ErrorContext<&'a str>> { + let (_, level) = level.nest(i)?; preceded( ws(char('(')), cut(terminated( @@ -82,8 +86,8 @@ impl<'a> Expr<'a> { )(i) } - pub(super) fn parse(i: &'a str, level: Level) -> IResult<&'a str, Self> { - let level = level.nest(i)?; + pub(super) fn parse(i: &'a str, level: Level) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + let (_, level) = level.nest(i)?; let range_right = move |i| { pair( ws(alt((tag("..="), tag("..")))), @@ -114,9 +118,13 @@ impl<'a> Expr<'a> { expr_prec_layer!(addsub, muldivmod, "+", "-"); expr_prec_layer!(muldivmod, filtered, "*", "/", "%"); - fn filtered(i: &'a str, level: Level) -> IResult<&'a str, Self> { - let level = level.nest(i)?; - fn filter(i: &str, level: Level) -> IResult<&str, (&str, Option>>)> { + fn filtered(i: &'a str, level: Level) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + let (_, level) = level.nest(i)?; + #[allow(clippy::type_complexity)] + fn filter( + i: &str, + level: Level, + ) -> IResult<&str, (&str, Option>>), ErrorContext<&str>> { let (i, (_, fname, args)) = tuple(( char('|'), ws(identifier), @@ -143,8 +151,8 @@ impl<'a> Expr<'a> { Ok((i, res)) } - fn prefix(i: &'a str, level: Level) -> IResult<&'a str, Self> { - let level = level.nest(i)?; + fn prefix(i: &'a str, level: Level) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + let (_, level) = level.nest(i)?; let (i, (ops, mut expr)) = pair(many0(ws(alt((tag("!"), tag("-"))))), |i| { Suffix::parse(i, level) })(i)?; @@ -154,8 +162,8 @@ impl<'a> Expr<'a> { Ok((i, expr)) } - fn single(i: &'a str, level: Level) -> IResult<&'a str, Self> { - let level = level.nest(i)?; + fn single(i: &'a str, level: Level) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + let (_, level) = level.nest(i)?; alt(( Self::num, Self::str, @@ -166,8 +174,8 @@ impl<'a> Expr<'a> { ))(i) } - fn group(i: &'a str, level: Level) -> IResult<&'a str, Self> { - let level = level.nest(i)?; + fn group(i: &'a str, level: Level) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + let (_, level) = level.nest(i)?; let (i, expr) = preceded(ws(char('(')), opt(|i| Self::parse(i, level)))(i)?; let expr = match expr { Some(expr) => expr, @@ -195,8 +203,8 @@ impl<'a> Expr<'a> { Ok((i, Self::Tuple(exprs))) } - fn array(i: &'a str, level: Level) -> IResult<&'a str, Self> { - let level = level.nest(i)?; + fn array(i: &'a str, level: Level) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + let (_, level) = level.nest(i)?; preceded( ws(char('[')), cut(terminated( @@ -209,7 +217,7 @@ impl<'a> Expr<'a> { )(i) } - fn path_var_bool(i: &'a str) -> IResult<&'a str, Self> { + fn path_var_bool(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { map(path_or_identifier, |v| match v { PathOrIdentifier::Path(v) => Self::Path(v), PathOrIdentifier::Identifier(v @ "true") => Self::BoolLit(v), @@ -218,15 +226,15 @@ impl<'a> Expr<'a> { })(i) } - fn str(i: &'a str) -> IResult<&'a str, Self> { + fn str(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { map(str_lit, Self::StrLit)(i) } - fn num(i: &'a str) -> IResult<&'a str, Self> { + fn num(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { map(num_lit, Self::NumLit)(i) } - fn char(i: &'a str) -> IResult<&'a str, Self> { + fn char(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { map(char_lit, Self::CharLit)(i) } } @@ -241,8 +249,8 @@ enum Suffix<'a> { } impl<'a> Suffix<'a> { - fn parse(i: &'a str, level: Level) -> IResult<&'a str, Expr<'a>> { - let level = level.nest(i)?; + fn parse(i: &'a str, level: Level) -> IResult<&'a str, Expr<'a>, ErrorContext<&'a str>> { + let (_, level) = level.nest(i)?; let (mut i, mut expr) = Expr::single(i, level)?; loop { let (j, suffix) = opt(alt(( @@ -271,8 +279,8 @@ impl<'a> Suffix<'a> { Ok((i, expr)) } - fn r#macro(i: &'a str) -> IResult<&'a str, Self> { - fn nested_parenthesis(input: &str) -> IResult<&str, ()> { + fn r#macro(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn nested_parenthesis(input: &str) -> IResult<&str, (), ErrorContext<&str>> { let mut nested = 0; let mut last = 0; let mut in_str = false; @@ -329,7 +337,7 @@ impl<'a> Suffix<'a> { )(i) } - fn attr(i: &'a str) -> IResult<&'a str, Self> { + fn attr(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { map( preceded( ws(pair(char('.'), not(char('.')))), @@ -339,8 +347,8 @@ impl<'a> Suffix<'a> { )(i) } - fn index(i: &'a str, level: Level) -> IResult<&'a str, Self> { - let level = level.nest(i)?; + fn index(i: &'a str, level: Level) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + let (_, level) = level.nest(i)?; map( preceded( ws(char('[')), @@ -350,12 +358,12 @@ impl<'a> Suffix<'a> { )(i) } - fn call(i: &'a str, level: Level) -> IResult<&'a str, Self> { - let level = level.nest(i)?; + fn call(i: &'a str, level: Level) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + let (_, level) = level.nest(i)?; map(move |i| Expr::arguments(i, level), Self::Call)(i) } - fn r#try(i: &'a str) -> IResult<&'a str, Self> { + fn r#try(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { map(preceded(take_till(not_ws), char('?')), |_| Self::Try)(i) } } diff --git a/askama_parser/src/lib.rs b/askama_parser/src/lib.rs index 78b080741..ba5f6a442 100644 --- a/askama_parser/src/lib.rs +++ b/askama_parser/src/lib.rs @@ -1,6 +1,7 @@ #![deny(unreachable_pub)] #![deny(elided_lifetimes_in_paths)] +use std::borrow::Cow; use std::cell::Cell; use std::{fmt, str}; @@ -9,7 +10,7 @@ use nom::bytes::complete::{escaped, is_not, tag, take_till}; use nom::character::complete::char; use nom::character::complete::{anychar, digit1}; use nom::combinator::{cut, eof, map, opt, recognize}; -use nom::error::ErrorKind; +use nom::error::{Error, ErrorKind, FromExternalError}; use nom::multi::many1; use nom::sequence::{delimited, pair, preceded, terminated, tuple}; use nom::{error_position, AsChar, IResult, InputTakeAtPosition}; @@ -76,14 +77,16 @@ pub struct Ast<'a> { impl<'a> Ast<'a> { pub fn from_str(src: &'a str, syntax: &Syntax<'_>) -> Result { let parse = |i: &'a str| Node::many(i, &State::new(syntax)); - let err = match terminated(parse, cut(eof))(src) { + let (input, message) = match terminated(parse, cut(eof))(src) { Ok(("", nodes)) => return Ok(Self { nodes }), Ok(_) => unreachable!("eof() is not eof?"), - Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => err, + Err( + nom::Err::Error(ErrorContext { input, message, .. }) + | nom::Err::Failure(ErrorContext { input, message, .. }), + ) => (input, message), Err(nom::Err::Incomplete(_)) => return Err(ParseError("parsing incomplete".into())), }; - let nom::error::Error { input, .. } = err; let offset = src.len() - input.len(); let (source_before, source_after) = src.split_at(offset); @@ -96,7 +99,12 @@ impl<'a> Ast<'a> { let column = last_line.chars().count(); let msg = format!( - "problems parsing template source at row {}, column {} near:\n{}", + "{}problems parsing template source at row {}, column {} near:\n{}", + if let Some(message) = message { + format!("error: {message}\n") + } else { + String::new() + }, row + 1, column, source_after, @@ -121,6 +129,55 @@ impl fmt::Display for ParseError { } } +/// This type is used to handle `nom` errors and in particular to add custom error messages. +/// It used to generate `ParserError`. +/// +/// It cannot be used to replace `ParseError` because it expects a generic, which would make +/// `askama`'s users experience less good (since this generic is only needed for `nom`). +#[derive(Debug)] +pub(crate) struct ErrorContext { + pub(crate) input: I, + pub(crate) message: Option>, +} + +impl nom::error::ParseError for ErrorContext { + fn from_error_kind(input: I, _code: ErrorKind) -> Self { + Self { + input, + message: None, + } + } + + fn append(_: I, _: ErrorKind, other: Self) -> Self { + other + } +} + +impl FromExternalError for ErrorContext { + fn from_external_error(input: I, _kind: ErrorKind, e: E) -> Self { + Self { + input, + message: Some(Cow::Owned(e.to_string())), + } + } +} + +impl ErrorContext { + pub(crate) fn from_err(error: nom::Err>) -> nom::Err { + match error { + nom::Err::Incomplete(i) => nom::Err::Incomplete(i), + nom::Err::Failure(Error { input, .. }) => nom::Err::Failure(Self { + input, + message: None, + }), + nom::Err::Error(Error { input, .. }) => nom::Err::Error(Self { + input, + message: None, + }), + } + } +} + fn is_ws(c: char) -> bool { matches!(c, ' ' | '\t' | '\r' | '\n') } @@ -130,16 +187,16 @@ fn not_ws(c: char) -> bool { } fn ws<'a, O>( - inner: impl FnMut(&'a str) -> IResult<&'a str, O>, -) -> impl FnMut(&'a str) -> IResult<&'a str, O> { + inner: impl FnMut(&'a str) -> IResult<&'a str, O, ErrorContext<&'a str>>, +) -> impl FnMut(&'a str) -> IResult<&'a str, O, ErrorContext<&'a str>> { delimited(take_till(not_ws), inner, take_till(not_ws)) } /// Skips input until `end` was found, but does not consume it. /// Returns tuple that would be returned when parsing `end`. fn skip_till<'a, O>( - end: impl FnMut(&'a str) -> IResult<&'a str, O>, -) -> impl FnMut(&'a str) -> IResult<&'a str, (&'a str, O)> { + end: impl FnMut(&'a str) -> IResult<&'a str, O, ErrorContext<&'a str>>, +) -> impl FnMut(&'a str) -> IResult<&'a str, (&'a str, O), ErrorContext<&'a str>> { enum Next { IsEnd(O), NotEnd(char), @@ -157,8 +214,10 @@ fn skip_till<'a, O>( } } -fn keyword<'a>(k: &'a str) -> impl FnMut(&'a str) -> IResult<&'a str, &'a str> { - move |i: &'a str| -> IResult<&'a str, &'a str> { +fn keyword<'a>( + k: &'a str, +) -> impl FnMut(&'a str) -> IResult<&'a str, &'a str, ErrorContext<&'a str>> { + move |i: &'a str| -> IResult<&'a str, &'a str, ErrorContext<&'a str>> { let (j, v) = identifier(i)?; if k == v { Ok((j, v)) @@ -168,15 +227,15 @@ fn keyword<'a>(k: &'a str) -> impl FnMut(&'a str) -> IResult<&'a str, &'a str> { } } -fn identifier(input: &str) -> IResult<&str, &str> { - fn start(s: &str) -> IResult<&str, &str> { +fn identifier(input: &str) -> IResult<&str, &str, ErrorContext<&str>> { + fn start(s: &str) -> IResult<&str, &str, ErrorContext<&str>> { s.split_at_position1_complete( |c| !(c.is_alpha() || c == '_' || c >= '\u{0080}'), nom::error::ErrorKind::Alpha, ) } - fn tail(s: &str) -> IResult<&str, &str> { + fn tail(s: &str) -> IResult<&str, &str, ErrorContext<&str>> { s.split_at_position1_complete( |c| !(c.is_alphanum() || c == '_' || c >= '\u{0080}'), nom::error::ErrorKind::Alpha, @@ -186,11 +245,11 @@ fn identifier(input: &str) -> IResult<&str, &str> { recognize(pair(start, opt(tail)))(input) } -fn bool_lit(i: &str) -> IResult<&str, &str> { +fn bool_lit(i: &str) -> IResult<&str, &str, ErrorContext<&str>> { alt((keyword("false"), keyword("true")))(i) } -fn num_lit(i: &str) -> IResult<&str, &str> { +fn num_lit(i: &str) -> IResult<&str, &str, ErrorContext<&str>> { recognize(tuple(( opt(char('-')), digit1, @@ -198,7 +257,7 @@ fn num_lit(i: &str) -> IResult<&str, &str> { )))(i) } -fn str_lit(i: &str) -> IResult<&str, &str> { +fn str_lit(i: &str) -> IResult<&str, &str, ErrorContext<&str>> { let (i, s) = delimited( char('"'), opt(escaped(is_not("\\\""), '\\', anychar)), @@ -207,7 +266,7 @@ fn str_lit(i: &str) -> IResult<&str, &str> { Ok((i, s.unwrap_or_default())) } -fn char_lit(i: &str) -> IResult<&str, &str> { +fn char_lit(i: &str) -> IResult<&str, &str, ErrorContext<&str>> { let (i, s) = delimited( char('\''), opt(escaped(is_not("\\\'"), '\\', anychar)), @@ -221,7 +280,7 @@ enum PathOrIdentifier<'a> { Identifier(&'a str), } -fn path_or_identifier(i: &str) -> IResult<&str, PathOrIdentifier<'_>> { +fn path_or_identifier(i: &str) -> IResult<&str, PathOrIdentifier<'_>, ErrorContext<&str>> { let root = ws(opt(tag("::"))); let tail = opt(many1(preceded(ws(tag("::")), identifier))); @@ -267,36 +326,40 @@ impl<'a> State<'a> { } } - fn nest<'b>(&self, i: &'b str) -> Result<(), nom::Err>> { - self.level.set(self.level.get().nest(i)?); - Ok(()) + fn nest<'b>(&self, i: &'b str) -> IResult<&'b str, (), ErrorContext<&'b str>> { + let (_, level) = self.level.get().nest(i)?; + self.level.set(level); + Ok((i, ())) } fn leave(&self) { self.level.set(self.level.get().leave()); } - fn tag_block_start<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str> { + fn tag_block_start<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str, ErrorContext<&'i str>> { tag(self.syntax.block_start)(i) } - fn tag_block_end<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str> { + fn tag_block_end<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str, ErrorContext<&'i str>> { tag(self.syntax.block_end)(i) } - fn tag_comment_start<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str> { + fn tag_comment_start<'i>( + &self, + i: &'i str, + ) -> IResult<&'i str, &'i str, ErrorContext<&'i str>> { tag(self.syntax.comment_start)(i) } - fn tag_comment_end<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str> { + fn tag_comment_end<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str, ErrorContext<&'i str>> { tag(self.syntax.comment_end)(i) } - fn tag_expr_start<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str> { + fn tag_expr_start<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str, ErrorContext<&'i str>> { tag(self.syntax.expr_start)(i) } - fn tag_expr_end<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str> { + fn tag_expr_end<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str, ErrorContext<&'i str>> { tag(self.syntax.expr_end)(i) } @@ -340,12 +403,15 @@ impl Default for Syntax<'static> { pub(crate) struct Level(u8); impl Level { - fn nest(self, i: &str) -> Result>> { + fn nest(self, i: &str) -> IResult<&str, Level, ErrorContext<&str>> { if self.0 >= Self::MAX_DEPTH { - return Err(nom::Err::Failure(error_position!(i, ErrorKind::TooLarge))); + return Err(ErrorContext::from_err(nom::Err::Failure(error_position!( + i, + ErrorKind::TooLarge + )))); } - Ok(Level(self.0 + 1)) + Ok((i, Level(self.0 + 1))) } fn leave(&self) -> Self { diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs index ba4d09edd..596b99ae7 100644 --- a/askama_parser/src/node.rs +++ b/askama_parser/src/node.rs @@ -11,6 +11,8 @@ use nom::multi::{fold_many0, many0, many1, separated_list0, separated_list1}; use nom::sequence::{delimited, pair, preceded, terminated, tuple}; use nom::{error_position, IResult}; +use crate::ErrorContext; + use super::{ bool_lit, char_lit, identifier, is_ws, keyword, num_lit, path_or_identifier, skip_till, str_lit, ws, Expr, PathOrIdentifier, State, @@ -37,7 +39,10 @@ pub enum Node<'a> { } impl<'a> Node<'a> { - pub(super) fn many(i: &'a str, s: &State<'_>) -> IResult<&'a str, Vec> { + pub(super) fn many( + i: &'a str, + s: &State<'_>, + ) -> IResult<&'a str, Vec, ErrorContext<&'a str>> { complete(many0(alt(( map(|i| Lit::parse(i, s), Self::Lit), map(|i| Comment::parse(i, s), Self::Comment), @@ -46,7 +51,7 @@ impl<'a> Node<'a> { ))))(i) } - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { + fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { let mut p = delimited( |i| s.tag_block_start(i), alt(( @@ -74,7 +79,7 @@ impl<'a> Node<'a> { result } - fn r#break(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { + fn r#break(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { let mut p = tuple(( opt(Whitespace::parse), ws(keyword("break")), @@ -87,7 +92,7 @@ impl<'a> Node<'a> { Ok((j, Self::Break(Ws(pws, nws)))) } - fn r#continue(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { + fn r#continue(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { let mut p = tuple(( opt(Whitespace::parse), ws(keyword("continue")), @@ -100,7 +105,7 @@ impl<'a> Node<'a> { Ok((j, Self::Continue(Ws(pws, nws)))) } - fn expr(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { + fn expr(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { let mut p = tuple(( |i| s.tag_expr_start(i), cut(tuple(( @@ -128,7 +133,7 @@ pub enum Target<'a> { } impl<'a> Target<'a> { - pub(super) fn parse(i: &'a str) -> IResult<&'a str, Self> { + pub(super) fn parse(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { let mut opt_opening_paren = map(opt(ws(char('('))), |o| o.is_some()); let mut opt_closing_paren = map(opt(ws(char(')'))), |o| o.is_some()); let mut opt_opening_brace = map(opt(ws(char('{'))), |o| o.is_some()); @@ -211,7 +216,7 @@ impl<'a> Target<'a> { map(identifier, Self::Name)(i) } - fn lit(i: &'a str) -> IResult<&'a str, Self> { + fn lit(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { alt(( map(str_lit, Self::StrLit), map(char_lit, Self::CharLit), @@ -220,7 +225,7 @@ impl<'a> Target<'a> { ))(i) } - fn named(i: &'a str) -> IResult<&str, (&str, Self)> { + fn named(i: &'a str) -> IResult<&str, (&str, Self), ErrorContext<&'a str>> { let (i, (src, target)) = pair(identifier, opt(preceded(ws(char(':')), Self::parse)))(i)?; Ok((i, (src, target.unwrap_or(Self::Name(src))))) } @@ -234,7 +239,7 @@ pub struct When<'a> { } impl<'a> When<'a> { - fn r#match(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { + fn r#match(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { let mut p = tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), @@ -257,7 +262,7 @@ impl<'a> When<'a> { } #[allow(clippy::self_named_constructors)] - fn when(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { + fn when(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { let mut p = tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), @@ -289,7 +294,7 @@ pub struct Cond<'a> { } impl<'a> Cond<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { + fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { let mut p = tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), @@ -320,7 +325,7 @@ pub struct CondTest<'a> { } impl<'a> CondTest<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { + fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { let mut p = preceded( ws(keyword("if")), cut(tuple(( @@ -345,7 +350,7 @@ pub enum Whitespace { } impl Whitespace { - fn parse(i: &str) -> IResult<&str, Self> { + fn parse(i: &str) -> IResult<&str, Self, ErrorContext<&str>> { alt(( value(Self::Preserve, char('+')), value(Self::Suppress, char('-')), @@ -367,8 +372,11 @@ pub struct Loop<'a> { } impl<'a> Loop<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { - fn content<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Vec>> { + fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn content<'a>( + i: &'a str, + s: &State<'_>, + ) -> IResult<&'a str, Vec>, ErrorContext<&'a str>> { s.enter_loop(); let result = Node::many(i, s); s.leave_loop(); @@ -448,8 +456,8 @@ pub struct Macro<'a> { } impl<'a> Macro<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { - fn parameters(i: &str) -> IResult<&str, Vec<&str>> { + fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn parameters(i: &str) -> IResult<&str, Vec<&str>, ErrorContext<&str>> { delimited( ws(char('(')), separated_list0(char(','), ws(identifier)), @@ -482,7 +490,10 @@ impl<'a> Macro<'a> { if name == "super" { // TODO: yield a a better error message here - return Err(nom::Err::Failure(Error::new(i, ErrorKind::Fail))); + return Err(ErrorContext::from_err(nom::Err::Failure(Error::new( + i, + ErrorKind::Fail, + )))); } Ok(( @@ -506,7 +517,7 @@ pub struct Import<'a> { } impl<'a> Import<'a> { - fn parse(i: &'a str) -> IResult<&'a str, Self> { + fn parse(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { let mut p = tuple(( opt(Whitespace::parse), ws(keyword("import")), @@ -537,7 +548,7 @@ pub struct Call<'a> { } impl<'a> Call<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { + fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { let mut p = tuple(( opt(Whitespace::parse), ws(keyword("call")), @@ -572,7 +583,7 @@ pub struct Match<'a> { } impl<'a> Match<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { + fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { let mut p = tuple(( opt(Whitespace::parse), ws(keyword("match")), @@ -623,7 +634,7 @@ pub struct BlockDef<'a> { } impl<'a> BlockDef<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { + fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { let mut start = tuple(( opt(Whitespace::parse), ws(keyword("block")), @@ -664,7 +675,7 @@ pub struct Lit<'a> { } impl<'a> Lit<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { + fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { let p_start = alt(( tag(s.syntax.block_start), tag(s.syntax.comment_start), @@ -704,7 +715,7 @@ pub struct Raw<'a> { } impl<'a> Raw<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { + fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { let endraw = tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), @@ -739,7 +750,7 @@ pub struct Let<'a> { } impl<'a> Let<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { + fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { let mut p = tuple(( opt(Whitespace::parse), ws(alt((keyword("let"), keyword("set")))), @@ -772,7 +783,7 @@ pub struct If<'a> { } impl<'a> If<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { + fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { let mut p = tuple(( opt(Whitespace::parse), |i| CondTest::parse(i, s), @@ -817,7 +828,7 @@ pub struct Include<'a> { } impl<'a> Include<'a> { - fn parse(i: &'a str) -> IResult<&'a str, Self> { + fn parse(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { let mut p = tuple(( opt(Whitespace::parse), ws(keyword("include")), @@ -840,7 +851,7 @@ pub struct Extends<'a> { } impl<'a> Extends<'a> { - fn parse(i: &'a str) -> IResult<&'a str, Self> { + fn parse(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { let (i, path) = preceded(ws(keyword("extends")), cut(ws(str_lit)))(i)?; Ok((i, Self { path })) } @@ -853,12 +864,15 @@ pub struct Comment<'a> { } impl<'a> Comment<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self> { - fn body<'a>(mut i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> { + fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn body<'a>( + mut i: &'a str, + s: &State<'_>, + ) -> IResult<&'a str, &'a str, ErrorContext<&'a str>> { let mut level = 0; loop { let (end, tail) = take_until(s.syntax.comment_end)(i)?; - match take_until::<_, _, Error<_>>(s.syntax.comment_start)(i) { + match take_until::<_, _, ErrorContext<_>>(s.syntax.comment_start)(i) { Ok((start, _)) if start.as_ptr() < end.as_ptr() => { level += 1; i = &start[2..]; From c3281e353d8192e6425f903216eeb6ecf9dd39fc Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 23 Oct 2023 21:15:08 +0200 Subject: [PATCH 26/44] Improve error for invalid name used in `endblock` --- askama_parser/src/lib.rs | 2 +- askama_parser/src/node.rs | 30 ++++++++++++++++++- .../tests/ui/name_mismatch_endblock.stderr | 3 +- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/askama_parser/src/lib.rs b/askama_parser/src/lib.rs index ba5f6a442..5abc7717a 100644 --- a/askama_parser/src/lib.rs +++ b/askama_parser/src/lib.rs @@ -101,7 +101,7 @@ impl<'a> Ast<'a> { let msg = format!( "{}problems parsing template source at row {}, column {} near:\n{}", if let Some(message) = message { - format!("error: {message}\n") + format!("{message}\n") } else { String::new() }, diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs index 596b99ae7..b321c80e4 100644 --- a/askama_parser/src/node.rs +++ b/askama_parser/src/node.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::str; use nom::branch::alt; @@ -650,7 +651,13 @@ impl<'a> BlockDef<'a> { |i| s.tag_block_start(i), opt(Whitespace::parse), ws(keyword("endblock")), - cut(tuple((opt(ws(keyword(name))), opt(Whitespace::parse)))), + cut(tuple(( + opt(|before| { + let (after, end_name) = ws(identifier)(before)?; + check_end_name(before, after, name, end_name, "block") + }), + opt(Whitespace::parse), + ))), ))), ))); let (i, (nodes, (_, pws2, _, (_, nws2)))) = end(i)?; @@ -667,6 +674,27 @@ impl<'a> BlockDef<'a> { } } +fn check_end_name<'a>( + before: &'a str, + after: &'a str, + name: &'a str, + end_name: &'a str, + kind: &str, +) -> IResult<&'a str, &'a str, ErrorContext<&'a str>> { + if name == end_name { + return Ok((after, end_name)); + } + let message = if name.is_empty() && !end_name.is_empty() { + format!("unexpected name `{end_name}` in `end{kind}` tag for unnamed `{kind}`") + } else { + format!("expected name `{name}` in `end{kind}` tag, found `{end_name}`") + }; + Err(nom::Err::Failure(ErrorContext { + input: before, + message: Some(Cow::Owned(message)), + })) +} + #[derive(Debug, PartialEq)] pub struct Lit<'a> { pub lws: &'a str, diff --git a/testing/tests/ui/name_mismatch_endblock.stderr b/testing/tests/ui/name_mismatch_endblock.stderr index 2cd7aafe1..958c4132a 100644 --- a/testing/tests/ui/name_mismatch_endblock.stderr +++ b/testing/tests/ui/name_mismatch_endblock.stderr @@ -1,4 +1,5 @@ -error: problems parsing template source at row 1, column 27 near: +error: expected name `foo` in `endblock` tag, found `not_foo` + problems parsing template source at row 1, column 27 near: "not_foo %}" --> tests/ui/name_mismatch_endblock.rs:3:10 | From c056d6287b0616aea9bc9fab43bcca97d0e86d23 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 24 Oct 2023 11:06:16 +0200 Subject: [PATCH 27/44] Improve error for invalid name used in `endmacro` --- askama_parser/src/node.rs | 8 +++++++- testing/tests/ui/name_mismatch_endmacro.stderr | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs index b321c80e4..93fca734c 100644 --- a/askama_parser/src/node.rs +++ b/askama_parser/src/node.rs @@ -484,7 +484,13 @@ impl<'a> Macro<'a> { |i| s.tag_block_start(i), opt(Whitespace::parse), ws(keyword("endmacro")), - cut(preceded(ws(opt(keyword(name))), opt(Whitespace::parse))), + cut(preceded( + opt(|before| { + let (after, end_name) = ws(identifier)(before)?; + check_end_name(before, after, name, end_name, "macro") + }), + opt(Whitespace::parse), + )), ))), ))); let (i, (contents, (_, pws2, _, nws2))) = end(i)?; diff --git a/testing/tests/ui/name_mismatch_endmacro.stderr b/testing/tests/ui/name_mismatch_endmacro.stderr index ef389a2c0..74101a458 100644 --- a/testing/tests/ui/name_mismatch_endmacro.stderr +++ b/testing/tests/ui/name_mismatch_endmacro.stderr @@ -1,4 +1,5 @@ -error: problems parsing template source at row 1, column 41 near: +error: expected name `foo` in `endmacro` tag, found `not_foo` + problems parsing template source at row 1, column 41 near: "not_foo %}" --> tests/ui/name_mismatch_endmacro.rs:3:10 | From d48ac76f0a6561cc16aa650f0f33a3d1f9e809b9 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 25 Oct 2023 11:10:41 +0200 Subject: [PATCH 28/44] Improve error for `elif` keyword --- askama_parser/src/node.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs index 93fca734c..dc05f48b0 100644 --- a/askama_parser/src/node.rs +++ b/askama_parser/src/node.rs @@ -299,7 +299,15 @@ impl<'a> Cond<'a> { let mut p = tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), - ws(keyword("else")), + ws(alt((keyword("else"), |i| { + let _ = keyword("elif")(i)?; + Err(nom::Err::Failure(ErrorContext { + input: i, + message: Some(Cow::Borrowed( + "unknown `elif` keyword; did you mean `else if`?", + )), + })) + }))), cut(tuple(( opt(|i| CondTest::parse(i, s)), opt(Whitespace::parse), From 77d5d28b20162c522483ed009afba2ca9a6ee390 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 25 Oct 2023 11:11:00 +0200 Subject: [PATCH 29/44] Add UI test for `elif` error message --- testing/tests/ui/elif.rs | 8 ++++++++ testing/tests/ui/elif.stderr | 9 +++++++++ 2 files changed, 17 insertions(+) create mode 100644 testing/tests/ui/elif.rs create mode 100644 testing/tests/ui/elif.stderr diff --git a/testing/tests/ui/elif.rs b/testing/tests/ui/elif.rs new file mode 100644 index 000000000..650f2923e --- /dev/null +++ b/testing/tests/ui/elif.rs @@ -0,0 +1,8 @@ +use askama::Template; + +#[derive(Template)] +#[template(source = "{% if true %}{% elif false %}{% endif %}", ext = "html")] +struct UnknownElif; + +fn main() { +} diff --git a/testing/tests/ui/elif.stderr b/testing/tests/ui/elif.stderr new file mode 100644 index 000000000..0d5de470a --- /dev/null +++ b/testing/tests/ui/elif.stderr @@ -0,0 +1,9 @@ +error: unknown `elif` keyword; did you mean `else if`? + problems parsing template source at row 1, column 16 near: + "elif false %}{% endif %}" + --> tests/ui/elif.rs:3:10 + | +3 | #[derive(Template)] + | ^^^^^^^^ + | + = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info) From 76462255ea6ce20cc13552164c24b34f729f9d0f Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 25 Oct 2023 15:22:32 +0200 Subject: [PATCH 30/44] Create a type alias for all `IResult` to simplify code reading --- askama_parser/src/expr.rs | 52 ++++++++++++--------------- askama_parser/src/lib.rs | 71 ++++++++++++++++++------------------ askama_parser/src/node.rs | 75 +++++++++++++++++---------------------- 3 files changed, 90 insertions(+), 108 deletions(-) diff --git a/askama_parser/src/expr.rs b/askama_parser/src/expr.rs index 803681e2d..7760a98ec 100644 --- a/askama_parser/src/expr.rs +++ b/askama_parser/src/expr.rs @@ -5,18 +5,18 @@ use nom::bytes::complete::{tag, take_till}; use nom::character::complete::char; use nom::combinator::{cut, map, not, opt, peek, recognize}; use nom::error::ErrorKind; +use nom::error_position; use nom::multi::{fold_many0, many0, separated_list0}; use nom::sequence::{pair, preceded, terminated, tuple}; -use nom::{error_position, IResult}; use super::{ char_lit, identifier, not_ws, num_lit, path_or_identifier, str_lit, ws, Level, PathOrIdentifier, }; -use crate::ErrorContext; +use crate::ParseResult; macro_rules! expr_prec_layer { ( $name:ident, $inner:ident, $op:expr ) => { - fn $name(i: &'a str, level: Level) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn $name(i: &'a str, level: Level) -> ParseResult<'a, Self> { let (_, level) = level.nest(i)?; let (i, left) = Self::$inner(i, level)?; let (i, right) = many0(pair( @@ -32,7 +32,7 @@ macro_rules! expr_prec_layer { } }; ( $name:ident, $inner:ident, $( $op:expr ),+ ) => { - fn $name(i: &'a str, level: Level) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn $name(i: &'a str, level: Level) -> ParseResult<'a, Self> { let (_, level) = level.nest(i)?; let (i, left) = Self::$inner(i, level)?; let (i, right) = many0(pair( @@ -72,10 +72,7 @@ pub enum Expr<'a> { } impl<'a> Expr<'a> { - pub(super) fn arguments( - i: &'a str, - level: Level, - ) -> IResult<&'a str, Vec, ErrorContext<&'a str>> { + pub(super) fn arguments(i: &'a str, level: Level) -> ParseResult<'a, Vec> { let (_, level) = level.nest(i)?; preceded( ws(char('(')), @@ -86,7 +83,7 @@ impl<'a> Expr<'a> { )(i) } - pub(super) fn parse(i: &'a str, level: Level) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + pub(super) fn parse(i: &'a str, level: Level) -> ParseResult<'a, Self> { let (_, level) = level.nest(i)?; let range_right = move |i| { pair( @@ -118,13 +115,10 @@ impl<'a> Expr<'a> { expr_prec_layer!(addsub, muldivmod, "+", "-"); expr_prec_layer!(muldivmod, filtered, "*", "/", "%"); - fn filtered(i: &'a str, level: Level) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + 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, - ) -> IResult<&str, (&str, Option>>), ErrorContext<&str>> { + fn filter(i: &str, level: Level) -> ParseResult<'_, (&str, Option>>)> { let (i, (_, fname, args)) = tuple(( char('|'), ws(identifier), @@ -151,7 +145,7 @@ impl<'a> Expr<'a> { Ok((i, res)) } - fn prefix(i: &'a str, level: Level) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn prefix(i: &'a str, level: Level) -> ParseResult<'a, Self> { let (_, level) = level.nest(i)?; let (i, (ops, mut expr)) = pair(many0(ws(alt((tag("!"), tag("-"))))), |i| { Suffix::parse(i, level) @@ -162,7 +156,7 @@ impl<'a> Expr<'a> { Ok((i, expr)) } - fn single(i: &'a str, level: Level) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn single(i: &'a str, level: Level) -> ParseResult<'a, Self> { let (_, level) = level.nest(i)?; alt(( Self::num, @@ -174,7 +168,7 @@ impl<'a> Expr<'a> { ))(i) } - fn group(i: &'a str, level: Level) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn group(i: &'a str, level: Level) -> ParseResult<'a, Self> { let (_, level) = level.nest(i)?; let (i, expr) = preceded(ws(char('(')), opt(|i| Self::parse(i, level)))(i)?; let expr = match expr { @@ -203,7 +197,7 @@ impl<'a> Expr<'a> { Ok((i, Self::Tuple(exprs))) } - fn array(i: &'a str, level: Level) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn array(i: &'a str, level: Level) -> ParseResult<'a, Self> { let (_, level) = level.nest(i)?; preceded( ws(char('[')), @@ -217,7 +211,7 @@ impl<'a> Expr<'a> { )(i) } - fn path_var_bool(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn path_var_bool(i: &'a str) -> ParseResult<'a, Self> { map(path_or_identifier, |v| match v { PathOrIdentifier::Path(v) => Self::Path(v), PathOrIdentifier::Identifier(v @ "true") => Self::BoolLit(v), @@ -226,15 +220,15 @@ impl<'a> Expr<'a> { })(i) } - fn str(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn str(i: &'a str) -> ParseResult<'a, Self> { map(str_lit, Self::StrLit)(i) } - fn num(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn num(i: &'a str) -> ParseResult<'a, Self> { map(num_lit, Self::NumLit)(i) } - fn char(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn char(i: &'a str) -> ParseResult<'a, Self> { map(char_lit, Self::CharLit)(i) } } @@ -249,7 +243,7 @@ enum Suffix<'a> { } impl<'a> Suffix<'a> { - fn parse(i: &'a str, level: Level) -> IResult<&'a str, Expr<'a>, ErrorContext<&'a str>> { + fn parse(i: &'a str, level: Level) -> ParseResult<'a, Expr<'a>> { let (_, level) = level.nest(i)?; let (mut i, mut expr) = Expr::single(i, level)?; loop { @@ -279,8 +273,8 @@ impl<'a> Suffix<'a> { Ok((i, expr)) } - fn r#macro(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { - fn nested_parenthesis(input: &str) -> IResult<&str, (), ErrorContext<&str>> { + fn r#macro(i: &'a str) -> ParseResult<'a, Self> { + fn nested_parenthesis(input: &str) -> ParseResult<'_, ()> { let mut nested = 0; let mut last = 0; let mut in_str = false; @@ -337,7 +331,7 @@ impl<'a> Suffix<'a> { )(i) } - fn attr(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn attr(i: &'a str) -> ParseResult<'a, Self> { map( preceded( ws(pair(char('.'), not(char('.')))), @@ -347,7 +341,7 @@ impl<'a> Suffix<'a> { )(i) } - fn index(i: &'a str, level: Level) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn index(i: &'a str, level: Level) -> ParseResult<'a, Self> { let (_, level) = level.nest(i)?; map( preceded( @@ -358,12 +352,12 @@ impl<'a> Suffix<'a> { )(i) } - fn call(i: &'a str, level: Level) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn call(i: &'a str, level: Level) -> ParseResult<'a, Self> { let (_, level) = level.nest(i)?; map(move |i| Expr::arguments(i, level), Self::Call)(i) } - fn r#try(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn r#try(i: &'a str) -> ParseResult<'a, Self> { map(preceded(take_till(not_ws), char('?')), |_| Self::Try)(i) } } diff --git a/askama_parser/src/lib.rs b/askama_parser/src/lib.rs index 5abc7717a..ff26483ae 100644 --- a/askama_parser/src/lib.rs +++ b/askama_parser/src/lib.rs @@ -13,7 +13,7 @@ use nom::combinator::{cut, eof, map, opt, recognize}; use nom::error::{Error, ErrorKind, FromExternalError}; use nom::multi::many1; use nom::sequence::{delimited, pair, preceded, terminated, tuple}; -use nom::{error_position, AsChar, IResult, InputTakeAtPosition}; +use nom::{error_position, AsChar, InputTakeAtPosition}; pub mod expr; pub use expr::Expr; @@ -129,32 +129,34 @@ impl fmt::Display for ParseError { } } +pub(crate) type ParseResult<'a, T = &'a str> = Result<(&'a str, T), nom::Err>>; + /// This type is used to handle `nom` errors and in particular to add custom error messages. /// It used to generate `ParserError`. /// /// It cannot be used to replace `ParseError` because it expects a generic, which would make /// `askama`'s users experience less good (since this generic is only needed for `nom`). #[derive(Debug)] -pub(crate) struct ErrorContext { - pub(crate) input: I, +pub(crate) struct ErrorContext<'a> { + pub(crate) input: &'a str, pub(crate) message: Option>, } -impl nom::error::ParseError for ErrorContext { - fn from_error_kind(input: I, _code: ErrorKind) -> Self { +impl<'a> nom::error::ParseError<&'a str> for ErrorContext<'a> { + fn from_error_kind(input: &'a str, _code: ErrorKind) -> Self { Self { input, message: None, } } - fn append(_: I, _: ErrorKind, other: Self) -> Self { + fn append(_: &'a str, _: ErrorKind, other: Self) -> Self { other } } -impl FromExternalError for ErrorContext { - fn from_external_error(input: I, _kind: ErrorKind, e: E) -> Self { +impl<'a, E: std::fmt::Display> FromExternalError<&'a str, E> for ErrorContext<'a> { + fn from_external_error(input: &'a str, _kind: ErrorKind, e: E) -> Self { Self { input, message: Some(Cow::Owned(e.to_string())), @@ -162,8 +164,8 @@ impl FromExternalError for ErrorContext { } } -impl ErrorContext { - pub(crate) fn from_err(error: nom::Err>) -> nom::Err { +impl<'a> ErrorContext<'a> { + pub(crate) fn from_err(error: nom::Err>) -> nom::Err { match error { nom::Err::Incomplete(i) => nom::Err::Incomplete(i), nom::Err::Failure(Error { input, .. }) => nom::Err::Failure(Self { @@ -187,16 +189,16 @@ fn not_ws(c: char) -> bool { } fn ws<'a, O>( - inner: impl FnMut(&'a str) -> IResult<&'a str, O, ErrorContext<&'a str>>, -) -> impl FnMut(&'a str) -> IResult<&'a str, O, ErrorContext<&'a str>> { + inner: impl FnMut(&'a str) -> ParseResult<'a, O>, +) -> impl FnMut(&'a str) -> ParseResult<'a, O> { delimited(take_till(not_ws), inner, take_till(not_ws)) } /// Skips input until `end` was found, but does not consume it. /// Returns tuple that would be returned when parsing `end`. fn skip_till<'a, O>( - end: impl FnMut(&'a str) -> IResult<&'a str, O, ErrorContext<&'a str>>, -) -> impl FnMut(&'a str) -> IResult<&'a str, (&'a str, O), ErrorContext<&'a str>> { + end: impl FnMut(&'a str) -> ParseResult<'a, O>, +) -> impl FnMut(&'a str) -> ParseResult<'a, (&'a str, O)> { enum Next { IsEnd(O), NotEnd(char), @@ -214,10 +216,8 @@ fn skip_till<'a, O>( } } -fn keyword<'a>( - k: &'a str, -) -> impl FnMut(&'a str) -> IResult<&'a str, &'a str, ErrorContext<&'a str>> { - move |i: &'a str| -> IResult<&'a str, &'a str, ErrorContext<&'a str>> { +fn keyword<'a>(k: &'a str) -> impl FnMut(&'a str) -> ParseResult<'_> { + move |i: &'a str| -> ParseResult<'a> { let (j, v) = identifier(i)?; if k == v { Ok((j, v)) @@ -227,15 +227,15 @@ fn keyword<'a>( } } -fn identifier(input: &str) -> IResult<&str, &str, ErrorContext<&str>> { - fn start(s: &str) -> IResult<&str, &str, ErrorContext<&str>> { +fn identifier(input: &str) -> ParseResult<'_> { + fn start(s: &str) -> ParseResult<'_> { s.split_at_position1_complete( |c| !(c.is_alpha() || c == '_' || c >= '\u{0080}'), nom::error::ErrorKind::Alpha, ) } - fn tail(s: &str) -> IResult<&str, &str, ErrorContext<&str>> { + fn tail(s: &str) -> ParseResult<'_> { s.split_at_position1_complete( |c| !(c.is_alphanum() || c == '_' || c >= '\u{0080}'), nom::error::ErrorKind::Alpha, @@ -245,11 +245,11 @@ fn identifier(input: &str) -> IResult<&str, &str, ErrorContext<&str>> { recognize(pair(start, opt(tail)))(input) } -fn bool_lit(i: &str) -> IResult<&str, &str, ErrorContext<&str>> { +fn bool_lit(i: &str) -> ParseResult<'_> { alt((keyword("false"), keyword("true")))(i) } -fn num_lit(i: &str) -> IResult<&str, &str, ErrorContext<&str>> { +fn num_lit(i: &str) -> ParseResult<'_> { recognize(tuple(( opt(char('-')), digit1, @@ -257,7 +257,7 @@ fn num_lit(i: &str) -> IResult<&str, &str, ErrorContext<&str>> { )))(i) } -fn str_lit(i: &str) -> IResult<&str, &str, ErrorContext<&str>> { +fn str_lit(i: &str) -> ParseResult<'_> { let (i, s) = delimited( char('"'), opt(escaped(is_not("\\\""), '\\', anychar)), @@ -266,7 +266,7 @@ fn str_lit(i: &str) -> IResult<&str, &str, ErrorContext<&str>> { Ok((i, s.unwrap_or_default())) } -fn char_lit(i: &str) -> IResult<&str, &str, ErrorContext<&str>> { +fn char_lit(i: &str) -> ParseResult<'_> { let (i, s) = delimited( char('\''), opt(escaped(is_not("\\\'"), '\\', anychar)), @@ -280,7 +280,7 @@ enum PathOrIdentifier<'a> { Identifier(&'a str), } -fn path_or_identifier(i: &str) -> IResult<&str, PathOrIdentifier<'_>, ErrorContext<&str>> { +fn path_or_identifier(i: &str) -> ParseResult<'_, PathOrIdentifier<'_>> { let root = ws(opt(tag("::"))); let tail = opt(many1(preceded(ws(tag("::")), identifier))); @@ -326,7 +326,7 @@ impl<'a> State<'a> { } } - fn nest<'b>(&self, i: &'b str) -> IResult<&'b str, (), ErrorContext<&'b str>> { + fn nest<'b>(&self, i: &'b str) -> ParseResult<'b, ()> { let (_, level) = self.level.get().nest(i)?; self.level.set(level); Ok((i, ())) @@ -336,30 +336,27 @@ impl<'a> State<'a> { self.level.set(self.level.get().leave()); } - fn tag_block_start<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str, ErrorContext<&'i str>> { + fn tag_block_start<'i>(&self, i: &'i str) -> ParseResult<'i> { tag(self.syntax.block_start)(i) } - fn tag_block_end<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str, ErrorContext<&'i str>> { + fn tag_block_end<'i>(&self, i: &'i str) -> ParseResult<'i> { tag(self.syntax.block_end)(i) } - fn tag_comment_start<'i>( - &self, - i: &'i str, - ) -> IResult<&'i str, &'i str, ErrorContext<&'i str>> { + fn tag_comment_start<'i>(&self, i: &'i str) -> ParseResult<'i> { tag(self.syntax.comment_start)(i) } - fn tag_comment_end<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str, ErrorContext<&'i str>> { + fn tag_comment_end<'i>(&self, i: &'i str) -> ParseResult<'i> { tag(self.syntax.comment_end)(i) } - fn tag_expr_start<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str, ErrorContext<&'i str>> { + fn tag_expr_start<'i>(&self, i: &'i str) -> ParseResult<'i> { tag(self.syntax.expr_start)(i) } - fn tag_expr_end<'i>(&self, i: &'i str) -> IResult<&'i str, &'i str, ErrorContext<&'i str>> { + fn tag_expr_end<'i>(&self, i: &'i str) -> ParseResult<'i> { tag(self.syntax.expr_end)(i) } @@ -403,7 +400,7 @@ impl Default for Syntax<'static> { pub(crate) struct Level(u8); impl Level { - fn nest(self, i: &str) -> IResult<&str, Level, ErrorContext<&str>> { + fn nest(self, i: &str) -> ParseResult<'_, Level> { if self.0 >= Self::MAX_DEPTH { return Err(ErrorContext::from_err(nom::Err::Failure(error_position!( i, diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs index dc05f48b0..69703e626 100644 --- a/askama_parser/src/node.rs +++ b/askama_parser/src/node.rs @@ -8,11 +8,11 @@ use nom::combinator::{ complete, consumed, cut, eof, map, map_res, not, opt, peek, recognize, value, }; use nom::error::{Error, ErrorKind}; +use nom::error_position; use nom::multi::{fold_many0, many0, many1, separated_list0, separated_list1}; use nom::sequence::{delimited, pair, preceded, terminated, tuple}; -use nom::{error_position, IResult}; -use crate::ErrorContext; +use crate::{ErrorContext, ParseResult}; use super::{ bool_lit, char_lit, identifier, is_ws, keyword, num_lit, path_or_identifier, skip_till, @@ -40,10 +40,7 @@ pub enum Node<'a> { } impl<'a> Node<'a> { - pub(super) fn many( - i: &'a str, - s: &State<'_>, - ) -> IResult<&'a str, Vec, ErrorContext<&'a str>> { + pub(super) fn many(i: &'a str, s: &State<'_>) -> ParseResult<'a, Vec> { complete(many0(alt(( map(|i| Lit::parse(i, s), Self::Lit), map(|i| Comment::parse(i, s), Self::Comment), @@ -52,7 +49,7 @@ impl<'a> Node<'a> { ))))(i) } - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = delimited( |i| s.tag_block_start(i), alt(( @@ -80,7 +77,7 @@ impl<'a> Node<'a> { result } - fn r#break(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn r#break(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = tuple(( opt(Whitespace::parse), ws(keyword("break")), @@ -93,7 +90,7 @@ impl<'a> Node<'a> { Ok((j, Self::Break(Ws(pws, nws)))) } - fn r#continue(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn r#continue(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = tuple(( opt(Whitespace::parse), ws(keyword("continue")), @@ -106,7 +103,7 @@ impl<'a> Node<'a> { Ok((j, Self::Continue(Ws(pws, nws)))) } - fn expr(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn expr(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = tuple(( |i| s.tag_expr_start(i), cut(tuple(( @@ -134,7 +131,7 @@ pub enum Target<'a> { } impl<'a> Target<'a> { - pub(super) fn parse(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + pub(super) fn parse(i: &'a str) -> ParseResult<'a, Self> { let mut opt_opening_paren = map(opt(ws(char('('))), |o| o.is_some()); let mut opt_closing_paren = map(opt(ws(char(')'))), |o| o.is_some()); let mut opt_opening_brace = map(opt(ws(char('{'))), |o| o.is_some()); @@ -217,7 +214,7 @@ impl<'a> Target<'a> { map(identifier, Self::Name)(i) } - fn lit(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn lit(i: &'a str) -> ParseResult<'a, Self> { alt(( map(str_lit, Self::StrLit), map(char_lit, Self::CharLit), @@ -226,7 +223,7 @@ impl<'a> Target<'a> { ))(i) } - fn named(i: &'a str) -> IResult<&str, (&str, Self), ErrorContext<&'a str>> { + fn named(i: &'a str) -> ParseResult<'a, (&str, Self)> { let (i, (src, target)) = pair(identifier, opt(preceded(ws(char(':')), Self::parse)))(i)?; Ok((i, (src, target.unwrap_or(Self::Name(src))))) } @@ -240,7 +237,7 @@ pub struct When<'a> { } impl<'a> When<'a> { - fn r#match(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn r#match(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), @@ -263,7 +260,7 @@ impl<'a> When<'a> { } #[allow(clippy::self_named_constructors)] - fn when(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn when(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), @@ -295,7 +292,7 @@ pub struct Cond<'a> { } impl<'a> Cond<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), @@ -334,7 +331,7 @@ pub struct CondTest<'a> { } impl<'a> CondTest<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = preceded( ws(keyword("if")), cut(tuple(( @@ -359,7 +356,7 @@ pub enum Whitespace { } impl Whitespace { - fn parse(i: &str) -> IResult<&str, Self, ErrorContext<&str>> { + fn parse(i: &str) -> ParseResult<'_, Self> { alt(( value(Self::Preserve, char('+')), value(Self::Suppress, char('-')), @@ -381,11 +378,8 @@ pub struct Loop<'a> { } impl<'a> Loop<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { - fn content<'a>( - i: &'a str, - s: &State<'_>, - ) -> IResult<&'a str, Vec>, ErrorContext<&'a str>> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { + fn content<'a>(i: &'a str, s: &State<'_>) -> ParseResult<'a, Vec>> { s.enter_loop(); let result = Node::many(i, s); s.leave_loop(); @@ -465,8 +459,8 @@ pub struct Macro<'a> { } impl<'a> Macro<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { - fn parameters(i: &str) -> IResult<&str, Vec<&str>, ErrorContext<&str>> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { + fn parameters(i: &str) -> ParseResult<'_, Vec<&str>> { delimited( ws(char('(')), separated_list0(char(','), ws(identifier)), @@ -532,7 +526,7 @@ pub struct Import<'a> { } impl<'a> Import<'a> { - fn parse(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn parse(i: &'a str) -> ParseResult<'a, Self> { let mut p = tuple(( opt(Whitespace::parse), ws(keyword("import")), @@ -563,7 +557,7 @@ pub struct Call<'a> { } impl<'a> Call<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = tuple(( opt(Whitespace::parse), ws(keyword("call")), @@ -598,7 +592,7 @@ pub struct Match<'a> { } impl<'a> Match<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = tuple(( opt(Whitespace::parse), ws(keyword("match")), @@ -649,7 +643,7 @@ pub struct BlockDef<'a> { } impl<'a> BlockDef<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut start = tuple(( opt(Whitespace::parse), ws(keyword("block")), @@ -694,7 +688,7 @@ fn check_end_name<'a>( name: &'a str, end_name: &'a str, kind: &str, -) -> IResult<&'a str, &'a str, ErrorContext<&'a str>> { +) -> ParseResult<'a> { if name == end_name { return Ok((after, end_name)); } @@ -717,7 +711,7 @@ pub struct Lit<'a> { } impl<'a> Lit<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let p_start = alt(( tag(s.syntax.block_start), tag(s.syntax.comment_start), @@ -757,7 +751,7 @@ pub struct Raw<'a> { } impl<'a> Raw<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let endraw = tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), @@ -792,7 +786,7 @@ pub struct Let<'a> { } impl<'a> Let<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = tuple(( opt(Whitespace::parse), ws(alt((keyword("let"), keyword("set")))), @@ -825,7 +819,7 @@ pub struct If<'a> { } impl<'a> If<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = tuple(( opt(Whitespace::parse), |i| CondTest::parse(i, s), @@ -870,7 +864,7 @@ pub struct Include<'a> { } impl<'a> Include<'a> { - fn parse(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn parse(i: &'a str) -> ParseResult<'a, Self> { let mut p = tuple(( opt(Whitespace::parse), ws(keyword("include")), @@ -893,7 +887,7 @@ pub struct Extends<'a> { } impl<'a> Extends<'a> { - fn parse(i: &'a str) -> IResult<&'a str, Self, ErrorContext<&'a str>> { + fn parse(i: &'a str) -> ParseResult<'a, Self> { let (i, path) = preceded(ws(keyword("extends")), cut(ws(str_lit)))(i)?; Ok((i, Self { path })) } @@ -906,15 +900,12 @@ pub struct Comment<'a> { } impl<'a> Comment<'a> { - fn parse(i: &'a str, s: &State<'_>) -> IResult<&'a str, Self, ErrorContext<&'a str>> { - fn body<'a>( - mut i: &'a str, - s: &State<'_>, - ) -> IResult<&'a str, &'a str, ErrorContext<&'a str>> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { + fn body<'a>(mut i: &'a str, s: &State<'_>) -> ParseResult<'a> { let mut level = 0; loop { let (end, tail) = take_until(s.syntax.comment_end)(i)?; - match take_until::<_, _, ErrorContext<_>>(s.syntax.comment_start)(i) { + match take_until::<_, _, ErrorContext<'_>>(s.syntax.comment_start)(i) { Ok((start, _)) if start.as_ptr() < end.as_ptr() => { level += 1; i = &start[2..]; From 40bb3382b0c98e97f484c81659207b6b950c5cb0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Oct 2023 04:16:04 +0000 Subject: [PATCH 31/44] build(deps): update futures-lite requirement from 1.12.0 to 2.0.0 Updates the requirements on [futures-lite](https://github.com/smol-rs/futures-lite) to permit the latest version. - [Release notes](https://github.com/smol-rs/futures-lite/releases) - [Changelog](https://github.com/smol-rs/futures-lite/blob/master/CHANGELOG.md) - [Commits](https://github.com/smol-rs/futures-lite/compare/v1.12.0...v2.0.0) --- updated-dependencies: - dependency-name: futures-lite dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- askama_rocket/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/askama_rocket/Cargo.toml b/askama_rocket/Cargo.toml index 0496a340d..9495d1ba0 100644 --- a/askama_rocket/Cargo.toml +++ b/askama_rocket/Cargo.toml @@ -18,7 +18,7 @@ askama = { version = "0.12", path = "../askama", default-features = false, featu rocket = { version = "0.5.0-rc.3", default-features = false } [dev-dependencies] -futures-lite = "1.12.0" +futures-lite = "2.0.0" [features] default = ["askama/default"] From 94d3bc1dc039f26fb157e50215dcf139078ab231 Mon Sep 17 00:00:00 2001 From: Mo <76752051+mo8it@users.noreply.github.com> Date: Sun, 29 Oct 2023 16:28:53 +0100 Subject: [PATCH 32/44] Improve the macros section --- book/src/template_syntax.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/book/src/template_syntax.md b/book/src/template_syntax.md index 0b76b22b2..9716369a3 100644 --- a/book/src/template_syntax.md +++ b/book/src/template_syntax.md @@ -444,9 +444,9 @@ struct Item<'a> { ## Macros -You can define macros within your template by using `{% macro name(args) %}`, ending with `{% endmacro %}` +You can define macros within your template by using `{% macro name(args) %}`, ending with `{% endmacro %}`. -You can then call it later with `{% call name(args) %}` +You can then call it with `{% call name(args) %}`: ``` {% macro heading(arg) %} @@ -458,7 +458,7 @@ You can then call it later with `{% call name(args) %}` {% call heading(s) %} ``` -You can place templates in a separate file and use it in your templates by using `{% import %}` +You can place macros in a separate file and use them in your templates by using `{% import %}`: ``` {%- import "macro.html" as scope -%} @@ -466,8 +466,7 @@ You can place templates in a separate file and use it in your templates by using {% call scope::heading(s) %} ``` -It is also possible to use the name of the `macro` in `endmacro` in the -declaration: +You can optionally specify the name of the macro in `endmacro`: ```html {% macro heading(arg) %}

{{arg}}

{% endmacro heading %} From 145420b68ebe6d310a81c8965f9a9e81aa4dbfe1 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 6 Oct 2023 11:30:37 +0200 Subject: [PATCH 33/44] Inline nested derive_template() implementation --- askama_derive/src/generator.rs | 12 +----------- askama_derive/src/lib.rs | 6 +++++- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 6f561f83b..cb4b10290 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -7,7 +7,6 @@ use parser::node::{ Call, Comment, CondTest, If, Include, Let, Lit, Loop, Match, Target, Whitespace, Ws, }; use parser::{Expr, Node, Parsed}; -use proc_macro::TokenStream; use quote::{quote, ToTokens}; use syn::punctuated::Punctuated; @@ -15,15 +14,6 @@ use std::collections::hash_map::{Entry, HashMap}; use std::path::{Path, PathBuf}; use std::{cmp, hash, mem, str}; -/// The actual implementation for askama_derive::Template -pub(crate) fn derive_template(input: TokenStream) -> TokenStream { - let ast: syn::DeriveInput = syn::parse(input).unwrap(); - match build_template(&ast) { - Ok(source) => source.parse().unwrap(), - Err(e) => e.into_compile_error(), - } -} - /// Takes a `syn::DeriveInput` and generates source code for it /// /// Reads the metadata from the `template()` attribute to get the template @@ -31,7 +21,7 @@ pub(crate) fn derive_template(input: TokenStream) -> TokenStream { /// parsed, and the parse tree is fed to the code generator. Will print /// the parse tree and/or generated source according to the `print` key's /// value as passed to the `template()` attribute. -fn build_template(ast: &syn::DeriveInput) -> Result { +pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result { let template_args = TemplateArgs::new(ast)?; let config_toml = read_config_file(template_args.config_path.as_deref())?; let config = Config::new(&config_toml, template_args.whitespace.as_ref())?; diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index 8a737aa9a..3b8063559 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -16,7 +16,11 @@ mod input; #[proc_macro_derive(Template, attributes(template))] pub fn derive_template(input: TokenStream) -> TokenStream { - generator::derive_template(input) + let ast = syn::parse::(input).unwrap(); + match generator::build_template(&ast) { + Ok(source) => source.parse().unwrap(), + Err(e) => e.into_compile_error(), + } } #[derive(Debug, Clone)] From b9e2187cd8b4a335113c966c0e9b53f3726e59d3 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 6 Oct 2023 11:33:06 +0200 Subject: [PATCH 34/44] Move TemplateArgs into input module --- askama_derive/src/generator.rs | 131 +------------------------------ askama_derive/src/input.rs | 137 +++++++++++++++++++++++++++++++-- 2 files changed, 134 insertions(+), 134 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index cb4b10290..3374161f5 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -1,14 +1,13 @@ use crate::config::{get_template_source, read_config_file, Config, WhitespaceHandling}; use crate::heritage::{Context, Heritage}; -use crate::input::{Print, Source, TemplateInput}; +use crate::input::{Print, Source, TemplateArgs, TemplateInput}; use crate::CompileError; use parser::node::{ Call, Comment, CondTest, If, Include, Let, Lit, Loop, Match, Target, Whitespace, Ws, }; use parser::{Expr, Node, Parsed}; -use quote::{quote, ToTokens}; -use syn::punctuated::Punctuated; +use quote::quote; use std::collections::hash_map::{Entry, HashMap}; use std::path::{Path, PathBuf}; @@ -67,132 +66,6 @@ pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result, - pub(crate) print: Print, - pub(crate) escaping: Option, - pub(crate) ext: Option, - pub(crate) syntax: Option, - pub(crate) config_path: Option, - pub(crate) whitespace: Option, -} - -impl TemplateArgs { - fn new(ast: &'_ syn::DeriveInput) -> Result { - // Check that an attribute called `template()` exists once and that it is - // the proper type (list). - let mut template_args = None; - for attr in &ast.attrs { - if !attr.path().is_ident("template") { - continue; - } - - match attr.parse_args_with(Punctuated::::parse_terminated) { - Ok(args) if template_args.is_none() => template_args = Some(args), - Ok(_) => return Err("duplicated 'template' attribute".into()), - Err(e) => return Err(format!("unable to parse template arguments: {e}").into()), - }; - } - - let template_args = - template_args.ok_or_else(|| CompileError::from("no attribute 'template' found"))?; - - let mut args = Self::default(); - // Loop over the meta attributes and find everything that we - // understand. Return a CompileError if something is not right. - // `source` contains an enum that can represent `path` or `source`. - for item in template_args { - let pair = match item { - syn::Meta::NameValue(pair) => pair, - _ => { - return Err(format!( - "unsupported attribute argument {:?}", - item.to_token_stream() - ) - .into()) - } - }; - - let ident = match pair.path.get_ident() { - Some(ident) => ident, - None => unreachable!("not possible in syn::Meta::NameValue(…)"), - }; - - let value = match pair.value { - syn::Expr::Lit(lit) => lit, - syn::Expr::Group(group) => match *group.expr { - syn::Expr::Lit(lit) => lit, - _ => { - return Err(format!("unsupported argument value type for {ident:?}").into()) - } - }, - _ => return Err(format!("unsupported argument value type for {ident:?}").into()), - }; - - if ident == "path" { - if let syn::Lit::Str(s) = value.lit { - if args.source.is_some() { - return Err("must specify 'source' or 'path', not both".into()); - } - args.source = Some(Source::Path(s.value())); - } else { - return Err("template path must be string literal".into()); - } - } else if ident == "source" { - if let syn::Lit::Str(s) = value.lit { - if args.source.is_some() { - return Err("must specify 'source' or 'path', not both".into()); - } - args.source = Some(Source::Source(s.value())); - } else { - return Err("template source must be string literal".into()); - } - } else if ident == "print" { - if let syn::Lit::Str(s) = value.lit { - args.print = s.value().parse()?; - } else { - return Err("print value must be string literal".into()); - } - } else if ident == "escape" { - if let syn::Lit::Str(s) = value.lit { - args.escaping = Some(s.value()); - } else { - return Err("escape value must be string literal".into()); - } - } else if ident == "ext" { - if let syn::Lit::Str(s) = value.lit { - args.ext = Some(s.value()); - } else { - return Err("ext value must be string literal".into()); - } - } else if ident == "syntax" { - if let syn::Lit::Str(s) = value.lit { - args.syntax = Some(s.value()) - } else { - return Err("syntax value must be string literal".into()); - } - } else if ident == "config" { - if let syn::Lit::Str(s) = value.lit { - args.config_path = Some(s.value()) - } else { - return Err("config value must be string literal".into()); - } - } else if ident == "whitespace" { - if let syn::Lit::Str(s) = value.lit { - args.whitespace = Some(s.value()) - } else { - return Err("whitespace value must be string literal".into()); - } - } else { - return Err(format!("unsupported attribute key {ident:?} found").into()); - } - } - - Ok(args) - } -} - fn find_used_templates( input: &TemplateInput<'_>, map: &mut HashMap, diff --git a/askama_derive/src/input.rs b/askama_derive/src/input.rs index d4e8ad920..59770580e 100644 --- a/askama_derive/src/input.rs +++ b/askama_derive/src/input.rs @@ -1,12 +1,13 @@ -use crate::config::Config; -use crate::generator::TemplateArgs; -use crate::CompileError; -use parser::Syntax; - use std::path::{Path, PathBuf}; use std::str::FromStr; use mime::Mime; +use quote::ToTokens; +use syn::punctuated::Punctuated; + +use crate::config::Config; +use crate::CompileError; +use parser::Syntax; pub(crate) struct TemplateInput<'a> { pub(crate) ast: &'a syn::DeriveInput, @@ -105,6 +106,132 @@ impl TemplateInput<'_> { } } +#[derive(Default)] +pub(crate) struct TemplateArgs { + pub(crate) source: Option, + pub(crate) print: Print, + pub(crate) escaping: Option, + pub(crate) ext: Option, + pub(crate) syntax: Option, + pub(crate) config_path: Option, + pub(crate) whitespace: Option, +} + +impl TemplateArgs { + pub(crate) fn new(ast: &'_ syn::DeriveInput) -> Result { + // Check that an attribute called `template()` exists once and that it is + // the proper type (list). + let mut template_args = None; + for attr in &ast.attrs { + if !attr.path().is_ident("template") { + continue; + } + + match attr.parse_args_with(Punctuated::::parse_terminated) { + Ok(args) if template_args.is_none() => template_args = Some(args), + Ok(_) => return Err("duplicated 'template' attribute".into()), + Err(e) => return Err(format!("unable to parse template arguments: {e}").into()), + }; + } + + let template_args = + template_args.ok_or_else(|| CompileError::from("no attribute 'template' found"))?; + + let mut args = Self::default(); + // Loop over the meta attributes and find everything that we + // understand. Return a CompileError if something is not right. + // `source` contains an enum that can represent `path` or `source`. + for item in template_args { + let pair = match item { + syn::Meta::NameValue(pair) => pair, + _ => { + return Err(format!( + "unsupported attribute argument {:?}", + item.to_token_stream() + ) + .into()) + } + }; + + let ident = match pair.path.get_ident() { + Some(ident) => ident, + None => unreachable!("not possible in syn::Meta::NameValue(…)"), + }; + + let value = match pair.value { + syn::Expr::Lit(lit) => lit, + syn::Expr::Group(group) => match *group.expr { + syn::Expr::Lit(lit) => lit, + _ => { + return Err(format!("unsupported argument value type for {ident:?}").into()) + } + }, + _ => return Err(format!("unsupported argument value type for {ident:?}").into()), + }; + + if ident == "path" { + if let syn::Lit::Str(s) = value.lit { + if args.source.is_some() { + return Err("must specify 'source' or 'path', not both".into()); + } + args.source = Some(Source::Path(s.value())); + } else { + return Err("template path must be string literal".into()); + } + } else if ident == "source" { + if let syn::Lit::Str(s) = value.lit { + if args.source.is_some() { + return Err("must specify 'source' or 'path', not both".into()); + } + args.source = Some(Source::Source(s.value())); + } else { + return Err("template source must be string literal".into()); + } + } else if ident == "print" { + if let syn::Lit::Str(s) = value.lit { + args.print = s.value().parse()?; + } else { + return Err("print value must be string literal".into()); + } + } else if ident == "escape" { + if let syn::Lit::Str(s) = value.lit { + args.escaping = Some(s.value()); + } else { + return Err("escape value must be string literal".into()); + } + } else if ident == "ext" { + if let syn::Lit::Str(s) = value.lit { + args.ext = Some(s.value()); + } else { + return Err("ext value must be string literal".into()); + } + } else if ident == "syntax" { + if let syn::Lit::Str(s) = value.lit { + args.syntax = Some(s.value()) + } else { + return Err("syntax value must be string literal".into()); + } + } else if ident == "config" { + if let syn::Lit::Str(s) = value.lit { + args.config_path = Some(s.value()) + } else { + return Err("config value must be string literal".into()); + } + } else if ident == "whitespace" { + if let syn::Lit::Str(s) = value.lit { + args.whitespace = Some(s.value()) + } else { + return Err("whitespace value must be string literal".into()); + } + } else { + return Err(format!("unsupported attribute key {ident:?} found").into()); + } + } + + Ok(args) + } +} + #[inline] fn ext_default_to_path<'a>(ext: Option<&'a str>, path: &'a Path) -> Option<&'a str> { ext.or_else(|| extension(path)) From 7979d5c1dd790d1821c9c4c77c848f47ac49c769 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 6 Oct 2023 11:36:59 +0200 Subject: [PATCH 35/44] Attach find_used_templates() to TemplateInput --- askama_derive/src/generator.rs | 43 +------------------------------ askama_derive/src/input.rs | 46 ++++++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 44 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 3374161f5..21b8d2588 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -31,7 +31,7 @@ pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result Result, - map: &mut HashMap, - source: String, -) -> Result<(), CompileError> { - let mut dependency_graph = Vec::new(); - let mut check = vec![(input.path.clone(), source)]; - while let Some((path, source)) = check.pop() { - let parsed = Parsed::new(source, input.syntax)?; - for n in parsed.nodes() { - match n { - Node::Extends(extends) => { - let extends = input.config.find_template(extends.path, Some(&path))?; - let dependency_path = (path.clone(), extends.clone()); - if dependency_graph.contains(&dependency_path) { - return Err(format!( - "cyclic dependency in graph {:#?}", - dependency_graph - .iter() - .map(|e| format!("{:#?} --> {:#?}", e.0, e.1)) - .collect::>() - ) - .into()); - } - dependency_graph.push(dependency_path); - let source = get_template_source(&extends)?; - check.push((extends, source)); - } - Node::Import(import) => { - let import = input.config.find_template(import.path, Some(&path))?; - let source = get_template_source(&import)?; - check.push((import, source)); - } - _ => {} - } - } - map.insert(path, parsed); - } - Ok(()) -} - struct Generator<'a> { // The template input state: original struct AST and attributes input: &'a TemplateInput<'a>, diff --git a/askama_derive/src/input.rs b/askama_derive/src/input.rs index 59770580e..03817f1cf 100644 --- a/askama_derive/src/input.rs +++ b/askama_derive/src/input.rs @@ -1,3 +1,4 @@ +use std::collections::hash_map::HashMap; use std::path::{Path, PathBuf}; use std::str::FromStr; @@ -5,9 +6,9 @@ use mime::Mime; use quote::ToTokens; use syn::punctuated::Punctuated; -use crate::config::Config; +use crate::config::{get_template_source, Config}; use crate::CompileError; -use parser::Syntax; +use parser::{Node, Parsed, Syntax}; pub(crate) struct TemplateInput<'a> { pub(crate) ast: &'a syn::DeriveInput, @@ -100,6 +101,47 @@ impl TemplateInput<'_> { }) } + pub(crate) fn find_used_templates( + &self, + map: &mut HashMap, + source: String, + ) -> Result<(), CompileError> { + let mut dependency_graph = Vec::new(); + let mut check = vec![(self.path.clone(), source)]; + while let Some((path, source)) = check.pop() { + let parsed = Parsed::new(source, self.syntax)?; + for n in parsed.nodes() { + match n { + Node::Extends(extends) => { + let extends = self.config.find_template(extends.path, Some(&path))?; + let dependency_path = (path.clone(), extends.clone()); + if dependency_graph.contains(&dependency_path) { + return Err(format!( + "cyclic dependency in graph {:#?}", + dependency_graph + .iter() + .map(|e| format!("{:#?} --> {:#?}", e.0, e.1)) + .collect::>() + ) + .into()); + } + dependency_graph.push(dependency_path); + let source = get_template_source(&extends)?; + check.push((extends, source)); + } + Node::Import(import) => { + let import = self.config.find_template(import.path, Some(&path))?; + let source = get_template_source(&import)?; + check.push((import, source)); + } + _ => {} + } + } + map.insert(path, parsed); + } + Ok(()) + } + #[inline] pub(crate) fn extension(&self) -> Option<&str> { ext_default_to_path(self.ext.as_deref(), &self.path) From a7d879311611599594a5e7048eeeb70efcc5bb6b Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 6 Oct 2023 11:39:16 +0200 Subject: [PATCH 36/44] Remove unnecessary type annotation --- askama_derive/src/generator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 21b8d2588..d4d86d0d1 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -25,7 +25,7 @@ pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result s.clone(), Source::Path(_) => get_template_source(&input.path)?, }; From 1782b63caa092b0f81d15dfcbb7063ed363dba60 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 6 Oct 2023 11:45:22 +0200 Subject: [PATCH 37/44] Move build_template() up to the crate root --- askama_derive/src/generator.rs | 69 ++++------------------------------ askama_derive/src/lib.rs | 61 +++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 63 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index d4d86d0d1..74d18694b 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -1,6 +1,6 @@ -use crate::config::{get_template_source, read_config_file, Config, WhitespaceHandling}; +use crate::config::{get_template_source, WhitespaceHandling}; use crate::heritage::{Context, Heritage}; -use crate::input::{Print, Source, TemplateArgs, TemplateInput}; +use crate::input::{Source, TemplateInput}; use crate::CompileError; use parser::node::{ @@ -13,60 +13,7 @@ use std::collections::hash_map::{Entry, HashMap}; use std::path::{Path, PathBuf}; use std::{cmp, hash, mem, str}; -/// Takes a `syn::DeriveInput` and generates source code for it -/// -/// Reads the metadata from the `template()` attribute to get the template -/// metadata, then fetches the source from the filesystem. The source is -/// parsed, and the parse tree is fed to the code generator. Will print -/// the parse tree and/or generated source according to the `print` key's -/// value as passed to the `template()` attribute. -pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result { - let template_args = TemplateArgs::new(ast)?; - let config_toml = read_config_file(template_args.config_path.as_deref())?; - let config = Config::new(&config_toml, template_args.whitespace.as_ref())?; - let input = TemplateInput::new(ast, &config, template_args)?; - let source = match input.source { - Source::Source(ref s) => s.clone(), - Source::Path(_) => get_template_source(&input.path)?, - }; - - let mut templates = HashMap::new(); - input.find_used_templates(&mut templates, source)?; - - let mut contexts = HashMap::new(); - for (path, parsed) in &templates { - contexts.insert( - path.as_path(), - Context::new(input.config, path, parsed.nodes())?, - ); - } - - let ctx = &contexts[input.path.as_path()]; - let heritage = if !ctx.blocks.is_empty() || ctx.extends.is_some() { - Some(Heritage::new(ctx, &contexts)) - } else { - None - }; - - if input.print == Print::Ast || input.print == Print::All { - eprintln!("{:?}", templates[input.path.as_path()].nodes()); - } - - let code = Generator::new( - &input, - &contexts, - heritage.as_ref(), - MapChain::new(), - config.whitespace, - ) - .build(&contexts[input.path.as_path()])?; - if input.print == Print::Code || input.print == Print::All { - eprintln!("{code}"); - } - Ok(code) -} - -struct Generator<'a> { +pub(crate) struct Generator<'a> { // The template input state: original struct AST and attributes input: &'a TemplateInput<'a>, // All contexts, keyed by the package-relative template path @@ -96,7 +43,7 @@ struct Generator<'a> { } impl<'a> Generator<'a> { - fn new<'n>( + pub(crate) fn new<'n>( input: &'n TemplateInput<'_>, contexts: &'n HashMap<&'n Path, Context<'n>>, heritage: Option<&'n Heritage<'_>>, @@ -119,7 +66,7 @@ impl<'a> Generator<'a> { } // Takes a Context and generates the relevant implementations. - fn build(mut self, ctx: &'a Context<'_>) -> Result { + pub(crate) fn build(mut self, ctx: &'a Context<'_>) -> Result { let mut buf = Buffer::new(0); self.impl_template(ctx, &mut buf)?; @@ -1792,7 +1739,7 @@ impl Buffer { } #[derive(Clone, Default)] -struct LocalMeta { +pub(crate) struct LocalMeta { refs: Option, initialized: bool, } @@ -1816,7 +1763,7 @@ impl LocalMeta { // type SetChain<'a, T> = MapChain<'a, T, ()>; #[derive(Debug)] -struct MapChain<'a, K, V> +pub(crate) struct MapChain<'a, K, V> where K: cmp::Eq + hash::Hash, { @@ -1828,7 +1775,7 @@ impl<'a, K: 'a, V: 'a> MapChain<'a, K, V> where K: cmp::Eq + hash::Hash, { - fn new() -> MapChain<'a, K, V> { + pub(crate) fn new() -> MapChain<'a, K, V> { MapChain { parent: None, scopes: vec![HashMap::new()], diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index 3b8063559..dc8ca7549 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -1,8 +1,8 @@ #![deny(elided_lifetimes_in_paths)] #![deny(unreachable_pub)] -use std::borrow::Cow; use std::fmt; +use std::{borrow::Cow, collections::HashMap}; use proc_macro::TokenStream; use proc_macro2::Span; @@ -10,19 +10,76 @@ use proc_macro2::Span; use parser::ParseError; mod config; +use config::{get_template_source, read_config_file, Config}; mod generator; +use generator::{Generator, MapChain}; mod heritage; +use heritage::{Context, Heritage}; mod input; +use input::{Print, Source, TemplateArgs, TemplateInput}; #[proc_macro_derive(Template, attributes(template))] pub fn derive_template(input: TokenStream) -> TokenStream { let ast = syn::parse::(input).unwrap(); - match generator::build_template(&ast) { + match build_template(&ast) { Ok(source) => source.parse().unwrap(), Err(e) => e.into_compile_error(), } } +/// Takes a `syn::DeriveInput` and generates source code for it +/// +/// Reads the metadata from the `template()` attribute to get the template +/// metadata, then fetches the source from the filesystem. The source is +/// parsed, and the parse tree is fed to the code generator. Will print +/// the parse tree and/or generated source according to the `print` key's +/// value as passed to the `template()` attribute. +pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result { + let template_args = TemplateArgs::new(ast)?; + let config_toml = read_config_file(template_args.config_path.as_deref())?; + let config = Config::new(&config_toml, template_args.whitespace.as_ref())?; + let input = TemplateInput::new(ast, &config, template_args)?; + let source = match input.source { + Source::Source(ref s) => s.clone(), + Source::Path(_) => get_template_source(&input.path)?, + }; + + let mut templates = HashMap::new(); + input.find_used_templates(&mut templates, source)?; + + let mut contexts = HashMap::new(); + for (path, parsed) in &templates { + contexts.insert( + path.as_path(), + Context::new(input.config, path, parsed.nodes())?, + ); + } + + let ctx = &contexts[input.path.as_path()]; + let heritage = if !ctx.blocks.is_empty() || ctx.extends.is_some() { + Some(Heritage::new(ctx, &contexts)) + } else { + None + }; + + if input.print == Print::Ast || input.print == Print::All { + eprintln!("{:?}", templates[input.path.as_path()].nodes()); + } + + let code = Generator::new( + &input, + &contexts, + heritage.as_ref(), + MapChain::new(), + config.whitespace, + ) + .build(&contexts[input.path.as_path()])?; + if input.print == Print::Code || input.print == Print::All { + eprintln!("{code}"); + } + Ok(code) +} + #[derive(Debug, Clone)] struct CompileError { msg: Cow<'static, str>, From c951fcefcafe604136cf0263c6fd51716860b754 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 6 Oct 2023 11:48:22 +0200 Subject: [PATCH 38/44] Implement Default for MapChain --- askama_derive/src/generator.rs | 16 +++++++++------- askama_derive/src/lib.rs | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 74d18694b..8d120be41 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -1775,13 +1775,6 @@ impl<'a, K: 'a, V: 'a> MapChain<'a, K, V> where K: cmp::Eq + hash::Hash, { - pub(crate) fn new() -> MapChain<'a, K, V> { - MapChain { - parent: None, - scopes: vec![HashMap::new()], - } - } - fn with_parent<'p>(parent: &'p MapChain<'_, K, V>) -> MapChain<'p, K, V> { MapChain { parent: Some(parent), @@ -1844,6 +1837,15 @@ impl MapChain<'_, &str, LocalMeta> { } } +impl<'a, K: Eq + hash::Hash, V> Default for MapChain<'a, K, V> { + fn default() -> Self { + Self { + parent: None, + scopes: vec![HashMap::new()], + } + } +} + /// Returns `true` if enough assumptions can be made, /// to determine that `self` is copyable. fn is_copyable(expr: &Expr<'_>) -> bool { diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index dc8ca7549..37e8a3bb6 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -70,7 +70,7 @@ pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result Date: Fri, 6 Oct 2023 11:50:56 +0200 Subject: [PATCH 39/44] Cleanup imports a bit --- askama_derive/src/config.rs | 1 - askama_derive/src/generator.rs | 8 ++++---- askama_derive/src/heritage.rs | 1 - 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/askama_derive/src/config.rs b/askama_derive/src/config.rs index b2a13aa82..47801cdcb 100644 --- a/askama_derive/src/config.rs +++ b/askama_derive/src/config.rs @@ -6,7 +6,6 @@ use std::{env, fs}; use serde::Deserialize; use crate::CompileError; - use parser::node::Whitespace; use parser::Syntax; diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 8d120be41..929e18c1b 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -1,3 +1,7 @@ +use std::collections::hash_map::{Entry, HashMap}; +use std::path::{Path, PathBuf}; +use std::{cmp, hash, mem, str}; + use crate::config::{get_template_source, WhitespaceHandling}; use crate::heritage::{Context, Heritage}; use crate::input::{Source, TemplateInput}; @@ -9,10 +13,6 @@ use parser::node::{ use parser::{Expr, Node, Parsed}; use quote::quote; -use std::collections::hash_map::{Entry, HashMap}; -use std::path::{Path, PathBuf}; -use std::{cmp, hash, mem, str}; - pub(crate) struct Generator<'a> { // The template input state: original struct AST and attributes input: &'a TemplateInput<'a>, diff --git a/askama_derive/src/heritage.rs b/askama_derive/src/heritage.rs index ddf9b9949..d75d0a597 100644 --- a/askama_derive/src/heritage.rs +++ b/askama_derive/src/heritage.rs @@ -3,7 +3,6 @@ use std::path::{Path, PathBuf}; use crate::config::Config; use crate::CompileError; - use parser::node::{BlockDef, Macro, Match}; use parser::Node; From a057f81c051ae71329c5377b0351e6a09231c30d Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 6 Oct 2023 11:53:05 +0200 Subject: [PATCH 40/44] Inline some logic into find_used_templates() --- askama_derive/src/input.rs | 6 +++++- askama_derive/src/lib.rs | 10 +++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/askama_derive/src/input.rs b/askama_derive/src/input.rs index 03817f1cf..891dec7fc 100644 --- a/askama_derive/src/input.rs +++ b/askama_derive/src/input.rs @@ -104,8 +104,12 @@ impl TemplateInput<'_> { pub(crate) fn find_used_templates( &self, map: &mut HashMap, - source: String, ) -> Result<(), CompileError> { + let source = match &self.source { + Source::Source(s) => s.clone(), + Source::Path(_) => get_template_source(&self.path)?, + }; + let mut dependency_graph = Vec::new(); let mut check = vec![(self.path.clone(), source)]; while let Some((path, source)) = check.pop() { diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index 37e8a3bb6..3046f9dbe 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -10,13 +10,13 @@ use proc_macro2::Span; use parser::ParseError; mod config; -use config::{get_template_source, read_config_file, Config}; +use config::{read_config_file, Config}; mod generator; use generator::{Generator, MapChain}; mod heritage; use heritage::{Context, Heritage}; mod input; -use input::{Print, Source, TemplateArgs, TemplateInput}; +use input::{Print, TemplateArgs, TemplateInput}; #[proc_macro_derive(Template, attributes(template))] pub fn derive_template(input: TokenStream) -> TokenStream { @@ -39,13 +39,9 @@ pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result s.clone(), - Source::Path(_) => get_template_source(&input.path)?, - }; let mut templates = HashMap::new(); - input.find_used_templates(&mut templates, source)?; + input.find_used_templates(&mut templates)?; let mut contexts = HashMap::new(); for (path, parsed) in &templates { From 2a4d58cbb2033114890415c98a61e730185d1f83 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 6 Oct 2023 12:06:17 +0200 Subject: [PATCH 41/44] Reorganize TemplateArgs and TemplateInput structure --- askama_derive/src/input.rs | 42 +++++++++++++++++++------------------- askama_derive/src/lib.rs | 7 +++---- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/askama_derive/src/input.rs b/askama_derive/src/input.rs index 891dec7fc..45da7f025 100644 --- a/askama_derive/src/input.rs +++ b/askama_derive/src/input.rs @@ -6,7 +6,7 @@ use mime::Mime; use quote::ToTokens; use syn::punctuated::Punctuated; -use crate::config::{get_template_source, Config}; +use crate::config::{get_template_source, read_config_file, Config}; use crate::CompileError; use parser::{Node, Parsed, Syntax}; @@ -14,10 +14,10 @@ pub(crate) struct TemplateInput<'a> { pub(crate) ast: &'a syn::DeriveInput, pub(crate) config: &'a Config<'a>, pub(crate) syntax: &'a Syntax<'a>, - pub(crate) source: Source, + pub(crate) source: &'a Source, pub(crate) print: Print, pub(crate) escaper: &'a str, - pub(crate) ext: Option, + pub(crate) ext: Option<&'a str>, pub(crate) mime_type: String, pub(crate) path: PathBuf, } @@ -29,7 +29,7 @@ impl TemplateInput<'_> { pub(crate) fn new<'n>( ast: &'n syn::DeriveInput, config: &'n Config<'_>, - args: TemplateArgs, + args: &'n TemplateArgs, ) -> Result, CompileError> { let TemplateArgs { source, @@ -43,7 +43,9 @@ impl TemplateInput<'_> { // Validate the `source` and `ext` value together, since they are // related. In case `source` was used instead of `path`, the value // of `ext` is merged into a synthetic `path` value here. - let source = source.expect("template path or source not found in attributes"); + let source = source + .as_ref() + .expect("template path or source not found in attributes"); let path = match (&source, &ext) { (Source::Path(path), _) => config.find_template(path, None)?, (&Source::Source(_), Some(ext)) => PathBuf::from(format!("{}.{}", ast.ident, ext)), @@ -53,28 +55,25 @@ impl TemplateInput<'_> { }; // Validate syntax - let syntax = syntax.map_or_else( + let syntax = syntax.as_deref().map_or_else( || Ok(config.syntaxes.get(config.default_syntax).unwrap()), |s| { config .syntaxes - .get(&s) + .get(s) .ok_or_else(|| CompileError::from(format!("attribute syntax {s} not exist"))) }, )?; // Match extension against defined output formats - let escaping = escaping.unwrap_or_else(|| { - path.extension() - .map(|s| s.to_str().unwrap()) - .unwrap_or("") - .to_string() - }); + let escaping = escaping + .as_deref() + .unwrap_or_else(|| path.extension().map(|s| s.to_str().unwrap()).unwrap_or("")); let mut escaper = None; for (extensions, path) in &config.escapers { - if extensions.contains(&escaping) { + if extensions.contains(escaping) { escaper = Some(path); break; } @@ -93,9 +92,9 @@ impl TemplateInput<'_> { config, syntax, source, - print, + print: *print, escaper, - ext, + ext: ext.as_deref(), mime_type, path, }) @@ -148,18 +147,18 @@ impl TemplateInput<'_> { #[inline] pub(crate) fn extension(&self) -> Option<&str> { - ext_default_to_path(self.ext.as_deref(), &self.path) + ext_default_to_path(self.ext, &self.path) } } -#[derive(Default)] +#[derive(Debug, Default)] pub(crate) struct TemplateArgs { pub(crate) source: Option, pub(crate) print: Print, pub(crate) escaping: Option, pub(crate) ext: Option, pub(crate) syntax: Option, - pub(crate) config_path: Option, + pub(crate) config: String, pub(crate) whitespace: Option, } @@ -259,7 +258,7 @@ impl TemplateArgs { } } else if ident == "config" { if let syn::Lit::Str(s) = value.lit { - args.config_path = Some(s.value()) + args.config = read_config_file(Some(&s.value()))?; } else { return Err("config value must be string literal".into()); } @@ -297,12 +296,13 @@ fn extension(path: &Path) -> Option<&str> { } } +#[derive(Debug)] pub(crate) enum Source { Path(String), Source(String), } -#[derive(PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub(crate) enum Print { All, Ast, diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index 3046f9dbe..a133813e3 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -10,7 +10,7 @@ use proc_macro2::Span; use parser::ParseError; mod config; -use config::{read_config_file, Config}; +use config::Config; mod generator; use generator::{Generator, MapChain}; mod heritage; @@ -36,9 +36,8 @@ pub fn derive_template(input: TokenStream) -> TokenStream { /// value as passed to the `template()` attribute. pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result { let template_args = TemplateArgs::new(ast)?; - let config_toml = read_config_file(template_args.config_path.as_deref())?; - let config = Config::new(&config_toml, template_args.whitespace.as_ref())?; - let input = TemplateInput::new(ast, &config, template_args)?; + let config = Config::new(&template_args.config, template_args.whitespace.as_ref())?; + let input = TemplateInput::new(ast, &config, &template_args)?; let mut templates = HashMap::new(); input.find_used_templates(&mut templates)?; From babea28312387a22642cb974cc4b97e9fe7fed49 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 10 Oct 2023 15:10:12 +0200 Subject: [PATCH 42/44] Build Config from TemplateArgs --- askama_derive/src/input.rs | 18 +++++++++++------- askama_derive/src/lib.rs | 3 +-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/askama_derive/src/input.rs b/askama_derive/src/input.rs index 45da7f025..ee9b94233 100644 --- a/askama_derive/src/input.rs +++ b/askama_derive/src/input.rs @@ -153,13 +153,13 @@ impl TemplateInput<'_> { #[derive(Debug, Default)] pub(crate) struct TemplateArgs { - pub(crate) source: Option, - pub(crate) print: Print, - pub(crate) escaping: Option, - pub(crate) ext: Option, - pub(crate) syntax: Option, - pub(crate) config: String, - pub(crate) whitespace: Option, + source: Option, + print: Print, + escaping: Option, + ext: Option, + syntax: Option, + config: String, + whitespace: Option, } impl TemplateArgs { @@ -275,6 +275,10 @@ impl TemplateArgs { Ok(args) } + + pub(crate) fn config(&self) -> Result, CompileError> { + Config::new(&self.config, self.whitespace.as_ref()) + } } #[inline] diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index a133813e3..a5eb67a30 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -10,7 +10,6 @@ use proc_macro2::Span; use parser::ParseError; mod config; -use config::Config; mod generator; use generator::{Generator, MapChain}; mod heritage; @@ -36,7 +35,7 @@ pub fn derive_template(input: TokenStream) -> TokenStream { /// value as passed to the `template()` attribute. pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result { let template_args = TemplateArgs::new(ast)?; - let config = Config::new(&template_args.config, template_args.whitespace.as_ref())?; + let config = template_args.config()?; let input = TemplateInput::new(ast, &config, &template_args)?; let mut templates = HashMap::new(); From daa404713e8e1e5294a3cf65040172d33e43a0eb Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 10 Oct 2023 15:17:36 +0200 Subject: [PATCH 43/44] Avoid passing around duplicate data --- askama_derive/src/generator.rs | 15 ++------------- askama_derive/src/lib.rs | 10 ++-------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 929e18c1b..9031e8bf3 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -37,9 +37,6 @@ pub(crate) struct Generator<'a> { buf_writable: Vec>, // Counter for write! hash named arguments named: usize, - // If set to `suppress`, the whitespace characters will be removed by default unless `+` is - // used. - whitespace: WhitespaceHandling, } impl<'a> Generator<'a> { @@ -48,7 +45,6 @@ impl<'a> Generator<'a> { contexts: &'n HashMap<&'n Path, Context<'n>>, heritage: Option<&'n Heritage<'_>>, locals: MapChain<'n, &'n str, LocalMeta>, - whitespace: WhitespaceHandling, ) -> Generator<'n> { Generator { input, @@ -61,7 +57,6 @@ impl<'a> Generator<'a> { super_block: None, buf_writable: vec![], named: 0, - whitespace, } } @@ -796,13 +791,7 @@ impl<'a> Generator<'a> { // handle the include's nodes. Unfortunately we can't easily share the `includes` cache. let locals = MapChain::with_parent(&self.locals); - let mut child = Self::new( - self.input, - self.contexts, - self.heritage, - locals, - self.whitespace, - ); + let mut child = Self::new(self.input, self.contexts, self.heritage, locals); let nodes = match self.contexts.get(path.as_path()) { Some(ctx) => ctx.nodes, @@ -1638,7 +1627,7 @@ impl<'a> Generator<'a> { Some(Whitespace::Suppress) => WhitespaceHandling::Suppress, Some(Whitespace::Preserve) => WhitespaceHandling::Preserve, Some(Whitespace::Minimize) => WhitespaceHandling::Minimize, - None => self.whitespace, + None => self.input.config.whitespace, } } diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index a5eb67a30..547a449f3 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -60,14 +60,8 @@ pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result Date: Mon, 30 Oct 2023 12:10:43 +0100 Subject: [PATCH 44/44] Remove unused `_did_loop` condition if `else` block is empty --- askama_derive/src/generator.rs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 9031e8bf3..eb293b0ac 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -601,9 +601,13 @@ impl<'a> Generator<'a> { let expr_code = self.visit_expr_root(&loop_block.iter)?; + let has_else_nodes = !loop_block.else_nodes.is_empty(); + let flushed = self.write_buf_writable(buf)?; buf.writeln("{")?; - buf.writeln("let mut _did_loop = false;")?; + if has_else_nodes { + buf.writeln("let mut _did_loop = false;")?; + } match loop_block.iter { Expr::Range(_, _, _) => buf.writeln(&format!("let _iter = {expr_code};")), Expr::Array(..) => buf.writeln(&format!("let _iter = {expr_code}.iter();")), @@ -639,20 +643,28 @@ impl<'a> Generator<'a> { self.visit_target(buf, true, true, &loop_block.var); buf.writeln(", _loop_item) in ::askama::helpers::TemplateLoop::new(_iter) {")?; - buf.writeln("_did_loop = true;")?; + if has_else_nodes { + buf.writeln("_did_loop = true;")?; + } let mut size_hint1 = self.handle(ctx, &loop_block.body, buf, AstLevel::Nested)?; self.handle_ws(loop_block.ws2); size_hint1 += self.write_buf_writable(buf)?; self.locals.pop(); buf.writeln("}")?; - buf.writeln("if !_did_loop {")?; - self.locals.push(); - let mut size_hint2 = self.handle(ctx, &loop_block.else_nodes, buf, AstLevel::Nested)?; - self.handle_ws(loop_block.ws3); - size_hint2 += self.write_buf_writable(buf)?; - self.locals.pop(); - buf.writeln("}")?; + let mut size_hint2; + if has_else_nodes { + buf.writeln("if !_did_loop {")?; + self.locals.push(); + size_hint2 = self.handle(ctx, &loop_block.else_nodes, buf, AstLevel::Nested)?; + self.handle_ws(loop_block.ws3); + size_hint2 += self.write_buf_writable(buf)?; + self.locals.pop(); + buf.writeln("}")?; + } else { + self.handle_ws(loop_block.ws3); + size_hint2 = self.write_buf_writable(buf)?; + } buf.writeln("}")?;