Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance match to include multiple targets #911

Merged
merged 5 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions askama_derive/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1575,6 +1575,16 @@ impl<'a> Generator<'a> {
}
buf.write(name);
}
Target::OrChain(targets) => match targets.first() {
None => buf.write("_"),
Some(first_target) => {
self.visit_target(buf, initialized, first_level, first_target);
for target in &targets[1..] {
buf.write(" | ");
self.visit_target(buf, initialized, first_level, target);
}
}
},
Target::Tuple(path, targets) => {
buf.write(&path.join("::"));
buf.write("(");
Expand Down
19 changes: 16 additions & 3 deletions askama_parser/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,23 @@ pub enum Target<'a> {
CharLit(&'a str),
BoolLit(&'a str),
Path(Vec<&'a str>),
OrChain(Vec<Target<'a>>),
}

impl<'a> Target<'a> {
/// Parses multiple targets with `or` separating them
pub(super) fn parse(i: &'a str) -> ParseResult<'a, Self> {
map(
separated_list1(ws(tag("or")), Self::parse_one),
|opts| match <[_; 1]>::try_from(opts) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't find this <[_; 1]>::try_from() idiom particularly clear. How about this:

match opts.len() {
    1 => opts.pop().unwrap(),
    _ => Self::OrChain(opts),
}

(Is this actually necessary? What happens if we just always yield a Vec<Target<'a>> from parse()?)

Copy link
Contributor Author

@PizzasBear PizzasBear Nov 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I liked the try_from because it avoided mut and .unwrap(), but I can change this.
The match statement helps avoid one item Target::OrChain which will help debugging by cleaning up the output of println!("{target:#?}") or other statements that'll result in pretty printing a target.

Ok([opt]) => opt,
Err(opts) => Self::OrChain(opts),
},
)(i)
}

/// Parses a single target without an `or`, unless it is wrapped in parentheses.
pub(super) fn parse_one(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());
Expand Down Expand Up @@ -337,7 +350,7 @@ impl<'a> CondTest<'a> {
cut(tuple((
opt(delimited(
ws(alt((keyword("let"), keyword("set")))),
ws(Target::parse),
ws(Target::parse_one),
ws(char('=')),
)),
ws(|i| Expr::parse(i, s.level.get())),
Expand Down Expand Up @@ -410,7 +423,7 @@ impl<'a> Loop<'a> {
opt(Whitespace::parse),
ws(keyword("for")),
cut(tuple((
ws(Target::parse),
ws(Target::parse_one),
ws(keyword("in")),
cut(tuple((
ws(|i| Expr::parse(i, s.level.get())),
Expand Down Expand Up @@ -791,7 +804,7 @@ impl<'a> Let<'a> {
opt(Whitespace::parse),
ws(alt((keyword("let"), keyword("set")))),
cut(tuple((
ws(Target::parse),
ws(Target::parse_one),
opt(preceded(
ws(char('=')),
ws(|i| Expr::parse(i, s.level.get())),
Expand Down
8 changes: 8 additions & 0 deletions testing/templates/match-enum-or.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
The card is
{%- match suit %}
{%- when Suit::Clubs or Suit::Spades -%}
{{ " black" }}
{%- when Suit::Diamonds or Suit::Hearts -%}
{{ " red" }}
{%- endmatch %}

29 changes: 29 additions & 0 deletions testing/tests/matches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,32 @@ fn test_match_with_comment() {
let s = MatchWithComment { good: false };
assert_eq!(s.render().unwrap(), "bad");
}

enum Suit {
Clubs,
Diamonds,
Hearts,
Spades,
}

#[derive(Template)]
#[template(path = "match-enum-or.html")]
struct MatchEnumOrTemplate {
suit: Suit,
}

#[test]
fn test_match_enum_or() {
let template = MatchEnumOrTemplate { suit: Suit::Clubs };
assert_eq!(template.render().unwrap(), "The card is black\n");
let template = MatchEnumOrTemplate { suit: Suit::Spades };
assert_eq!(template.render().unwrap(), "The card is black\n");

let template = MatchEnumOrTemplate { suit: Suit::Hearts };
assert_eq!(template.render().unwrap(), "The card is red\n");

let template = MatchEnumOrTemplate {
suit: Suit::Diamonds,
};
assert_eq!(template.render().unwrap(), "The card is red\n");
}