Repository: djc/askama (rust)

Issue:
Add  `elif` keyword
Hi,

we recently migrated from Tera to Askama and overall it was quite straightforward. One thing that did not work out of the box was the `elif` keyword, so it would be nice to include that. In addition, `elif` seems to be [supported by jinja](https://jinja.palletsprojects.com/en/3.1.x/templates/#if).

Pull request description:
Add support for `elif`
Fixes #1056.

Patch:
diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs
index feb13d0..708d880 100644
--- a/askama_parser/src/node.rs
+++ b/askama_parser/src/node.rs
@@ -369,24 +369,20 @@ pub struct Cond<'a> {
 
 impl<'a> Cond<'a> {
     fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
-        let mut p = tuple((
+        let (i, (_, pws, cond, nws, _, nodes)) = tuple((
             |i| s.tag_block_start(i),
             opt(Whitespace::parse),
-            ws(alt((keyword("else"), |i| {
-                let _ = keyword("elif")(i)?;
-                Err(nom::Err::Failure(ErrorContext::new(
-                    "unknown `elif` keyword; did you mean `else if`?",
-                    i,
-                )))
-            }))),
-            cut(tuple((
-                opt(|i| CondTest::parse(i, s)),
-                opt(Whitespace::parse),
-                |i| s.tag_block_end(i),
-                cut(|i| Node::many(i, s)),
-            ))),
-        ));
-        let (i, (_, pws, _, (cond, nws, _, nodes))) = p(i)?;
+            alt((
+                preceded(ws(keyword("else")), opt(|i| CondTest::parse(i, s))),
+                preceded(
+                    ws(keyword("elif")),
+                    cut(map(|i| CondTest::parse_cond(i, s), Some)),
+                ),
+            )),
+            opt(Whitespace::parse),
+            cut(|i| s.tag_block_end(i)),
+            cut(|i| Node::many(i, s)),
+        ))(i)?;
         Ok((
             i,
             Self {
@@ -406,18 +402,18 @@ pub struct CondTest<'a> {
 
 impl<'a> CondTest<'a> {
     fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
-        let mut p = preceded(
-            ws(keyword("if")),
-            cut(tuple((
-                opt(delimited(
-                    ws(alt((keyword("let"), keyword("set")))),
-                    ws(|i| Target::parse(i, s)),
-                    ws(char('=')),
-                )),
-                ws(|i| Expr::parse(i, s.level.get())),
-            ))),
-        );
-        let (i, (target, expr)) = p(i)?;
+        preceded(ws(keyword("if")), cut(|i| Self::parse_cond(i, s)))(i)
+    }
+
+    fn parse_cond(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
+        let (i, (target, expr)) = pair(
+            opt(delimited(
+                ws(alt((keyword("let"), keyword("set")))),
+                ws(|i| Target::parse(i, s)),
+                ws(char('=')),
+            )),
+            ws(|i| Expr::parse(i, s.level.get())),
+        )(i)?;
         Ok((i, Self { target, expr }))
     }
 }
diff --git a/book/src/template_syntax.md b/book/src/template_syntax.md
index 351cbff..d81229b 100644
--- a/book/src/template_syntax.md
+++ b/book/src/template_syntax.md
@@ -459,6 +459,8 @@ and is used as you might expect:
   No users
 {% else if users.len() == 1 %}
   1 user
+{% elif users.len() == 2 %}
+  2 users
 {% else %}
   {{ users.len() }} users
 {% endif %}


Test patch:
diff --git a/testing/tests/if.rs b/testing/tests/if.rs
new file mode 100644
index 0000000..bbe2865
--- /dev/null
+++ b/testing/tests/if.rs
@@ -0,0 +1,26 @@
+use askama::Template;
+
+#[derive(Template)]
+#[template(
+    source = r#"{%- if s == "" -%}
+empty
+{%- else if s == "b" -%}
+b
+{%- elif s == "c" -%}
+c
+{%- else -%}
+else
+{%- endif -%}"#,
+    ext = "txt"
+)]
+struct If<'a> {
+    s: &'a str,
+}
+
+#[test]
+fn test_if() {
+    assert_eq!(If { s: "" }.render().unwrap(), "empty");
+    assert_eq!(If { s: "b" }.render().unwrap(), "b");
+    assert_eq!(If { s: "c" }.render().unwrap(), "c");
+    assert_eq!(If { s: "d" }.render().unwrap(), "else");
+}
diff --git a/testing/tests/if_let.rs b/testing/tests/if_let.rs
index 3e4e2b5..d383418 100644
--- a/testing/tests/if_let.rs
+++ b/testing/tests/if_let.rs
@@ -107,3 +107,24 @@ fn test_if_let_else() {
     };
     assert_eq!(s.render().unwrap(), "fail");
 }
+
+#[derive(Template)]
+#[template(
+    source = r#"{%- if s.is_none() -%}
+empty
+{%- elif let Some(a) = s -%}
+{{a}}
+{%- else -%}
+else
+{%- endif -%}"#,
+    ext = "txt"
+)]
+struct Elif<'a> {
+    s: Option<&'a str>,
+}
+
+#[test]
+fn test_elif() {
+    assert_eq!(Elif { s: None }.render().unwrap(), "empty");
+    assert_eq!(Elif { s: Some("tada") }.render().unwrap(), "tada");
+}
diff --git a/testing/tests/ui/elif.rs b/testing/tests/ui/elif.rs
deleted file mode 100644
index 650f292..0000000
--- a/testing/tests/ui/elif.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-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
deleted file mode 100644
index 593e5c4..0000000
--- a/testing/tests/ui/elif.stderr
+++ /dev/null
@@ -1,9 +0,0 @@
-error: unknown `elif` keyword; did you mean `else if`?
-       failed to parse 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)