askama/filters/
alloc.rs

1use alloc::borrow::Cow;
2use alloc::str;
3use alloc::string::String;
4use core::cell::Cell;
5use core::convert::Infallible;
6use core::fmt::{self, Write};
7use core::ops::Deref;
8use core::pin::Pin;
9
10use super::MAX_LEN;
11use super::escape::HtmlSafeOutput;
12use crate::{FastWritable, Result};
13
14/// Return an ephemeral `&str` for `$src: impl fmt::Display`
15///
16/// If `$str` is `&str` or `String`, this macro simply passes on its content.
17/// If it is neither, then the formatted data is collection into `&buffer`.
18///
19/// `return`s with an error if the formatting failed.
20macro_rules! try_to_str {
21    ($src:expr => $buffer:ident) => {
22        match format_args!("{}", $src) {
23            args => {
24                if let Some(s) = args.as_str() {
25                    s
26                } else {
27                    $buffer = String::new();
28                    $buffer.write_fmt(args)?;
29                    &$buffer
30                }
31            }
32        }
33    };
34}
35
36/// Formats arguments according to the specified format
37///
38/// The *second* argument to this filter must be a string literal (as in normal
39/// Rust). The two arguments are passed through to the `format!()`
40/// [macro](https://doc.rust-lang.org/stable/std/macro.format.html) by
41/// the Askama code generator, but the order is swapped to support filter
42/// composition.
43///
44/// ```ignore
45/// {{ value|fmt("{:?}") }}
46/// ```
47///
48/// ```
49/// # #[cfg(feature = "code-in-doc")] {
50/// # use askama::Template;
51/// /// ```jinja
52/// /// <div>{{ value|fmt("{:?}") }}</div>
53/// /// ```
54/// #[derive(Template)]
55/// #[template(ext = "html", in_doc = true)]
56/// struct Example {
57///     value: (usize, usize),
58/// }
59///
60/// assert_eq!(
61///     Example { value: (3, 4) }.to_string(),
62///     "<div>(3, 4)</div>"
63/// );
64/// # }
65/// ```
66///
67/// Compare with [format](./fn.format.html).
68pub fn fmt() {}
69
70/// Formats arguments according to the specified format
71///
72/// The first argument to this filter must be a string literal (as in normal
73/// Rust). All arguments are passed through to the `format!()`
74/// [macro](https://doc.rust-lang.org/stable/std/macro.format.html) by
75/// the Askama code generator.
76///
77/// ```ignore
78/// {{ "{:?}{:?}"|format(value, other_value) }}
79/// ```
80///
81/// ```
82/// # #[cfg(feature = "code-in-doc")] {
83/// # use askama::Template;
84/// /// ```jinja
85/// /// <div>{{ "{:?}"|format(value) }}</div>
86/// /// ```
87/// #[derive(Template)]
88/// #[template(ext = "html", in_doc = true)]
89/// struct Example {
90///     value: (usize, usize),
91/// }
92///
93/// assert_eq!(
94///     Example { value: (3, 4) }.to_string(),
95///     "<div>(3, 4)</div>"
96/// );
97/// # }
98/// ```
99///
100/// Compare with [fmt](./fn.fmt.html).
101pub fn format() {}
102
103/// Replaces line breaks in plain text with appropriate HTML
104///
105/// A single newline becomes an HTML line break `<br>` and a new line
106/// followed by a blank line becomes a paragraph break `<p>`.
107///
108/// ```
109/// # #[cfg(feature = "code-in-doc")] {
110/// # use askama::Template;
111/// /// ```jinja
112/// /// <div>{{ example|linebreaks }}</div>
113/// /// ```
114/// #[derive(Template)]
115/// #[template(ext = "html", in_doc = true)]
116/// struct Example<'a> {
117///     example: &'a str,
118/// }
119///
120/// assert_eq!(
121///     Example { example: "Foo\nBar\n\nBaz" }.to_string(),
122///     "<div><p>Foo<br/>Bar</p><p>Baz</p></div>"
123/// );
124/// # }
125/// ```
126#[inline]
127pub fn linebreaks<S: fmt::Display>(source: S) -> Result<HtmlSafeOutput<Linebreaks<S>>, Infallible> {
128    Ok(HtmlSafeOutput(Linebreaks(source)))
129}
130
131pub struct Linebreaks<S>(S);
132
133impl<S: fmt::Display> fmt::Display for Linebreaks<S> {
134    #[inline]
135    fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
136        let mut buffer;
137        flush_linebreaks(dest, try_to_str!(self.0 => buffer))
138    }
139}
140
141impl<S: FastWritable> FastWritable for Linebreaks<S> {
142    #[inline]
143    fn write_into<W: fmt::Write + ?Sized>(
144        &self,
145        dest: &mut W,
146        values: &dyn crate::Values,
147    ) -> crate::Result<()> {
148        let mut buffer = String::new();
149        self.0.write_into(&mut buffer, values)?;
150        Ok(flush_linebreaks(dest, &buffer)?)
151    }
152}
153
154fn flush_linebreaks(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
155    let linebroken = s.replace("\n\n", "</p><p>").replace('\n', "<br/>");
156    write!(dest, "<p>{linebroken}</p>")
157}
158
159/// Converts all newlines in a piece of plain text to HTML line breaks
160///
161/// ```
162/// # #[cfg(feature = "code-in-doc")] {
163/// # use askama::Template;
164/// /// ```jinja
165/// /// <div>{{ lines|linebreaksbr }}</div>
166/// /// ```
167/// #[derive(Template)]
168/// #[template(ext = "html", in_doc = true)]
169/// struct Example<'a> {
170///     lines: &'a str,
171/// }
172///
173/// assert_eq!(
174///     Example { lines: "a\nb\nc" }.to_string(),
175///     "<div>a<br/>b<br/>c</div>"
176/// );
177/// # }
178/// ```
179#[inline]
180pub fn linebreaksbr<S: fmt::Display>(
181    source: S,
182) -> Result<HtmlSafeOutput<Linebreaksbr<S>>, Infallible> {
183    Ok(HtmlSafeOutput(Linebreaksbr(source)))
184}
185
186pub struct Linebreaksbr<S>(S);
187
188impl<S: fmt::Display> fmt::Display for Linebreaksbr<S> {
189    #[inline]
190    fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
191        let mut buffer;
192        flush_linebreaksbr(dest, try_to_str!(self.0 => buffer))
193    }
194}
195
196impl<S: FastWritable> FastWritable for Linebreaksbr<S> {
197    #[inline]
198    fn write_into<W: fmt::Write + ?Sized>(
199        &self,
200        dest: &mut W,
201        values: &dyn crate::Values,
202    ) -> crate::Result<()> {
203        let mut buffer = String::new();
204        self.0.write_into(&mut buffer, values)?;
205        Ok(flush_linebreaksbr(dest, &buffer)?)
206    }
207}
208
209fn flush_linebreaksbr(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
210    dest.write_str(&s.replace('\n', "<br/>"))
211}
212
213/// Replaces only paragraph breaks in plain text with appropriate HTML
214///
215/// A new line followed by a blank line becomes a paragraph break `<p>`.
216/// Paragraph tags only wrap content; empty paragraphs are removed.
217/// No `<br/>` tags are added.
218///
219/// ```
220/// # #[cfg(feature = "code-in-doc")] {
221/// # use askama::Template;
222/// /// ```jinja
223/// /// {{ lines|paragraphbreaks }}
224/// /// ```
225/// #[derive(Template)]
226/// #[template(ext = "html", in_doc = true)]
227/// struct Example<'a> {
228///     lines: &'a str,
229/// }
230///
231/// assert_eq!(
232///     Example { lines: "Foo\nBar\n\nBaz" }.to_string(),
233///     "<p>Foo\nBar</p><p>Baz</p>"
234/// );
235/// # }
236/// ```
237#[inline]
238pub fn paragraphbreaks<S: fmt::Display>(
239    source: S,
240) -> Result<HtmlSafeOutput<Paragraphbreaks<S>>, Infallible> {
241    Ok(HtmlSafeOutput(Paragraphbreaks(source)))
242}
243
244pub struct Paragraphbreaks<S>(S);
245
246impl<S: fmt::Display> fmt::Display for Paragraphbreaks<S> {
247    #[inline]
248    fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
249        let mut buffer;
250        flush_paragraphbreaks(dest, try_to_str!(self.0 => buffer))
251    }
252}
253
254impl<S: FastWritable> FastWritable for Paragraphbreaks<S> {
255    #[inline]
256    fn write_into<W: fmt::Write + ?Sized>(
257        &self,
258        dest: &mut W,
259        values: &dyn crate::Values,
260    ) -> crate::Result<()> {
261        let mut buffer = String::new();
262        self.0.write_into(&mut buffer, values)?;
263        Ok(flush_paragraphbreaks(dest, &buffer)?)
264    }
265}
266
267fn flush_paragraphbreaks(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
268    let linebroken = s.replace("\n\n", "</p><p>").replace("<p></p>", "");
269    write!(dest, "<p>{linebroken}</p>")
270}
271
272/// Converts to lowercase
273///
274/// ```
275/// # #[cfg(feature = "code-in-doc")] {
276/// # use askama::Template;
277/// /// ```jinja
278/// /// <div>{{ word|lower }}</div>
279/// /// ```
280/// #[derive(Template)]
281/// #[template(ext = "html", in_doc = true)]
282/// struct Example<'a> {
283///     word: &'a str,
284/// }
285///
286/// assert_eq!(
287///     Example { word: "FOO" }.to_string(),
288///     "<div>foo</div>"
289/// );
290///
291/// assert_eq!(
292///     Example { word: "FooBar" }.to_string(),
293///     "<div>foobar</div>"
294/// );
295/// # }
296/// ```
297#[inline]
298pub fn lower<S: fmt::Display>(source: S) -> Result<Lower<S>, Infallible> {
299    Ok(Lower(source))
300}
301
302pub struct Lower<S>(S);
303
304impl<S: fmt::Display> fmt::Display for Lower<S> {
305    #[inline]
306    fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
307        let mut buffer;
308        flush_lower(dest, try_to_str!(self.0 => buffer))
309    }
310}
311
312impl<S: FastWritable> FastWritable for Lower<S> {
313    #[inline]
314    fn write_into<W: fmt::Write + ?Sized>(
315        &self,
316        dest: &mut W,
317        values: &dyn crate::Values,
318    ) -> crate::Result<()> {
319        let mut buffer = String::new();
320        self.0.write_into(&mut buffer, values)?;
321        Ok(flush_lower(dest, &buffer)?)
322    }
323}
324
325fn flush_lower(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
326    dest.write_str(&s.to_lowercase())
327}
328
329/// Converts to lowercase, alias for the `|lower` filter
330///
331/// ```
332/// # #[cfg(feature = "code-in-doc")] {
333/// # use askama::Template;
334/// /// ```jinja
335/// /// <div>{{ word|lowercase }}</div>
336/// /// ```
337/// #[derive(Template)]
338/// #[template(ext = "html", in_doc = true)]
339/// struct Example<'a> {
340///     word: &'a str,
341/// }
342///
343/// assert_eq!(
344///     Example { word: "FOO" }.to_string(),
345///     "<div>foo</div>"
346/// );
347///
348/// assert_eq!(
349///     Example { word: "FooBar" }.to_string(),
350///     "<div>foobar</div>"
351/// );
352/// # }
353/// ```
354#[inline]
355pub fn lowercase<S: fmt::Display>(source: S) -> Result<Lower<S>, Infallible> {
356    lower(source)
357}
358
359/// Converts to uppercase
360///
361/// ```
362/// # #[cfg(feature = "code-in-doc")] {
363/// # use askama::Template;
364/// /// ```jinja
365/// /// <div>{{ word|upper }}</div>
366/// /// ```
367/// #[derive(Template)]
368/// #[template(ext = "html", in_doc = true)]
369/// struct Example<'a> {
370///     word: &'a str,
371/// }
372///
373/// assert_eq!(
374///     Example { word: "foo" }.to_string(),
375///     "<div>FOO</div>"
376/// );
377///
378/// assert_eq!(
379///     Example { word: "FooBar" }.to_string(),
380///     "<div>FOOBAR</div>"
381/// );
382/// # }
383/// ```
384#[inline]
385pub fn upper<S: fmt::Display>(source: S) -> Result<Upper<S>, Infallible> {
386    Ok(Upper(source))
387}
388
389pub struct Upper<S>(S);
390
391impl<S: fmt::Display> fmt::Display for Upper<S> {
392    #[inline]
393    fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
394        let mut buffer;
395        flush_upper(dest, try_to_str!(self.0 => buffer))
396    }
397}
398
399impl<S: FastWritable> FastWritable for Upper<S> {
400    #[inline]
401    fn write_into<W: fmt::Write + ?Sized>(
402        &self,
403        dest: &mut W,
404        values: &dyn crate::Values,
405    ) -> crate::Result<()> {
406        let mut buffer = String::new();
407        self.0.write_into(&mut buffer, values)?;
408        Ok(flush_upper(dest, &buffer)?)
409    }
410}
411
412fn flush_upper(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
413    dest.write_str(&s.to_uppercase())
414}
415
416/// Converts to uppercase, alias for the `|upper` filter
417///
418/// ```
419/// # #[cfg(feature = "code-in-doc")] {
420/// # use askama::Template;
421/// /// ```jinja
422/// /// <div>{{ word|uppercase }}</div>
423/// /// ```
424/// #[derive(Template)]
425/// #[template(ext = "html", in_doc = true)]
426/// struct Example<'a> {
427///     word: &'a str,
428/// }
429///
430/// assert_eq!(
431///     Example { word: "foo" }.to_string(),
432///     "<div>FOO</div>"
433/// );
434///
435/// assert_eq!(
436///     Example { word: "FooBar" }.to_string(),
437///     "<div>FOOBAR</div>"
438/// );
439/// # }
440/// ```
441#[inline]
442pub fn uppercase<S: fmt::Display>(source: S) -> Result<Upper<S>, Infallible> {
443    upper(source)
444}
445
446/// Strip leading and trailing whitespace
447///
448/// ```
449/// # #[cfg(feature = "code-in-doc")] {
450/// # use askama::Template;
451/// /// ```jinja
452/// /// <div>{{ example|trim }}</div>
453/// /// ```
454/// #[derive(Template)]
455/// #[template(ext = "html", in_doc = true)]
456/// struct Example<'a> {
457///     example: &'a str,
458/// }
459///
460/// assert_eq!(
461///     Example { example: " Hello\tworld\t" }.to_string(),
462///     "<div>Hello\tworld</div>"
463/// );
464/// # }
465/// ```
466#[inline]
467pub fn trim<S: fmt::Display>(source: S) -> Result<Trim<S>, Infallible> {
468    Ok(Trim(source))
469}
470
471pub struct Trim<S>(S);
472
473impl<S: fmt::Display> fmt::Display for Trim<S> {
474    #[inline]
475    fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
476        let mut collector = TrimCollector(String::new());
477        write!(collector, "{}", self.0)?;
478        flush_trim(dest, collector)
479    }
480}
481
482impl<S: FastWritable> FastWritable for Trim<S> {
483    #[inline]
484    fn write_into<W: fmt::Write + ?Sized>(
485        &self,
486        dest: &mut W,
487        values: &dyn crate::Values,
488    ) -> crate::Result<()> {
489        let mut collector = TrimCollector(String::new());
490        self.0.write_into(&mut collector, values)?;
491        Ok(flush_trim(dest, collector)?)
492    }
493}
494
495struct TrimCollector(String);
496
497impl fmt::Write for TrimCollector {
498    fn write_str(&mut self, s: &str) -> fmt::Result {
499        match self.0.is_empty() {
500            true => self.0.write_str(s.trim_start()),
501            false => self.0.write_str(s),
502        }
503    }
504}
505
506fn flush_trim(dest: &mut (impl fmt::Write + ?Sized), collector: TrimCollector) -> fmt::Result {
507    dest.write_str(collector.0.trim_end())
508}
509
510/// Indent lines with spaces or a prefix.
511///
512/// The first line and blank lines are not indented by default.
513/// The filter has two optional [`bool`] arguments, `first` and `blank`, that can be set to `true`
514/// to indent the first and blank lines, resp.
515///
516/// ### Example of `indent` with spaces
517///
518/// ```
519/// # #[cfg(feature = "code-in-doc")] {
520/// # use askama::Template;
521/// /// ```jinja
522/// /// <div>{{ example|indent(4) }}</div>
523/// /// ```
524/// #[derive(Template)]
525/// #[template(ext = "html", in_doc = true)]
526/// struct Example<'a> {
527///     example: &'a str,
528/// }
529///
530/// assert_eq!(
531///     Example { example: "hello\nfoo\nbar" }.to_string(),
532///     "<div>hello\n    foo\n    bar</div>"
533/// );
534/// # }
535/// ```
536///
537/// ### Example of `indent` with prefix a custom prefix
538///
539/// ```
540/// # #[cfg(feature = "code-in-doc")] {
541/// # use askama::Template;
542/// /// ```jinja
543/// /// <div>{{ example|indent("$$$ ") }}</div>
544/// /// ```
545/// #[derive(Template)]
546/// #[template(ext = "html", in_doc = true)]
547/// struct Example<'a> {
548///     example: &'a str,
549/// }
550///
551/// assert_eq!(
552///     Example { example: "hello\nfoo\nbar" }.to_string(),
553///     "<div>hello\n$$$ foo\n$$$ bar</div>"
554/// );
555/// # }
556/// ```
557#[inline]
558pub fn indent<S, I: AsIndent>(
559    source: S,
560    indent: I,
561    first: bool,
562    blank: bool,
563) -> Result<Indent<S, I>, Infallible> {
564    Ok(Indent {
565        source,
566        indent,
567        first,
568        blank,
569    })
570}
571
572pub struct Indent<S, I> {
573    source: S,
574    indent: I,
575    first: bool,
576    blank: bool,
577}
578
579impl<S: fmt::Display, I: AsIndent> fmt::Display for Indent<S, I> {
580    fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
581        let indent = self.indent.as_indent();
582        if indent.len() >= MAX_LEN || indent.is_empty() {
583            write!(dest, "{}", self.source)
584        } else {
585            let mut buffer;
586            let buffer = try_to_str!(self.source => buffer);
587            flush_indent(dest, indent, buffer, self.first, self.blank)
588        }
589    }
590}
591
592impl<S: FastWritable, I: AsIndent> FastWritable for Indent<S, I> {
593    fn write_into<W: fmt::Write + ?Sized>(
594        &self,
595        dest: &mut W,
596        values: &dyn crate::Values,
597    ) -> crate::Result<()> {
598        let indent = self.indent.as_indent();
599        if indent.len() >= MAX_LEN || indent.is_empty() {
600            self.source.write_into(dest, values)
601        } else {
602            let mut buffer = String::new();
603            self.source.write_into(&mut buffer, values)?;
604            Ok(flush_indent(dest, indent, &buffer, self.first, self.blank)?)
605        }
606    }
607}
608
609fn flush_indent(
610    dest: &mut (impl fmt::Write + ?Sized),
611    indent: &str,
612    s: &str,
613    first: bool,
614    blank: bool,
615) -> fmt::Result {
616    if s.len() >= MAX_LEN {
617        return dest.write_str(s);
618    }
619
620    for (idx, line) in s.split_inclusive('\n').enumerate() {
621        if (first || idx > 0) && (blank || !matches!(line, "\n" | "\r\n")) {
622            dest.write_str(indent)?;
623        }
624        dest.write_str(line)?;
625    }
626    Ok(())
627}
628
629/// Capitalize a value. The first character will be uppercase, all others lowercase.
630///
631/// ```
632/// # #[cfg(feature = "code-in-doc")] {
633/// # use askama::Template;
634/// /// ```jinja
635/// /// <div>{{ example|capitalize }}</div>
636/// /// ```
637/// #[derive(Template)]
638/// #[template(ext = "html", in_doc = true)]
639/// struct Example<'a> {
640///     example: &'a str,
641/// }
642///
643/// assert_eq!(
644///     Example { example: "hello" }.to_string(),
645///     "<div>Hello</div>"
646/// );
647///
648/// assert_eq!(
649///     Example { example: "hElLO" }.to_string(),
650///     "<div>Hello</div>"
651/// );
652/// # }
653/// ```
654#[inline]
655pub fn capitalize<S: fmt::Display>(source: S) -> Result<Capitalize<S>, Infallible> {
656    Ok(Capitalize(source))
657}
658
659pub struct Capitalize<S>(S);
660
661impl<S: fmt::Display> fmt::Display for Capitalize<S> {
662    #[inline]
663    fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
664        let mut buffer;
665        flush_capitalize(dest, try_to_str!(self.0 => buffer))
666    }
667}
668
669impl<S: FastWritable> FastWritable for Capitalize<S> {
670    #[inline]
671    fn write_into<W: fmt::Write + ?Sized>(
672        &self,
673        dest: &mut W,
674        values: &dyn crate::Values,
675    ) -> crate::Result<()> {
676        let mut buffer = String::new();
677        self.0.write_into(&mut buffer, values)?;
678        Ok(flush_capitalize(dest, &buffer)?)
679    }
680}
681
682fn flush_capitalize(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
683    let mut chars = s.chars();
684    if let Some(c) = chars.next() {
685        write!(
686            dest,
687            "{}{}",
688            c.to_uppercase(),
689            chars.as_str().to_lowercase()
690        )
691    } else {
692        Ok(())
693    }
694}
695
696/// Count the words in that string.
697///
698/// ```
699/// # #[cfg(feature = "code-in-doc")] {
700/// # use askama::Template;
701/// /// ```jinja
702/// /// <div>{{ example|wordcount }}</div>
703/// /// ```
704/// #[derive(Template)]
705/// #[template(ext = "html", in_doc = true)]
706/// struct Example<'a> {
707///     example: &'a str,
708/// }
709///
710/// assert_eq!(
711///     Example { example: "askama is sort of cool" }.to_string(),
712///     "<div>5</div>"
713/// );
714/// # }
715/// ```
716#[inline]
717pub fn wordcount<S>(source: S) -> Wordcount<S> {
718    Wordcount(Cell::new(Some(WordcountInner {
719        source,
720        buffer: String::new(),
721    })))
722}
723
724pub struct Wordcount<S>(Cell<Option<WordcountInner<S>>>);
725
726struct WordcountInner<S> {
727    source: S,
728    buffer: String,
729}
730
731impl<S: fmt::Display> fmt::Display for Wordcount<S> {
732    #[inline]
733    fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
734        if let Some(mut inner) = self.0.take() {
735            write!(inner.buffer, "{}", inner.source)?;
736            self.0.set(Some(inner));
737        }
738        Ok(())
739    }
740}
741
742impl<S: FastWritable> FastWritable for Wordcount<S> {
743    #[inline]
744    fn write_into<W: fmt::Write + ?Sized>(
745        &self,
746        _: &mut W,
747        values: &dyn crate::Values,
748    ) -> crate::Result<()> {
749        if let Some(mut inner) = self.0.take() {
750            inner.source.write_into(&mut inner.buffer, values)?;
751            self.0.set(Some(inner));
752        }
753        Ok(())
754    }
755}
756
757impl<S> Wordcount<S> {
758    pub fn into_count(self) -> usize {
759        if let Some(inner) = self.0.into_inner() {
760            inner.buffer.split_whitespace().count()
761        } else {
762            0
763        }
764    }
765}
766
767/// Return a title cased version of the value. Words will start with uppercase letters, all
768/// remaining characters are lowercase.
769///
770/// ```
771/// # #[cfg(feature = "code-in-doc")] {
772/// # use askama::Template;
773/// /// ```jinja
774/// /// <div>{{ example|title }}</div>
775/// /// ```
776/// #[derive(Template)]
777/// #[template(ext = "html", in_doc = true)]
778/// struct Example<'a> {
779///     example: &'a str,
780/// }
781///
782/// assert_eq!(
783///     Example { example: "hello WORLD" }.to_string(),
784///     "<div>Hello World</div>"
785/// );
786/// # }
787/// ```
788#[inline]
789pub fn title<S: fmt::Display>(source: S) -> Result<Title<S>, Infallible> {
790    Ok(Title(source))
791}
792
793pub struct Title<S>(S);
794
795impl<S: fmt::Display> fmt::Display for Title<S> {
796    #[inline]
797    fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
798        let mut buffer;
799        flush_title(dest, try_to_str!(self.0 => buffer))
800    }
801}
802
803impl<S: FastWritable> FastWritable for Title<S> {
804    #[inline]
805    fn write_into<W: fmt::Write + ?Sized>(
806        &self,
807        dest: &mut W,
808        values: &dyn crate::Values,
809    ) -> crate::Result<()> {
810        let mut buffer = String::new();
811        self.0.write_into(&mut buffer, values)?;
812        Ok(flush_title(dest, &buffer)?)
813    }
814}
815
816fn flush_title(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
817    for word in s.split_inclusive(char::is_whitespace) {
818        flush_capitalize(dest, word)?;
819    }
820    Ok(())
821}
822
823/// Return a title cased version of the value. Alias for the [`|title`](title) filter.
824///
825/// ```
826/// # #[cfg(feature = "code-in-doc")] {
827/// # use askama::Template;
828/// /// ```jinja
829/// /// <div>{{ example|titlecase }}</div>
830/// /// ```
831/// #[derive(Template)]
832/// #[template(ext = "html", in_doc = true)]
833/// struct Example<'a> {
834///     example: &'a str,
835/// }
836///
837/// assert_eq!(
838///     Example { example: "hello WORLD" }.to_string(),
839///     "<div>Hello World</div>"
840/// );
841/// # }
842/// ```
843#[inline]
844pub fn titlecase<S: fmt::Display>(source: S) -> Result<Title<S>, Infallible> {
845    title(source)
846}
847
848/// A prefix usable for indenting [prettified JSON data](super::json::json_pretty) and
849/// [`|indent`](indent)
850///
851/// ```
852/// # use askama::filters::AsIndent;
853/// assert_eq!(4.as_indent(), "    ");
854/// assert_eq!(" -> ".as_indent(), " -> ");
855/// ```
856pub trait AsIndent {
857    /// Borrow `self` as prefix to use.
858    fn as_indent(&self) -> &str;
859}
860
861impl AsIndent for str {
862    #[inline]
863    fn as_indent(&self) -> &str {
864        self
865    }
866}
867
868#[cfg(feature = "alloc")]
869impl AsIndent for alloc::string::String {
870    #[inline]
871    fn as_indent(&self) -> &str {
872        self
873    }
874}
875
876impl AsIndent for usize {
877    #[inline]
878    fn as_indent(&self) -> &str {
879        spaces(*self)
880    }
881}
882
883impl AsIndent for core::num::Wrapping<usize> {
884    #[inline]
885    fn as_indent(&self) -> &str {
886        spaces(self.0)
887    }
888}
889
890impl AsIndent for core::num::NonZeroUsize {
891    #[inline]
892    fn as_indent(&self) -> &str {
893        spaces(self.get())
894    }
895}
896
897fn spaces(width: usize) -> &'static str {
898    const MAX_SPACES: usize = 16;
899    const SPACES: &str = match str::from_utf8(&[b' '; MAX_SPACES]) {
900        Ok(spaces) => spaces,
901        Err(_) => panic!(),
902    };
903
904    &SPACES[..width.min(SPACES.len())]
905}
906
907#[cfg(feature = "alloc")]
908impl<T: AsIndent + alloc::borrow::ToOwned + ?Sized> AsIndent for Cow<'_, T> {
909    #[inline]
910    fn as_indent(&self) -> &str {
911        T::as_indent(self)
912    }
913}
914
915crate::impl_for_ref! {
916    impl AsIndent for T {
917        #[inline]
918        fn as_indent(&self) -> &str {
919            <T>::as_indent(self)
920        }
921    }
922}
923
924impl<T> AsIndent for Pin<T>
925where
926    T: Deref,
927    <T as Deref>::Target: AsIndent,
928{
929    #[inline]
930    fn as_indent(&self) -> &str {
931        self.as_ref().get_ref().as_indent()
932    }
933}
934
935#[cfg(test)]
936mod tests {
937    use alloc::string::ToString;
938    use std::borrow::ToOwned;
939
940    use super::*;
941    use crate::NO_VALUES;
942
943    #[test]
944    fn test_linebreaks() {
945        assert_eq!(
946            linebreaks("Foo\nBar Baz").unwrap().to_string(),
947            "<p>Foo<br/>Bar Baz</p>"
948        );
949        assert_eq!(
950            linebreaks("Foo\nBar\n\nBaz").unwrap().to_string(),
951            "<p>Foo<br/>Bar</p><p>Baz</p>"
952        );
953    }
954
955    #[test]
956    fn test_linebreaksbr() {
957        assert_eq!(linebreaksbr("Foo\nBar").unwrap().to_string(), "Foo<br/>Bar");
958        assert_eq!(
959            linebreaksbr("Foo\nBar\n\nBaz").unwrap().to_string(),
960            "Foo<br/>Bar<br/><br/>Baz"
961        );
962    }
963
964    #[test]
965    fn test_paragraphbreaks() {
966        assert_eq!(
967            paragraphbreaks("Foo\nBar Baz").unwrap().to_string(),
968            "<p>Foo\nBar Baz</p>"
969        );
970        assert_eq!(
971            paragraphbreaks("Foo\nBar\n\nBaz").unwrap().to_string(),
972            "<p>Foo\nBar</p><p>Baz</p>"
973        );
974        assert_eq!(
975            paragraphbreaks("Foo\n\n\n\n\nBar\n\nBaz")
976                .unwrap()
977                .to_string(),
978            "<p>Foo</p><p>\nBar</p><p>Baz</p>"
979        );
980    }
981
982    #[test]
983    fn test_lower() {
984        assert_eq!(lower("Foo").unwrap().to_string(), "foo");
985        assert_eq!(lower("FOO").unwrap().to_string(), "foo");
986        assert_eq!(lower("FooBar").unwrap().to_string(), "foobar");
987        assert_eq!(lower("foo").unwrap().to_string(), "foo");
988    }
989
990    #[test]
991    fn test_upper() {
992        assert_eq!(upper("Foo").unwrap().to_string(), "FOO");
993        assert_eq!(upper("FOO").unwrap().to_string(), "FOO");
994        assert_eq!(upper("FooBar").unwrap().to_string(), "FOOBAR");
995        assert_eq!(upper("foo").unwrap().to_string(), "FOO");
996    }
997
998    #[test]
999    fn test_trim() {
1000        assert_eq!(trim(" Hello\tworld\t").unwrap().to_string(), "Hello\tworld");
1001    }
1002
1003    #[test]
1004    fn test_indent() {
1005        assert_eq!(
1006            indent("hello", 2, false, false).unwrap().to_string(),
1007            "hello"
1008        );
1009        assert_eq!(
1010            indent("hello\n", 2, false, false).unwrap().to_string(),
1011            "hello\n"
1012        );
1013        assert_eq!(
1014            indent("hello\nfoo", 2, false, false).unwrap().to_string(),
1015            "hello\n  foo"
1016        );
1017        assert_eq!(
1018            indent("hello\nfoo\n bar", 4, false, false)
1019                .unwrap()
1020                .to_string(),
1021            "hello\n    foo\n     bar"
1022        );
1023        assert_eq!(
1024            indent("hello", 267_332_238_858, false, false)
1025                .unwrap()
1026                .to_string(),
1027            "hello"
1028        );
1029
1030        assert_eq!(
1031            indent("hello\n\n bar", 4, false, false)
1032                .unwrap()
1033                .to_string(),
1034            "hello\n\n     bar"
1035        );
1036        assert_eq!(
1037            indent("hello\n\n bar", 4, false, true).unwrap().to_string(),
1038            "hello\n    \n     bar"
1039        );
1040        assert_eq!(
1041            indent("hello\n\n bar", 4, true, false).unwrap().to_string(),
1042            "    hello\n\n     bar"
1043        );
1044        assert_eq!(
1045            indent("hello\n\n bar", 4, true, true).unwrap().to_string(),
1046            "    hello\n    \n     bar"
1047        );
1048    }
1049
1050    #[test]
1051    fn test_indent_str() {
1052        assert_eq!(
1053            indent("hello\n\n bar", "❗❓", false, false)
1054                .unwrap()
1055                .to_string(),
1056            "hello\n\n❗❓ bar"
1057        );
1058        assert_eq!(
1059            indent("hello\n\n bar", "❗❓", false, true)
1060                .unwrap()
1061                .to_string(),
1062            "hello\n❗❓\n❗❓ bar"
1063        );
1064        assert_eq!(
1065            indent("hello\n\n bar", "❗❓", true, false)
1066                .unwrap()
1067                .to_string(),
1068            "❗❓hello\n\n❗❓ bar"
1069        );
1070        assert_eq!(
1071            indent("hello\n\n bar", "❗❓", true, true)
1072                .unwrap()
1073                .to_string(),
1074            "❗❓hello\n❗❓\n❗❓ bar"
1075        );
1076    }
1077
1078    #[test]
1079    #[allow(clippy::arc_with_non_send_sync)] // it's only a test, it does not have to make sense
1080    #[allow(clippy::type_complexity)] // it's only a test, it does not have to be pretty
1081    fn test_indent_complicated() {
1082        use std::boxed::Box;
1083        use std::cell::{RefCell, RefMut};
1084        use std::rc::Rc;
1085        use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockWriteGuard};
1086
1087        let prefix = Mutex::new(Box::pin("❗❓".to_owned()));
1088        let prefix = RefCell::new(Arc::new(prefix.try_lock().unwrap()));
1089        let prefix = RwLock::new(Rc::new(prefix.borrow_mut()));
1090        let prefix: RwLockWriteGuard<'_, Rc<RefMut<'_, Arc<MutexGuard<'_, Pin<Box<String>>>>>>> =
1091            prefix.try_write().unwrap();
1092
1093        assert_eq!(
1094            indent("hello\n\n bar", &prefix, false, false)
1095                .unwrap()
1096                .to_string(),
1097            "hello\n\n❗❓ bar"
1098        );
1099        assert_eq!(
1100            indent("hello\n\n bar", &prefix, false, true)
1101                .unwrap()
1102                .to_string(),
1103            "hello\n❗❓\n❗❓ bar"
1104        );
1105        assert_eq!(
1106            indent("hello\n\n bar", &prefix, true, false)
1107                .unwrap()
1108                .to_string(),
1109            "❗❓hello\n\n❗❓ bar"
1110        );
1111        assert_eq!(
1112            indent("hello\n\n bar", &prefix, true, true)
1113                .unwrap()
1114                .to_string(),
1115            "❗❓hello\n❗❓\n❗❓ bar"
1116        );
1117    }
1118
1119    #[test]
1120    fn test_capitalize() {
1121        assert_eq!(capitalize("foo").unwrap().to_string(), "Foo".to_string());
1122        assert_eq!(capitalize("f").unwrap().to_string(), "F".to_string());
1123        assert_eq!(capitalize("fO").unwrap().to_string(), "Fo".to_string());
1124        assert_eq!(capitalize("").unwrap().to_string(), String::new());
1125        assert_eq!(capitalize("FoO").unwrap().to_string(), "Foo".to_string());
1126        assert_eq!(
1127            capitalize("foO BAR").unwrap().to_string(),
1128            "Foo bar".to_string()
1129        );
1130        assert_eq!(
1131            capitalize("äØÄÅÖ").unwrap().to_string(),
1132            "Äøäåö".to_string()
1133        );
1134        assert_eq!(capitalize("ß").unwrap().to_string(), "SS".to_string());
1135        assert_eq!(capitalize("ßß").unwrap().to_string(), "SSß".to_string());
1136    }
1137
1138    #[test]
1139    fn test_wordcount() {
1140        for &(word, count) in &[
1141            ("", 0),
1142            (" \n\t", 0),
1143            ("foo", 1),
1144            ("foo bar", 2),
1145            ("foo  bar", 2),
1146        ] {
1147            let w = wordcount(word);
1148            let _ = w.to_string();
1149            assert_eq!(w.into_count(), count, "fmt: {word:?}");
1150
1151            let w = wordcount(word);
1152            w.write_into(&mut String::new(), NO_VALUES).unwrap();
1153            assert_eq!(w.into_count(), count, "FastWritable: {word:?}");
1154        }
1155    }
1156
1157    #[test]
1158    fn test_title() {
1159        assert_eq!(&title("").unwrap().to_string(), "");
1160        assert_eq!(&title(" \n\t").unwrap().to_string(), " \n\t");
1161        assert_eq!(&title("foo").unwrap().to_string(), "Foo");
1162        assert_eq!(&title(" foo").unwrap().to_string(), " Foo");
1163        assert_eq!(&title("foo bar").unwrap().to_string(), "Foo Bar");
1164        assert_eq!(&title("foo  bar ").unwrap().to_string(), "Foo  Bar ");
1165        assert_eq!(&title("fOO").unwrap().to_string(), "Foo");
1166        assert_eq!(&title("fOo BaR").unwrap().to_string(), "Foo Bar");
1167        assert_eq!(&title("foo\r\nbar").unwrap().to_string(), "Foo\r\nBar");
1168        assert_eq!(
1169            &title("Fo\x0boo\x0coO\u{2002}OO\u{3000}baR")
1170                .unwrap()
1171                .to_string(),
1172            "Fo\x0bOo\x0cOo\u{2002}Oo\u{3000}Bar"
1173        );
1174    }
1175
1176    #[test]
1177    fn fuzzed_indent_filter() {
1178        let s = "hello\nfoo\nbar".to_string().repeat(1024);
1179        assert_eq!(indent(s.clone(), 4, false, false).unwrap().to_string(), s);
1180    }
1181}