From 8c723615f3fe93351ac43a88ea0f0e4a70af01d7 Mon Sep 17 00:00:00 2001 From: Raimundo Saona <37874270+saona-raimundo@users.noreply.github.com> Date: Thu, 10 Nov 2022 21:17:22 +0100 Subject: [PATCH 1/3] Extend documentation of custom filters --- book/src/filters.md | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/book/src/filters.md b/book/src/filters.md index 4b2aab72d..6be6cc4fe 100644 --- a/book/src/filters.md +++ b/book/src/filters.md @@ -408,18 +408,25 @@ This will output formatted YAML for any value that implements the required ## Custom Filters [#custom-filters]: #custom-filters -To define your own filters, simply have a module named filters in scope of the context deriving a `Template` impl. +To define your own filters, simply have a module named `filters` in scope of the context deriving a `Template` impl and defines the filters as functions within this module. The functions must have a first argument `&str`, but otherwise can have other inputs. The output must be `::askama::Result`. -Note that in case of name collision, the built in filters take precedence. +Note that built-in filters have preference over custom filters, so, in case of name collision, the built-in filter is applied. +### Examples + +Implementing a filter that replaces all instances of `"oo"` for `"aa"`. ```rust +use askama::Template; + #[derive(Template)] #[template(source = "{{ s|myfilter }}", ext = "txt")] struct MyFilterTemplate<'a> { s: &'a str, } +// Any filter defined in the module `filters` is accessible in your template. mod filters { + // This filter does not have extra arguments pub fn myfilter(s: &str) -> ::askama::Result { Ok(s.replace("oo", "aa")) } @@ -430,3 +437,30 @@ fn main() { assert_eq!(t.render().unwrap(), "faa"); } ``` + +Implementing a filter that replaces all instances of `"oo"` for `n` times `"a"`. +```rust +use askama::Template; + +#[derive(Template)] +#[template(source = "{{ s|myfilter(4) }}", ext = "txt")] +struct MyFilterTemplate<'a> { + s: &'a str, +} + +// Any filter defined in the module `filters` is accessible in your template. +mod filters { + // This filter requires a `usize` input when called in templates + pub fn myfilter(s: &str, n: usize) -> ::askama::Result { + let mut replace = String::with_capacity(n); + replace.extend((0..n).map(|_| "a")); + Ok(s.replace("oo", &replace)) + } +} + +fn main() { + let t = MyFilterTemplate { s: "foo" }; + assert_eq!(t.render().unwrap(), "faaaa"); +} +``` + From d5becdb38f70208ed3ee37d4ce2e7287656e75e2 Mon Sep 17 00:00:00 2001 From: Raimundo Saona <37874270+saona-raimundo@users.noreply.github.com> Date: Mon, 14 Nov 2022 13:36:36 +0100 Subject: [PATCH 2/3] Explain how arguments are passed to filters - Mention that Askama may pass arguments by reference. - Best practice is to bound parameters by traits - No exact wording on how Askama resolves the reference issue, so that documentation does not goes out of date so soon. --- book/src/filters.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/book/src/filters.md b/book/src/filters.md index 6be6cc4fe..5b584c717 100644 --- a/book/src/filters.md +++ b/book/src/filters.md @@ -408,7 +408,9 @@ This will output formatted YAML for any value that implements the required ## Custom Filters [#custom-filters]: #custom-filters -To define your own filters, simply have a module named `filters` in scope of the context deriving a `Template` impl and defines the filters as functions within this module. The functions must have a first argument `&str`, but otherwise can have other inputs. The output must be `::askama::Result`. +To define your own filters, simply have a module named `filters` in scope of the context deriving a `Template` impl and define the filters as functions within this module. The functions must have at least one argument and the return type must be `::askama::Result` where `T` is some type that implements `Display`. + +The arguments to the filters are passed as follows. The first argument corresponds to the expression they are applied to. Subsequent arguments, if any, must be given directly when calling the filter. The first argument may be passed as reference whenever Askama can not determine that it can be passed directly. If you want a filter to accept a first argument with or without reference, you may use traits to bound the argument. For example, the `trim` built-in filter accepts any value implementing `Display`. Its signature is similar to `fn trim(s: T) -> ::askama::Result`. Note that built-in filters have preference over custom filters, so, in case of name collision, the built-in filter is applied. @@ -427,7 +429,8 @@ struct MyFilterTemplate<'a> { // Any filter defined in the module `filters` is accessible in your template. mod filters { // This filter does not have extra arguments - pub fn myfilter(s: &str) -> ::askama::Result { + pub fn myfilter(s: T) -> ::askama::Result { + let s = s.to_string(); Ok(s.replace("oo", "aa")) } } @@ -451,9 +454,10 @@ struct MyFilterTemplate<'a> { // Any filter defined in the module `filters` is accessible in your template. mod filters { // This filter requires a `usize` input when called in templates - pub fn myfilter(s: &str, n: usize) -> ::askama::Result { - let mut replace = String::with_capacity(n); - replace.extend((0..n).map(|_| "a")); + pub fn myfilter(s: T, n: usize) -> ::askama::Result { + let s = s.to_string(); + let mut replace = String::with_capacity(n); + replace.extend((0..n).map(|_| "a")); Ok(s.replace("oo", &replace)) } } @@ -463,4 +467,3 @@ fn main() { assert_eq!(t.render().unwrap(), "faaaa"); } ``` - From 57b611df78375e71c3eaf6d8a41246a04619350f Mon Sep 17 00:00:00 2001 From: Raimundo Saona <37874270+saona-raimundo@users.noreply.github.com> Date: Tue, 15 Nov 2022 13:04:17 +0100 Subject: [PATCH 3/3] Update wording and remove restriction The return type of filters do not necessarily implement Display, only the final result of a chain of filters. --- book/src/filters.md | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/book/src/filters.md b/book/src/filters.md index 5b584c717..e85068f53 100644 --- a/book/src/filters.md +++ b/book/src/filters.md @@ -13,7 +13,8 @@ is passed to the next. {{ "HELLO"|lower }} ``` -Askama has a collection of built-in filters, documented below, but can also include custom filters. Additionally, the `json` and `yaml` filters are included in the built-in filters, +Askama has a collection of built-in filters, documented below, but can also include custom filters. +Additionally, the `json` and `yaml` filters are included in the built-in filters, but are disabled by default. Enable them with Cargo features (see below for more information). **Table of contents** @@ -105,7 +106,10 @@ Output: Escape <>& ``` -Optionally, it is possible to specify and override which escaper is used. Consider a template where the escaper is configured as [`escape = "none"`]. However, somewhere escaping using the HTML escaper is desired. Then it is possible to override and use the HTML escaper like this: +Optionally, it is possible to specify and override which escaper is used. +Consider a template where the escaper is configured as [`escape = "none"`]. +However, somewhere escaping using the HTML escaper is desired. +Then it is possible to override and use the HTML escaper like this: ```jinja {{ "Don't Escape <>&"|escape }} @@ -408,9 +412,19 @@ This will output formatted YAML for any value that implements the required ## Custom Filters [#custom-filters]: #custom-filters -To define your own filters, simply have a module named `filters` in scope of the context deriving a `Template` impl and define the filters as functions within this module. The functions must have at least one argument and the return type must be `::askama::Result` where `T` is some type that implements `Display`. - -The arguments to the filters are passed as follows. The first argument corresponds to the expression they are applied to. Subsequent arguments, if any, must be given directly when calling the filter. The first argument may be passed as reference whenever Askama can not determine that it can be passed directly. If you want a filter to accept a first argument with or without reference, you may use traits to bound the argument. For example, the `trim` built-in filter accepts any value implementing `Display`. Its signature is similar to `fn trim(s: T) -> ::askama::Result`. +To define your own filters, simply have a module named `filters` in scope of the context deriving a `Template` impl +and define the filters as functions within this module. +The functions must have at least one argument and the return type must be `::askama::Result`. +Although there are no restrictions on `T` for a single filter, +the final result of a chain of filters must implement `Display`. + +The arguments to the filters are passed as follows. +The first argument corresponds to the expression they are applied to. +Subsequent arguments, if any, must be given directly when calling the filter. +The first argument may or may not be a reference, depending on the context in which the filter is called. +To abstract over ownership, consider defining your argument as a trait bound. +For example, the `trim` built-in filter accepts any value implementing `Display`. +Its signature is similar to `fn trim(s: impl std::fmt::Display) -> ::askama::Result`. Note that built-in filters have preference over custom filters, so, in case of name collision, the built-in filter is applied.