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.
This commit is contained in:
René Kijewski 2022-01-14 15:32:53 +01:00 committed by Dirkjan Ochtman
parent 85ad2e6ba3
commit da0b6ead0e
3 changed files with 245 additions and 3 deletions

View File

@ -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,

View File

@ -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"))
),
)],
);
}
}

82
testing/tests/tuple.rs Normal file
View File

@ -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!");
}