mirror of
https://github.com/askama-rs/askama.git
synced 2026-02-28 10:59:35 +00:00
Merge pull request #697 from GuillaumeGomez/let-blocks
Add support for `let blocks`
This commit is contained in:
commit
313f2ca655
@ -36,7 +36,7 @@ pub(crate) fn template_to_string(
|
||||
heritage,
|
||||
MapChain::default(),
|
||||
input.block.is_some(),
|
||||
0,
|
||||
BlockInfo::new(),
|
||||
);
|
||||
let size_hint = match generator.impl_template(buf, tmpl_kind) {
|
||||
Err(mut err) if err.span.is_none() => {
|
||||
@ -72,6 +72,34 @@ enum RenderFor {
|
||||
Extends,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct BlockInfo {
|
||||
block_name: &'static str,
|
||||
level: usize,
|
||||
}
|
||||
|
||||
impl BlockInfo {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
block_name: "",
|
||||
level: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Instead of this error-prone API, we should use something relying on `Drop` to
|
||||
// decrement, or use a callback which would decrement on exit.
|
||||
fn increase(&mut self, block_name: &'static str) {
|
||||
if self.level == 0 {
|
||||
self.block_name = block_name;
|
||||
}
|
||||
self.level += 1;
|
||||
}
|
||||
|
||||
fn decrease(&mut self) {
|
||||
self.level -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
struct Generator<'a, 'h> {
|
||||
/// The template input state: original struct AST and attributes
|
||||
input: &'a TemplateInput<'a>,
|
||||
@ -92,8 +120,8 @@ struct Generator<'a, 'h> {
|
||||
super_block: Option<(&'a str, usize)>,
|
||||
/// Buffer for writable
|
||||
buf_writable: WritableBuffer<'a>,
|
||||
/// Used in blocks to check if we are inside a filter block.
|
||||
is_in_filter_block: usize,
|
||||
/// Used in blocks to check if we are inside a filter/let block.
|
||||
is_in_block: BlockInfo,
|
||||
/// Set of called macros we are currently in. Used to prevent (indirect) recursions.
|
||||
seen_callers: Vec<(&'a Macro<'a>, Option<FileInfo<'a>>)>,
|
||||
/// The directory path of the calling file.
|
||||
@ -113,7 +141,7 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
heritage: Option<&'h Heritage<'a, 'h>>,
|
||||
locals: MapChain<'a>,
|
||||
buf_writable_discard: bool,
|
||||
is_in_filter_block: usize,
|
||||
is_in_block: BlockInfo,
|
||||
) -> Self {
|
||||
Self {
|
||||
input,
|
||||
@ -127,7 +155,7 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
discard: buf_writable_discard,
|
||||
..Default::default()
|
||||
},
|
||||
is_in_filter_block,
|
||||
is_in_block,
|
||||
seen_callers: Vec::new(),
|
||||
caller_dir: CallerDir::Unresolved,
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ use parser::node::{
|
||||
Call, Comment, Compound, Cond, CondTest, Declare, FilterBlock, If, Include, Let, Lit, Loop,
|
||||
Match, Whitespace, Ws,
|
||||
};
|
||||
use parser::{Expr, Node, Span, Target, WithSpan};
|
||||
use parser::{Expr, LetValueOrBlock, Node, Span, Target, WithSpan};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote_spanned;
|
||||
use rustc_hash::FxBuildHasher;
|
||||
@ -82,7 +82,7 @@ impl<'a> Generator<'a, '_> {
|
||||
heritage,
|
||||
locals,
|
||||
self.buf_writable.discard,
|
||||
self.is_in_filter_block,
|
||||
self.is_in_block,
|
||||
);
|
||||
child.buf_writable = buf_writable;
|
||||
let res = callback(&mut child);
|
||||
@ -727,7 +727,7 @@ impl<'a> Generator<'a, '_> {
|
||||
|
||||
let mut size_hint = self.write_buf_writable(ctx, buf)?;
|
||||
self.flush_ws(filter.ws1);
|
||||
self.is_in_filter_block += 1;
|
||||
self.is_in_block.increase("filter");
|
||||
size_hint += self.write_buf_writable(ctx, buf)?;
|
||||
let span = ctx.span_for_node(filter.span());
|
||||
|
||||
@ -785,7 +785,7 @@ impl<'a> Generator<'a, '_> {
|
||||
}
|
||||
} });
|
||||
|
||||
self.is_in_filter_block -= 1;
|
||||
self.is_in_block.decrease();
|
||||
self.prepare_ws(filter.ws2);
|
||||
Ok(size_hint)
|
||||
}
|
||||
@ -926,47 +926,25 @@ impl<'a> Generator<'a, '_> {
|
||||
|
||||
fn write_let(
|
||||
&mut self,
|
||||
ctx: &Context<'_>,
|
||||
ctx: &Context<'a>,
|
||||
buf: &mut Buffer,
|
||||
l: &'a WithSpan<Let<'_>>,
|
||||
) -> Result<SizeHint, CompileError> {
|
||||
self.handle_ws(l.ws);
|
||||
let span = ctx.span_for_node(l.span());
|
||||
|
||||
let Some(val) = &l.val else {
|
||||
let file_info = ctx
|
||||
.file_info_of(l.span())
|
||||
.map(|info| format!(" {info}:"))
|
||||
.unwrap_or_default();
|
||||
eprintln!(
|
||||
"⚠️{file_info} `let` tag will stop supporting declaring variables without value. \
|
||||
Use `create` instead for this case",
|
||||
);
|
||||
let size_hint = self.write_buf_writable(ctx, buf)?;
|
||||
buf.write_token(Token![let], span);
|
||||
if l.is_mutable {
|
||||
buf.write_token(Token![mut], span);
|
||||
}
|
||||
self.visit_target(ctx, buf, false, true, &l.var, span);
|
||||
buf.write_token(Token![;], span);
|
||||
return Ok(size_hint);
|
||||
};
|
||||
|
||||
// Handle when this statement creates a new alias of a caller variable (or of another alias),
|
||||
if let Target::Name(dstvar) = l.var
|
||||
&& let Expr::Var(srcvar) = ***val
|
||||
&& let Some(caller_alias) = self.locals.get_caller(srcvar)
|
||||
{
|
||||
self.locals.insert(
|
||||
Cow::Borrowed(*dstvar),
|
||||
LocalMeta::CallerAlias(caller_alias.clone()),
|
||||
);
|
||||
return Ok(SizeHint::EMPTY);
|
||||
match &l.val {
|
||||
LetValueOrBlock::Value(val) => self.write_let_value(ctx, buf, l, val),
|
||||
LetValueOrBlock::Block { nodes, ws } => self.write_let_block(ctx, buf, l, nodes, *ws),
|
||||
}
|
||||
}
|
||||
|
||||
let mut expr_buf = Buffer::new();
|
||||
self.visit_expr(ctx, &mut expr_buf, val)?;
|
||||
|
||||
fn write_let_target(
|
||||
&mut self,
|
||||
ctx: &Context<'_>,
|
||||
buf: &mut Buffer,
|
||||
l: &'a WithSpan<Let<'_>>,
|
||||
span: proc_macro2::Span,
|
||||
) -> Result<SizeHint, CompileError> {
|
||||
let shadowed = self.is_shadowing_variable(ctx, &l.var, l.span())?;
|
||||
let size_hint = if shadowed {
|
||||
// Need to flush the buffer if the variable is being shadowed,
|
||||
@ -986,6 +964,104 @@ impl<'a> Generator<'a, '_> {
|
||||
}
|
||||
|
||||
self.visit_target(ctx, buf, true, true, &l.var, span);
|
||||
Ok(size_hint)
|
||||
}
|
||||
|
||||
fn write_let_block(
|
||||
&mut self,
|
||||
ctx: &Context<'a>,
|
||||
buf: &mut Buffer,
|
||||
l: &'a WithSpan<Let<'_>>,
|
||||
nodes: &'a [Box<Node<'a>>],
|
||||
ws: Ws,
|
||||
) -> Result<SizeHint, CompileError> {
|
||||
let var_let_source = crate::var_let_source();
|
||||
|
||||
let mut size_hint = self.write_buf_writable(ctx, buf)?;
|
||||
self.flush_ws(l.ws);
|
||||
self.is_in_block.increase("let/set");
|
||||
size_hint += self.write_buf_writable(ctx, buf)?;
|
||||
let span = ctx.span_for_node(l.span());
|
||||
|
||||
// build `FmtCell` that contains the inner block
|
||||
let mut filter_def_buf = Buffer::new();
|
||||
size_hint += self.push_locals(|this| {
|
||||
this.prepare_ws(l.ws);
|
||||
let mut size_hint = this.handle(
|
||||
ctx,
|
||||
nodes,
|
||||
&mut filter_def_buf,
|
||||
AstLevel::Nested,
|
||||
RenderFor::Template,
|
||||
)?;
|
||||
this.flush_ws(ws);
|
||||
size_hint += this.write_buf_writable(ctx, &mut filter_def_buf)?;
|
||||
Ok(size_hint)
|
||||
})?;
|
||||
let filter_def_buf = filter_def_buf.into_token_stream();
|
||||
|
||||
size_hint += self.write_let_target(ctx, buf, l, span)?;
|
||||
buf.write_token(Token![=], span);
|
||||
|
||||
let var_writer = crate::var_writer();
|
||||
let filter_def_buf = quote_spanned!(span=>
|
||||
let #var_let_source = askama::helpers::FmtCell::new(
|
||||
|#var_writer: &mut askama::helpers::core::fmt::Formatter<'_>| -> askama::Result<()> {
|
||||
#filter_def_buf
|
||||
askama::Result::Ok(())
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
// display the `FmtCell`
|
||||
let mut filter_buf = Buffer::new();
|
||||
quote_into!(&mut filter_buf, span, { askama::filters::Safe(&#var_let_source) });
|
||||
let filter_buf = filter_buf.into_token_stream();
|
||||
let escaper = TokenStream::from_str(self.input.escaper).unwrap();
|
||||
let filter_buf = quote_spanned!(span=>
|
||||
(&&askama::filters::AutoEscaper::new(
|
||||
&(#filter_buf), #escaper
|
||||
)).askama_auto_escape()?
|
||||
);
|
||||
quote_into!(buf, span, { {
|
||||
#filter_def_buf
|
||||
let mut __askama_tmp_write = String::new();
|
||||
if askama::helpers::core::write!(&mut __askama_tmp_write, "{}", #filter_buf).is_err() {
|
||||
return #var_let_source.take_err();
|
||||
}
|
||||
__askama_tmp_write
|
||||
}; });
|
||||
|
||||
self.is_in_block.decrease();
|
||||
self.prepare_ws(ws);
|
||||
Ok(size_hint)
|
||||
}
|
||||
|
||||
fn write_let_value(
|
||||
&mut self,
|
||||
ctx: &Context<'_>,
|
||||
buf: &mut Buffer,
|
||||
l: &'a WithSpan<Let<'_>>,
|
||||
val: &WithSpan<Box<Expr<'a>>>,
|
||||
) -> Result<SizeHint, CompileError> {
|
||||
let span = ctx.span_for_node(l.span());
|
||||
// Handle when this statement creates a new alias of a caller variable (or of another alias),
|
||||
if let Target::Name(dstvar) = l.var
|
||||
&& let Expr::Var(srcvar) = ***val
|
||||
&& let Some(caller_alias) = self.locals.get_caller(srcvar)
|
||||
{
|
||||
self.locals.insert(
|
||||
Cow::Borrowed(*dstvar),
|
||||
LocalMeta::CallerAlias(caller_alias.clone()),
|
||||
);
|
||||
return Ok(SizeHint::EMPTY);
|
||||
}
|
||||
|
||||
let mut expr_buf = Buffer::new();
|
||||
self.visit_expr(ctx, &mut expr_buf, val)?;
|
||||
|
||||
let size_hint = self.write_let_target(ctx, buf, l, span)?;
|
||||
|
||||
// If it's not taking the ownership of a local variable or copyable, then we need to add
|
||||
// a reference.
|
||||
let borrow = !matches!(***val, Expr::Try(..))
|
||||
@ -1036,8 +1112,14 @@ impl<'a> Generator<'a, '_> {
|
||||
outer: Ws,
|
||||
node: Span,
|
||||
) -> Result<SizeHint, CompileError> {
|
||||
if self.is_in_filter_block > 0 {
|
||||
return Err(ctx.generate_error("cannot have a block inside a filter block", node));
|
||||
if self.is_in_block.level > 0 {
|
||||
return Err(ctx.generate_error(
|
||||
format!(
|
||||
"cannot have a block inside a {} block",
|
||||
self.is_in_block.block_name
|
||||
),
|
||||
node,
|
||||
));
|
||||
}
|
||||
// Flush preceding whitespace according to the outer WS spec
|
||||
self.flush_ws(outer);
|
||||
|
||||
@ -753,6 +753,10 @@ fn var_filter_source() -> Ident {
|
||||
syn::Ident::new("__askama_filter_block", proc_macro2::Span::call_site())
|
||||
}
|
||||
|
||||
fn var_let_source() -> Ident {
|
||||
syn::Ident::new("__askama_let_block", proc_macro2::Span::call_site())
|
||||
}
|
||||
|
||||
fn var_values() -> Ident {
|
||||
syn::Ident::new("__askama_values", proc_macro2::Span::call_site())
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ use winnow::{LocatingSlice, ModalParser, ModalResult, Parser, Stateful};
|
||||
|
||||
use crate::ascii_str::{AsciiChar, AsciiStr};
|
||||
pub use crate::expr::{AssociatedItem, Expr, Filter, PathComponent, TyGenerics};
|
||||
pub use crate::node::Node;
|
||||
pub use crate::node::{LetValueOrBlock, Node};
|
||||
pub use crate::target::{NamedTarget, Target};
|
||||
|
||||
mod _parsed {
|
||||
|
||||
@ -1239,11 +1239,17 @@ impl<'a: 'l, 'l> Declare<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum LetValueOrBlock<'a> {
|
||||
Value(WithSpan<Box<Expr<'a>>>),
|
||||
Block { nodes: Vec<Box<Node<'a>>>, ws: Ws },
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Let<'a> {
|
||||
pub ws: Ws,
|
||||
pub var: Target<'a>,
|
||||
pub val: Option<WithSpan<Box<Expr<'a>>>>,
|
||||
pub val: LetValueOrBlock<'a>,
|
||||
pub is_mutable: bool,
|
||||
}
|
||||
|
||||
@ -1322,11 +1328,57 @@ impl<'a: 'l, 'l> Let<'a> {
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(val) = val {
|
||||
return Ok(Box::new(Node::Let(WithSpan::new(
|
||||
Let {
|
||||
ws: Ws(pws, nws),
|
||||
var,
|
||||
val: LetValueOrBlock::Value(val),
|
||||
is_mutable: is_mut.is_some(),
|
||||
},
|
||||
span,
|
||||
))));
|
||||
}
|
||||
|
||||
// We do this operation
|
||||
if block_end.parse_next(i).is_err() {
|
||||
return Err(
|
||||
ErrorContext::unclosed("block", i.state.syntax.block_end, Span::new(span)).cut(),
|
||||
);
|
||||
}
|
||||
|
||||
let (keyword, end_keyword) = if tag == "let" {
|
||||
("let", "endlet")
|
||||
} else {
|
||||
("set", "endset")
|
||||
};
|
||||
|
||||
let keyword_span = Span::new(span.clone());
|
||||
let mut end = cut_node(
|
||||
Some(keyword),
|
||||
(
|
||||
Node::many,
|
||||
cut_node(
|
||||
Some(keyword),
|
||||
(
|
||||
|i: &mut _| check_block_start(i, keyword_span, keyword, end_keyword),
|
||||
opt(Whitespace::parse),
|
||||
end_node(keyword, end_keyword),
|
||||
opt(Whitespace::parse),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
let (nodes, (_, pws2, _, nws2)) = end.parse_next(i)?;
|
||||
|
||||
Ok(Box::new(Node::Let(WithSpan::new(
|
||||
Let {
|
||||
ws: Ws(pws, nws),
|
||||
var,
|
||||
val,
|
||||
val: LetValueOrBlock::Block {
|
||||
nodes,
|
||||
ws: Ws(pws2, nws2),
|
||||
},
|
||||
is_mutable: is_mut.is_some(),
|
||||
},
|
||||
span,
|
||||
|
||||
@ -5,8 +5,8 @@ use winnow::{LocatingSlice, Parser};
|
||||
use crate::expr::BinOp;
|
||||
use crate::node::{Let, Lit, Raw, Whitespace, Ws};
|
||||
use crate::{
|
||||
Ast, Expr, Filter, InnerSyntax, InputStream, Level, Node, Num, PathComponent, PathOrIdentifier,
|
||||
State, StrLit, Syntax, SyntaxBuilder, Target, WithSpan,
|
||||
Ast, Expr, Filter, InnerSyntax, InputStream, LetValueOrBlock, Level, Node, Num, PathComponent,
|
||||
PathOrIdentifier, State, StrLit, Syntax, SyntaxBuilder, Target, WithSpan,
|
||||
};
|
||||
|
||||
fn as_path<'a>(path: &'a [&'a str]) -> Vec<PathComponent<'a>> {
|
||||
@ -1085,8 +1085,12 @@ fn fuzzed_comment_depth() {
|
||||
fn let_set() {
|
||||
let syntax = Syntax::default();
|
||||
assert_eq!(
|
||||
Ast::from_str("{% let a %}", None, &syntax).unwrap().nodes(),
|
||||
Ast::from_str("{% set a %}", None, &syntax).unwrap().nodes(),
|
||||
Ast::from_str("{% let a = 1 %}", None, &syntax)
|
||||
.unwrap()
|
||||
.nodes(),
|
||||
Ast::from_str("{% set a = 1 %}", None, &syntax)
|
||||
.unwrap()
|
||||
.nodes(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1655,7 +1659,9 @@ fn regression_tests_span_change() {
|
||||
var: Target::Array(WithSpan::no_span(vec![Target::Placeholder(
|
||||
WithSpan::no_span(())
|
||||
)])),
|
||||
val: Some(WithSpan::no_span(Box::new(Expr::Array(vec![int_lit("2")])))),
|
||||
val: LetValueOrBlock::Value(WithSpan::no_span(Box::new(Expr::Array(vec![int_lit(
|
||||
"2"
|
||||
)])))),
|
||||
is_mutable: false,
|
||||
})))],
|
||||
);
|
||||
@ -1667,7 +1673,9 @@ fn regression_tests_span_change() {
|
||||
[Box::new(Node::Let(WithSpan::no_span(Let {
|
||||
ws: Ws(Some(Whitespace::Suppress), Some(Whitespace::Suppress)),
|
||||
var: Target::Placeholder(WithSpan::no_span(())),
|
||||
val: Some(WithSpan::no_span(Box::new(Expr::Array(vec![int_lit("2")])))),
|
||||
val: LetValueOrBlock::Value(WithSpan::no_span(Box::new(Expr::Array(vec![int_lit(
|
||||
"2"
|
||||
)])))),
|
||||
is_mutable: false,
|
||||
})))],
|
||||
);
|
||||
|
||||
@ -91,6 +91,16 @@ you need it to be mutable #}
|
||||
|
||||
For compatibility with Jinja, `set` can be used in place of `let`.
|
||||
|
||||
### Let/set blocks
|
||||
|
||||
You can create a variable and initialize it with a block computed string:
|
||||
|
||||
```jinja
|
||||
{% let x %}
|
||||
{{ crate::some_function() }} = {{ a * b}}
|
||||
{% endlet %}
|
||||
```
|
||||
|
||||
### Set variable values later
|
||||
|
||||
If you want to create a variable but set its value based on a condition, you can
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{% let val -%}
|
||||
{% decl val -%}
|
||||
{% if cond -%}
|
||||
{% let val = "foo" -%}
|
||||
{% else -%}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{%- let a = 1 -%}
|
||||
{%- let b -%}
|
||||
{%- decl b -%}
|
||||
|
||||
{%- if cond -%}
|
||||
{%- let b = 22 -%}
|
||||
|
||||
@ -535,7 +535,7 @@ fn test_default_with_forward_declaration() {
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "\
|
||||
{% let var %}\
|
||||
{% decl var %}\
|
||||
{{ var | default(\"unknown\") }}\
|
||||
{% if true %}{% let var = 42 %}{% endif %}",
|
||||
ext = "html"
|
||||
@ -547,7 +547,7 @@ fn test_default_with_forward_declaration() {
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "\
|
||||
{% let var %}{# shadowing happens here #}\
|
||||
{% decl var %}{# shadowing happens here #}\
|
||||
{{ var | default(\"unknown\") }}\
|
||||
{% if true %}{% let var = 42 %}{% endif %}",
|
||||
ext = "html"
|
||||
@ -568,7 +568,7 @@ fn test_defined_or_with_forward_declaration() {
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "\
|
||||
{% let var %}\
|
||||
{% decl var %}\
|
||||
{{ var | defined_or(\"unknown\") }}\
|
||||
{% if true %}{% let var = 42 %}{% endif %}",
|
||||
ext = "html"
|
||||
@ -580,7 +580,7 @@ fn test_defined_or_with_forward_declaration() {
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "\
|
||||
{% let var %}{# shadowing happens here #}\
|
||||
{% decl var %}{# shadowing happens here #}\
|
||||
{{ var | defined_or(\"unknown\") }}\
|
||||
{% if true %}{% let var = 42 %}{% endif %}",
|
||||
ext = "html"
|
||||
|
||||
@ -5,7 +5,7 @@ use askama::Template;
|
||||
fn let_macro() {
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = r#"{%- let x -%}
|
||||
source = r#"{%- decl x -%}
|
||||
{%- if y -%}
|
||||
{%- let x = String::new() %}
|
||||
{%- else -%}
|
||||
@ -53,3 +53,20 @@ fn underscore_ident2() {
|
||||
|
||||
assert_eq!(X.render().unwrap(), "hey\nhoy\nmatched");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_block() {
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = r#"
|
||||
{%- set navigation %}{{b}}: c{% endset -%}
|
||||
{{ navigation -}}
|
||||
"#,
|
||||
ext = "txt"
|
||||
)]
|
||||
struct Foo {
|
||||
b: u32,
|
||||
}
|
||||
|
||||
assert_eq!(Foo { b: 0 }.render().unwrap(), "0: c");
|
||||
}
|
||||
|
||||
@ -17,5 +17,22 @@ use askama::Template;
|
||||
)]
|
||||
struct BlockInFilter;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = r#"{% extends "html-base.html" %}
|
||||
|
||||
{%- block body -%}
|
||||
<h1>Metadata</h1>
|
||||
|
||||
{% let x %}
|
||||
{% block title %}New title{% endblock %}
|
||||
a b
|
||||
{% endlet %}
|
||||
{%- endblock body %}
|
||||
"#,
|
||||
ext = "html"
|
||||
)]
|
||||
struct BlockInLetBlock;
|
||||
|
||||
fn main() {
|
||||
}
|
||||
|
||||
@ -12,3 +12,18 @@ error: cannot have a block inside a filter block
|
||||
14 | | {%- endblock body %}
|
||||
15 | | "#,
|
||||
| |__^
|
||||
|
||||
error: cannot have a block inside a let/set block
|
||||
--> BlockInLetBlock.html:7:11
|
||||
"block title %}New title{% endblock %}\n a b\n {% endlet %}\n{%- endblock "...
|
||||
--> tests/ui/block_in_filter_block.rs:22:14
|
||||
|
|
||||
22 | source = r#"{% extends "html-base.html" %}
|
||||
| ______________^
|
||||
23 | |
|
||||
24 | | {%- block body -%}
|
||||
25 | | <h1>Metadata</h1>
|
||||
... |
|
||||
31 | | {%- endblock body %}
|
||||
32 | | "#,
|
||||
| |__^
|
||||
|
||||
@ -32,6 +32,18 @@ struct Node3;
|
||||
#[template(source = "{% let x -%", ext = "txt")]
|
||||
struct Node4;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(source = "{% let x %}{% endlet", ext = "txt")]
|
||||
struct Node5;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(source = "{% let x %}{% endset %}", ext = "txt")]
|
||||
struct Node6;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(source = "{% set x %}{% endlet %}", ext = "txt")]
|
||||
struct Node7;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(source = "{# comment", ext = "txt")]
|
||||
struct Comment1;
|
||||
|
||||
@ -31,65 +31,89 @@ error: failed to parse template source
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
error: unclosed block, missing "%}"
|
||||
--> <source attribute>:1:0
|
||||
"{% let x"
|
||||
--> <source attribute>:1:3
|
||||
"let x"
|
||||
--> tests/ui/unclosed-nodes.rs:20:21
|
||||
|
|
||||
20 | #[template(source = "{% let x", ext = "txt")]
|
||||
| ^^^^^^^^^^
|
||||
|
||||
error: unclosed block, missing "%}"
|
||||
--> <source attribute>:1:0
|
||||
"{% let x "
|
||||
--> <source attribute>:1:3
|
||||
"let x "
|
||||
--> tests/ui/unclosed-nodes.rs:24:21
|
||||
|
|
||||
24 | #[template(source = "{% let x ", ext = "txt")]
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
error: unclosed block, missing "%}"
|
||||
--> <source attribute>:1:0
|
||||
"{% let x -"
|
||||
--> <source attribute>:1:3
|
||||
"let x -"
|
||||
--> tests/ui/unclosed-nodes.rs:28:21
|
||||
|
|
||||
28 | #[template(source = "{% let x -", ext = "txt")]
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
error: failed to parse template source
|
||||
--> <source attribute>:1:10
|
||||
"%"
|
||||
error: unclosed block, missing "%}"
|
||||
--> <source attribute>:1:3
|
||||
"let x -%"
|
||||
--> tests/ui/unclosed-nodes.rs:32:21
|
||||
|
|
||||
32 | #[template(source = "{% let x -%", ext = "txt")]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: unclosed block, missing "%}"
|
||||
--> <source attribute>:1:0
|
||||
"{% let x %}{% endlet"
|
||||
--> tests/ui/unclosed-nodes.rs:36:21
|
||||
|
|
||||
36 | #[template(source = "{% let x %}{% endlet", ext = "txt")]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: expected `endlet` to terminate `let` node, found `endset`
|
||||
--> <source attribute>:1:14
|
||||
"endset %}"
|
||||
--> tests/ui/unclosed-nodes.rs:40:21
|
||||
|
|
||||
40 | #[template(source = "{% let x %}{% endset %}", ext = "txt")]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: expected `endset` to terminate `set` node, found `endlet`
|
||||
--> <source attribute>:1:14
|
||||
"endlet %}"
|
||||
--> tests/ui/unclosed-nodes.rs:44:21
|
||||
|
|
||||
44 | #[template(source = "{% set x %}{% endlet %}", ext = "txt")]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unclosed comment, missing "#}"
|
||||
--> <source attribute>:1:0
|
||||
"{# comment"
|
||||
--> tests/ui/unclosed-nodes.rs:36:21
|
||||
--> tests/ui/unclosed-nodes.rs:48:21
|
||||
|
|
||||
36 | #[template(source = "{# comment", ext = "txt")]
|
||||
48 | #[template(source = "{# comment", ext = "txt")]
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
error: unclosed comment, missing "#}"
|
||||
--> <source attribute>:1:0
|
||||
"{# comment "
|
||||
--> tests/ui/unclosed-nodes.rs:40:21
|
||||
--> tests/ui/unclosed-nodes.rs:52:21
|
||||
|
|
||||
40 | #[template(source = "{# comment ", ext = "txt")]
|
||||
52 | #[template(source = "{# comment ", ext = "txt")]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: unclosed comment, missing "#}"
|
||||
--> <source attribute>:1:0
|
||||
"{# comment -"
|
||||
--> tests/ui/unclosed-nodes.rs:44:21
|
||||
--> tests/ui/unclosed-nodes.rs:56:21
|
||||
|
|
||||
44 | #[template(source = "{# comment -", ext = "txt")]
|
||||
56 | #[template(source = "{# comment -", ext = "txt")]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: unclosed comment, missing "#}"
|
||||
--> <source attribute>:1:0
|
||||
"{# comment -#"
|
||||
--> tests/ui/unclosed-nodes.rs:48:21
|
||||
--> tests/ui/unclosed-nodes.rs:60:21
|
||||
|
|
||||
48 | #[template(source = "{# comment -#", ext = "txt")]
|
||||
60 | #[template(source = "{# comment -#", ext = "txt")]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
||||
@ -30,14 +30,6 @@ error: node `when` was not expected in the current context
|
||||
34 | /// ```askama
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: unexpected closing tag `endlet`
|
||||
--> <source attribute>:1:21
|
||||
"endlet %}"
|
||||
--> tests/ui/unexpected-tag.rs:43:1
|
||||
|
|
||||
43 | /// ```askama
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: unknown node `syntax`
|
||||
--> <source attribute>:1:3
|
||||
"syntax error %}"
|
||||
|
||||
@ -123,7 +123,7 @@ fn test_decl_range() {
|
||||
fn test_decl_assign_range() {
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "{% let x %}{% let x = 1 %}{% for x in x..=x %}{{ x }}{% endfor %}",
|
||||
source = "{% decl x %}{% let x = 1 %}{% for x in x..=x %}{{ x }}{% endfor %}",
|
||||
ext = "txt"
|
||||
)]
|
||||
struct DeclAssignRange;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user