aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar René Kijewski <kijewski@library.vetmed.fu-berlin.de>2021-07-18 16:32:49 +0200
committerLibravatar Dirkjan Ochtman <dirkjan@ochtman.nl>2021-11-11 15:35:45 +0100
commit10b2d9c615460c9dbb241b887d0e6e17e3c001ca (patch)
tree4ec234cf416f7bcdc0bc037f787ebbd7ac4cb67e
parenta8503e0fa2d6065b1c471becf76dde68571b7984 (diff)
downloadaskama-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.rs43
-rw-r--r--askama_shared/src/heritage.rs9
-rw-r--r--askama_shared/src/parser.rs49
-rw-r--r--testing/tests/loop_else.rs19
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");
+}