Skip to content

Commit

Permalink
Added set block support (#86)
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed Sep 1, 2022
1 parent 9d32d39 commit 28c524e
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 17 deletions.
10 changes: 10 additions & 0 deletions minijinja/src/ast.rs
Expand Up @@ -57,6 +57,7 @@ pub enum Stmt<'a> {
IfCond(Spanned<IfCond<'a>>),
WithBlock(Spanned<WithBlock<'a>>),
Set(Spanned<Set<'a>>),
SetBlock(Spanned<SetBlock<'a>>),
Block(Spanned<Block<'a>>),
Extends(Spanned<Extends<'a>>),
Include(Spanned<Include<'a>>),
Expand All @@ -75,6 +76,7 @@ impl<'a> fmt::Debug for Stmt<'a> {
Stmt::IfCond(s) => fmt::Debug::fmt(s, f),
Stmt::WithBlock(s) => fmt::Debug::fmt(s, f),
Stmt::Set(s) => fmt::Debug::fmt(s, f),
Stmt::SetBlock(s) => fmt::Debug::fmt(s, f),
Stmt::Block(s) => fmt::Debug::fmt(s, f),
Stmt::Extends(s) => fmt::Debug::fmt(s, f),
Stmt::Include(s) => fmt::Debug::fmt(s, f),
Expand Down Expand Up @@ -160,6 +162,14 @@ pub struct Set<'a> {
pub expr: Expr<'a>,
}

/// A set capture statement.
#[cfg_attr(feature = "internal_debug", derive(Debug))]
pub struct SetBlock<'a> {
pub target: Expr<'a>,
pub filter: Option<Expr<'a>>,
pub body: Vec<Stmt<'a>>,
}

/// A block for inheritance elements.
#[cfg_attr(feature = "internal_debug", derive(Debug))]
pub struct Block<'a> {
Expand Down
12 changes: 12 additions & 0 deletions minijinja/src/compiler.rs
Expand Up @@ -269,6 +269,18 @@ impl<'source> Compiler<'source> {
self.compile_expr(&set.expr)?;
self.compile_assignment(&set.target)?;
}
ast::Stmt::SetBlock(set_block) => {
self.set_location_from_span(set_block.span());
self.add(Instruction::BeginCapture);
for node in &set_block.body {
self.compile_stmt(node)?;
}
self.add(Instruction::EndCapture);
if let Some(ref filter) = set_block.filter {
self.compile_expr(filter)?;
}
self.compile_assignment(&set_block.target)?;
}
ast::Stmt::Block(block) => {
self.set_location_from_span(block.span());
let mut sub_compiler =
Expand Down
7 changes: 7 additions & 0 deletions minijinja/src/meta.rs
Expand Up @@ -179,6 +179,12 @@ pub fn find_undeclared_variables(source: &str) -> Result<HashSet<String>, Error>
assign_nested(&stmt.target, state);
visit_expr(&stmt.expr, state);
}
ast::Stmt::SetBlock(stmt) => {
assign_nested(&stmt.target, state);
state.push();
stmt.body.iter().for_each(|x| walk(x, state));
state.pop();
}
ast::Stmt::Block(stmt) => {
state.push();
state.assign("super");
Expand Down Expand Up @@ -241,6 +247,7 @@ pub fn find_referenced_templates(source: &str) -> Result<HashSet<String>, Error>
match node {
ast::Stmt::Template(stmt) => stmt.children.iter().for_each(|x| walk(x, out)),
ast::Stmt::EmitExpr(_) | ast::Stmt::EmitRaw(_) | ast::Stmt::Set(_) => {}
ast::Stmt::SetBlock(stmt) => stmt.body.iter().for_each(|x| walk(x, out)),
ast::Stmt::ForLoop(stmt) => stmt
.body
.iter()
Expand Down
61 changes: 47 additions & 14 deletions minijinja/src/parser.rs
Expand Up @@ -56,6 +56,11 @@ macro_rules! expect_token {
}};
}

enum SetParseResult<'a> {
Set(ast::Set<'a>),
SetBlock(ast::SetBlock<'a>),
}

struct TokenStream<'a> {
iter: Box<dyn Iterator<Item = Result<(Token<'a>, Span), Error>> + 'a>,
current: Option<Result<(Token<'a>, Span), Error>>,
Expand Down Expand Up @@ -547,10 +552,14 @@ impl<'a> Parser<'a> {
self.parse_with_block()?,
self.stream.expand_span(span),
))),
Token::Ident("set") => Ok(ast::Stmt::Set(Spanned::new(
self.parse_set()?,
self.stream.expand_span(span),
))),
Token::Ident("set") => Ok(match self.parse_set()? {
SetParseResult::Set(rv) => {
ast::Stmt::Set(Spanned::new(rv, self.stream.expand_span(span)))
}
SetParseResult::SetBlock(rv) => {
ast::Stmt::SetBlock(Spanned::new(rv, self.stream.expand_span(span)))
}
}),
Token::Ident("block") => Ok(ast::Stmt::Block(Spanned::new(
self.parse_block()?,
self.stream.expand_span(span),
Expand Down Expand Up @@ -722,19 +731,41 @@ impl<'a> Parser<'a> {
Ok(ast::WithBlock { assignments, body })
}

fn parse_set(&mut self) -> Result<ast::Set<'a>, Error> {
let target = if matches!(self.stream.current()?, Some((Token::ParenOpen, _))) {
fn parse_set(&mut self) -> Result<SetParseResult<'a>, Error> {
let (target, in_paren) = if matches!(self.stream.current()?, Some((Token::ParenOpen, _))) {
self.stream.next()?;
let assign = self.parse_assignment()?;
expect_token!(self, Token::ParenClose, "`)`")?;
assign
(assign, true)
} else {
self.parse_assign_name()?
(self.parse_assign_name()?, false)
};
expect_token!(self, Token::Assign, "assignment operator")?;
let expr = self.parse_expr()?;

Ok(ast::Set { target, expr })
if !in_paren
&& matches!(
self.stream.current()?,
Some((Token::BlockEnd(..), _)) | Some((Token::Pipe, _))
)
{
let filter = if matches!(self.stream.current()?, Some((Token::Pipe, _))) {
self.stream.next()?;
Some(self.parse_filter_chain()?)
} else {
None
};
expect_token!(self, Token::BlockEnd(..), "end of block")?;
let body = self.subparse(&|tok| matches!(tok, Token::Ident("endset")))?;
self.stream.next()?;
Ok(SetParseResult::SetBlock(ast::SetBlock {
target,
filter,
body,
}))
} else {
expect_token!(self, Token::Assign, "assignment operator")?;
let expr = self.parse_expr()?;
Ok(SetParseResult::Set(ast::Set { target, expr }))
}
}

fn parse_block(&mut self) -> Result<ast::Block<'a>, Error> {
Expand Down Expand Up @@ -786,7 +817,7 @@ impl<'a> Parser<'a> {
Ok(ast::AutoEscape { enabled, body })
}

fn parse_filter_block(&mut self) -> Result<ast::FilterBlock<'a>, Error> {
fn parse_filter_chain(&mut self) -> Result<ast::Expr<'a>, Error> {
let mut filter = None;

while !matches!(self.stream.current()?, Some((Token::BlockEnd(..), _))) {
Expand All @@ -809,9 +840,11 @@ impl<'a> Parser<'a> {
)));
}

let filter = filter
.ok_or_else(|| Error::new(ErrorKind::InvalidSyntax, "filter block without filter"))?;
filter.ok_or_else(|| Error::new(ErrorKind::InvalidSyntax, "expected a filter"))
}

fn parse_filter_block(&mut self) -> Result<ast::FilterBlock<'a>, Error> {
let filter = self.parse_filter_chain()?;
expect_token!(self, Token::BlockEnd(..), "end of block")?;
let body = self.subparse(&|tok| matches!(tok, Token::Ident("endfilter")))?;
self.stream.next()?;
Expand Down
20 changes: 20 additions & 0 deletions minijinja/src/syntax.rs
Expand Up @@ -419,6 +419,26 @@
//! and have them show up outside of it. This also applies to loops. The only
//! exception to that rule are if statements which do not introduce a scope.
//!
//! It's also possible to capture blocks of template code into a variable by using
//! the `set` statement as a block. In that case, instead of using an equals sign
//! and a value, you just write the variable name and then everything until
//! `{% endset %}` is captured.
//!
//! ```jinja
//! {% set navigation %}
//! <li><a href="/">Index</a>
//! <li><a href="/downloads">Downloads</a>
//! {% endset %}
//! ```
//!
//! The `navigation` variable then contains the navigation HTML source.
//!
//! This can also be combined with applying a filter:
//!
//! ```jinja
//! {% set title | upper %}Title of the page{% endset %}
//! ```
//!
//! ## `{% filter %}`
//!
//! Filter sections allow you to apply regular [filters](crate::filters) on a
Expand Down
10 changes: 9 additions & 1 deletion minijinja/tests/inputs/set.txt
Expand Up @@ -32,4 +32,12 @@ world" %}
Multiline:
{% set multiline = "hello
world" %}
{{ multiline }}
{{ multiline }}

Block:
{% set var %}This is a {{ foo }}{% endset %}
[{{ var }}]

Filter block
{% set upper_var | upper %}This is a {{ foo }}{% endset %}
[{{ upper_var }}]
8 changes: 7 additions & 1 deletion minijinja/tests/parser-inputs/set.txt
@@ -1,2 +1,8 @@
{% set variable = value %}
{% set (a, b) = (1, 2) %}
{% set (a, b) = (1, 2) %}
{% set variable2 %}
this is the {{ body }}
{% endset %}
{% set variable3 | upper %}
this is the {{ body }} with filter
{% endset %}
52 changes: 51 additions & 1 deletion minijinja/tests/snapshots/test_parser__parser@set.txt.snap
Expand Up @@ -39,6 +39,56 @@ Ok(
],
} @ 2:16-2:21,
} @ 2:3-2:22,
EmitRaw {
raw: "\n",
} @ 2:25-3:0,
SetBlock {
target: Var {
id: "variable2",
} @ 3:7-3:16,
filter: None,
body: [
EmitRaw {
raw: "\n this is the ",
} @ 3:19-4:16,
EmitExpr {
expr: Var {
id: "body",
} @ 4:19-4:23,
} @ 4:16-4:23,
EmitRaw {
raw: "\n",
} @ 4:26-5:0,
],
} @ 3:3-5:9,
EmitRaw {
raw: "\n",
} @ 5:12-6:0,
SetBlock {
target: Var {
id: "variable3",
} @ 6:7-6:16,
filter: Some(
Filter {
name: "upper",
expr: None,
args: [],
} @ 6:19-6:24,
),
body: [
EmitRaw {
raw: "\n this is the ",
} @ 6:27-7:16,
EmitExpr {
expr: Var {
id: "body",
} @ 7:19-7:23,
} @ 7:16-7:23,
EmitRaw {
raw: " with filter\n",
} @ 7:26-8:0,
],
} @ 6:3-8:9,
],
} @ 0:0-2:25,
} @ 0:0-8:12,
)
8 changes: 8 additions & 0 deletions minijinja/tests/snapshots/test_templates__vm@set.txt.snap
Expand Up @@ -43,3 +43,11 @@ Multiline:
hello
world

Block:

[This is a was true]

Filter block

[THIS IS A WAS TRUE]

0 comments on commit 28c524e

Please sign in to comment.