From 1d727f7ee9c1a1227dce60389c8639a21016bf69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Sun, 18 Jul 2021 16:32:49 +0200 Subject: [PATCH 1/3] 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 %}
  • {{ result }}
  • {% else %}
  • no results
  • {% endfor %} ``` --- askama_shared/src/generator.rs | 43 +++++++++++++++++------------ askama_shared/src/heritage.rs | 9 ++++--- askama_shared/src/parser.rs | 49 +++++++++++++++++++++++++++------- testing/tests/loop_else.rs | 19 +++++++++++++ 4 files changed, 91 insertions(+), 29 deletions(-) create mode 100644 testing/tests/loop_else.rs diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs index 6c1b15115..0c2e8545f 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 { - 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 9f90273d9..a0b5460fa 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 d1bc42533..90c2fe362 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>, Ws), Match(Ws, Expr<'a>, Vec>, Ws), - Loop(Ws, Target<'a>, Expr<'a>, Vec>, Ws), + Loop(Loop<'a>), Extends(Expr<'a>), BlockDef(Ws, &'a str, Vec>, Ws), Include(Ws, &'a str), @@ -33,6 +33,17 @@ pub enum Node<'a> { Continue(Ws), } +#[derive(Debug, PartialEq)] +pub struct Loop<'a> { + pub ws1: Ws, + pub var: Target<'a>, + pub iter: Expr<'a>, + pub body: Vec>, + pub ws2: Ws, + pub else_block: Vec>, + pub ws3: Ws, +} + #[derive(Debug, PartialEq)] pub enum Expr<'a> { BoolLit(&'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>> { 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 000000000..99dfc4b86 --- /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"); +} From a79cfb24e681839c060c3b4d63991b43ca62d85f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Wed, 10 Nov 2021 18:59:22 +0100 Subject: [PATCH 2/3] Add exhaustive whitespace tests for for-else --- testing/tests/gen_loop_else.py | 58 ++ testing/tests/loop_else.rs | 1089 ++++++++++++++++++++++++++++++++ 2 files changed, 1147 insertions(+) create mode 100755 testing/tests/gen_loop_else.py diff --git a/testing/tests/gen_loop_else.py b/testing/tests/gen_loop_else.py new file mode 100755 index 000000000..30afe09b3 --- /dev/null +++ b/testing/tests/gen_loop_else.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 + +print(r'''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"); +} +''') + +for i in range(2**6): + a = '-' if i & 2**0 else ' ' + b = '-' if i & 2**1 else ' ' + c = '-' if i & 2**2 else ' ' + d = '-' if i & 2**3 else ' ' + e = '-' if i & 2**4 else ' ' + f = '-' if i & 2**5 else ' ' + source = fr'a {{%{a}for v in values{b}%}}\t{{{{v}}}}\t{{%{c}else{d}%}}\nX\n{{%{e}endfor{f}%}} b' + + a = '' if i & 2**0 else r' ' + b = '' if i & 2**1 else r'\t' + c = '' if i & 2**2 else r'\t' + d = '' if i & 2**3 else r'\n' + e = '' if i & 2**4 else r'\n' + f = '' if i & 2**5 else r' ' + some = f'a{a}{b}1{c}{f}b' + none = f'a{a}{d}X{e}{f}b' + + print(f'''#[derive(Template)] +#[template( + source = "{source}", + ext = "txt" +)] +struct LoopElseTrim{i:02}<'a> {{ + values: &'a [i32], +}} + +#[test] +fn test_loop_else_trim{i:02}() {{ + let t = LoopElseTrim{i:02} {{ values: &[1] }}; + assert_eq!(t.render().unwrap(), "{some}"); + + let t = LoopElseTrim{i:02} {{ values: &[] }}; + assert_eq!(t.render().unwrap(), "{none}"); +}}''') diff --git a/testing/tests/loop_else.rs b/testing/tests/loop_else.rs index 99dfc4b86..767350991 100644 --- a/testing/tests/loop_else.rs +++ b/testing/tests/loop_else.rs @@ -17,3 +17,1092 @@ fn test_for_else() { let t = ForElse { values: &[] }; assert_eq!(t.render().unwrap(), "empty"); } + +#[derive(Template)] +#[template( + source = "a {% for v in values %}\t{{v}}\t{% else %}\nX\n{% endfor %} b", + ext = "txt" +)] +struct LoopElseTrim00<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim00() { + let t = LoopElseTrim00 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a \t1\t b"); + + let t = LoopElseTrim00 { values: &[] }; + assert_eq!(t.render().unwrap(), "a \nX\n b"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values %}\t{{v}}\t{% else %}\nX\n{% endfor %} b", + ext = "txt" +)] +struct LoopElseTrim01<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim01() { + let t = LoopElseTrim01 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a\t1\t b"); + + let t = LoopElseTrim01 { values: &[] }; + assert_eq!(t.render().unwrap(), "a\nX\n b"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values-%}\t{{v}}\t{% else %}\nX\n{% endfor %} b", + ext = "txt" +)] +struct LoopElseTrim02<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim02() { + let t = LoopElseTrim02 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a 1\t b"); + + let t = LoopElseTrim02 { values: &[] }; + assert_eq!(t.render().unwrap(), "a \nX\n b"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values-%}\t{{v}}\t{% else %}\nX\n{% endfor %} b", + ext = "txt" +)] +struct LoopElseTrim03<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim03() { + let t = LoopElseTrim03 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a1\t b"); + + let t = LoopElseTrim03 { values: &[] }; + assert_eq!(t.render().unwrap(), "a\nX\n b"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values %}\t{{v}}\t{%-else %}\nX\n{% endfor %} b", + ext = "txt" +)] +struct LoopElseTrim04<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim04() { + let t = LoopElseTrim04 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a \t1 b"); + + let t = LoopElseTrim04 { values: &[] }; + assert_eq!(t.render().unwrap(), "a \nX\n b"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values %}\t{{v}}\t{%-else %}\nX\n{% endfor %} b", + ext = "txt" +)] +struct LoopElseTrim05<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim05() { + let t = LoopElseTrim05 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a\t1 b"); + + let t = LoopElseTrim05 { values: &[] }; + assert_eq!(t.render().unwrap(), "a\nX\n b"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values-%}\t{{v}}\t{%-else %}\nX\n{% endfor %} b", + ext = "txt" +)] +struct LoopElseTrim06<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim06() { + let t = LoopElseTrim06 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a 1 b"); + + let t = LoopElseTrim06 { values: &[] }; + assert_eq!(t.render().unwrap(), "a \nX\n b"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values-%}\t{{v}}\t{%-else %}\nX\n{% endfor %} b", + ext = "txt" +)] +struct LoopElseTrim07<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim07() { + let t = LoopElseTrim07 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a1 b"); + + let t = LoopElseTrim07 { values: &[] }; + assert_eq!(t.render().unwrap(), "a\nX\n b"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values %}\t{{v}}\t{% else-%}\nX\n{% endfor %} b", + ext = "txt" +)] +struct LoopElseTrim08<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim08() { + let t = LoopElseTrim08 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a \t1\t b"); + + let t = LoopElseTrim08 { values: &[] }; + assert_eq!(t.render().unwrap(), "a X\n b"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values %}\t{{v}}\t{% else-%}\nX\n{% endfor %} b", + ext = "txt" +)] +struct LoopElseTrim09<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim09() { + let t = LoopElseTrim09 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a\t1\t b"); + + let t = LoopElseTrim09 { values: &[] }; + assert_eq!(t.render().unwrap(), "aX\n b"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values-%}\t{{v}}\t{% else-%}\nX\n{% endfor %} b", + ext = "txt" +)] +struct LoopElseTrim10<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim10() { + let t = LoopElseTrim10 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a 1\t b"); + + let t = LoopElseTrim10 { values: &[] }; + assert_eq!(t.render().unwrap(), "a X\n b"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values-%}\t{{v}}\t{% else-%}\nX\n{% endfor %} b", + ext = "txt" +)] +struct LoopElseTrim11<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim11() { + let t = LoopElseTrim11 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a1\t b"); + + let t = LoopElseTrim11 { values: &[] }; + assert_eq!(t.render().unwrap(), "aX\n b"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values %}\t{{v}}\t{%-else-%}\nX\n{% endfor %} b", + ext = "txt" +)] +struct LoopElseTrim12<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim12() { + let t = LoopElseTrim12 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a \t1 b"); + + let t = LoopElseTrim12 { values: &[] }; + assert_eq!(t.render().unwrap(), "a X\n b"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values %}\t{{v}}\t{%-else-%}\nX\n{% endfor %} b", + ext = "txt" +)] +struct LoopElseTrim13<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim13() { + let t = LoopElseTrim13 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a\t1 b"); + + let t = LoopElseTrim13 { values: &[] }; + assert_eq!(t.render().unwrap(), "aX\n b"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values-%}\t{{v}}\t{%-else-%}\nX\n{% endfor %} b", + ext = "txt" +)] +struct LoopElseTrim14<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim14() { + let t = LoopElseTrim14 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a 1 b"); + + let t = LoopElseTrim14 { values: &[] }; + assert_eq!(t.render().unwrap(), "a X\n b"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values-%}\t{{v}}\t{%-else-%}\nX\n{% endfor %} b", + ext = "txt" +)] +struct LoopElseTrim15<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim15() { + let t = LoopElseTrim15 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a1 b"); + + let t = LoopElseTrim15 { values: &[] }; + assert_eq!(t.render().unwrap(), "aX\n b"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values %}\t{{v}}\t{% else %}\nX\n{%-endfor %} b", + ext = "txt" +)] +struct LoopElseTrim16<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim16() { + let t = LoopElseTrim16 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a \t1\t b"); + + let t = LoopElseTrim16 { values: &[] }; + assert_eq!(t.render().unwrap(), "a \nX b"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values %}\t{{v}}\t{% else %}\nX\n{%-endfor %} b", + ext = "txt" +)] +struct LoopElseTrim17<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim17() { + let t = LoopElseTrim17 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a\t1\t b"); + + let t = LoopElseTrim17 { values: &[] }; + assert_eq!(t.render().unwrap(), "a\nX b"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values-%}\t{{v}}\t{% else %}\nX\n{%-endfor %} b", + ext = "txt" +)] +struct LoopElseTrim18<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim18() { + let t = LoopElseTrim18 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a 1\t b"); + + let t = LoopElseTrim18 { values: &[] }; + assert_eq!(t.render().unwrap(), "a \nX b"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values-%}\t{{v}}\t{% else %}\nX\n{%-endfor %} b", + ext = "txt" +)] +struct LoopElseTrim19<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim19() { + let t = LoopElseTrim19 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a1\t b"); + + let t = LoopElseTrim19 { values: &[] }; + assert_eq!(t.render().unwrap(), "a\nX b"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values %}\t{{v}}\t{%-else %}\nX\n{%-endfor %} b", + ext = "txt" +)] +struct LoopElseTrim20<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim20() { + let t = LoopElseTrim20 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a \t1 b"); + + let t = LoopElseTrim20 { values: &[] }; + assert_eq!(t.render().unwrap(), "a \nX b"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values %}\t{{v}}\t{%-else %}\nX\n{%-endfor %} b", + ext = "txt" +)] +struct LoopElseTrim21<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim21() { + let t = LoopElseTrim21 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a\t1 b"); + + let t = LoopElseTrim21 { values: &[] }; + assert_eq!(t.render().unwrap(), "a\nX b"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values-%}\t{{v}}\t{%-else %}\nX\n{%-endfor %} b", + ext = "txt" +)] +struct LoopElseTrim22<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim22() { + let t = LoopElseTrim22 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a 1 b"); + + let t = LoopElseTrim22 { values: &[] }; + assert_eq!(t.render().unwrap(), "a \nX b"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values-%}\t{{v}}\t{%-else %}\nX\n{%-endfor %} b", + ext = "txt" +)] +struct LoopElseTrim23<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim23() { + let t = LoopElseTrim23 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a1 b"); + + let t = LoopElseTrim23 { values: &[] }; + assert_eq!(t.render().unwrap(), "a\nX b"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values %}\t{{v}}\t{% else-%}\nX\n{%-endfor %} b", + ext = "txt" +)] +struct LoopElseTrim24<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim24() { + let t = LoopElseTrim24 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a \t1\t b"); + + let t = LoopElseTrim24 { values: &[] }; + assert_eq!(t.render().unwrap(), "a X b"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values %}\t{{v}}\t{% else-%}\nX\n{%-endfor %} b", + ext = "txt" +)] +struct LoopElseTrim25<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim25() { + let t = LoopElseTrim25 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a\t1\t b"); + + let t = LoopElseTrim25 { values: &[] }; + assert_eq!(t.render().unwrap(), "aX b"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values-%}\t{{v}}\t{% else-%}\nX\n{%-endfor %} b", + ext = "txt" +)] +struct LoopElseTrim26<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim26() { + let t = LoopElseTrim26 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a 1\t b"); + + let t = LoopElseTrim26 { values: &[] }; + assert_eq!(t.render().unwrap(), "a X b"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values-%}\t{{v}}\t{% else-%}\nX\n{%-endfor %} b", + ext = "txt" +)] +struct LoopElseTrim27<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim27() { + let t = LoopElseTrim27 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a1\t b"); + + let t = LoopElseTrim27 { values: &[] }; + assert_eq!(t.render().unwrap(), "aX b"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values %}\t{{v}}\t{%-else-%}\nX\n{%-endfor %} b", + ext = "txt" +)] +struct LoopElseTrim28<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim28() { + let t = LoopElseTrim28 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a \t1 b"); + + let t = LoopElseTrim28 { values: &[] }; + assert_eq!(t.render().unwrap(), "a X b"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values %}\t{{v}}\t{%-else-%}\nX\n{%-endfor %} b", + ext = "txt" +)] +struct LoopElseTrim29<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim29() { + let t = LoopElseTrim29 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a\t1 b"); + + let t = LoopElseTrim29 { values: &[] }; + assert_eq!(t.render().unwrap(), "aX b"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values-%}\t{{v}}\t{%-else-%}\nX\n{%-endfor %} b", + ext = "txt" +)] +struct LoopElseTrim30<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim30() { + let t = LoopElseTrim30 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a 1 b"); + + let t = LoopElseTrim30 { values: &[] }; + assert_eq!(t.render().unwrap(), "a X b"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values-%}\t{{v}}\t{%-else-%}\nX\n{%-endfor %} b", + ext = "txt" +)] +struct LoopElseTrim31<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim31() { + let t = LoopElseTrim31 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a1 b"); + + let t = LoopElseTrim31 { values: &[] }; + assert_eq!(t.render().unwrap(), "aX b"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values %}\t{{v}}\t{% else %}\nX\n{% endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim32<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim32() { + let t = LoopElseTrim32 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a \t1\tb"); + + let t = LoopElseTrim32 { values: &[] }; + assert_eq!(t.render().unwrap(), "a \nX\nb"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values %}\t{{v}}\t{% else %}\nX\n{% endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim33<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim33() { + let t = LoopElseTrim33 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a\t1\tb"); + + let t = LoopElseTrim33 { values: &[] }; + assert_eq!(t.render().unwrap(), "a\nX\nb"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values-%}\t{{v}}\t{% else %}\nX\n{% endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim34<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim34() { + let t = LoopElseTrim34 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a 1\tb"); + + let t = LoopElseTrim34 { values: &[] }; + assert_eq!(t.render().unwrap(), "a \nX\nb"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values-%}\t{{v}}\t{% else %}\nX\n{% endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim35<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim35() { + let t = LoopElseTrim35 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a1\tb"); + + let t = LoopElseTrim35 { values: &[] }; + assert_eq!(t.render().unwrap(), "a\nX\nb"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values %}\t{{v}}\t{%-else %}\nX\n{% endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim36<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim36() { + let t = LoopElseTrim36 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a \t1b"); + + let t = LoopElseTrim36 { values: &[] }; + assert_eq!(t.render().unwrap(), "a \nX\nb"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values %}\t{{v}}\t{%-else %}\nX\n{% endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim37<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim37() { + let t = LoopElseTrim37 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a\t1b"); + + let t = LoopElseTrim37 { values: &[] }; + assert_eq!(t.render().unwrap(), "a\nX\nb"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values-%}\t{{v}}\t{%-else %}\nX\n{% endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim38<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim38() { + let t = LoopElseTrim38 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a 1b"); + + let t = LoopElseTrim38 { values: &[] }; + assert_eq!(t.render().unwrap(), "a \nX\nb"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values-%}\t{{v}}\t{%-else %}\nX\n{% endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim39<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim39() { + let t = LoopElseTrim39 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a1b"); + + let t = LoopElseTrim39 { values: &[] }; + assert_eq!(t.render().unwrap(), "a\nX\nb"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values %}\t{{v}}\t{% else-%}\nX\n{% endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim40<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim40() { + let t = LoopElseTrim40 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a \t1\tb"); + + let t = LoopElseTrim40 { values: &[] }; + assert_eq!(t.render().unwrap(), "a X\nb"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values %}\t{{v}}\t{% else-%}\nX\n{% endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim41<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim41() { + let t = LoopElseTrim41 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a\t1\tb"); + + let t = LoopElseTrim41 { values: &[] }; + assert_eq!(t.render().unwrap(), "aX\nb"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values-%}\t{{v}}\t{% else-%}\nX\n{% endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim42<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim42() { + let t = LoopElseTrim42 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a 1\tb"); + + let t = LoopElseTrim42 { values: &[] }; + assert_eq!(t.render().unwrap(), "a X\nb"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values-%}\t{{v}}\t{% else-%}\nX\n{% endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim43<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim43() { + let t = LoopElseTrim43 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a1\tb"); + + let t = LoopElseTrim43 { values: &[] }; + assert_eq!(t.render().unwrap(), "aX\nb"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values %}\t{{v}}\t{%-else-%}\nX\n{% endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim44<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim44() { + let t = LoopElseTrim44 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a \t1b"); + + let t = LoopElseTrim44 { values: &[] }; + assert_eq!(t.render().unwrap(), "a X\nb"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values %}\t{{v}}\t{%-else-%}\nX\n{% endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim45<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim45() { + let t = LoopElseTrim45 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a\t1b"); + + let t = LoopElseTrim45 { values: &[] }; + assert_eq!(t.render().unwrap(), "aX\nb"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values-%}\t{{v}}\t{%-else-%}\nX\n{% endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim46<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim46() { + let t = LoopElseTrim46 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a 1b"); + + let t = LoopElseTrim46 { values: &[] }; + assert_eq!(t.render().unwrap(), "a X\nb"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values-%}\t{{v}}\t{%-else-%}\nX\n{% endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim47<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim47() { + let t = LoopElseTrim47 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a1b"); + + let t = LoopElseTrim47 { values: &[] }; + assert_eq!(t.render().unwrap(), "aX\nb"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values %}\t{{v}}\t{% else %}\nX\n{%-endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim48<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim48() { + let t = LoopElseTrim48 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a \t1\tb"); + + let t = LoopElseTrim48 { values: &[] }; + assert_eq!(t.render().unwrap(), "a \nXb"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values %}\t{{v}}\t{% else %}\nX\n{%-endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim49<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim49() { + let t = LoopElseTrim49 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a\t1\tb"); + + let t = LoopElseTrim49 { values: &[] }; + assert_eq!(t.render().unwrap(), "a\nXb"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values-%}\t{{v}}\t{% else %}\nX\n{%-endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim50<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim50() { + let t = LoopElseTrim50 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a 1\tb"); + + let t = LoopElseTrim50 { values: &[] }; + assert_eq!(t.render().unwrap(), "a \nXb"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values-%}\t{{v}}\t{% else %}\nX\n{%-endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim51<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim51() { + let t = LoopElseTrim51 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a1\tb"); + + let t = LoopElseTrim51 { values: &[] }; + assert_eq!(t.render().unwrap(), "a\nXb"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values %}\t{{v}}\t{%-else %}\nX\n{%-endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim52<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim52() { + let t = LoopElseTrim52 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a \t1b"); + + let t = LoopElseTrim52 { values: &[] }; + assert_eq!(t.render().unwrap(), "a \nXb"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values %}\t{{v}}\t{%-else %}\nX\n{%-endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim53<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim53() { + let t = LoopElseTrim53 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a\t1b"); + + let t = LoopElseTrim53 { values: &[] }; + assert_eq!(t.render().unwrap(), "a\nXb"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values-%}\t{{v}}\t{%-else %}\nX\n{%-endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim54<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim54() { + let t = LoopElseTrim54 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a 1b"); + + let t = LoopElseTrim54 { values: &[] }; + assert_eq!(t.render().unwrap(), "a \nXb"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values-%}\t{{v}}\t{%-else %}\nX\n{%-endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim55<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim55() { + let t = LoopElseTrim55 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a1b"); + + let t = LoopElseTrim55 { values: &[] }; + assert_eq!(t.render().unwrap(), "a\nXb"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values %}\t{{v}}\t{% else-%}\nX\n{%-endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim56<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim56() { + let t = LoopElseTrim56 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a \t1\tb"); + + let t = LoopElseTrim56 { values: &[] }; + assert_eq!(t.render().unwrap(), "a Xb"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values %}\t{{v}}\t{% else-%}\nX\n{%-endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim57<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim57() { + let t = LoopElseTrim57 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a\t1\tb"); + + let t = LoopElseTrim57 { values: &[] }; + assert_eq!(t.render().unwrap(), "aXb"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values-%}\t{{v}}\t{% else-%}\nX\n{%-endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim58<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim58() { + let t = LoopElseTrim58 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a 1\tb"); + + let t = LoopElseTrim58 { values: &[] }; + assert_eq!(t.render().unwrap(), "a Xb"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values-%}\t{{v}}\t{% else-%}\nX\n{%-endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim59<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim59() { + let t = LoopElseTrim59 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a1\tb"); + + let t = LoopElseTrim59 { values: &[] }; + assert_eq!(t.render().unwrap(), "aXb"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values %}\t{{v}}\t{%-else-%}\nX\n{%-endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim60<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim60() { + let t = LoopElseTrim60 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a \t1b"); + + let t = LoopElseTrim60 { values: &[] }; + assert_eq!(t.render().unwrap(), "a Xb"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values %}\t{{v}}\t{%-else-%}\nX\n{%-endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim61<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim61() { + let t = LoopElseTrim61 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a\t1b"); + + let t = LoopElseTrim61 { values: &[] }; + assert_eq!(t.render().unwrap(), "aXb"); +} +#[derive(Template)] +#[template( + source = "a {% for v in values-%}\t{{v}}\t{%-else-%}\nX\n{%-endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim62<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim62() { + let t = LoopElseTrim62 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a 1b"); + + let t = LoopElseTrim62 { values: &[] }; + assert_eq!(t.render().unwrap(), "a Xb"); +} +#[derive(Template)] +#[template( + source = "a {%-for v in values-%}\t{{v}}\t{%-else-%}\nX\n{%-endfor-%} b", + ext = "txt" +)] +struct LoopElseTrim63<'a> { + values: &'a [i32], +} + +#[test] +fn test_loop_else_trim63() { + let t = LoopElseTrim63 { values: &[1] }; + assert_eq!(t.render().unwrap(), "a1b"); + + let t = LoopElseTrim63 { values: &[] }; + assert_eq!(t.render().unwrap(), "aXb"); +} From 668d74b35040cb872b33a1f468dd3704b942cf25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Wed, 10 Nov 2021 19:00:41 +0100 Subject: [PATCH 3/3] =?UTF-8?q?Implement=20`for=20=E2=80=A6=20in=20?= =?UTF-8?q?=E2=80=A6=20if=20=E2=80=A6`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- askama_shared/src/generator.rs | 56 ++++++++++++++++------------------ askama_shared/src/parser.rs | 7 ++++- testing/tests/loops.rs | 18 +++++++++++ 3 files changed, 51 insertions(+), 30 deletions(-) diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs index 0c2e8545f..5c7293411 100644 --- a/askama_shared/src/generator.rs +++ b/askama_shared/src/generator.rs @@ -605,52 +605,50 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { 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, &loop_block.var); + buf.writeln("let mut _did_loop = false;")?; match loop_block.iter { - Expr::Range(_, _, _) => buf.writeln(&format!( - ", _loop_item) in ::askama::helpers::TemplateLoop::new({}) {{", - expr_code - )), - Expr::Array(..) => buf.writeln(&format!( - ", _loop_item) in ::askama::helpers::TemplateLoop::new({}.iter()) {{", - expr_code - )), + Expr::Range(_, _, _) => buf.writeln(&format!("let _iter = {};", expr_code)), + Expr::Array(..) => buf.writeln(&format!("let _iter = {}.iter();", expr_code)), // If `iter` is a call then we assume it's something that returns // an iterator. If not then the user can explicitly add the needed // call without issues. - Expr::MethodCall(..) | Expr::PathCall(..) | Expr::Index(..) => buf.writeln(&format!( - ", _loop_item) in ::askama::helpers::TemplateLoop::new(({}).into_iter()) {{", - expr_code - )), + Expr::MethodCall(..) | Expr::PathCall(..) | Expr::Index(..) => { + buf.writeln(&format!("let _iter = ({}).into_iter();", expr_code)) + } // If accessing `self` then it most likely needs to be // borrowed, to prevent an attempt of moving. - _ if expr_code.starts_with("self.") => buf.writeln(&format!( - ", _loop_item) in ::askama::helpers::TemplateLoop::new(((&{}).into_iter())) {{", - expr_code - )), + _ if expr_code.starts_with("self.") => { + buf.writeln(&format!("let _iter = (&{}).into_iter();", expr_code)) + } // If accessing a field then it most likely needs to be // borrowed, to prevent an attempt of moving. - Expr::Attr(..) => buf.writeln(&format!( - ", _loop_item) in ::askama::helpers::TemplateLoop::new(((&{}).into_iter())) {{", - expr_code - )), + Expr::Attr(..) => buf.writeln(&format!("let _iter = (&{}).into_iter();", expr_code)), // Otherwise, we borrow `iter` assuming that it implements `IntoIterator`. - _ => buf.writeln(&format!( - ", _loop_item) in ::askama::helpers::TemplateLoop::new(({}).into_iter()) {{", - expr_code - )), + _ => buf.writeln(&format!("let _iter = ({}).into_iter();", expr_code)), }?; + if let Some(cond) = &loop_block.cond { + self.locals.push(); + buf.write("let _iter = _iter.filter(|"); + self.visit_target(buf, true, true, &loop_block.var); + buf.write("| -> bool {"); + self.visit_expr(buf, cond)?; + buf.writeln("});")?; + self.locals.pop(); + } + + self.locals.push(); + buf.write("for ("); + self.visit_target(buf, true, true, &loop_block.var); + buf.writeln(", _loop_item) in ::askama::helpers::TemplateLoop::new(_iter) {")?; - buf.writeln("_did_not_loop = false;")?; + buf.writeln("_did_loop = true;")?; 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 {")?; + buf.writeln("if !_did_loop {")?; self.locals.push(); let mut size_hint2 = self.handle(ctx, &loop_block.else_block, buf, AstLevel::Nested)?; self.handle_ws(loop_block.ws3); diff --git a/askama_shared/src/parser.rs b/askama_shared/src/parser.rs index 90c2fe362..34a391d33 100644 --- a/askama_shared/src/parser.rs +++ b/askama_shared/src/parser.rs @@ -38,6 +38,7 @@ pub struct Loop<'a> { pub ws1: Ws, pub var: Target<'a>, pub iter: Expr<'a>, + pub cond: Option>, pub body: Vec>, pub ws2: Ws, pub else_block: Vec>, @@ -914,6 +915,7 @@ fn parse_loop_content<'a>(i: &'a [u8], s: &State<'_>) -> IResult<&'a [u8], Vec(i: &'a [u8], s: &State<'_>) -> IResult<&'a [u8], Node<'a>> { + let if_cond = preceded(ws(tag("if")), cut(ws(expr_any))); let else_block = |i| { let mut p = preceded( ws(tag("else")), @@ -938,6 +940,7 @@ fn block_for<'a>(i: &'a [u8], s: &State<'_>) -> IResult<&'a [u8], Node<'a>> { ws(tag("in")), cut(tuple(( ws(expr_any), + opt(if_cond), opt(char('-')), |i| tag_block_end(i, s), cut(tuple(( @@ -953,7 +956,8 @@ fn block_for<'a>(i: &'a [u8], s: &State<'_>) -> IResult<&'a [u8], Node<'a>> { ))), ))), )); - let (i, (pws1, _, (var, _, (iter, nws1, _, (body, (_, pws2, else_block, _, nws2)))))) = p(i)?; + let (i, (pws1, _, (var, _, (iter, cond, nws1, _, (body, (_, pws2, else_block, _, nws2)))))) = + p(i)?; let (nws3, else_block, pws3) = else_block.unwrap_or_default(); Ok(( i, @@ -961,6 +965,7 @@ fn block_for<'a>(i: &'a [u8], s: &State<'_>) -> IResult<&'a [u8], Node<'a>> { ws1: Ws(pws1.is_some(), nws1.is_some()), var, iter, + cond, body, ws2: Ws(pws2.is_some(), nws3), else_block, diff --git a/testing/tests/loops.rs b/testing/tests/loops.rs index f01406899..4a0215381 100644 --- a/testing/tests/loops.rs +++ b/testing/tests/loops.rs @@ -417,3 +417,21 @@ fn test_for_cycle_empty() { }; assert!(t.render().is_err()) } + +#[derive(Template)] +#[template( + source = "{% for i in 0..limit if i % 2 == 1 %}{{i}}.{% else %}:({% endfor %}", + ext = "txt" +)] +struct ForInIf { + limit: usize, +} + +#[test] +fn test_for_in_if() { + let t = ForInIf { limit: 10 }; + assert_eq!(t.render().unwrap(), "1.3.5.7.9."); + + let t = ForInIf { limit: 1 }; + assert_eq!(t.render().unwrap(), ":("); +}