diff options
author | René Kijewski <kijewski@library.vetmed.fu-berlin.de> | 2022-01-14 15:32:53 +0100 |
---|---|---|
committer | Dirkjan Ochtman <dirkjan@ochtman.nl> | 2022-01-28 11:21:06 +0100 |
commit | da0b6ead0e75082bfffa24f8529d1f83961ba45c (patch) | |
tree | 58bbdb1de244e99c77b378a9a9d4eaf4dd873474 | |
parent | 85ad2e6ba3a205e9b648431318aac0e75c027a82 (diff) | |
download | askama-da0b6ead0e75082bfffa24f8529d1f83961ba45c.tar.gz askama-da0b6ead0e75082bfffa24f8529d1f83961ba45c.tar.bz2 askama-da0b6ead0e75082bfffa24f8529d1f83961ba45c.zip |
Parse tuple expressions
Askama understands how to destructure tuples in let and match
statements, but it does not understand how to build a tuple.
This PR fixes this shortcoming.
Diffstat (limited to '')
-rw-r--r-- | askama_shared/src/generator.rs | 18 | ||||
-rw-r--r-- | askama_shared/src/parser.rs | 148 | ||||
-rw-r--r-- | testing/tests/tuple.rs | 82 |
3 files changed, 245 insertions, 3 deletions
diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs index de2fdcb..7080b87 100644 --- a/askama_shared/src/generator.rs +++ b/askama_shared/src/generator.rs @@ -1061,6 +1061,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { Expr::Call(ref obj, ref args) => self.visit_call(buf, obj, args)?, Expr::RustMacro(name, args) => self.visit_rust_macro(buf, name, args), Expr::Try(ref expr) => self.visit_try(buf, expr.as_ref())?, + Expr::Tuple(ref exprs) => self.visit_tuple(buf, exprs)?, }) } @@ -1403,6 +1404,23 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { Ok(DisplayWrap::Unwrapped) } + fn visit_tuple( + &mut self, + buf: &mut Buffer, + exprs: &[Expr<'_>], + ) -> Result<DisplayWrap, CompileError> { + buf.write("("); + for (index, expr) in exprs.iter().enumerate() { + if index > 0 { + buf.write(" "); + } + self.visit_expr(buf, expr)?; + buf.write(","); + } + buf.write(")"); + Ok(DisplayWrap::Unwrapped) + } + fn visit_array( &mut self, buf: &mut Buffer, diff --git a/askama_shared/src/parser.rs b/askama_shared/src/parser.rs index 63cea0c..085d73b 100644 --- a/askama_shared/src/parser.rs +++ b/askama_shared/src/parser.rs @@ -61,6 +61,7 @@ pub enum Expr<'a> { BinOp(&'a str, Box<Expr<'a>>, Box<Expr<'a>>), Range(&'a str, Option<Box<Expr<'a>>>, Option<Box<Expr<'a>>>), Group(Box<Expr<'a>>), + Tuple(Vec<Expr<'a>>), Call(Box<Expr<'a>>, Vec<Expr<'a>>), RustMacro(&'a str, &'a str), Try(Box<Expr<'a>>), @@ -488,9 +489,31 @@ fn parameters(i: &str) -> IResult<&str, Vec<&str>> { } fn expr_group(i: &str) -> IResult<&str, Expr<'_>> { - map(delimited(ws(char('(')), expr_any, ws(char(')'))), |s| { - Expr::Group(Box::new(s)) - })(i) + let (i, expr) = preceded(ws(char('(')), opt(expr_any))(i)?; + let expr = match expr { + Some(expr) => expr, + None => { + let (i, _) = char(')')(i)?; + return Ok((i, Expr::Tuple(vec![]))); + } + }; + + let (i, comma) = ws(opt(peek(char(','))))(i)?; + if comma.is_none() { + let (i, _) = char(')')(i)?; + return Ok((i, Expr::Group(Box::new(expr)))); + } + + let mut exprs = vec![expr]; + let (i, _) = fold_many0( + preceded(char(','), ws(expr_any)), + || (), + |_, expr| { + exprs.push(expr); + }, + )(i)?; + let (i, _) = pair(ws(opt(char(','))), char(')'))(i)?; + Ok((i, Expr::Tuple(exprs))) } fn expr_single(i: &str) -> IResult<&str, Expr<'_>> { @@ -1687,4 +1710,123 @@ mod tests { vec![Node::Comment(Ws(false, false))], ); } + + #[test] + fn test_parse_tuple() { + use super::Expr::*; + let syntax = Syntax::default(); + assert_eq!( + super::parse("{{ () }}", &syntax).unwrap(), + vec![Node::Expr(Ws(false, false), Tuple(vec![]),)], + ); + assert_eq!( + super::parse("{{ (1) }}", &syntax).unwrap(), + vec![Node::Expr(Ws(false, false), Group(Box::new(NumLit("1"))),)], + ); + assert_eq!( + super::parse("{{ (1,) }}", &syntax).unwrap(), + vec![Node::Expr(Ws(false, false), Tuple(vec![NumLit("1")]),)], + ); + assert_eq!( + super::parse("{{ (1, ) }}", &syntax).unwrap(), + vec![Node::Expr(Ws(false, false), Tuple(vec![NumLit("1")]),)], + ); + assert_eq!( + super::parse("{{ (1 ,) }}", &syntax).unwrap(), + vec![Node::Expr(Ws(false, false), Tuple(vec![NumLit("1")]),)], + ); + assert_eq!( + super::parse("{{ (1 , ) }}", &syntax).unwrap(), + vec![Node::Expr(Ws(false, false), Tuple(vec![NumLit("1")]),)], + ); + assert_eq!( + super::parse("{{ (1, 2) }}", &syntax).unwrap(), + vec![Node::Expr( + Ws(false, false), + Tuple(vec![NumLit("1"), NumLit("2")]), + )], + ); + assert_eq!( + super::parse("{{ (1, 2,) }}", &syntax).unwrap(), + vec![Node::Expr( + Ws(false, false), + Tuple(vec![NumLit("1"), NumLit("2")]), + )], + ); + assert_eq!( + super::parse("{{ (1, 2, 3) }}", &syntax).unwrap(), + vec![Node::Expr( + Ws(false, false), + Tuple(vec![NumLit("1"), NumLit("2"), NumLit("3")]), + )], + ); + assert_eq!( + super::parse("{{ ()|abs }}", &syntax).unwrap(), + vec![Node::Expr( + Ws(false, false), + Filter("abs", vec![Tuple(vec![])]), + )], + ); + assert_eq!( + super::parse("{{ () | abs }}", &syntax).unwrap(), + vec![Node::Expr( + Ws(false, false), + BinOp("|", Box::new(Tuple(vec![])), Box::new(Var("abs"))), + )], + ); + assert_eq!( + super::parse("{{ (1)|abs }}", &syntax).unwrap(), + vec![Node::Expr( + Ws(false, false), + Filter("abs", vec![Group(Box::new(NumLit("1")))]), + )], + ); + assert_eq!( + super::parse("{{ (1) | abs }}", &syntax).unwrap(), + vec![Node::Expr( + Ws(false, false), + BinOp( + "|", + Box::new(Group(Box::new(NumLit("1")))), + Box::new(Var("abs")) + ), + )], + ); + assert_eq!( + super::parse("{{ (1,)|abs }}", &syntax).unwrap(), + vec![Node::Expr( + Ws(false, false), + Filter("abs", vec![Tuple(vec![NumLit("1")])]), + )], + ); + assert_eq!( + super::parse("{{ (1,) | abs }}", &syntax).unwrap(), + vec![Node::Expr( + Ws(false, false), + BinOp( + "|", + Box::new(Tuple(vec![NumLit("1")])), + Box::new(Var("abs")) + ), + )], + ); + assert_eq!( + super::parse("{{ (1, 2)|abs }}", &syntax).unwrap(), + vec![Node::Expr( + Ws(false, false), + Filter("abs", vec![Tuple(vec![NumLit("1"), NumLit("2")])]), + )], + ); + assert_eq!( + super::parse("{{ (1, 2) | abs }}", &syntax).unwrap(), + vec![Node::Expr( + Ws(false, false), + BinOp( + "|", + Box::new(Tuple(vec![NumLit("1"), NumLit("2")])), + Box::new(Var("abs")) + ), + )], + ); + } } diff --git a/testing/tests/tuple.rs b/testing/tests/tuple.rs new file mode 100644 index 0000000..37d313c --- /dev/null +++ b/testing/tests/tuple.rs @@ -0,0 +1,82 @@ +use askama::Template; + +struct Post { + id: u32, +} + +struct Client<'a> { + can_post_ids: &'a [u32], + can_update_ids: &'a [u32], +} + +impl Client<'_> { + fn can_post(&self, post: &Post) -> bool { + self.can_post_ids.contains(&post.id) + } + + fn can_update(&self, post: &Post) -> bool { + self.can_update_ids.contains(&post.id) + } +} + +#[derive(Template)] +#[template( + source = r#" +{%- match (client.can_post(post), client.can_update(post)) -%} + {%- when (false, false) -%} + No! + {%- when (can_post, can_update) -%} + <ul> + {%- if can_post -%}<li>post</li>{%- endif -%} + {%- if can_update -%}<li>update</li>{%- endif -%} + </ul> +{%- endmatch -%} +"#, + ext = "txt" +)] +struct TupleTemplate<'a> { + client: &'a Client<'a>, + post: &'a Post, +} + +#[test] +fn test_tuple() { + let template = TupleTemplate { + client: &Client { + can_post_ids: &[1, 2], + can_update_ids: &[2, 3], + }, + post: &Post { id: 1 }, + }; + assert_eq!(template.render().unwrap(), "<ul><li>post</li></ul>"); + + let template = TupleTemplate { + client: &Client { + can_post_ids: &[1, 2], + can_update_ids: &[2, 3], + }, + post: &Post { id: 2 }, + }; + assert_eq!( + template.render().unwrap(), + "<ul><li>post</li><li>update</li></ul>" + ); + + let template = TupleTemplate { + client: &Client { + can_post_ids: &[1, 2], + can_update_ids: &[2, 3], + }, + post: &Post { id: 3 }, + }; + assert_eq!(template.render().unwrap(), "<ul><li>update</li></ul>"); + + let template = TupleTemplate { + client: &Client { + can_post_ids: &[1, 2], + can_update_ids: &[2, 3], + }, + post: &Post { id: 4 }, + }; + assert_eq!(template.render().unwrap(), "No!"); +} |