diff options
author | René Kijewski <kijewski@library.vetmed.fu-berlin.de> | 2021-07-18 16:32:49 +0200 |
---|---|---|
committer | Dirkjan Ochtman <dirkjan@ochtman.nl> | 2021-11-11 15:35:45 +0100 |
commit | 10b2d9c615460c9dbb241b887d0e6e17e3c001ca (patch) | |
tree | 4ec234cf416f7bcdc0bc037f787ebbd7ac4cb67e | |
parent | a8503e0fa2d6065b1c471becf76dde68571b7984 (diff) | |
download | askama-10b2d9c615460c9dbb241b887d0e6e17e3c001ca.tar.gz askama-10b2d9c615460c9dbb241b887d0e6e17e3c001ca.tar.bz2 askama-10b2d9c615460c9dbb241b887d0e6e17e3c001ca.zip |
Implement for-else
This PR implements for-else statements like in Jinja. They make it easy
to print an alternative message if the loop iterator was empty. E.g.
```rs
{% for result in result %}
<li>{{ result }}</li>
{% else %}
<li><em>no results</em></li>
{% endfor %}
```
Diffstat (limited to '')
-rw-r--r-- | askama_shared/src/generator.rs | 43 | ||||
-rw-r--r-- | askama_shared/src/heritage.rs | 9 | ||||
-rw-r--r-- | askama_shared/src/parser.rs | 49 | ||||
-rw-r--r-- | testing/tests/loop_else.rs | 19 |
4 files changed, 91 insertions, 29 deletions
diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs index 6c1b151..0c2e854 100644 --- a/askama_shared/src/generator.rs +++ b/askama_shared/src/generator.rs @@ -2,7 +2,7 @@ use super::{get_template_source, CompileError, Integrations}; use crate::filters; use crate::heritage::{Context, Heritage}; use crate::input::{Source, TemplateInput}; -use crate::parser::{parse, Cond, CondTest, Expr, Node, Target, When, Ws}; +use crate::parser::{parse, Cond, CondTest, Expr, Loop, Node, Target, When, Ws}; use proc_macro2::Span; @@ -420,8 +420,8 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { Node::Match(ws1, ref expr, ref arms, ws2) => { self.write_match(ctx, buf, ws1, expr, arms, ws2)?; } - Node::Loop(ws1, ref var, ref iter, ref body, ws2) => { - self.write_loop(ctx, buf, ws1, var, iter, body, ws2)?; + Node::Loop(ref loop_block) => { + self.write_loop(ctx, buf, loop_block)?; } Node::BlockDef(ws1, name, _, ws2) => { self.write_block(buf, Some(name), Ws(ws1.0, ws2.1))?; @@ -596,21 +596,19 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { &mut self, ctx: &'a Context<'_>, buf: &mut Buffer, - ws1: Ws, - var: &'a Target<'_>, - iter: &Expr<'_>, - body: &'a [Node<'_>], - ws2: Ws, + loop_block: &'a Loop<'_>, ) -> Result<usize, CompileError> { - self.handle_ws(ws1); + self.handle_ws(loop_block.ws1); self.locals.push(); - let expr_code = self.visit_expr_root(iter)?; + let expr_code = self.visit_expr_root(&loop_block.iter)?; let flushed = self.write_buf_writable(buf)?; + buf.writeln("{")?; + buf.writeln("let mut _did_not_loop = true;")?; buf.write("for ("); - self.visit_target(buf, true, true, var); - match iter { + self.visit_target(buf, true, true, &loop_block.var); + match loop_block.iter { Expr::Range(_, _, _) => buf.writeln(&format!( ", _loop_item) in ::askama::helpers::TemplateLoop::new({}) {{", expr_code @@ -645,13 +643,24 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { )), }?; - let mut size_hint = self.handle(ctx, body, buf, AstLevel::Nested)?; - self.handle_ws(ws2); - - size_hint += self.write_buf_writable(buf)?; + buf.writeln("_did_not_loop = false;")?; + let mut size_hint1 = self.handle(ctx, &loop_block.body, buf, AstLevel::Nested)?; + self.handle_ws(loop_block.ws2); + size_hint1 += self.write_buf_writable(buf)?; + self.locals.pop(); buf.writeln("}")?; + + buf.writeln("if _did_not_loop {")?; + self.locals.push(); + let mut size_hint2 = self.handle(ctx, &loop_block.else_block, buf, AstLevel::Nested)?; + self.handle_ws(loop_block.ws3); + size_hint2 += self.write_buf_writable(buf)?; self.locals.pop(); - Ok(flushed + (size_hint * 3)) + buf.writeln("}")?; + + buf.writeln("}")?; + + Ok(flushed + ((size_hint1 * 3) + size_hint2) / 2) } fn write_call( diff --git a/askama_shared/src/heritage.rs b/askama_shared/src/heritage.rs index 9f90273..a0b5460 100644 --- a/askama_shared/src/heritage.rs +++ b/askama_shared/src/heritage.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; -use crate::parser::{Expr, Macro, Node}; +use crate::parser::{Expr, Loop, Macro, Node}; use crate::{CompileError, Config}; pub struct Heritage<'a> { @@ -86,8 +86,11 @@ impl<'a> Context<'a> { nested.push(nodes); } } - Node::Loop(_, _, _, nodes, _) => { - nested.push(nodes); + Node::Loop(Loop { + body, else_block, .. + }) => { + nested.push(body); + nested.push(else_block); } Node::Match(_, _, arms, _) => { for (_, _, arm) in arms { diff --git a/askama_shared/src/parser.rs b/askama_shared/src/parser.rs index d1bc425..90c2fe3 100644 --- a/askama_shared/src/parser.rs +++ b/askama_shared/src/parser.rs @@ -22,7 +22,7 @@ pub enum Node<'a> { Let(Ws, Target<'a>, Expr<'a>), Cond(Vec<Cond<'a>>, Ws), Match(Ws, Expr<'a>, Vec<When<'a>>, Ws), - Loop(Ws, Target<'a>, Expr<'a>, Vec<Node<'a>>, Ws), + Loop(Loop<'a>), Extends(Expr<'a>), BlockDef(Ws, &'a str, Vec<Node<'a>>, Ws), Include(Ws, &'a str), @@ -34,6 +34,17 @@ pub enum Node<'a> { } #[derive(Debug, PartialEq)] +pub struct Loop<'a> { + pub ws1: Ws, + pub var: Target<'a>, + pub iter: Expr<'a>, + pub body: Vec<Node<'a>>, + pub ws2: Ws, + pub else_block: Vec<Node<'a>>, + pub ws3: Ws, +} + +#[derive(Debug, PartialEq)] pub enum Expr<'a> { BoolLit(&'a str), NumLit(&'a str), @@ -897,12 +908,28 @@ fn block_let(i: &[u8]) -> IResult<&[u8], Node<'_>> { fn parse_loop_content<'a>(i: &'a [u8], s: &State<'_>) -> IResult<&'a [u8], Vec<Node<'a>>> { s.loop_depth.set(s.loop_depth.get() + 1); - let (i, node) = parse_template(i, s)?; + let result = parse_template(i, s); s.loop_depth.set(s.loop_depth.get() - 1); - Ok((i, node)) + result } fn block_for<'a>(i: &'a [u8], s: &State<'_>) -> IResult<&'a [u8], Node<'a>> { + let else_block = |i| { + let mut p = preceded( + ws(tag("else")), + cut(tuple(( + opt(tag("-")), + delimited( + |i| tag_block_end(i, s), + |i| parse_template(i, s), + |i| tag_block_start(i, s), + ), + opt(tag("-")), + ))), + ); + let (i, (pws, nodes, nws)) = p(i)?; + Ok((i, (pws.is_some(), nodes, nws.is_some()))) + }; let mut p = tuple(( opt(char('-')), ws(tag("for")), @@ -918,6 +945,7 @@ fn block_for<'a>(i: &'a [u8], s: &State<'_>) -> IResult<&'a [u8], Node<'a>> { cut(tuple(( |i| tag_block_start(i, s), opt(char('-')), + opt(else_block), ws(tag("endfor")), opt(char('-')), ))), @@ -925,16 +953,19 @@ fn block_for<'a>(i: &'a [u8], s: &State<'_>) -> IResult<&'a [u8], Node<'a>> { ))), ))), )); - let (i, (pws1, _, (var, _, (iter, nws1, _, (block, (_, pws2, _, nws2)))))) = p(i)?; + let (i, (pws1, _, (var, _, (iter, nws1, _, (body, (_, pws2, else_block, _, nws2)))))) = p(i)?; + let (nws3, else_block, pws3) = else_block.unwrap_or_default(); Ok(( i, - Node::Loop( - Ws(pws1.is_some(), nws1.is_some()), + Node::Loop(Loop { + ws1: Ws(pws1.is_some(), nws1.is_some()), var, iter, - block, - Ws(pws2.is_some(), nws2.is_some()), - ), + body, + ws2: Ws(pws2.is_some(), nws3), + else_block, + ws3: Ws(pws3, nws2.is_some()), + }), )) } diff --git a/testing/tests/loop_else.rs b/testing/tests/loop_else.rs new file mode 100644 index 0000000..99dfc4b --- /dev/null +++ b/testing/tests/loop_else.rs @@ -0,0 +1,19 @@ +use askama::Template; + +#[derive(Template)] +#[template( + source = "{% for v in values %}{{ v }}{% else %}empty{% endfor %}", + ext = "txt" +)] +struct ForElse<'a> { + values: &'a [i32], +} + +#[test] +fn test_for_else() { + let t = ForElse { values: &[1, 2, 3] }; + assert_eq!(t.render().unwrap(), "123"); + + let t = ForElse { values: &[] }; + assert_eq!(t.render().unwrap(), "empty"); +} |