diff --git a/askama_derive/src/generator/filter.rs b/askama_derive/src/generator/filter.rs index 71079c33..bc89c534 100644 --- a/askama_derive/src/generator/filter.rs +++ b/askama_derive/src/generator/filter.rs @@ -5,7 +5,7 @@ use parser::{Expr, IntKind, Num, Span, StrLit, StrPrefix, TyGenerics, WithSpan}; use super::{DisplayWrap, Generator, TargetIsize, TargetUsize}; use crate::heritage::Context; use crate::integration::Buffer; -use crate::{BUILTIN_FILTERS, BUILTIN_FILTERS_NEED_ALLOC, CompileError, MsgValidEscapers}; +use crate::{BUILTIN_FILTERS, CompileError, MsgValidEscapers}; impl<'a> Generator<'a, '_> { pub(super) fn visit_filter( @@ -18,6 +18,7 @@ impl<'a> Generator<'a, '_> { node: Span<'_>, ) -> Result { let filter = match name { + "center" => Self::visit_center_filter, "deref" => Self::visit_deref_filter, "escape" | "e" => Self::visit_escape_filter, "filesizeformat" => Self::visit_humansize, @@ -32,6 +33,7 @@ impl<'a> Generator<'a, '_> { "pluralize" => Self::visit_pluralize_filter, "ref" => Self::visit_ref_filter, "safe" => Self::visit_safe_filter, + "truncate" => Self::visit_truncate_filter, "urlencode" => Self::visit_urlencode_filter, "urlencode_strict" => Self::visit_urlencode_strict_filter, "value" => return self.visit_value(ctx, buf, args, generics, node, "`value` filter"), @@ -79,9 +81,6 @@ impl<'a> Generator<'a, '_> { generics: &[WithSpan<'a, TyGenerics<'a>>], node: Span<'_>, ) -> Result { - if BUILTIN_FILTERS_NEED_ALLOC.contains(&name) { - ensure_filter_has_feature_alloc(ctx, name, node)?; - } if !generics.is_empty() { return Err( ctx.generate_error(format_args!("unexpected generics on filter `{name}`"), node) @@ -516,6 +515,60 @@ impl<'a> Generator<'a, '_> { buf.write(")?"); Ok(DisplayWrap::Unwrapped) } + + fn visit_center_filter( + &mut self, + ctx: &Context<'_>, + buf: &mut Buffer, + args: &[WithSpan<'a, Expr<'a>>], + node: Span<'_>, + ) -> Result { + self.visit_center_truncate_filter(ctx, buf, args, node, "center") + } + + fn visit_truncate_filter( + &mut self, + ctx: &Context<'_>, + buf: &mut Buffer, + args: &[WithSpan<'a, Expr<'a>>], + node: Span<'_>, + ) -> Result { + self.visit_center_truncate_filter(ctx, buf, args, node, "truncate") + } + + fn visit_center_truncate_filter( + &mut self, + ctx: &Context<'_>, + buf: &mut Buffer, + args: &[WithSpan<'a, Expr<'a>>], + node: Span<'_>, + name: &str, + ) -> Result { + ensure_filter_has_feature_alloc(ctx, name, node)?; + let [arg, length] = args else { + return Err(ctx.generate_error( + format_args!("`{name}` filter needs one argument, the `length`"), + node, + )); + }; + + buf.write(format_args!("askama::filters::{name}(")); + self.visit_arg(ctx, buf, arg)?; + buf.write( + "\ + ,\ + askama::helpers::core::primitive::usize::try_from(\ + askama::helpers::get_primitive_value(&(", + ); + self.visit_arg(ctx, buf, length)?; + buf.write( + "\ + ))\ + ).map_err(|_| askama::Error::Fmt)?\ + )?", + ); + Ok(DisplayWrap::Unwrapped) + } } fn ensure_filter_has_feature_alloc( diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index 3af9c748..ac33a753 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -602,15 +602,10 @@ pub(crate) use {fmt_left, fmt_right}; // Askama or should refer to a local `filters` module. const BUILTIN_FILTERS: &[&str] = &[ "capitalize", - "center", "lower", "lowercase", "title", "trim", - "truncate", "upper", "uppercase", ]; - -// Built-in filters that need the `alloc` feature. -const BUILTIN_FILTERS_NEED_ALLOC: &[&str] = &["center", "truncate"]; diff --git a/testing/tests/filters.rs b/testing/tests/filters.rs index 30fb551c..35e944b7 100644 --- a/testing/tests/filters.rs +++ b/testing/tests/filters.rs @@ -3,6 +3,7 @@ extern crate serde_json; use askama::Template; +use assert_matches::assert_matches; #[cfg(feature = "serde_json")] use serde_json::Value; @@ -298,6 +299,32 @@ fn test_filter_truncate() { foo: "alpha bar".into(), }; assert_eq!(t.render().unwrap(), "alpha baralpha..."); + + #[derive(Template)] + #[template(source = "{{ foo | truncate(length) }}", ext = "txt")] + struct TruncateFilterLength<'a> { + foo: String, + length: &'a &'a &'a i32, + } + + assert_eq!( + TruncateFilterLength { + foo: "alpha bar".into(), + length: &&&5, + } + .render() + .unwrap(), + "alpha..." + ); + assert_matches!( + TruncateFilterLength { + foo: "alpha bar".into(), + length: &&&-5, + } + .render() + .unwrap_err(), + askama::Error::Fmt + ); } #[cfg(feature = "serde_json")] diff --git a/testing/tests/ui/truncate.rs b/testing/tests/ui/truncate.rs new file mode 100644 index 00000000..d427d036 --- /dev/null +++ b/testing/tests/ui/truncate.rs @@ -0,0 +1,24 @@ +use askama::Template; + +#[derive(Template)] +#[template(source = r#"{{ text | truncate }}"#, ext = "html")] +struct NoArgument<'a> { + text: &'a str, +} + +#[derive(Template)] +#[template(source = r#"{{ text | truncate(length) }}"#, ext = "html")] +struct WrongArgumentType<'a> { + text: &'a str, + length: f32, +} + +#[derive(Template)] +#[template(source = r#"{{ text | truncate(length, extra) }}"#, ext = "html")] +struct TooManyArguments<'a> { + text: &'a str, + length: usize, + extra: bool, +} + +fn main() {} diff --git a/testing/tests/ui/truncate.stderr b/testing/tests/ui/truncate.stderr new file mode 100644 index 00000000..6ff8bf5e --- /dev/null +++ b/testing/tests/ui/truncate.stderr @@ -0,0 +1,30 @@ +error: filter `truncate` needs one argument, the `length` + --> NoArgument.html:1:3 + "text | truncate }}" + --> tests/ui/truncate.rs:4:21 + | +4 | #[template(source = r#"{{ text | truncate }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: filter `truncate` needs one argument, the `length` + --> TooManyArguments.html:1:3 + "text | truncate(length, extra) }}" + --> tests/ui/truncate.rs:17:21 + | +17 | #[template(source = r#"{{ text | truncate(length, extra) }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0277]: the trait bound `usize: TryFrom` is not satisfied + --> tests/ui/truncate.rs:9:10 + | +9 | #[derive(Template)] + | ^^^^^^^^ the trait `From` is not implemented for `usize` + | + = help: the following other types implement trait `From`: + `usize` implements `From` + `usize` implements `From` + `usize` implements `From` + `usize` implements `From` + = note: required for `f32` to implement `Into` + = note: required for `usize` to implement `TryFrom` + = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)