Skip to main content

ocre/
filters.rs

1//! askama filters for Ocre's [`helpers`] (feature `html`): `{{ post.price|number_to_currency("$") }}`.
2//!
3//! askama looks custom filters up in a module named `filters` in scope where
4//! the template struct is defined. Bring these in with `use ocre::filters;`
5//! in the controller module (generated controllers do). To add filters of
6//! your own, make that module yours and re-export Ocre's:
7//!
8//! ```
9//! mod filters {
10//!     pub use ocre::filters::*;
11//!
12//!     /// `{{ name|shout }}`: `Ocre!`.
13//!     #[askama::filter_fn]
14//!     pub fn shout(value: impl std::fmt::Display, _: &dyn askama::Values) -> askama::Result<String> {
15//!         Ok(format!("{value}!"))
16//!     }
17//! }
18//!
19//! #[derive(askama::Template)]
20//! #[template(source = "{{ name|shout }} {{ views|number_with_delimiter }}", ext = "html")]
21//! struct Hello {
22//!     name: &'static str,
23//!     views: i64,
24//! }
25//!
26//! assert_eq!(askama::Template::render(&Hello { name: "Ocre", views: 12345 }).unwrap(), "Ocre! 12,345");
27//! ```
28//!
29//! | Filter | Output | Helper |
30//! |---|---|---|
31//! | `{{ views\|number_with_delimiter }}` | `1,234,567` | [`number_with_delimiter`](crate::helpers::number_with_delimiter) |
32//! | `{{ ratio\|number_with_precision(2) }}` | `3.14` | [`number_with_precision`](crate::helpers::number_with_precision) |
33//! | `{{ price\|number_to_currency("$") }}` | `$1,234.50` | [`number_to_currency`](crate::helpers::number_to_currency) |
34//! | `{{ rate\|number_to_percentage(1) }}` | `12.3%` | [`number_to_percentage`](crate::helpers::number_to_percentage) |
35//! | `{{ size\|number_to_human_size }}` | `1.5 KB` | [`number_to_human_size`](crate::helpers::number_to_human_size) |
36//! | `{{ visits\|number_to_human }}` | `1.23 Million` | [`number_to_human`](crate::helpers::number_to_human) |
37//! | `{{ post.created_at\|time_ago_in_words }} ago` | `about 3 hours ago` | [`time_ago_in_words`](crate::helpers::time_ago_in_words) |
38//! | `{{ from\|distance_of_time_in_words(to) }}` | `2 days` | [`distance_of_time_in_words`](crate::helpers::distance_of_time_in_words) |
39//! | `{{ post.created_at\|strftime("%b %-d, %Y") }}` | `Sep 29, 2026` | [`strftime`](crate::helpers::strftime) |
40//! | `{{ post.body\|excerpt(q, 40) }}` | `...around the match...` | [`excerpt`](crate::helpers::excerpt) |
41//! | `{{ post.body\|highlight(q) }}` | escaped text with `<mark>` | [`highlight`](crate::helpers::highlight) |
42//! | `{{ post.body\|word_wrap(72) }}` | lines of at most 72 characters | [`word_wrap`](crate::helpers::word_wrap) |
43//! | `{{ post.body\|rich_text }}` | the HTML of a `rich_text` field, sanitized | [`sanitize`](crate::security::sanitize) |
44//! | `{{ post.body\|plain_text\|truncate(80) }}` | its text, without tags | [`strip_tags`](crate::security::strip_tags) |
45//!
46//! askama's built-in filters cover the rest of Rails' text helpers:
47//! `truncate(n)`, `wordcount`, `linebreaks` / `linebreaksbr` /
48//! `paragraphbreaks` (`simple_format`), `pluralize`
49//! (`{{ n }} post{{ n|pluralize }}`), `filesizeformat`, `urlencode`,
50//! `upper` / `lower` / `title` / `capitalize`, `json` and `fmt`
51//! (`{{ ratio|fmt("{:.2}") }}`).
52//!
53//! The items below are generated by [`askama::filter_fn`]; call the
54//! [`helpers`] functions from Rust code.
55#![allow(missing_docs)]
56
57use std::fmt::Display;
58
59use askama::{Values, filters::Safe};
60
61use crate::helpers;
62
63#[askama::filter_fn]
64pub fn number_with_delimiter(value: impl Display, _: &dyn Values) -> askama::Result<String> {
65    Ok(helpers::number_with_delimiter(value))
66}
67
68#[askama::filter_fn]
69pub fn number_with_precision(value: impl Display, _: &dyn Values, precision: usize) -> askama::Result<String> {
70    Ok(helpers::number_with_precision(value, precision))
71}
72
73#[askama::filter_fn]
74pub fn number_to_currency(value: impl Display, _: &dyn Values, unit: &str) -> askama::Result<String> {
75    Ok(helpers::number_to_currency(value, unit))
76}
77
78#[askama::filter_fn]
79pub fn number_to_percentage(value: impl Display, _: &dyn Values, precision: usize) -> askama::Result<String> {
80    Ok(helpers::number_to_percentage(value, precision))
81}
82
83#[askama::filter_fn]
84pub fn number_to_human_size(value: impl Display, _: &dyn Values) -> askama::Result<String> {
85    Ok(helpers::number_to_human_size(value))
86}
87
88#[askama::filter_fn]
89pub fn number_to_human(value: impl Display, _: &dyn Values) -> askama::Result<String> {
90    Ok(helpers::number_to_human(value))
91}
92
93#[askama::filter_fn]
94pub fn time_ago_in_words(value: impl Display, _: &dyn Values) -> askama::Result<String> {
95    Ok(helpers::time_ago_in_words(value))
96}
97
98#[askama::filter_fn]
99pub fn distance_of_time_in_words<T: Display>(value: impl Display, _: &dyn Values, to: T) -> askama::Result<String> {
100    Ok(helpers::distance_of_time_in_words(value, to))
101}
102
103#[askama::filter_fn]
104pub fn strftime(value: impl Display, _: &dyn Values, format: &str) -> askama::Result<String> {
105    Ok(helpers::strftime(value, format))
106}
107
108#[askama::filter_fn]
109pub fn excerpt(value: impl Display, _: &dyn Values, phrase: &str, radius: usize) -> askama::Result<String> {
110    Ok(helpers::excerpt(value, phrase, radius))
111}
112
113#[askama::filter_fn]
114pub fn highlight(value: impl Display, _: &dyn Values, phrase: &str) -> askama::Result<Safe<String>> {
115    Ok(Safe(helpers::highlight(value, phrase)))
116}
117
118#[askama::filter_fn]
119pub fn word_wrap(value: impl Display, _: &dyn Values, width: usize) -> askama::Result<String> {
120    Ok(helpers::word_wrap(value, width))
121}
122
123#[askama::filter_fn]
124pub fn rich_text(value: impl Display, _: &dyn Values) -> askama::Result<Safe<String>> {
125    Ok(Safe(crate::security::sanitize(&value.to_string())))
126}
127
128#[askama::filter_fn]
129pub fn plain_text(value: impl Display, _: &dyn Values) -> askama::Result<String> {
130    Ok(crate::security::strip_tags(&value.to_string()))
131}
132
133#[cfg(test)]
134#[path = "../tests/filters.rs"]
135mod tests;