askama/filters/
alloc.rs

1use alloc::str;
2use alloc::string::String;
3use core::convert::Infallible;
4use core::fmt::{self, Write};
5
6use crate::{FastWritable, Result};
7
8/// Return an ephemeral `&str` for `$src: impl fmt::Display`
9///
10/// If `$str` is `&str` or `String`, this macro simply passes on its content.
11/// If it is neither, then the formatted data is collection into `&buffer`.
12///
13/// `return`s with an error if the formatting failed.
14macro_rules! try_to_str {
15    ($src:expr => $buffer:ident) => {
16        match format_args!("{}", $src) {
17            args => {
18                if let Some(s) = args.as_str() {
19                    s
20                } else {
21                    $buffer = String::new();
22                    $buffer.write_fmt(args)?;
23                    &$buffer
24                }
25            }
26        }
27    };
28}
29
30/// Formats arguments according to the specified format
31///
32/// The *second* argument to this filter must be a string literal (as in normal
33/// Rust). The two arguments are passed through to the `format!()`
34/// [macro](https://doc.rust-lang.org/stable/std/macro.format.html) by
35/// the Askama code generator, but the order is swapped to support filter
36/// composition.
37///
38/// ```ignore
39/// {{ value|fmt("{:?}") }}
40/// ```
41///
42/// ```
43/// # #[cfg(feature = "code-in-doc")] {
44/// # use askama::Template;
45/// /// ```jinja
46/// /// <div>{{ value|fmt("{:?}") }}</div>
47/// /// ```
48/// #[derive(Template)]
49/// #[template(ext = "html", in_doc = true)]
50/// struct Example {
51///     value: (usize, usize),
52/// }
53///
54/// assert_eq!(
55///     Example { value: (3, 4) }.to_string(),
56///     "<div>(3, 4)</div>"
57/// );
58/// # }
59/// ```
60///
61/// Compare with [format](./fn.format.html).
62pub fn fmt() {}
63
64/// Formats arguments according to the specified format
65///
66/// The first argument to this filter must be a string literal (as in normal
67/// Rust). All arguments are passed through to the `format!()`
68/// [macro](https://doc.rust-lang.org/stable/std/macro.format.html) by
69/// the Askama code generator.
70///
71/// ```ignore
72/// {{ "{:?}{:?}"|format(value, other_value) }}
73/// ```
74///
75/// ```
76/// # #[cfg(feature = "code-in-doc")] {
77/// # use askama::Template;
78/// /// ```jinja
79/// /// <div>{{ "{:?}"|format(value) }}</div>
80/// /// ```
81/// #[derive(Template)]
82/// #[template(ext = "html", in_doc = true)]
83/// struct Example {
84///     value: (usize, usize),
85/// }
86///
87/// assert_eq!(
88///     Example { value: (3, 4) }.to_string(),
89///     "<div>(3, 4)</div>"
90/// );
91/// # }
92/// ```
93///
94/// Compare with [fmt](./fn.fmt.html).
95pub fn format() {}
96
97/// Converts to lowercase
98///
99/// ```
100/// # #[cfg(feature = "code-in-doc")] {
101/// # use askama::Template;
102/// /// ```jinja
103/// /// <div>{{ word|lower }}</div>
104/// /// ```
105/// #[derive(Template)]
106/// #[template(ext = "html", in_doc = true)]
107/// struct Example<'a> {
108///     word: &'a str,
109/// }
110///
111/// assert_eq!(
112///     Example { word: "FOO" }.to_string(),
113///     "<div>foo</div>"
114/// );
115///
116/// assert_eq!(
117///     Example { word: "FooBar" }.to_string(),
118///     "<div>foobar</div>"
119/// );
120/// # }
121/// ```
122#[inline]
123pub fn lower<S: fmt::Display>(source: S) -> Result<Lower<S>, Infallible> {
124    Ok(Lower(source))
125}
126
127pub struct Lower<S>(S);
128
129impl<S: fmt::Display> fmt::Display for Lower<S> {
130    #[inline]
131    fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
132        let mut buffer;
133        flush_lower(dest, try_to_str!(self.0 => buffer))
134    }
135}
136
137impl<S: FastWritable> FastWritable for Lower<S> {
138    #[inline]
139    fn write_into(
140        &self,
141        dest: &mut dyn fmt::Write,
142        values: &dyn crate::Values,
143    ) -> crate::Result<()> {
144        let mut buffer = String::new();
145        self.0.write_into(&mut buffer, values)?;
146        Ok(flush_lower(dest, &buffer)?)
147    }
148}
149
150fn flush_lower(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
151    dest.write_str(&s.to_lowercase())
152}
153
154/// Converts to lowercase, alias for the `|lower` filter
155///
156/// ```
157/// # #[cfg(feature = "code-in-doc")] {
158/// # use askama::Template;
159/// /// ```jinja
160/// /// <div>{{ word|lowercase }}</div>
161/// /// ```
162/// #[derive(Template)]
163/// #[template(ext = "html", in_doc = true)]
164/// struct Example<'a> {
165///     word: &'a str,
166/// }
167///
168/// assert_eq!(
169///     Example { word: "FOO" }.to_string(),
170///     "<div>foo</div>"
171/// );
172///
173/// assert_eq!(
174///     Example { word: "FooBar" }.to_string(),
175///     "<div>foobar</div>"
176/// );
177/// # }
178/// ```
179#[inline]
180pub fn lowercase<S: fmt::Display>(source: S) -> Result<Lower<S>, Infallible> {
181    lower(source)
182}
183
184/// Converts to uppercase
185///
186/// ```
187/// # #[cfg(feature = "code-in-doc")] {
188/// # use askama::Template;
189/// /// ```jinja
190/// /// <div>{{ word|upper }}</div>
191/// /// ```
192/// #[derive(Template)]
193/// #[template(ext = "html", in_doc = true)]
194/// struct Example<'a> {
195///     word: &'a str,
196/// }
197///
198/// assert_eq!(
199///     Example { word: "foo" }.to_string(),
200///     "<div>FOO</div>"
201/// );
202///
203/// assert_eq!(
204///     Example { word: "FooBar" }.to_string(),
205///     "<div>FOOBAR</div>"
206/// );
207/// # }
208/// ```
209#[inline]
210pub fn upper<S: fmt::Display>(source: S) -> Result<Upper<S>, Infallible> {
211    Ok(Upper(source))
212}
213
214pub struct Upper<S>(S);
215
216impl<S: fmt::Display> fmt::Display for Upper<S> {
217    #[inline]
218    fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
219        let mut buffer;
220        flush_upper(dest, try_to_str!(self.0 => buffer))
221    }
222}
223
224impl<S: FastWritable> FastWritable for Upper<S> {
225    #[inline]
226    fn write_into(
227        &self,
228        dest: &mut dyn fmt::Write,
229        values: &dyn crate::Values,
230    ) -> crate::Result<()> {
231        let mut buffer = String::new();
232        self.0.write_into(&mut buffer, values)?;
233        Ok(flush_upper(dest, &buffer)?)
234    }
235}
236
237fn flush_upper(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
238    dest.write_str(&s.to_uppercase())
239}
240
241/// Converts to uppercase, alias for the `|upper` filter
242///
243/// ```
244/// # #[cfg(feature = "code-in-doc")] {
245/// # use askama::Template;
246/// /// ```jinja
247/// /// <div>{{ word|uppercase }}</div>
248/// /// ```
249/// #[derive(Template)]
250/// #[template(ext = "html", in_doc = true)]
251/// struct Example<'a> {
252///     word: &'a str,
253/// }
254///
255/// assert_eq!(
256///     Example { word: "foo" }.to_string(),
257///     "<div>FOO</div>"
258/// );
259///
260/// assert_eq!(
261///     Example { word: "FooBar" }.to_string(),
262///     "<div>FOOBAR</div>"
263/// );
264/// # }
265/// ```
266#[inline]
267pub fn uppercase<S: fmt::Display>(source: S) -> Result<Upper<S>, Infallible> {
268    upper(source)
269}
270
271/// Strip leading and trailing whitespace
272///
273/// ```
274/// # #[cfg(feature = "code-in-doc")] {
275/// # use askama::Template;
276/// /// ```jinja
277/// /// <div>{{ example|trim }}</div>
278/// /// ```
279/// #[derive(Template)]
280/// #[template(ext = "html", in_doc = true)]
281/// struct Example<'a> {
282///     example: &'a str,
283/// }
284///
285/// assert_eq!(
286///     Example { example: " Hello\tworld\t" }.to_string(),
287///     "<div>Hello\tworld</div>"
288/// );
289/// # }
290/// ```
291#[inline]
292pub fn trim<S: fmt::Display>(source: S) -> Result<Trim<S>, Infallible> {
293    Ok(Trim(source))
294}
295
296pub struct Trim<S>(S);
297
298impl<S: fmt::Display> fmt::Display for Trim<S> {
299    #[inline]
300    fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
301        let mut collector = TrimCollector(String::new());
302        write!(collector, "{}", self.0)?;
303        flush_trim(dest, collector)
304    }
305}
306
307impl<S: FastWritable> FastWritable for Trim<S> {
308    #[inline]
309    fn write_into(
310        &self,
311        dest: &mut dyn fmt::Write,
312        values: &dyn crate::Values,
313    ) -> crate::Result<()> {
314        let mut collector = TrimCollector(String::new());
315        self.0.write_into(&mut collector, values)?;
316        Ok(flush_trim(dest, collector)?)
317    }
318}
319
320struct TrimCollector(String);
321
322impl fmt::Write for TrimCollector {
323    fn write_str(&mut self, s: &str) -> fmt::Result {
324        match self.0.is_empty() {
325            true => self.0.write_str(s.trim_start()),
326            false => self.0.write_str(s),
327        }
328    }
329}
330
331fn flush_trim(dest: &mut (impl fmt::Write + ?Sized), collector: TrimCollector) -> fmt::Result {
332    dest.write_str(collector.0.trim_end())
333}
334
335/// Capitalize a value. The first character will be uppercase, all others lowercase.
336///
337/// ```
338/// # #[cfg(feature = "code-in-doc")] {
339/// # use askama::Template;
340/// /// ```jinja
341/// /// <div>{{ example|capitalize }}</div>
342/// /// ```
343/// #[derive(Template)]
344/// #[template(ext = "html", in_doc = true)]
345/// struct Example<'a> {
346///     example: &'a str,
347/// }
348///
349/// assert_eq!(
350///     Example { example: "hello" }.to_string(),
351///     "<div>Hello</div>"
352/// );
353///
354/// assert_eq!(
355///     Example { example: "hElLO" }.to_string(),
356///     "<div>Hello</div>"
357/// );
358/// # }
359/// ```
360#[inline]
361pub fn capitalize<S: fmt::Display>(source: S) -> Result<Capitalize<S>, Infallible> {
362    Ok(Capitalize(source))
363}
364
365pub struct Capitalize<S>(S);
366
367impl<S: fmt::Display> fmt::Display for Capitalize<S> {
368    #[inline]
369    fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
370        let mut buffer;
371        flush_capitalize(dest, try_to_str!(self.0 => buffer))
372    }
373}
374
375impl<S: FastWritable> FastWritable for Capitalize<S> {
376    #[inline]
377    fn write_into(
378        &self,
379        dest: &mut dyn fmt::Write,
380        values: &dyn crate::Values,
381    ) -> crate::Result<()> {
382        let mut buffer = String::new();
383        self.0.write_into(&mut buffer, values)?;
384        Ok(flush_capitalize(dest, &buffer)?)
385    }
386}
387
388fn flush_capitalize(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
389    let mut chars = s.chars();
390    if let Some(c) = chars.next() {
391        write!(
392            dest,
393            "{}{}",
394            c.to_uppercase(),
395            chars.as_str().to_lowercase()
396        )
397    } else {
398        Ok(())
399    }
400}
401
402/// Return a title cased version of the value. Words will start with uppercase letters, all
403/// remaining characters are lowercase.
404///
405/// ```
406/// # #[cfg(feature = "code-in-doc")] {
407/// # use askama::Template;
408/// /// ```jinja
409/// /// <div>{{ example|title }}</div>
410/// /// ```
411/// #[derive(Template)]
412/// #[template(ext = "html", in_doc = true)]
413/// struct Example<'a> {
414///     example: &'a str,
415/// }
416///
417/// assert_eq!(
418///     Example { example: "hello WORLD" }.to_string(),
419///     "<div>Hello World</div>"
420/// );
421/// # }
422/// ```
423#[inline]
424pub fn title<S: fmt::Display>(source: S) -> Result<Title<S>, Infallible> {
425    Ok(Title(source))
426}
427
428pub struct Title<S>(S);
429
430impl<S: fmt::Display> fmt::Display for Title<S> {
431    #[inline]
432    fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
433        let mut buffer;
434        flush_title(dest, try_to_str!(self.0 => buffer))
435    }
436}
437
438impl<S: FastWritable> FastWritable for Title<S> {
439    #[inline]
440    fn write_into(
441        &self,
442        dest: &mut dyn fmt::Write,
443        values: &dyn crate::Values,
444    ) -> crate::Result<()> {
445        let mut buffer = String::new();
446        self.0.write_into(&mut buffer, values)?;
447        Ok(flush_title(dest, &buffer)?)
448    }
449}
450
451fn flush_title(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
452    for word in s.split_inclusive(char::is_whitespace) {
453        flush_capitalize(dest, word)?;
454    }
455    Ok(())
456}
457
458/// Return a title cased version of the value. Alias for the [`|title`](title) filter.
459///
460/// ```
461/// # #[cfg(feature = "code-in-doc")] {
462/// # use askama::Template;
463/// /// ```jinja
464/// /// <div>{{ example|titlecase }}</div>
465/// /// ```
466/// #[derive(Template)]
467/// #[template(ext = "html", in_doc = true)]
468/// struct Example<'a> {
469///     example: &'a str,
470/// }
471///
472/// assert_eq!(
473///     Example { example: "hello WORLD" }.to_string(),
474///     "<div>Hello World</div>"
475/// );
476/// # }
477/// ```
478#[inline]
479pub fn titlecase<S: fmt::Display>(source: S) -> Result<Title<S>, Infallible> {
480    title(source)
481}
482
483#[cfg(test)]
484mod tests {
485    use alloc::string::ToString;
486
487    use super::*;
488
489    #[test]
490    fn test_lower() {
491        assert_eq!(lower("Foo").unwrap().to_string(), "foo");
492        assert_eq!(lower("FOO").unwrap().to_string(), "foo");
493        assert_eq!(lower("FooBar").unwrap().to_string(), "foobar");
494        assert_eq!(lower("foo").unwrap().to_string(), "foo");
495    }
496
497    #[test]
498    fn test_upper() {
499        assert_eq!(upper("Foo").unwrap().to_string(), "FOO");
500        assert_eq!(upper("FOO").unwrap().to_string(), "FOO");
501        assert_eq!(upper("FooBar").unwrap().to_string(), "FOOBAR");
502        assert_eq!(upper("foo").unwrap().to_string(), "FOO");
503    }
504
505    #[test]
506    fn test_trim() {
507        assert_eq!(trim(" Hello\tworld\t").unwrap().to_string(), "Hello\tworld");
508    }
509
510    #[test]
511    fn test_capitalize() {
512        assert_eq!(capitalize("foo").unwrap().to_string(), "Foo".to_string());
513        assert_eq!(capitalize("f").unwrap().to_string(), "F".to_string());
514        assert_eq!(capitalize("fO").unwrap().to_string(), "Fo".to_string());
515        assert_eq!(capitalize("").unwrap().to_string(), String::new());
516        assert_eq!(capitalize("FoO").unwrap().to_string(), "Foo".to_string());
517        assert_eq!(
518            capitalize("foO BAR").unwrap().to_string(),
519            "Foo bar".to_string()
520        );
521        assert_eq!(
522            capitalize("äØÄÅÖ").unwrap().to_string(),
523            "Äøäåö".to_string()
524        );
525        assert_eq!(capitalize("ß").unwrap().to_string(), "SS".to_string());
526        assert_eq!(capitalize("ßß").unwrap().to_string(), "SSß".to_string());
527    }
528
529    #[test]
530    fn test_title() {
531        assert_eq!(&title("").unwrap().to_string(), "");
532        assert_eq!(&title(" \n\t").unwrap().to_string(), " \n\t");
533        assert_eq!(&title("foo").unwrap().to_string(), "Foo");
534        assert_eq!(&title(" foo").unwrap().to_string(), " Foo");
535        assert_eq!(&title("foo bar").unwrap().to_string(), "Foo Bar");
536        assert_eq!(&title("foo  bar ").unwrap().to_string(), "Foo  Bar ");
537        assert_eq!(&title("fOO").unwrap().to_string(), "Foo");
538        assert_eq!(&title("fOo BaR").unwrap().to_string(), "Foo Bar");
539        assert_eq!(&title("foo\r\nbar").unwrap().to_string(), "Foo\r\nBar");
540        assert_eq!(
541            &title("Fo\x0boo\x0coO\u{2002}OO\u{3000}baR")
542                .unwrap()
543                .to_string(),
544            "Fo\x0bOo\x0cOo\u{2002}Oo\u{3000}Bar"
545        );
546    }
547}