aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar René Kijewski <kijewski@library.vetmed.fu-berlin.de>2022-01-14 15:32:53 +0100
committerLibravatar Dirkjan Ochtman <dirkjan@ochtman.nl>2022-01-28 11:21:06 +0100
commitda0b6ead0e75082bfffa24f8529d1f83961ba45c (patch)
tree58bbdb1de244e99c77b378a9a9d4eaf4dd873474
parent85ad2e6ba3a205e9b648431318aac0e75c027a82 (diff)
downloadaskama-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.rs18
-rw-r--r--askama_shared/src/parser.rs148
-rw-r--r--testing/tests/tuple.rs82
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!");
+}