mirror of
https://github.com/askama-rs/askama.git
synced 2025-09-30 06:21:13 +00:00
generator: add named arguments for filters
This commit is contained in:
parent
6f8de0ca84
commit
b402936db3
@ -65,6 +65,7 @@ impl<'a> Generator<'a, '_> {
|
|||||||
Expr::As(ref expr, target) => self.visit_as(ctx, buf, expr, target)?,
|
Expr::As(ref expr, target) => self.visit_as(ctx, buf, expr, target)?,
|
||||||
Expr::Concat(ref exprs) => self.visit_concat(ctx, buf, exprs)?,
|
Expr::Concat(ref exprs) => self.visit_concat(ctx, buf, exprs)?,
|
||||||
Expr::LetCond(ref cond) => self.visit_let_cond(ctx, buf, cond)?,
|
Expr::LetCond(ref cond) => self.visit_let_cond(ctx, buf, cond)?,
|
||||||
|
Expr::ArgumentPlaceholder => DisplayWrap::Unwrapped,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::fmt::{self, Write};
|
||||||
|
use std::mem::replace;
|
||||||
|
|
||||||
use parser::{Expr, IntKind, Num, Span, StrLit, StrPrefix, TyGenerics, WithSpan};
|
use parser::{Expr, IntKind, Num, Span, StrLit, StrPrefix, TyGenerics, WithSpan};
|
||||||
|
|
||||||
use super::{DisplayWrap, Generator, TargetIsize, TargetUsize};
|
use super::{DisplayWrap, Generator, TargetIsize, TargetUsize};
|
||||||
use crate::heritage::Context;
|
use crate::heritage::Context;
|
||||||
use crate::integration::Buffer;
|
use crate::integration::Buffer;
|
||||||
use crate::{CompileError, MsgValidEscapers};
|
use crate::{CompileError, MsgValidEscapers, fmt_left, fmt_right};
|
||||||
|
|
||||||
impl<'a> Generator<'a, '_> {
|
impl<'a> Generator<'a, '_> {
|
||||||
pub(super) fn visit_filter(
|
pub(super) fn visit_filter(
|
||||||
@ -60,8 +62,9 @@ impl<'a> Generator<'a, '_> {
|
|||||||
name: &str,
|
name: &str,
|
||||||
args: &[WithSpan<'a, Expr<'a>>],
|
args: &[WithSpan<'a, Expr<'a>>],
|
||||||
generics: &[WithSpan<'a, TyGenerics<'a>>],
|
generics: &[WithSpan<'a, TyGenerics<'a>>],
|
||||||
_node: Span<'_>,
|
node: Span<'_>,
|
||||||
) -> Result<DisplayWrap, CompileError> {
|
) -> Result<DisplayWrap, CompileError> {
|
||||||
|
ensure_no_named_arguments(ctx, name, args, node)?;
|
||||||
buf.write(format_args!("filters::{name}"));
|
buf.write(format_args!("filters::{name}"));
|
||||||
self.visit_call_generics(buf, generics);
|
self.visit_call_generics(buf, generics);
|
||||||
buf.write('(');
|
buf.write('(');
|
||||||
@ -111,10 +114,11 @@ impl<'a> Generator<'a, '_> {
|
|||||||
node: Span<'_>,
|
node: Span<'_>,
|
||||||
) -> Result<DisplayWrap, CompileError> {
|
) -> Result<DisplayWrap, CompileError> {
|
||||||
ensure_no_generics(ctx, name, node, generics)?;
|
ensure_no_generics(ctx, name, node, generics)?;
|
||||||
|
let arg = no_arguments(ctx, name, args)?;
|
||||||
buf.write(format_args!("askama::filters::{name}"));
|
buf.write(format_args!("askama::filters::{name}"));
|
||||||
self.visit_call_generics(buf, generics);
|
self.visit_call_generics(buf, generics);
|
||||||
buf.write('(');
|
buf.write('(');
|
||||||
self.visit_args(ctx, buf, args)?;
|
self.visit_arg(ctx, buf, arg)?;
|
||||||
buf.write(")?");
|
buf.write(")?");
|
||||||
Ok(DisplayWrap::Unwrapped)
|
Ok(DisplayWrap::Unwrapped)
|
||||||
}
|
}
|
||||||
@ -154,11 +158,12 @@ impl<'a> Generator<'a, '_> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let arg = no_arguments(ctx, name, args)?;
|
||||||
// Both filters return HTML-safe strings.
|
// Both filters return HTML-safe strings.
|
||||||
buf.write(format_args!(
|
buf.write(format_args!(
|
||||||
"askama::filters::HtmlSafeOutput(askama::filters::{name}(",
|
"askama::filters::HtmlSafeOutput(askama::filters::{name}(",
|
||||||
));
|
));
|
||||||
self.visit_args(ctx, buf, args)?;
|
self.visit_arg(ctx, buf, arg)?;
|
||||||
buf.write(")?)");
|
buf.write(")?)");
|
||||||
Ok(DisplayWrap::Unwrapped)
|
Ok(DisplayWrap::Unwrapped)
|
||||||
}
|
}
|
||||||
@ -171,15 +176,10 @@ impl<'a> Generator<'a, '_> {
|
|||||||
node: Span<'_>,
|
node: Span<'_>,
|
||||||
) -> Result<DisplayWrap, CompileError> {
|
) -> Result<DisplayWrap, CompileError> {
|
||||||
ensure_filter_has_feature_alloc(ctx, "wordcount", node)?;
|
ensure_filter_has_feature_alloc(ctx, "wordcount", node)?;
|
||||||
if args.len() != 1 {
|
|
||||||
return Err(ctx.generate_error(
|
|
||||||
format_args!("unexpected argument(s) in `wordcount` filter"),
|
|
||||||
node,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let arg = no_arguments(ctx, "wordcount", args)?;
|
||||||
buf.write("match askama::filters::wordcount(&(");
|
buf.write("match askama::filters::wordcount(&(");
|
||||||
self.visit_args(ctx, buf, args)?;
|
self.visit_arg(ctx, buf, arg)?;
|
||||||
buf.write(
|
buf.write(
|
||||||
")) {\
|
")) {\
|
||||||
expr0 => {\
|
expr0 => {\
|
||||||
@ -201,12 +201,13 @@ impl<'a> Generator<'a, '_> {
|
|||||||
args: &[WithSpan<'a, Expr<'a>>],
|
args: &[WithSpan<'a, Expr<'a>>],
|
||||||
_node: Span<'_>,
|
_node: Span<'_>,
|
||||||
) -> Result<DisplayWrap, CompileError> {
|
) -> Result<DisplayWrap, CompileError> {
|
||||||
|
let arg = no_arguments(ctx, "humansize", args)?;
|
||||||
// All filters return numbers, and any default formatted number is HTML safe.
|
// All filters return numbers, and any default formatted number is HTML safe.
|
||||||
buf.write(format_args!(
|
buf.write(format_args!(
|
||||||
"askama::filters::HtmlSafeOutput(askama::filters::filesizeformat(\
|
"askama::filters::HtmlSafeOutput(askama::filters::filesizeformat(\
|
||||||
askama::helpers::get_primitive_value(&("
|
askama::helpers::get_primitive_value(&("
|
||||||
));
|
));
|
||||||
self.visit_args(ctx, buf, args)?;
|
self.visit_arg(ctx, buf, arg)?;
|
||||||
buf.write(")) as askama::helpers::core::primitive::f32)?)");
|
buf.write(")) as askama::helpers::core::primitive::f32)?)");
|
||||||
Ok(DisplayWrap::Unwrapped)
|
Ok(DisplayWrap::Unwrapped)
|
||||||
}
|
}
|
||||||
@ -228,17 +229,20 @@ impl<'a> Generator<'a, '_> {
|
|||||||
prefix: None,
|
prefix: None,
|
||||||
content: "s",
|
content: "s",
|
||||||
}));
|
}));
|
||||||
|
const ARGUMENTS: &[&FilterArgument; 3] = &[
|
||||||
|
FILTER_SOURCE,
|
||||||
|
&FilterArgument {
|
||||||
|
name: "sg",
|
||||||
|
default_value: Some(SINGULAR),
|
||||||
|
},
|
||||||
|
&FilterArgument {
|
||||||
|
name: "pl",
|
||||||
|
default_value: Some(PLURAL),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let [count, sg, pl] = collect_filter_args(ctx, "pluralize", node, args, ARGUMENTS)?;
|
||||||
|
|
||||||
let (count, sg, pl) = match args {
|
|
||||||
[count] => (count, SINGULAR, PLURAL),
|
|
||||||
[count, sg] => (count, sg, PLURAL),
|
|
||||||
[count, sg, pl] => (count, sg, pl),
|
|
||||||
_ => {
|
|
||||||
return Err(
|
|
||||||
ctx.generate_error("unexpected argument(s) in `pluralize` filter", node)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Some(is_singular) = expr_is_int_lit_plus_minus_one(count) {
|
if let Some(is_singular) = expr_is_int_lit_plus_minus_one(count) {
|
||||||
let value = if is_singular { sg } else { pl };
|
let value = if is_singular { sg } else { pl };
|
||||||
self.visit_auto_escaped_arg(ctx, buf, value)?;
|
self.visit_auto_escaped_arg(ctx, buf, value)?;
|
||||||
@ -293,16 +297,11 @@ impl<'a> Generator<'a, '_> {
|
|||||||
node: Span<'_>,
|
node: Span<'_>,
|
||||||
) -> Result<DisplayWrap, CompileError> {
|
) -> Result<DisplayWrap, CompileError> {
|
||||||
ensure_filter_has_feature_alloc(ctx, name, node)?;
|
ensure_filter_has_feature_alloc(ctx, name, node)?;
|
||||||
if args.len() != 1 {
|
let arg = no_arguments(ctx, name, args)?;
|
||||||
return Err(ctx.generate_error(
|
|
||||||
format_args!("unexpected argument(s) in `{name}` filter"),
|
|
||||||
node,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
buf.write(format_args!(
|
buf.write(format_args!(
|
||||||
"askama::filters::{name}(&(&&askama::filters::AutoEscaper::new(&(",
|
"askama::filters::{name}(&(&&askama::filters::AutoEscaper::new(&(",
|
||||||
));
|
));
|
||||||
self.visit_args(ctx, buf, args)?;
|
self.visit_arg(ctx, buf, arg)?;
|
||||||
// The input is always HTML escaped, regardless of the selected escaper:
|
// The input is always HTML escaped, regardless of the selected escaper:
|
||||||
buf.write("), askama::filters::Html)).askama_auto_escape()?)?");
|
buf.write("), askama::filters::Html)).askama_auto_escape()?)?");
|
||||||
// The output is marked as HTML safe, not safe in all contexts:
|
// The output is marked as HTML safe, not safe in all contexts:
|
||||||
@ -314,12 +313,9 @@ impl<'a> Generator<'a, '_> {
|
|||||||
ctx: &Context<'_>,
|
ctx: &Context<'_>,
|
||||||
buf: &mut Buffer,
|
buf: &mut Buffer,
|
||||||
args: &[WithSpan<'a, Expr<'a>>],
|
args: &[WithSpan<'a, Expr<'a>>],
|
||||||
node: Span<'_>,
|
_node: Span<'_>,
|
||||||
) -> Result<DisplayWrap, CompileError> {
|
) -> Result<DisplayWrap, CompileError> {
|
||||||
let arg = match args {
|
let arg = no_arguments(ctx, "ref", args)?;
|
||||||
[arg] => arg,
|
|
||||||
_ => return Err(ctx.generate_error("unexpected argument(s) in `as_ref` filter", node)),
|
|
||||||
};
|
|
||||||
buf.write('&');
|
buf.write('&');
|
||||||
self.visit_expr(ctx, buf, arg)?;
|
self.visit_expr(ctx, buf, arg)?;
|
||||||
Ok(DisplayWrap::Unwrapped)
|
Ok(DisplayWrap::Unwrapped)
|
||||||
@ -330,12 +326,9 @@ impl<'a> Generator<'a, '_> {
|
|||||||
ctx: &Context<'_>,
|
ctx: &Context<'_>,
|
||||||
buf: &mut Buffer,
|
buf: &mut Buffer,
|
||||||
args: &[WithSpan<'a, Expr<'a>>],
|
args: &[WithSpan<'a, Expr<'a>>],
|
||||||
node: Span<'_>,
|
_node: Span<'_>,
|
||||||
) -> Result<DisplayWrap, CompileError> {
|
) -> Result<DisplayWrap, CompileError> {
|
||||||
let arg = match args {
|
let arg = no_arguments(ctx, "deref", args)?;
|
||||||
[arg] => arg,
|
|
||||||
_ => return Err(ctx.generate_error("unexpected argument(s) in `deref` filter", node)),
|
|
||||||
};
|
|
||||||
buf.write('*');
|
buf.write('*');
|
||||||
self.visit_expr(ctx, buf, arg)?;
|
self.visit_expr(ctx, buf, arg)?;
|
||||||
Ok(DisplayWrap::Unwrapped)
|
Ok(DisplayWrap::Unwrapped)
|
||||||
@ -348,6 +341,14 @@ impl<'a> Generator<'a, '_> {
|
|||||||
args: &[WithSpan<'a, Expr<'a>>],
|
args: &[WithSpan<'a, Expr<'a>>],
|
||||||
node: Span<'_>,
|
node: Span<'_>,
|
||||||
) -> Result<DisplayWrap, CompileError> {
|
) -> Result<DisplayWrap, CompileError> {
|
||||||
|
const ARGUMENTS: &[&FilterArgument; 2] = &[
|
||||||
|
FILTER_SOURCE,
|
||||||
|
&FilterArgument {
|
||||||
|
name: "indent",
|
||||||
|
default_value: Some(ARGUMENT_PLACEHOLDER),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
if cfg!(not(feature = "serde_json")) {
|
if cfg!(not(feature = "serde_json")) {
|
||||||
return Err(ctx.generate_error(
|
return Err(ctx.generate_error(
|
||||||
"the `json` filter requires the `serde_json` feature to be enabled",
|
"the `json` filter requires the `serde_json` feature to be enabled",
|
||||||
@ -355,14 +356,18 @@ impl<'a> Generator<'a, '_> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let filter = match args.len() {
|
let [value, indent] = collect_filter_args(ctx, "json", node, args, ARGUMENTS)?;
|
||||||
1 => "json",
|
if is_argument_placeholder(indent) {
|
||||||
2 => "json_pretty",
|
buf.write(format_args!("askama::filters::json("));
|
||||||
_ => return Err(ctx.generate_error("unexpected argument(s) in `json` filter", node)),
|
self.visit_arg(ctx, buf, value)?;
|
||||||
};
|
buf.write(")?");
|
||||||
buf.write(format_args!("askama::filters::{filter}("));
|
} else {
|
||||||
self.visit_args(ctx, buf, args)?;
|
buf.write(format_args!("askama::filters::json_pretty("));
|
||||||
buf.write(")?");
|
self.visit_arg(ctx, buf, value)?;
|
||||||
|
buf.write(',');
|
||||||
|
self.visit_arg(ctx, buf, indent)?;
|
||||||
|
buf.write(")?");
|
||||||
|
}
|
||||||
Ok(DisplayWrap::Unwrapped)
|
Ok(DisplayWrap::Unwrapped)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,18 +380,25 @@ impl<'a> Generator<'a, '_> {
|
|||||||
) -> Result<DisplayWrap, CompileError> {
|
) -> Result<DisplayWrap, CompileError> {
|
||||||
const FALSE: &WithSpan<'static, Expr<'static>> =
|
const FALSE: &WithSpan<'static, Expr<'static>> =
|
||||||
&WithSpan::new_without_span(Expr::BoolLit(false));
|
&WithSpan::new_without_span(Expr::BoolLit(false));
|
||||||
|
const ARGUMENTS: &[&FilterArgument; 4] = &[
|
||||||
|
FILTER_SOURCE,
|
||||||
|
&FilterArgument {
|
||||||
|
name: "width",
|
||||||
|
default_value: None,
|
||||||
|
},
|
||||||
|
&FilterArgument {
|
||||||
|
name: "first",
|
||||||
|
default_value: Some(FALSE),
|
||||||
|
},
|
||||||
|
&FilterArgument {
|
||||||
|
name: "blank",
|
||||||
|
default_value: Some(FALSE),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
ensure_filter_has_feature_alloc(ctx, "indent", node)?;
|
ensure_filter_has_feature_alloc(ctx, "indent", node)?;
|
||||||
let (source, indent, first, blank) =
|
let [source, indent, first, blank] =
|
||||||
match args {
|
collect_filter_args(ctx, "indent", node, args, ARGUMENTS)?;
|
||||||
[source, indent] => (source, indent, FALSE, FALSE),
|
|
||||||
[source, indent, first] => (source, indent, first, FALSE),
|
|
||||||
[source, indent, first, blank] => (source, indent, first, blank),
|
|
||||||
_ => return Err(ctx.generate_error(
|
|
||||||
"filter `indent` needs a `width` argument, and can have two optional arguments",
|
|
||||||
node,
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
buf.write("askama::filters::indent(");
|
buf.write("askama::filters::indent(");
|
||||||
self.visit_arg(ctx, buf, source)?;
|
self.visit_arg(ctx, buf, source)?;
|
||||||
buf.write(",");
|
buf.write(",");
|
||||||
@ -404,13 +416,11 @@ impl<'a> Generator<'a, '_> {
|
|||||||
ctx: &Context<'_>,
|
ctx: &Context<'_>,
|
||||||
buf: &mut Buffer,
|
buf: &mut Buffer,
|
||||||
args: &[WithSpan<'a, Expr<'a>>],
|
args: &[WithSpan<'a, Expr<'a>>],
|
||||||
node: Span<'_>,
|
_node: Span<'_>,
|
||||||
) -> Result<DisplayWrap, CompileError> {
|
) -> Result<DisplayWrap, CompileError> {
|
||||||
if args.len() != 1 {
|
let arg = no_arguments(ctx, "safe", args)?;
|
||||||
return Err(ctx.generate_error("unexpected argument(s) in `safe` filter", node));
|
|
||||||
}
|
|
||||||
buf.write("askama::filters::safe(");
|
buf.write("askama::filters::safe(");
|
||||||
self.visit_args(ctx, buf, args)?;
|
self.visit_arg(ctx, buf, arg)?;
|
||||||
buf.write(format_args!(", {})?", self.input.escaper));
|
buf.write(format_args!(", {})?", self.input.escaper));
|
||||||
Ok(DisplayWrap::Wrapped)
|
Ok(DisplayWrap::Wrapped)
|
||||||
}
|
}
|
||||||
@ -422,31 +432,37 @@ impl<'a> Generator<'a, '_> {
|
|||||||
args: &[WithSpan<'a, Expr<'a>>],
|
args: &[WithSpan<'a, Expr<'a>>],
|
||||||
node: Span<'_>,
|
node: Span<'_>,
|
||||||
) -> Result<DisplayWrap, CompileError> {
|
) -> Result<DisplayWrap, CompileError> {
|
||||||
if args.len() > 2 {
|
const ARGUMENTS: &[&FilterArgument; 2] = &[
|
||||||
return Err(ctx.generate_error("only two arguments allowed to escape filter", node));
|
FILTER_SOURCE,
|
||||||
}
|
&FilterArgument {
|
||||||
let opt_escaper = match args.get(1).map(|expr| &**expr) {
|
name: "escaper",
|
||||||
Some(Expr::StrLit(StrLit { prefix, content })) => {
|
default_value: Some(ARGUMENT_PLACEHOLDER),
|
||||||
if let Some(prefix) = prefix {
|
},
|
||||||
let kind = if *prefix == StrPrefix::Binary {
|
];
|
||||||
"slice"
|
|
||||||
} else {
|
let [source, opt_escaper] = collect_filter_args(ctx, "escape", node, args, ARGUMENTS)?;
|
||||||
"CStr"
|
let opt_escaper = if !is_argument_placeholder(opt_escaper) {
|
||||||
};
|
let Expr::StrLit(StrLit { prefix, content }) = **opt_escaper else {
|
||||||
return Err(ctx.generate_error(
|
|
||||||
format_args!(
|
|
||||||
"invalid escaper `b{content:?}`. Expected a string, found a {kind}"
|
|
||||||
),
|
|
||||||
args[1].span(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Some(content)
|
|
||||||
}
|
|
||||||
Some(_) => {
|
|
||||||
return Err(ctx.generate_error("invalid escaper type for escape filter", node));
|
return Err(ctx.generate_error("invalid escaper type for escape filter", node));
|
||||||
|
};
|
||||||
|
if let Some(prefix) = prefix {
|
||||||
|
let kind = if prefix == StrPrefix::Binary {
|
||||||
|
"slice"
|
||||||
|
} else {
|
||||||
|
"CStr"
|
||||||
|
};
|
||||||
|
return Err(ctx.generate_error(
|
||||||
|
format_args!(
|
||||||
|
"invalid escaper `b{content:?}`. Expected a string, found a {kind}"
|
||||||
|
),
|
||||||
|
opt_escaper.span(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
None => None,
|
Some(content)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let escaper = match opt_escaper {
|
let escaper = match opt_escaper {
|
||||||
Some(name) => self
|
Some(name) => self
|
||||||
.input
|
.input
|
||||||
@ -461,7 +477,8 @@ impl<'a> Generator<'a, '_> {
|
|||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
ctx.generate_error(
|
ctx.generate_error(
|
||||||
format_args!(
|
format_args!(
|
||||||
"invalid escaper '{name}' for `escape` filter. {}",
|
"invalid escaper `{}` for `escape` filter. {}",
|
||||||
|
name.escape_debug(),
|
||||||
MsgValidEscapers(&self.input.config.escapers),
|
MsgValidEscapers(&self.input.config.escapers),
|
||||||
),
|
),
|
||||||
node,
|
node,
|
||||||
@ -470,7 +487,7 @@ impl<'a> Generator<'a, '_> {
|
|||||||
None => self.input.escaper,
|
None => self.input.escaper,
|
||||||
};
|
};
|
||||||
buf.write("askama::filters::escape(");
|
buf.write("askama::filters::escape(");
|
||||||
self.visit_args(ctx, buf, &args[..1])?;
|
self.visit_arg(ctx, buf, source)?;
|
||||||
buf.write(format_args!(", {escaper})?"));
|
buf.write(format_args!(", {escaper})?"));
|
||||||
Ok(DisplayWrap::Wrapped)
|
Ok(DisplayWrap::Wrapped)
|
||||||
}
|
}
|
||||||
@ -483,6 +500,7 @@ impl<'a> Generator<'a, '_> {
|
|||||||
node: Span<'_>,
|
node: Span<'_>,
|
||||||
) -> Result<DisplayWrap, CompileError> {
|
) -> Result<DisplayWrap, CompileError> {
|
||||||
ensure_filter_has_feature_alloc(ctx, "format", node)?;
|
ensure_filter_has_feature_alloc(ctx, "format", node)?;
|
||||||
|
ensure_no_named_arguments(ctx, "format", args, node)?;
|
||||||
if !args.is_empty() {
|
if !args.is_empty() {
|
||||||
if let Expr::StrLit(ref fmt) = *args[0] {
|
if let Expr::StrLit(ref fmt) = *args[0] {
|
||||||
buf.write("askama::helpers::alloc::format!(");
|
buf.write("askama::helpers::alloc::format!(");
|
||||||
@ -495,7 +513,10 @@ impl<'a> Generator<'a, '_> {
|
|||||||
return Ok(DisplayWrap::Unwrapped);
|
return Ok(DisplayWrap::Unwrapped);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(ctx.generate_error(r#"use filter format like `"a={} b={}"|format(a, b)`"#, node))
|
Err(ctx.generate_error(
|
||||||
|
r#"use `format` filter like `"a={} b={}"|format(a, b)`"#,
|
||||||
|
node,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_fmt_filter(
|
fn visit_fmt_filter(
|
||||||
@ -505,18 +526,25 @@ impl<'a> Generator<'a, '_> {
|
|||||||
args: &[WithSpan<'a, Expr<'a>>],
|
args: &[WithSpan<'a, Expr<'a>>],
|
||||||
node: Span<'_>,
|
node: Span<'_>,
|
||||||
) -> Result<DisplayWrap, CompileError> {
|
) -> Result<DisplayWrap, CompileError> {
|
||||||
|
const ARGUMENTS: &[&FilterArgument; 2] = &[
|
||||||
|
FILTER_SOURCE,
|
||||||
|
&FilterArgument {
|
||||||
|
name: "format",
|
||||||
|
default_value: None,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
ensure_filter_has_feature_alloc(ctx, "fmt", node)?;
|
ensure_filter_has_feature_alloc(ctx, "fmt", node)?;
|
||||||
if let [_, arg2] = args {
|
let [source, fmt] = collect_filter_args(ctx, "fmt", node, args, ARGUMENTS)?;
|
||||||
if let Expr::StrLit(ref fmt) = **arg2 {
|
let Expr::StrLit(ref fmt) = **fmt else {
|
||||||
buf.write("askama::helpers::alloc::format!(");
|
return Err(ctx.generate_error(r#"use `fmt` filter like `value|fmt("{:?}")`"#, node));
|
||||||
self.visit_str_lit(buf, fmt);
|
};
|
||||||
buf.write(',');
|
buf.write("askama::helpers::alloc::format!(");
|
||||||
self.visit_args(ctx, buf, &args[..1])?;
|
self.visit_str_lit(buf, fmt);
|
||||||
buf.write(')');
|
buf.write(',');
|
||||||
return Ok(DisplayWrap::Unwrapped);
|
self.visit_arg(ctx, buf, source)?;
|
||||||
}
|
buf.write(')');
|
||||||
}
|
Ok(DisplayWrap::Unwrapped)
|
||||||
Err(ctx.generate_error(r#"use filter fmt like `value|fmt("{:?}")`"#, node))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force type coercion on first argument to `join` filter (see #39).
|
// Force type coercion on first argument to `join` filter (see #39).
|
||||||
@ -525,18 +553,21 @@ impl<'a> Generator<'a, '_> {
|
|||||||
ctx: &Context<'_>,
|
ctx: &Context<'_>,
|
||||||
buf: &mut Buffer,
|
buf: &mut Buffer,
|
||||||
args: &[WithSpan<'a, Expr<'a>>],
|
args: &[WithSpan<'a, Expr<'a>>],
|
||||||
_node: Span<'_>,
|
node: Span<'_>,
|
||||||
) -> Result<DisplayWrap, CompileError> {
|
) -> Result<DisplayWrap, CompileError> {
|
||||||
buf.write("askama::filters::join((&");
|
const ARGUMENTS: &[&FilterArgument; 2] = &[
|
||||||
for (i, arg) in args.iter().enumerate() {
|
FILTER_SOURCE,
|
||||||
if i > 0 {
|
&FilterArgument {
|
||||||
buf.write(", &");
|
name: "separator",
|
||||||
}
|
default_value: None,
|
||||||
self.visit_expr(ctx, buf, arg)?;
|
},
|
||||||
if i == 0 {
|
];
|
||||||
buf.write(").into_iter()");
|
|
||||||
}
|
let [iterable, separator] = collect_filter_args(ctx, "join", node, args, ARGUMENTS)?;
|
||||||
}
|
buf.write("askama::filters::join((&(");
|
||||||
|
self.visit_arg(ctx, buf, iterable)?;
|
||||||
|
buf.write(")).into_iter(),");
|
||||||
|
self.visit_arg(ctx, buf, separator)?;
|
||||||
buf.write(")?");
|
buf.write(")?");
|
||||||
Ok(DisplayWrap::Unwrapped)
|
Ok(DisplayWrap::Unwrapped)
|
||||||
}
|
}
|
||||||
@ -569,14 +600,16 @@ impl<'a> Generator<'a, '_> {
|
|||||||
node: Span<'_>,
|
node: Span<'_>,
|
||||||
name: &str,
|
name: &str,
|
||||||
) -> Result<DisplayWrap, CompileError> {
|
) -> Result<DisplayWrap, CompileError> {
|
||||||
ensure_filter_has_feature_alloc(ctx, name, node)?;
|
const ARGUMENTS: &[&FilterArgument; 2] = &[
|
||||||
let [arg, length] = args else {
|
FILTER_SOURCE,
|
||||||
return Err(ctx.generate_error(
|
&FilterArgument {
|
||||||
format_args!("`{name}` filter needs one argument, the `length`"),
|
name: "length",
|
||||||
node,
|
default_value: None,
|
||||||
));
|
},
|
||||||
};
|
];
|
||||||
|
|
||||||
|
ensure_filter_has_feature_alloc(ctx, name, node)?;
|
||||||
|
let [arg, length] = collect_filter_args(ctx, name, node, args, ARGUMENTS)?;
|
||||||
buf.write(format_args!("askama::filters::{name}("));
|
buf.write(format_args!("askama::filters::{name}("));
|
||||||
self.visit_arg(ctx, buf, arg)?;
|
self.visit_arg(ctx, buf, arg)?;
|
||||||
buf.write(
|
buf.write(
|
||||||
@ -697,6 +730,185 @@ fn expr_is_int_lit_plus_minus_one(expr: &WithSpan<'_, Expr<'_>>) -> Option<bool>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct FilterArgument {
|
||||||
|
name: &'static str,
|
||||||
|
/// If set to `None`, then a value is needed.
|
||||||
|
/// If set to `Some(ARGUMENT_PLACEHOLDER)`, then no value has to be assigned.
|
||||||
|
/// If set to `Some(&WithSpan...)`, then this value will be used if no argument was supplied.
|
||||||
|
default_value: Option<&'static WithSpan<'static, Expr<'static>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Must be the first entry to `collect_filter_args()`'s argument `filter_args`.
|
||||||
|
const FILTER_SOURCE: &FilterArgument = &FilterArgument {
|
||||||
|
name: "",
|
||||||
|
default_value: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ARGUMENT_PLACEHOLDER: &WithSpan<'_, Expr<'_>> =
|
||||||
|
&WithSpan::new_without_span(Expr::ArgumentPlaceholder);
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_argument_placeholder(arg: &WithSpan<'_, Expr<'_>>) -> bool {
|
||||||
|
matches!(**arg, Expr::ArgumentPlaceholder)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn no_arguments<'a, 'b>(
|
||||||
|
ctx: &Context<'_>,
|
||||||
|
name: &str,
|
||||||
|
args: &'b [WithSpan<'a, Expr<'a>>],
|
||||||
|
) -> Result<&'b WithSpan<'a, Expr<'a>>, CompileError> {
|
||||||
|
match args {
|
||||||
|
[arg] => Ok(arg),
|
||||||
|
[_, arg, ..] => Err(ctx.generate_error(
|
||||||
|
format_args!("`{name}` filter does not have any arguments"),
|
||||||
|
arg.span(),
|
||||||
|
)),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn collect_filter_args<'a, 'b, const N: usize>(
|
||||||
|
ctx: &Context<'_>,
|
||||||
|
name: &str,
|
||||||
|
node: Span<'_>,
|
||||||
|
input_args: &'b [WithSpan<'a, Expr<'a>>],
|
||||||
|
filter_args: &'static [&'static FilterArgument; N],
|
||||||
|
) -> Result<[&'b WithSpan<'a, Expr<'a>>; N], CompileError> {
|
||||||
|
let mut collected_args = [ARGUMENT_PLACEHOLDER; N];
|
||||||
|
// rationale: less code duplication by implementing the bulk of the function non-generic
|
||||||
|
collect_filter_args_inner(
|
||||||
|
ctx,
|
||||||
|
name,
|
||||||
|
node,
|
||||||
|
input_args,
|
||||||
|
filter_args,
|
||||||
|
&mut collected_args,
|
||||||
|
)?;
|
||||||
|
Ok(collected_args)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_filter_args_inner<'a, 'b>(
|
||||||
|
ctx: &Context<'_>,
|
||||||
|
name: &str,
|
||||||
|
node: Span<'_>,
|
||||||
|
input_args: &'b [WithSpan<'a, Expr<'a>>],
|
||||||
|
filter_args: &'static [&'static FilterArgument],
|
||||||
|
collected_args: &mut [&'b WithSpan<'a, Expr<'a>>],
|
||||||
|
) -> Result<(), CompileError> {
|
||||||
|
// invariant: the parser ensures that named arguments come after positional arguments
|
||||||
|
let mut arg_idx = 0;
|
||||||
|
for arg in input_args {
|
||||||
|
let (idx, value) = if let Expr::NamedArgument(arg_name, ref value) = **arg {
|
||||||
|
let Some(idx) = filter_args
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find_map(|(idx, arg)| (arg.name == arg_name).then_some(idx))
|
||||||
|
else {
|
||||||
|
return Err(ctx.generate_error(
|
||||||
|
match filter_args.len() {
|
||||||
|
1 => fmt_left!("`{name}` filter does not have any arguments"),
|
||||||
|
_ => fmt_right!(
|
||||||
|
"`{name}` filter does not have an argument `{}`{}",
|
||||||
|
arg_name.escape_debug(),
|
||||||
|
ItsArgumentsAre(filter_args),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
arg.span(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
(idx, &**value)
|
||||||
|
} else {
|
||||||
|
let idx = arg_idx;
|
||||||
|
arg_idx += 1;
|
||||||
|
(idx, arg)
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(collected_arg) = collected_args.get_mut(idx) else {
|
||||||
|
return Err(ctx.generate_error(
|
||||||
|
format_args!(
|
||||||
|
"`{name}` filter accepts at most {} argument{}{}",
|
||||||
|
filter_args.len() - 1,
|
||||||
|
if filter_args.len() != 2 { "s" } else { "" },
|
||||||
|
ItsArgumentsAre(filter_args),
|
||||||
|
),
|
||||||
|
arg.span(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
if !is_argument_placeholder(replace(collected_arg, value)) {
|
||||||
|
return Err(ctx.generate_error(
|
||||||
|
format_args!(
|
||||||
|
"`{}` argument to `{}` filter was already set{}",
|
||||||
|
filter_args[idx].name.escape_debug(),
|
||||||
|
name.escape_debug(),
|
||||||
|
ItsArgumentsAre(filter_args),
|
||||||
|
),
|
||||||
|
arg.span(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (&arg, collected) in filter_args.iter().zip(collected_args) {
|
||||||
|
if !is_argument_placeholder(collected) {
|
||||||
|
continue;
|
||||||
|
} else if let Some(default) = arg.default_value {
|
||||||
|
*collected = default;
|
||||||
|
} else {
|
||||||
|
return Err(ctx.generate_error(
|
||||||
|
format_args!(
|
||||||
|
"`{}` argument is missing when calling `{name}` filter{}",
|
||||||
|
arg.name.escape_debug(),
|
||||||
|
ItsArgumentsAre(filter_args),
|
||||||
|
),
|
||||||
|
node,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ItsArgumentsAre(&'static [&'static FilterArgument]);
|
||||||
|
|
||||||
|
impl fmt::Display for ItsArgumentsAre {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str("; its arguments are: (")?;
|
||||||
|
for (idx, arg) in self.0.iter().enumerate() {
|
||||||
|
match idx {
|
||||||
|
0 => continue,
|
||||||
|
1 => {}
|
||||||
|
_ => f.write_str(", ")?,
|
||||||
|
}
|
||||||
|
if arg.default_value.is_some() {
|
||||||
|
write!(f, "[{}]", arg.name)?;
|
||||||
|
} else {
|
||||||
|
f.write_str(arg.name)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.write_char(')')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_no_named_arguments(
|
||||||
|
ctx: &Context<'_>,
|
||||||
|
name: &str,
|
||||||
|
args: &[WithSpan<'_, Expr<'_>>],
|
||||||
|
node: Span<'_>,
|
||||||
|
) -> Result<(), CompileError> {
|
||||||
|
for arg in args {
|
||||||
|
if is_argument_placeholder(arg) {
|
||||||
|
return Err(ctx.generate_error(
|
||||||
|
format_args!(
|
||||||
|
"`{}` filter cannot accept named arguments",
|
||||||
|
name.escape_debug()
|
||||||
|
),
|
||||||
|
node,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// These built-in filters take no arguments, no generics, and are not feature gated.
|
// These built-in filters take no arguments, no generics, and are not feature gated.
|
||||||
const BUILTIN_FILTERS: &[&str] = &[];
|
const BUILTIN_FILTERS: &[&str] = &[];
|
||||||
|
|
||||||
|
@ -207,7 +207,8 @@ impl<'a> Generator<'a, '_> {
|
|||||||
| Expr::FilterSource
|
| Expr::FilterSource
|
||||||
| Expr::As(_, _)
|
| Expr::As(_, _)
|
||||||
| Expr::Concat(_)
|
| Expr::Concat(_)
|
||||||
| Expr::LetCond(_) => {
|
| Expr::LetCond(_)
|
||||||
|
| Expr::ArgumentPlaceholder => {
|
||||||
*only_contains_is_defined = false;
|
*only_contains_is_defined = false;
|
||||||
(EvaluatedResult::Unknown, WithSpan::new(expr, span))
|
(EvaluatedResult::Unknown, WithSpan::new(expr, span))
|
||||||
}
|
}
|
||||||
@ -1559,5 +1560,6 @@ fn is_cacheable(expr: &WithSpan<'_, Expr<'_>>) -> bool {
|
|||||||
Expr::RustMacro(_, _) => false,
|
Expr::RustMacro(_, _) => false,
|
||||||
// Should never be encountered:
|
// Should never be encountered:
|
||||||
Expr::FilterSource => unreachable!("FilterSource in expression?"),
|
Expr::FilterSource => unreachable!("FilterSource in expression?"),
|
||||||
|
Expr::ArgumentPlaceholder => unreachable!("ExpressionPlaceholder in expression?"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,6 +93,10 @@ fn check_expr<'a>(
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Expr::ArgumentPlaceholder => Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||||
|
"unreachable",
|
||||||
|
expr.span,
|
||||||
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,6 +141,9 @@ pub enum Expr<'a> {
|
|||||||
Concat(Vec<WithSpan<'a, Expr<'a>>>),
|
Concat(Vec<WithSpan<'a, Expr<'a>>>),
|
||||||
/// If you have `&& let Some(y)`, this variant handles it.
|
/// If you have `&& let Some(y)`, this variant handles it.
|
||||||
LetCond(Box<WithSpan<'a, CondTest<'a>>>),
|
LetCond(Box<WithSpan<'a, CondTest<'a>>>),
|
||||||
|
/// This variant should never be used directly.
|
||||||
|
/// It is used for the handling of named arguments in the generator, esp. with filters.
|
||||||
|
ArgumentPlaceholder,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Expr<'a> {
|
impl<'a> Expr<'a> {
|
||||||
@ -526,7 +533,8 @@ impl<'a> Expr<'a> {
|
|||||||
| Self::BinOp(_, _, _)
|
| Self::BinOp(_, _, _)
|
||||||
| Self::Path(_)
|
| Self::Path(_)
|
||||||
| Self::Concat(_)
|
| Self::Concat(_)
|
||||||
| Self::LetCond(_) => false,
|
| Self::LetCond(_)
|
||||||
|
| Self::ArgumentPlaceholder => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1037,7 +1037,7 @@ fn filter<'a>(
|
|||||||
cut_err((
|
cut_err((
|
||||||
ws(identifier),
|
ws(identifier),
|
||||||
opt(|i: &mut _| expr::call_generics(i, level)).map(|generics| generics.unwrap_or_default()),
|
opt(|i: &mut _| expr::call_generics(i, level)).map(|generics| generics.unwrap_or_default()),
|
||||||
opt(|i: &mut _| Expr::arguments(i, level, false)),
|
opt(|i: &mut _| Expr::arguments(i, level, true)),
|
||||||
))
|
))
|
||||||
.parse_next(i)
|
.parse_next(i)
|
||||||
}
|
}
|
||||||
|
188
testing/tests/named_filter_arguments.rs
Normal file
188
testing/tests/named_filter_arguments.rs
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
use askama::Template;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pluralize_two_positional_args() {
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = r#"I have {{ count }} butterfl{{ count | pluralize("y", "ies") }}."#,
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct CountButterflies {
|
||||||
|
count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
CountButterflies { count: 0 }.render().unwrap(),
|
||||||
|
"I have 0 butterflies."
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
CountButterflies { count: 1 }.render().unwrap(),
|
||||||
|
"I have 1 butterfly."
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
CountButterflies { count: 2 }.render().unwrap(),
|
||||||
|
"I have 2 butterflies."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pluralize_positional_and_named() {
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = r#"I have {{ count }} butterfl{{ count | pluralize("y", pl = "ies") }}."#,
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct CountButterflies {
|
||||||
|
count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
CountButterflies { count: 0 }.render().unwrap(),
|
||||||
|
"I have 0 butterflies."
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
CountButterflies { count: 1 }.render().unwrap(),
|
||||||
|
"I have 1 butterfly."
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
CountButterflies { count: 2 }.render().unwrap(),
|
||||||
|
"I have 2 butterflies."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pluralize_named_reordered() {
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = r#"I have {{ count }} butterfl{{ count | pluralize(pl = "ies", sg = "y") }}."#,
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct CountButterflies {
|
||||||
|
count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
CountButterflies { count: 0 }.render().unwrap(),
|
||||||
|
"I have 0 butterflies."
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
CountButterflies { count: 1 }.render().unwrap(),
|
||||||
|
"I have 1 butterfly."
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
CountButterflies { count: 2 }.render().unwrap(),
|
||||||
|
"I have 2 butterflies."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pluralize_defaulted_and_named() {
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = r#"I have {{ count }} potato{{ count | pluralize(pl = "es") }}."#,
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct CountPotatoes {
|
||||||
|
count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
CountPotatoes { count: 0 }.render().unwrap(),
|
||||||
|
"I have 0 potatoes."
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
CountPotatoes { count: 1 }.render().unwrap(),
|
||||||
|
"I have 1 potato."
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
CountPotatoes { count: 2 }.render().unwrap(),
|
||||||
|
"I have 2 potatoes."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ident() {
|
||||||
|
const TEXT: &str = "\
|
||||||
|
Lorem Ipsum has been the industry's\n\
|
||||||
|
\n\
|
||||||
|
standard dummy text ever since the 1500s";
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(source = r#"<{{ text | indent("$$", blank = true) }}>"#, ext = "txt")]
|
||||||
|
struct IdentBlank<'a> {
|
||||||
|
text: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
IdentBlank { text: TEXT }.render().unwrap(),
|
||||||
|
"\
|
||||||
|
<Lorem Ipsum has been the industry's\n\
|
||||||
|
$$\n\
|
||||||
|
$$standard dummy text ever since the 1500s>"
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(source = r#"<{{ text | indent("$$", first = true) }}>"#, ext = "txt")]
|
||||||
|
struct IdentFirst<'a> {
|
||||||
|
text: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
IdentFirst { text: TEXT }.render().unwrap(),
|
||||||
|
"\
|
||||||
|
<$$Lorem Ipsum has been the industry's\n\
|
||||||
|
\n\
|
||||||
|
$$standard dummy text ever since the 1500s>"
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = r#"<{{ text | indent("$$", blank = true, first = true) }}>"#,
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct IdentBoth<'a> {
|
||||||
|
text: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
IdentBoth { text: TEXT }.render().unwrap(),
|
||||||
|
"\
|
||||||
|
<$$Lorem Ipsum has been the industry's\n\
|
||||||
|
$$\n\
|
||||||
|
$$standard dummy text ever since the 1500s>"
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = r#"<{{ text | indent("$$", first = true, blank = true) }}>"#,
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct IdentBoth2<'a> {
|
||||||
|
text: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
IdentBoth2 { text: TEXT }.render().unwrap(),
|
||||||
|
"\
|
||||||
|
<$$Lorem Ipsum has been the industry's\n\
|
||||||
|
$$\n\
|
||||||
|
$$standard dummy text ever since the 1500s>"
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = r#"<{{ text | indent(first = true, width = "$$", blank = true) }}>"#,
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct IdentBoth3<'a> {
|
||||||
|
text: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
IdentBoth3 { text: TEXT }.render().unwrap(),
|
||||||
|
"\
|
||||||
|
<$$Lorem Ipsum has been the industry's\n\
|
||||||
|
$$\n\
|
||||||
|
$$standard dummy text ever since the 1500s>"
|
||||||
|
);
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
error: unexpected argument(s) in `json` filter
|
error: `json` filter accepts at most 1 argument; its arguments are: ([indent])
|
||||||
--> OneTwoThree.txt:1:3
|
--> OneTwoThree.txt:1:13
|
||||||
"1|json(2, 3) }}"
|
"3) }}"
|
||||||
--> tests/ui/json-too-many-args.rs:6:34
|
--> tests/ui/json-too-many-args.rs:6:34
|
||||||
|
|
|
|
||||||
6 | #[template(ext = "txt", source = "{{ 1|json(2, 3) }}")]
|
6 | #[template(ext = "txt", source = "{{ 1|json(2, 3) }}")]
|
||||||
|
48
testing/tests/ui/named_filter_arguments.rs
Normal file
48
testing/tests/ui/named_filter_arguments.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
use askama::Template;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = r#"I have {{ count }} butterfl{{ count | pluralize(pl = "ies", "y") }}."#,
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct PositionalAfterNamed {
|
||||||
|
count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = r#"I have {{ count }} butterfl{{ count | pluralize(pl = "y", pl = "ies") }}."#,
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct NamedRepeated {
|
||||||
|
count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = r#"I have {{ count }} butterfl{{ count | pluralize("y", sg = "ies") }}."#,
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct NamedButAlreadyPositional {
|
||||||
|
count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = r#"I have {{ count }} butterfl{{ count | pluralize("y", plural = "ies") }}."#,
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct NoSuchNamedArgument {
|
||||||
|
count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = r#"Scream: {{ message | uppercase(case = "upper") }}"#,
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct NamedArgumentButNoArgumentExpected<'a> {
|
||||||
|
message: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
39
testing/tests/ui/named_filter_arguments.stderr
Normal file
39
testing/tests/ui/named_filter_arguments.stderr
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
error: named arguments must always be passed last
|
||||||
|
--> <source attribute>:1:47
|
||||||
|
"(pl = \"ies\", \"y\") }}."
|
||||||
|
--> tests/ui/named_filter_arguments.rs:5:14
|
||||||
|
|
|
||||||
|
5 | source = r#"I have {{ count }} butterfl{{ count | pluralize(pl = "ies", "y") }}."#,
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: named argument `pl` was passed more than once
|
||||||
|
--> <source attribute>:1:47
|
||||||
|
"(pl = \"y\", pl = \"ies\") }}."
|
||||||
|
--> tests/ui/named_filter_arguments.rs:14:14
|
||||||
|
|
|
||||||
|
14 | source = r#"I have {{ count }} butterfl{{ count | pluralize(pl = "y", pl = "ies") }}."#,
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: `sg` argument to `pluralize` filter was already set; its arguments are: ([sg], [pl])
|
||||||
|
--> NamedButAlreadyPositional.txt:1:47
|
||||||
|
"(\"y\", sg = \"ies\") }}."
|
||||||
|
--> tests/ui/named_filter_arguments.rs:23:14
|
||||||
|
|
|
||||||
|
23 | source = r#"I have {{ count }} butterfl{{ count | pluralize("y", sg = "ies") }}."#,
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: `pluralize` filter does not have an argument `plural`; its arguments are: ([sg], [pl])
|
||||||
|
--> NoSuchNamedArgument.txt:1:47
|
||||||
|
"(\"y\", plural = \"ies\") }}."
|
||||||
|
--> tests/ui/named_filter_arguments.rs:32:14
|
||||||
|
|
|
||||||
|
32 | source = r#"I have {{ count }} butterfl{{ count | pluralize("y", plural = "ies") }}."#,
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: `uppercase` filter does not have any arguments
|
||||||
|
--> NamedArgumentButNoArgumentExpected.txt:1:30
|
||||||
|
"(case = \"upper\") }}"
|
||||||
|
--> tests/ui/named_filter_arguments.rs:41:14
|
||||||
|
|
|
||||||
|
41 | source = r#"Scream: {{ message | uppercase(case = "upper") }}"#,
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
@ -1,4 +1,4 @@
|
|||||||
error: invalid escaper 'latex' for `escape` filter. The available extensions are: "", "askama", "htm", "html", "j2", "jinja", "jinja2", "md", "none", "rinja", "svg", "txt", "xml", "yml"
|
error: invalid escaper `latex` for `escape` filter. The available extensions are: "", "askama", "htm", "html", "j2", "jinja", "jinja2", "md", "none", "rinja", "svg", "txt", "xml", "yml"
|
||||||
--> LocalEscaper.html:1:38
|
--> LocalEscaper.html:1:38
|
||||||
"text|escape(\"latex\")}}`."
|
"text|escape(\"latex\")}}`."
|
||||||
--> tests/ui/no-such-escaper.rs:6:14
|
--> tests/ui/no-such-escaper.rs:6:14
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
error: filter `truncate` needs one argument, the `length`
|
error: `length` argument is missing when calling `truncate` filter; its arguments are: (length)
|
||||||
--> NoArgument.html:1:3
|
--> NoArgument.html:1:3
|
||||||
"text | truncate }}"
|
"text | truncate }}"
|
||||||
--> tests/ui/truncate.rs:4:21
|
--> tests/ui/truncate.rs:4:21
|
||||||
@ -6,9 +6,9 @@ error: filter `truncate` needs one argument, the `length`
|
|||||||
4 | #[template(source = r#"{{ text | truncate }}"#, ext = "html")]
|
4 | #[template(source = r#"{{ text | truncate }}"#, ext = "html")]
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
error: filter `truncate` needs one argument, the `length`
|
error: `truncate` filter accepts at most 1 argument; its arguments are: (length)
|
||||||
--> TooManyArguments.html:1:3
|
--> TooManyArguments.html:1:27
|
||||||
"text | truncate(length, extra) }}"
|
"extra) }}"
|
||||||
--> tests/ui/truncate.rs:17:21
|
--> tests/ui/truncate.rs:17:21
|
||||||
|
|
|
|
||||||
17 | #[template(source = r#"{{ text | truncate(length, extra) }}"#, ext = "html")]
|
17 | #[template(source = r#"{{ text | truncate(length, extra) }}"#, ext = "html")]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user