1 //! Module for built-in filter functions
3 //! Contains all the built-in filter functions for use in templates.
4 //! You can define your own filters, as well.
5 //! For more information, read the [book](https://djc.github.io/askama/filters.html).
6 #![allow(clippy::trivially_copy_pass_by_ref)]
10 #[cfg(feature = "serde_json")]
12 #[cfg(feature = "serde_json")]
13 pub use self::json
::json
;
15 #[cfg(feature = "serde_yaml")]
17 #[cfg(feature = "serde_yaml")]
18 pub use self::yaml
::yaml
;
20 #[allow(unused_imports)]
21 use crate::error
::Error
::Fmt
;
22 use askama_escape
::{Escaper, MarkupDisplay}
;
23 #[cfg(feature = "humansize")]
24 use humansize
::{file_size_opts, FileSize}
;
25 #[cfg(feature = "num-traits")]
26 use num_traits
::{cast::NumCast, Signed}
;
27 #[cfg(feature = "percent-encoding")]
28 use percent_encoding
::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC}
;
32 #[cfg(feature = "percent-encoding")]
33 // Urlencode char encoding set. Only the characters in the unreserved set don't
34 // have any special purpose in any part of a URI and can be safely left
35 // unencoded as specified in https://tools.ietf.org/html/rfc3986.html#section-2.3
36 const URLENCODE_STRICT_SET
: &AsciiSet
= &NON_ALPHANUMERIC
42 #[cfg(feature = "percent-encoding")]
43 // Same as URLENCODE_STRICT_SET, but preserves forward slashes for encoding paths
44 const URLENCODE_SET
: &AsciiSet
= &URLENCODE_STRICT_SET
.remove(b'
/'
);
46 // This is used by the code generator to decide whether a named filter is part of
47 // Askama or should refer to a local `filters` module. It should contain all the
48 // filters shipped with Askama, even the optional ones (since optional inclusion
49 // in the const vector based on features seems impossible right now).
50 pub const BUILT_IN_FILTERS
: [&str; 27] = [
76 "json", // Optional feature; reserve the name anyway
77 "yaml", // Optional feature; reserve the name anyway
80 /// Marks a string (or other `Display` type) as safe
82 /// Use this is you want to allow markup in an expression, or if you know
83 /// that the expression's contents don't need to be escaped.
85 /// Askama will automatically insert the first (`Escaper`) argument,
86 /// so this filter only takes a single argument of any type that implements
88 pub fn safe
<E
, T
>(e
: E
, v
: T
) -> Result
<MarkupDisplay
<E
, T
>>
93 Ok(MarkupDisplay
::new_safe(v
, e
))
96 /// Escapes `&`, `<` and `>` in strings
98 /// Askama will automatically insert the first (`Escaper`) argument,
99 /// so this filter only takes a single argument of any type that implements
101 pub fn escape
<E
, T
>(e
: E
, v
: T
) -> Result
<MarkupDisplay
<E
, T
>>
106 Ok(MarkupDisplay
::new_unsafe(v
, e
))
109 #[cfg(feature = "humansize")]
110 /// Returns adequate string representation (in KB, ..) of number of bytes
111 pub fn filesizeformat
<B
: FileSize
>(b
: &B
) -> Result
<String
> {
112 b
.file_size(file_size_opts
::DECIMAL
)
113 .map_err(|_
| Fmt(fmt
::Error
))
116 #[cfg(feature = "percent-encoding")]
117 /// Percent-encodes the argument for safe use in URI; does not encode `/`.
119 /// This should be safe for all parts of URI (paths segments, query keys, query
120 /// values). In the rare case that the server can't deal with forward slashes in
121 /// the query string, use [`urlencode_strict`], which encodes them as well.
123 /// Encodes all characters except ASCII letters, digits, and `_.-~/`. In other
124 /// words, encodes all characters which are not in the unreserved set,
125 /// as specified by [RFC3986](https://tools.ietf.org/html/rfc3986#section-2.3),
126 /// with the exception of `/`.
129 /// <a href="/metro{{ "/stations/Château d'Eau"|urlencode }}">Station</a>
130 /// <a href="/page?text={{ "look, unicode/emojis ✨"|urlencode }}">Page</a>
133 /// To encode `/` as well, see [`urlencode_strict`](./fn.urlencode_strict.html).
135 /// [`urlencode_strict`]: ./fn.urlencode_strict.html
136 pub fn urlencode
<T
: fmt
::Display
>(s
: T
) -> Result
<String
> {
137 let s
= s
.to_string();
138 Ok(utf8_percent_encode(&s
, URLENCODE_SET
).to_string())
141 #[cfg(feature = "percent-encoding")]
142 /// Percent-encodes the argument for safe use in URI; encodes `/`.
144 /// Use this filter for encoding query keys and values in the rare case that
145 /// the server can't process them unencoded.
147 /// Encodes all characters except ASCII letters, digits, and `_.-~`. In other
148 /// words, encodes all characters which are not in the unreserved set,
149 /// as specified by [RFC3986](https://tools.ietf.org/html/rfc3986#section-2.3).
152 /// <a href="/page?text={{ "look, unicode/emojis ✨"|urlencode_strict }}">Page</a>
155 /// If you want to preserve `/`, see [`urlencode`](./fn.urlencode.html).
156 pub fn urlencode_strict
<T
: fmt
::Display
>(s
: T
) -> Result
<String
> {
157 let s
= s
.to_string();
158 Ok(utf8_percent_encode(&s
, URLENCODE_STRICT_SET
).to_string())
161 /// Formats arguments according to the specified format
163 /// The *second* argument to this filter must be a string literal (as in normal
164 /// Rust). The two arguments are passed through to the `format!()`
165 /// [macro](https://doc.rust-lang.org/stable/std/macro.format.html) by
166 /// the Askama code generator, but the order is swapped to support filter
170 /// {{ value | fmt("{:?}") }}
173 /// Compare with [format](./fn.format.html).
176 /// Formats arguments according to the specified format
178 /// The first argument to this filter must be a string literal (as in normal
179 /// Rust). All arguments are passed through to the `format!()`
180 /// [macro](https://doc.rust-lang.org/stable/std/macro.format.html) by
181 /// the Askama code generator.
184 /// {{ "{:?}{:?}" | format(value, other_value) }}
187 /// Compare with [fmt](./fn.fmt.html).
190 /// Replaces line breaks in plain text with appropriate HTML
192 /// A single newline becomes an HTML line break `<br>` and a new line
193 /// followed by a blank line becomes a paragraph break `<p>`.
194 pub fn linebreaks
<T
: fmt
::Display
>(s
: T
) -> Result
<String
> {
195 let s
= s
.to_string();
196 let linebroken
= s
.replace("\n\n", "</p><p>").replace('
\n'
, "<br/>");
198 Ok(format
!("<p>{}</p>", linebroken
))
201 /// Converts all newlines in a piece of plain text to HTML line breaks
202 pub fn linebreaksbr
<T
: fmt
::Display
>(s
: T
) -> Result
<String
> {
203 let s
= s
.to_string();
204 Ok(s
.replace('
\n'
, "<br/>"))
207 /// Replaces only paragraph breaks in plain text with appropriate HTML
209 /// A new line followed by a blank line becomes a paragraph break `<p>`.
210 /// Paragraph tags only wrap content; empty paragraphs are removed.
211 /// No `<br/>` tags are added.
212 pub fn paragraphbreaks
<T
: fmt
::Display
>(s
: T
) -> Result
<String
> {
213 let s
= s
.to_string();
214 let linebroken
= s
.replace("\n\n", "</p><p>").replace("<p></p>", "");
216 Ok(format
!("<p>{}</p>", linebroken
))
219 /// Converts to lowercase
220 pub fn lower
<T
: fmt
::Display
>(s
: T
) -> Result
<String
> {
221 let s
= s
.to_string();
225 /// Alias for the `lower()` filter
226 pub fn lowercase
<T
: fmt
::Display
>(s
: T
) -> Result
<String
> {
230 /// Converts to uppercase
231 pub fn upper
<T
: fmt
::Display
>(s
: T
) -> Result
<String
> {
232 let s
= s
.to_string();
236 /// Alias for the `upper()` filter
237 pub fn uppercase
<T
: fmt
::Display
>(s
: T
) -> Result
<String
> {
241 /// Strip leading and trailing whitespace
242 pub fn trim
<T
: fmt
::Display
>(s
: T
) -> Result
<String
> {
243 let s
= s
.to_string();
244 Ok(s
.trim().to_owned())
247 /// Limit string length, appends '...' if truncated
248 pub fn truncate
<T
: fmt
::Display
>(s
: T
, len
: usize) -> Result
<String
> {
249 let mut s
= s
.to_string();
251 let mut real_len
= len
;
252 while !s
.is_char_boundary(real_len
) {
255 s
.truncate(real_len
);
261 /// Indent lines with `width` spaces
262 pub fn indent
<T
: fmt
::Display
>(s
: T
, width
: usize) -> Result
<String
> {
263 let s
= s
.to_string();
265 let mut indented
= String
::new();
267 for (i
, c
) in s
.char_indices() {
270 if c
== '
\n'
&& i
< s
.len() - 1 {
280 #[cfg(feature = "num-traits")]
281 /// Casts number to f64
282 pub fn into_f64
<T
>(number
: T
) -> Result
<f64>
286 number
.to_f64().ok_or(Fmt(fmt
::Error
))
289 #[cfg(feature = "num-traits")]
290 /// Casts number to isize
291 pub fn into_isize
<T
>(number
: T
) -> Result
<isize>
295 number
.to_isize().ok_or(Fmt(fmt
::Error
))
298 /// Joins iterable into a string separated by provided argument
299 pub fn join
<T
, I
, S
>(input
: I
, separator
: S
) -> Result
<String
>
302 I
: Iterator
<Item
= T
>,
305 let separator
: &str = separator
.as_ref();
307 let mut rv
= String
::new();
309 for (num
, item
) in input
.enumerate() {
311 rv
.push_str(separator
);
314 rv
.push_str(&format
!("{}", item
));
320 #[cfg(feature = "num-traits")]
322 pub fn abs
<T
>(number
: T
) -> Result
<T
>
329 /// Capitalize a value. The first character will be uppercase, all others lowercase.
330 pub fn capitalize
<T
: fmt
::Display
>(s
: T
) -> Result
<String
> {
331 let mut s
= s
.to_string();
333 match s
.get_mut(0..1).map(|s
| {
334 s
.make_ascii_uppercase();
339 s
.get_mut(1..).map(|s
| {
340 s
.make_ascii_lowercase();
348 /// Centers the value in a field of a given width
349 pub fn center(src
: &dyn fmt
::Display
, dst_len
: usize) -> Result
<String
> {
350 let src
= src
.to_string();
356 let diff
= dst_len
- len
;
359 let mut buf
= String
::with_capacity(dst_len
);
367 for _
in 0..mid
+ r
{
375 /// Count the words in that string
376 pub fn wordcount
<T
: fmt
::Display
>(s
: T
) -> Result
<usize> {
377 let s
= s
.to_string();
379 Ok(s
.split_whitespace().count())
385 #[cfg(feature = "num-traits")]
386 use std
::f64::INFINITY
;
388 #[cfg(feature = "humansize")]
390 fn test_filesizeformat() {
391 assert_eq
!(filesizeformat(&0).unwrap(), "0 B");
392 assert_eq
!(filesizeformat(&999u64).unwrap(), "999 B");
393 assert_eq
!(filesizeformat(&1000i32).unwrap(), "1 KB");
394 assert_eq
!(filesizeformat(&1023).unwrap(), "1.02 KB");
395 assert_eq
!(filesizeformat(&1024usize
).unwrap(), "1.02 KB");
398 #[cfg(feature = "percent-encoding")]
400 fn test_urlencoding() {
401 // Unreserved (https://tools.ietf.org/html/rfc3986.html#section-2.3)
403 assert_eq
!(urlencode(&"AZaz09").unwrap(), "AZaz09");
404 assert_eq
!(urlencode_strict(&"AZaz09").unwrap(), "AZaz09");
406 assert_eq
!(urlencode(&"_.-~").unwrap(), "_.-~");
407 assert_eq
!(urlencode_strict(&"_.-~").unwrap(), "_.-~");
409 // Reserved (https://tools.ietf.org/html/rfc3986.html#section-2.2)
411 assert_eq
!(urlencode(&":/?#[]@").unwrap(), "%3A/%3F%23%5B%5D%40");
413 urlencode_strict(&":/?#[]@").unwrap(),
414 "%3A%2F%3F%23%5B%5D%40"
418 urlencode(&"!$&'()*+,;=").unwrap(),
419 "%21%24%26%27%28%29%2A%2B%2C%3B%3D"
422 urlencode_strict(&"!$&'()*+,;=").unwrap(),
423 "%21%24%26%27%28%29%2A%2B%2C%3B%3D"
428 urlencode(&"žŠďŤňĚáÉóŮ").unwrap(),
429 "%C5%BE%C5%A0%C4%8F%C5%A4%C5%88%C4%9A%C3%A1%C3%89%C3%B3%C5%AE"
432 urlencode_strict(&"žŠďŤňĚáÉóŮ").unwrap(),
433 "%C5%BE%C5%A0%C4%8F%C5%A4%C5%88%C4%9A%C3%A1%C3%89%C3%B3%C5%AE"
437 assert_eq
!(urlencode(&"🦀").unwrap(), "%F0%9F%A6%80");
438 assert_eq
!(urlencode_strict(&"🦀").unwrap(), "%F0%9F%A6%80");
442 fn test_linebreaks() {
444 linebreaks(&"Foo\nBar Baz").unwrap(),
445 "<p>Foo<br/>Bar Baz</p>"
448 linebreaks(&"Foo\nBar\n\nBaz").unwrap(),
449 "<p>Foo<br/>Bar</p><p>Baz</p>"
454 fn test_linebreaksbr() {
455 assert_eq
!(linebreaksbr(&"Foo\nBar").unwrap(), "Foo<br/>Bar");
457 linebreaksbr(&"Foo\nBar\n\nBaz").unwrap(),
458 "Foo<br/>Bar<br/><br/>Baz"
463 fn test_paragraphbreaks() {
465 paragraphbreaks(&"Foo\nBar Baz").unwrap(),
466 "<p>Foo\nBar Baz</p>"
469 paragraphbreaks(&"Foo\nBar\n\nBaz").unwrap(),
470 "<p>Foo\nBar</p><p>Baz</p>"
473 paragraphbreaks(&"Foo\n\n\n\n\nBar\n\nBaz").unwrap(),
474 "<p>Foo</p><p>\nBar</p><p>Baz</p>"
480 assert_eq
!(lower(&"Foo").unwrap(), "foo");
481 assert_eq
!(lower(&"FOO").unwrap(), "foo");
482 assert_eq
!(lower(&"FooBar").unwrap(), "foobar");
483 assert_eq
!(lower(&"foo").unwrap(), "foo");
488 assert_eq
!(upper(&"Foo").unwrap(), "FOO");
489 assert_eq
!(upper(&"FOO").unwrap(), "FOO");
490 assert_eq
!(upper(&"FooBar").unwrap(), "FOOBAR");
491 assert_eq
!(upper(&"foo").unwrap(), "FOO");
496 assert_eq
!(trim(&" Hello\tworld\t").unwrap(), "Hello\tworld");
501 assert_eq
!(truncate(&"hello", 2).unwrap(), "he...");
502 let a
= String
::from("您好");
503 assert_eq
!(a
.len(), 6);
504 assert_eq
!(String
::from("您").len(), 3);
505 assert_eq
!(truncate(&"您好", 1).unwrap(), "您...");
506 assert_eq
!(truncate(&"您好", 2).unwrap(), "您...");
507 assert_eq
!(truncate(&"您好", 3).unwrap(), "您...");
508 assert_eq
!(truncate(&"您好", 4).unwrap(), "您好...");
509 assert_eq
!(truncate(&"您好", 6).unwrap(), "您好");
510 assert_eq
!(truncate(&"您好", 7).unwrap(), "您好");
511 let s
= String
::from("🤚a🤚");
512 assert_eq
!(s
.len(), 9);
513 assert_eq
!(String
::from("🤚").len(), 4);
514 assert_eq
!(truncate(&"🤚a🤚", 1).unwrap(), "🤚...");
515 assert_eq
!(truncate(&"🤚a🤚", 2).unwrap(), "🤚...");
516 assert_eq
!(truncate(&"🤚a🤚", 3).unwrap(), "🤚...");
517 assert_eq
!(truncate(&"🤚a🤚", 4).unwrap(), "🤚...");
518 assert_eq
!(truncate(&"🤚a🤚", 5).unwrap(), "🤚a...");
519 assert_eq
!(truncate(&"🤚a🤚", 6).unwrap(), "🤚a🤚...");
520 assert_eq
!(truncate(&"🤚a🤚", 9).unwrap(), "🤚a🤚");
521 assert_eq
!(truncate(&"🤚a🤚", 10).unwrap(), "🤚a🤚");
526 assert_eq
!(indent(&"hello", 2).unwrap(), "hello");
527 assert_eq
!(indent(&"hello\n", 2).unwrap(), "hello\n");
528 assert_eq
!(indent(&"hello\nfoo", 2).unwrap(), "hello\n foo");
530 indent(&"hello\nfoo\n bar", 4).unwrap(),
535 #[cfg(feature = "num-traits")]
537 #[allow(clippy::float_cmp)]
539 assert_eq
!(into_f64(1).unwrap(), 1.0_f64);
540 assert_eq
!(into_f64(1.9).unwrap(), 1.9_f64);
541 assert_eq
!(into_f64(-1.9).unwrap(), -1.9_f64);
542 assert_eq
!(into_f64(INFINITY
as f32).unwrap(), INFINITY
);
543 assert_eq
!(into_f64(-INFINITY
as f32).unwrap(), -INFINITY
);
546 #[cfg(feature = "num-traits")]
548 fn test_into_isize() {
549 assert_eq
!(into_isize(1).unwrap(), 1_isize
);
550 assert_eq
!(into_isize(1.9).unwrap(), 1_isize
);
551 assert_eq
!(into_isize(-1.9).unwrap(), -1_isize
);
552 assert_eq
!(into_isize(1.5_f64).unwrap(), 1_isize
);
553 assert_eq
!(into_isize(-1.5_f64).unwrap(), -1_isize
);
554 match into_isize(INFINITY
) {
555 Err(Fmt(fmt
::Error
)) => {}
556 _
=> panic
!("Should return error of type Err(Fmt(fmt::Error))"),
560 #[allow(clippy::needless_borrow)]
564 join((&["hello", "world"]).iter(), ", ").unwrap(),
567 assert_eq
!(join((&["hello"]).iter(), ", ").unwrap(), "hello");
569 let empty
: &[&str] = &[];
570 assert_eq
!(join(empty
.iter(), ", ").unwrap(), "");
572 let input
: Vec
<String
> = vec
!["foo".into(), "bar".into(), "bazz".into()];
574 join((&input
).iter(), ":".to_string()).unwrap(),
577 assert_eq
!(join(input
.iter(), ":").unwrap(), "foo:bar:bazz");
578 assert_eq
!(join(input
.iter(), ":".to_string()).unwrap(), "foo:bar:bazz");
580 let input
: &[String
] = &["foo".into(), "bar".into()];
581 assert_eq
!(join(input
.iter(), ":").unwrap(), "foo:bar");
582 assert_eq
!(join(input
.iter(), ":".to_string()).unwrap(), "foo:bar");
584 let real
: String
= "blah".into();
585 let input
: Vec
<&str> = vec
![&real
];
586 assert_eq
!(join(input
.iter(), ";").unwrap(), "blah");
589 join((&&&&&["foo", "bar"]).iter(), ", ").unwrap(),
594 #[cfg(feature = "num-traits")]
596 #[allow(clippy::float_cmp)]
598 assert_eq
!(abs(1).unwrap(), 1);
599 assert_eq
!(abs(-1).unwrap(), 1);
600 assert_eq
!(abs(1.0).unwrap(), 1.0);
601 assert_eq
!(abs(-1.0).unwrap(), 1.0);
602 assert_eq
!(abs(1.0_f64).unwrap(), 1.0_f64);
603 assert_eq
!(abs(-1.0_f64).unwrap(), 1.0_f64);
607 fn test_capitalize() {
608 assert_eq
!(capitalize(&"foo").unwrap(), "Foo".to_string());
609 assert_eq
!(capitalize(&"f").unwrap(), "F".to_string());
610 assert_eq
!(capitalize(&"fO").unwrap(), "Fo".to_string());
611 assert_eq
!(capitalize(&"").unwrap(), "".to_string());
612 assert_eq
!(capitalize(&"FoO").unwrap(), "Foo".to_string());
613 assert_eq
!(capitalize(&"foO BAR").unwrap(), "Foo bar".to_string());
618 assert_eq
!(center(&"f", 3).unwrap(), " f ".to_string());
619 assert_eq
!(center(&"f", 4).unwrap(), " f ".to_string());
620 assert_eq
!(center(&"foo", 1).unwrap(), "foo".to_string());
621 assert_eq
!(center(&"foo bar", 8).unwrap(), "foo bar ".to_string());
625 fn test_wordcount() {
626 assert_eq
!(wordcount(&"").unwrap(), 0);
627 assert_eq
!(wordcount(&" \n\t").unwrap(), 0);
628 assert_eq
!(wordcount(&"foo").unwrap(), 1);
629 assert_eq
!(wordcount(&"foo bar").unwrap(), 2);