Skip to main content

askama/filters/
core.rs

1use core::cell::Cell;
2use core::convert::Infallible;
3use core::fmt::{self, Write};
4use core::mem::replace;
5use core::ops::Deref;
6use core::pin::Pin;
7
8use super::MAX_LEN;
9use crate::filters::HtmlSafeOutput;
10use crate::{Error, FastWritable, Result, Values};
11
12/// Limit string length, appends '...' if truncated
13///
14/// ```
15/// # #[cfg(feature = "code-in-doc")] {
16/// # use askama::Template;
17/// /// ```jinja
18/// /// <div>{{ example|truncate(2) }}</div>
19/// /// ```
20/// #[derive(Template)]
21/// #[template(ext = "html", in_doc = true)]
22/// struct Example<'a> {
23///     example: &'a str,
24/// }
25///
26/// assert_eq!(
27///     Example { example: "hello" }.to_string(),
28///     "<div>he...</div>"
29/// );
30/// # }
31/// ```
32#[inline]
33pub fn truncate<S: fmt::Display>(
34    source: S,
35    remaining: usize,
36) -> Result<TruncateFilter<S>, Infallible> {
37    Ok(TruncateFilter { source, remaining })
38}
39
40pub struct TruncateFilter<S> {
41    source: S,
42    remaining: usize,
43}
44
45impl<S: fmt::Display> fmt::Display for TruncateFilter<S> {
46    #[inline]
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        write!(TruncateWriter::new(f, self.remaining), "{}", self.source)
49    }
50}
51
52impl<S: FastWritable> FastWritable for TruncateFilter<S> {
53    #[inline]
54    fn write_into(&self, dest: &mut dyn fmt::Write, values: &dyn Values) -> crate::Result<()> {
55        self.source
56            .write_into(&mut TruncateWriter::new(dest, self.remaining), values)
57    }
58}
59
60struct TruncateWriter<W> {
61    dest: Option<W>,
62    remaining: usize,
63}
64
65impl<W> TruncateWriter<W> {
66    fn new(dest: W, remaining: usize) -> Self {
67        TruncateWriter {
68            dest: Some(dest),
69            remaining,
70        }
71    }
72}
73
74impl<W: fmt::Write> fmt::Write for TruncateWriter<W> {
75    fn write_str(&mut self, s: &str) -> fmt::Result {
76        let Some(dest) = &mut self.dest else {
77            return Ok(());
78        };
79        let mut rem = self.remaining;
80        if rem >= s.len() {
81            dest.write_str(s)?;
82            self.remaining -= s.len();
83        } else {
84            if rem > 0 {
85                while !s.is_char_boundary(rem) {
86                    rem += 1;
87                }
88                if rem == s.len() {
89                    // Don't write "..." if the char bound extends to the end of string.
90                    self.remaining = 0;
91                    return dest.write_str(s);
92                }
93                dest.write_str(&s[..rem])?;
94            }
95            dest.write_str("...")?;
96            self.dest = None;
97        }
98        Ok(())
99    }
100
101    #[inline]
102    fn write_char(&mut self, c: char) -> fmt::Result {
103        match self.dest.is_some() {
104            true => self.write_str(c.encode_utf8(&mut [0; 4])),
105            false => Ok(()),
106        }
107    }
108
109    #[inline]
110    fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
111        match self.dest.is_some() {
112            true => fmt::write(self, args),
113            false => Ok(()),
114        }
115    }
116}
117
118/// Joins iterable into a string separated by provided argument
119///
120/// ```
121/// # #[cfg(feature = "code-in-doc")] {
122/// # use askama::Template;
123/// /// ```jinja
124/// /// <div>{{ example|join(", ") }}</div>
125/// /// ```
126/// #[derive(Template)]
127/// #[template(ext = "html", in_doc = true)]
128/// struct Example<'a> {
129///     example: &'a [&'a str],
130/// }
131///
132/// assert_eq!(
133///     Example { example: &["foo", "bar", "bazz"] }.to_string(),
134///     "<div>foo, bar, bazz</div>"
135/// );
136/// # }
137/// ```
138#[inline]
139pub fn join<I, S>(input: I, separator: S) -> Result<JoinFilter<I, S>, Infallible>
140where
141    I: IntoIterator,
142    I::Item: fmt::Display,
143    S: fmt::Display,
144{
145    Ok(JoinFilter(Cell::new(Some((input, separator)))))
146}
147
148/// Result of the filter [`join()`].
149///
150/// ## Note
151///
152/// This struct implements [`fmt::Display`], but only produces a string once.
153/// Any subsequent call to `.to_string()` will result in an empty string, because the iterator is
154/// already consumed.
155// The filter contains a [`Cell`], so we can modify iterator inside a method that takes `self` by
156// reference: [`fmt::Display::fmt()`] normally has the contract that it will produce the same result
157// in multiple invocations for the same object. We break this contract, because have to consume the
158// iterator, unless we want to enforce `I: Clone`, nor do we want to "memorize" the result of the
159// joined data.
160pub struct JoinFilter<I, S>(Cell<Option<(I, S)>>);
161
162impl<I, S> fmt::Display for JoinFilter<I, S>
163where
164    I: IntoIterator,
165    I::Item: fmt::Display,
166    S: fmt::Display,
167{
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        let Some((iter, separator)) = self.0.take() else {
170            return Ok(());
171        };
172        for (idx, token) in iter.into_iter().enumerate() {
173            match idx {
174                0 => f.write_fmt(format_args!("{token}"))?,
175                _ => f.write_fmt(format_args!("{separator}{token}"))?,
176            }
177        }
178        Ok(())
179    }
180}
181
182/// Centers the value in a field of a given width
183///
184/// ```
185/// # #[cfg(feature = "code-in-doc")] {
186/// # use askama::Template;
187/// /// ```jinja
188/// /// <div>-{{ example|center(5) }}-</div>
189/// /// ```
190/// #[derive(Template)]
191/// #[template(ext = "html", in_doc = true)]
192/// struct Example<'a> {
193///     example: &'a str,
194/// }
195///
196/// assert_eq!(
197///     Example { example: "a" }.to_string(),
198///     "<div>-  a  -</div>"
199/// );
200/// # }
201/// ```
202#[inline]
203pub fn center<T: fmt::Display>(src: T, width: usize) -> Result<Center<T>, Infallible> {
204    Ok(Center { src, width })
205}
206
207pub struct Center<T> {
208    src: T,
209    width: usize,
210}
211
212impl<T: fmt::Display> fmt::Display for Center<T> {
213    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214        if self.width < MAX_LEN {
215            write!(f, "{: ^1$}", self.src, self.width)
216        } else {
217            write!(f, "{}", self.src)
218        }
219    }
220}
221
222/// For a value of `±1` by default an empty string `""` is returned, otherwise `"s"`.
223///
224/// # Examples
225///
226/// ## With default arguments
227///
228/// ```
229/// # #[cfg(feature = "code-in-doc")] {
230/// # use askama::Template;
231/// /// ```jinja
232/// /// I have {{dogs}} dog{{dogs|pluralize}} and {{cats}} cat{{cats|pluralize}}.
233/// /// ```
234/// #[derive(Template)]
235/// #[template(ext = "html", in_doc = true)]
236/// struct Pets {
237///     dogs: i8,
238///     cats: i8,
239/// }
240///
241/// assert_eq!(
242///     Pets { dogs: 0, cats: 0 }.to_string(),
243///     "I have 0 dogs and 0 cats."
244/// );
245/// assert_eq!(
246///     Pets { dogs: 1, cats: 1 }.to_string(),
247///     "I have 1 dog and 1 cat."
248/// );
249/// assert_eq!(
250///     Pets { dogs: -1, cats: 99 }.to_string(),
251///     "I have -1 dog and 99 cats."
252/// );
253/// # }
254/// ```
255///
256/// ## Overriding the singular case
257///
258/// ```
259/// # #[cfg(feature = "code-in-doc")] {
260/// # use askama::Template;
261/// /// ```jinja
262/// /// I have {{dogs}} dog{{ dogs|pluralize("go") }}.
263/// /// ```
264/// #[derive(Template)]
265/// #[template(ext = "html", in_doc = true)]
266/// struct Dog {
267///     dogs: i8,
268/// }
269///
270/// assert_eq!(
271///     Dog { dogs: 0 }.to_string(),
272///     "I have 0 dogs."
273/// );
274/// assert_eq!(
275///     Dog { dogs: 1 }.to_string(),
276///     "I have 1 doggo."
277/// );
278/// # }
279/// ```
280///
281/// ## Overriding singular and plural cases
282///
283/// ```
284/// # #[cfg(feature = "code-in-doc")] {
285/// # use askama::Template;
286/// /// ```jinja
287/// /// I have {{mice}} {{ mice|pluralize("mouse", "mice") }}.
288/// /// ```
289/// #[derive(Template)]
290/// #[template(ext = "html", in_doc = true)]
291/// struct Mice {
292///     mice: i8,
293/// }
294///
295/// assert_eq!(
296///     Mice { mice: 42 }.to_string(),
297///     "I have 42 mice."
298/// );
299/// assert_eq!(
300///     Mice { mice: 1 }.to_string(),
301///     "I have 1 mouse."
302/// );
303/// # }
304/// ```
305///
306/// ## Arguments get escaped
307///
308/// ```
309/// # #[cfg(feature = "code-in-doc")] {
310/// # use askama::Template;
311/// /// ```jinja
312/// /// You are number {{ number|pluralize("<b>ONE</b>", number) }}!
313/// /// ```
314/// #[derive(Template)]
315/// #[template(ext = "html", in_doc = true)]
316/// struct Number {
317///     number: usize
318/// }
319///
320/// assert_eq!(
321///     Number { number: 1 }.to_string(),
322///     "You are number &#60;b&#62;ONE&#60;/b&#62;!",
323/// );
324/// assert_eq!(
325///     Number { number: 9000 }.to_string(),
326///     "You are number 9000!",
327/// );
328/// # }
329/// ```
330#[inline]
331pub fn pluralize<C, S, P>(count: C, singular: S, plural: P) -> Result<Either<S, P>, C::Error>
332where
333    C: PluralizeCount,
334{
335    match count.is_singular()? {
336        true => Ok(Either::Left(singular)),
337        false => Ok(Either::Right(plural)),
338    }
339}
340
341/// An integer that can have the value `+1` and maybe `-1`.
342pub trait PluralizeCount {
343    /// A possible error that can occur while checking the value.
344    type Error: Into<Error>;
345
346    /// Returns `true` if and only if the value is `±1`.
347    fn is_singular(&self) -> Result<bool, Self::Error>;
348}
349
350const _: () = {
351    crate::impl_for_ref! {
352        impl PluralizeCount for T {
353            type Error = T::Error;
354
355            #[inline]
356            fn is_singular(&self) -> Result<bool, Self::Error> {
357                <T>::is_singular(self)
358            }
359        }
360    }
361
362    impl<T> PluralizeCount for Pin<T>
363    where
364        T: Deref,
365        <T as Deref>::Target: PluralizeCount,
366    {
367        type Error = <<T as Deref>::Target as PluralizeCount>::Error;
368
369        #[inline]
370        fn is_singular(&self) -> Result<bool, Self::Error> {
371            self.as_ref().get_ref().is_singular()
372        }
373    }
374
375    /// implement `PluralizeCount` for unsigned integer types
376    macro_rules! impl_pluralize_for_unsigned_int {
377        ($($ty:ty)*) => { $(
378            impl PluralizeCount for $ty {
379                type Error = Infallible;
380
381                #[inline]
382                fn is_singular(&self) -> Result<bool, Self::Error> {
383                    Ok(*self == 1)
384                }
385            }
386        )* };
387    }
388
389    impl_pluralize_for_unsigned_int!(u8 u16 u32 u64 u128 usize);
390
391    /// implement `PluralizeCount` for signed integer types
392    macro_rules! impl_pluralize_for_signed_int {
393        ($($ty:ty)*) => { $(
394            impl PluralizeCount for $ty {
395                type Error = Infallible;
396
397                #[inline]
398                fn is_singular(&self) -> Result<bool, Self::Error> {
399                    Ok(*self == 1 || *self == -1)
400                }
401            }
402        )* };
403    }
404
405    impl_pluralize_for_signed_int!(i8 i16 i32 i64 i128 isize);
406
407    /// implement `PluralizeCount` for non-zero integer types
408    macro_rules! impl_pluralize_for_non_zero {
409        ($($ty:ident)*) => { $(
410            impl PluralizeCount for core::num::$ty {
411                type Error = Infallible;
412
413                #[inline]
414                fn is_singular(&self) -> Result<bool, Self::Error> {
415                    self.get().is_singular()
416                }
417            }
418        )* };
419    }
420
421    impl_pluralize_for_non_zero! {
422        NonZeroI8 NonZeroI16 NonZeroI32 NonZeroI64 NonZeroI128 NonZeroIsize
423        NonZeroU8 NonZeroU16 NonZeroU32 NonZeroU64 NonZeroU128 NonZeroUsize
424    }
425};
426
427/// Render either `L` or `R`
428pub enum Either<L, R> {
429    /// First variant
430    Left(L),
431    /// Second variant
432    Right(R),
433}
434
435impl<L: fmt::Display, R: fmt::Display> fmt::Display for Either<L, R> {
436    #[inline]
437    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
438        match self {
439            Either::Left(value) => write!(f, "{value}"),
440            Either::Right(value) => write!(f, "{value}"),
441        }
442    }
443}
444
445impl<L: FastWritable, R: FastWritable> FastWritable for Either<L, R> {
446    #[inline]
447    fn write_into(&self, dest: &mut dyn fmt::Write, values: &dyn Values) -> crate::Result<()> {
448        match self {
449            Either::Left(value) => value.write_into(dest, values),
450            Either::Right(value) => value.write_into(dest, values),
451        }
452    }
453}
454
455/// Returns an iterator without filtered out values.
456///
457/// ```
458/// # use askama::Template;
459/// #[derive(Template)]
460/// #[template(
461///       ext = "html",
462///       source = r#"{% for elem in strs|reject("a") %}{{ elem }},{% endfor %}"#,
463/// )]
464/// struct Example<'a> {
465///     strs: Vec<&'a str>,
466/// }
467///
468/// assert_eq!(
469///     Example { strs: vec!["a", "b", "c"] }.to_string(),
470///     "b,c,"
471/// );
472/// ```
473#[inline]
474pub fn reject<'a, T: PartialEq + 'a>(
475    it: impl Iterator<Item = T> + 'a,
476    filter: &'a T,
477) -> Result<impl Iterator<Item = T> + 'a, Infallible> {
478    reject_with(it, move |v| v == filter)
479}
480
481/// Returns an iterator without filtered out values.
482///
483/// ```
484/// # use askama::Template;
485///
486/// fn is_odd(v: &&u32) -> bool {
487///     **v & 1 != 0
488/// }
489///
490/// #[derive(Template)]
491/// #[template(
492///       ext = "html",
493///       source = r#"{% for elem in numbers | reject(self::is_odd) %}{{ elem }},{% endfor %}"#,
494/// )]
495/// struct Example {
496///     numbers: Vec<u32>,
497/// }
498///
499/// # fn main() { // so `self::` can be accessed
500/// assert_eq!(
501///     Example { numbers: vec![1, 2, 3, 4] }.to_string(),
502///     "2,4,"
503/// );
504/// # }
505/// ```
506#[inline]
507pub fn reject_with<T: PartialEq>(
508    it: impl Iterator<Item = T>,
509    mut callback: impl FnMut(&T) -> bool,
510) -> Result<impl Iterator<Item = T>, Infallible> {
511    Ok(it.filter(move |v| !callback(v)))
512}
513
514/// Count the words in that string.
515///
516/// ```
517/// # #[cfg(feature = "code-in-doc")] {
518/// # use askama::Template;
519/// /// ```jinja
520/// /// <div>{{ example|wordcount }}</div>
521/// /// ```
522/// #[derive(Template)]
523/// #[template(ext = "html", in_doc = true)]
524/// struct Example<'a> {
525///     example: &'a str,
526/// }
527///
528/// assert_eq!(
529///     Example { example: "askama is sort of cool" }.to_string(),
530///     "<div>5</div>"
531/// );
532/// # }
533/// ```
534#[inline]
535pub fn wordcount<S>(source: S) -> Wordcount<S> {
536    Wordcount {
537        source,
538        count: Cell::new(WordcountInner {
539            count: 0,
540            ends_with_whitespace: true,
541        }),
542    }
543}
544
545pub struct Wordcount<S> {
546    source: S,
547    count: Cell<WordcountInner>,
548}
549
550impl<S> Wordcount<S> {
551    pub fn into_count(self) -> usize {
552        self.count.get().count
553    }
554}
555
556impl<S: fmt::Display> fmt::Display for Wordcount<S> {
557    #[inline]
558    fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
559        let mut inner = self.count.get();
560        write!(WordCountWriter(&mut inner), "{}", self.source)?;
561        self.count.set(inner);
562        Ok(())
563    }
564}
565
566impl<S: FastWritable> FastWritable for Wordcount<S> {
567    #[inline]
568    fn write_into(&self, _: &mut dyn fmt::Write, values: &dyn crate::Values) -> crate::Result<()> {
569        let mut inner = self.count.get();
570        self.source
571            .write_into(&mut WordCountWriter(&mut inner), values)?;
572        self.count.set(inner);
573        Ok(())
574    }
575}
576
577#[derive(Clone, Copy)]
578struct WordcountInner {
579    count: usize,
580    ends_with_whitespace: bool,
581}
582
583struct WordCountWriter<'a>(&'a mut WordcountInner);
584
585impl<'a> fmt::Write for WordCountWriter<'a> {
586    fn write_str(&mut self, s: &str) -> fmt::Result {
587        if s.is_empty() {
588            // If the input is empty, nothing to be done.
589            return Ok(());
590        } else if s.trim().is_empty() {
591            // If the input only contains whitespace characters, we set `ends_with_whitespace` to
592            // `true`. It is to handle this case: `["a", " ", "b"]`. In total we should have two
593            // words count.
594            self.0.ends_with_whitespace = true;
595            return Ok(());
596        }
597        self.0.count += s.split_whitespace().count();
598        if !self.0.ends_with_whitespace && !s.starts_with(char::is_whitespace) {
599            // This covers this case: `["a", "b c"]`. Here, we have two words ("ab" and "c") so we
600            // need to subtract one from the count on "b c" because it returns 2 whereas "a" word is
601            // not "finished".
602            self.0.count -= 1;
603        }
604        // And again, if the string ends with a whitespace character, we change the value of
605        // `ends_with_whitespace`.
606        self.0.ends_with_whitespace = s.ends_with(char::is_whitespace);
607        Ok(())
608    }
609}
610
611/// Replaces line breaks in plain text with appropriate HTML.
612///
613/// A single newline becomes an HTML line break `<br>` and a new line
614/// followed by a blank line becomes a paragraph break `<p>`.
615///
616/// ```
617/// # #[cfg(feature = "code-in-doc")] {
618/// # use askama::Template;
619/// /// ```jinja
620/// /// <div>{{ example|linebreaks }}</div>
621/// /// ```
622/// #[derive(Template)]
623/// #[template(ext = "html", in_doc = true)]
624/// struct Example<'a> {
625///     example: &'a str,
626/// }
627///
628/// assert_eq!(
629///     Example { example: "Foo\nBar\n\nBaz" }.to_string(),
630///     "<div><p>Foo<br/>Bar</p><p>Baz</p></div>"
631/// );
632/// # }
633/// ```
634#[inline]
635pub fn linebreaks<S: fmt::Display>(
636    source: S,
637) -> Result<HtmlSafeOutput<NewlineCounting<S>>, Infallible> {
638    Ok(HtmlSafeOutput(NewlineCounting {
639        source,
640        one: "<br/>",
641    }))
642}
643
644/// Replaces only paragraph breaks in plain text with appropriate HTML
645///
646/// A new line followed by a blank line becomes a paragraph break `<p>`.
647/// Paragraph tags only wrap content; empty paragraphs are removed.
648/// No `<br/>` tags are added.
649///
650/// ```
651/// # #[cfg(feature = "code-in-doc")] {
652/// # use askama::Template;
653/// /// ```jinja
654/// /// {{ lines|paragraphbreaks }}
655/// /// ```
656/// #[derive(Template)]
657/// #[template(ext = "html", in_doc = true)]
658/// struct Example<'a> {
659///     lines: &'a str,
660/// }
661///
662/// assert_eq!(
663///     Example { lines: "Foo\nBar\n\nBaz" }.to_string(),
664///     "<p>Foo\nBar</p><p>Baz</p>"
665/// );
666/// # }
667/// ```
668#[inline]
669pub fn paragraphbreaks<S: fmt::Display>(
670    source: S,
671) -> Result<HtmlSafeOutput<NewlineCounting<S>>, Infallible> {
672    Ok(HtmlSafeOutput(NewlineCounting { source, one: "\n" }))
673}
674
675pub struct NewlineCounting<S> {
676    source: S,
677    one: &'static str,
678}
679
680impl<S> NewlineCounting<S> {
681    #[inline]
682    fn run<'a, F, W, E>(&self, dest: &'a mut W, inner: F) -> Result<(), E>
683    where
684        W: fmt::Write + ?Sized,
685        F: FnOnce(&mut NewlineCountingFormatter<'a, W>) -> Result<(), E>,
686        E: From<fmt::Error>,
687    {
688        let mut formatter = NewlineCountingFormatter {
689            dest,
690            counter: -1,
691            one: self.one,
692        };
693        formatter.dest.write_str("<p>")?;
694        inner(&mut formatter)?;
695        formatter.dest.write_str("</p>")?;
696        Ok(())
697    }
698}
699
700impl<S: fmt::Display> fmt::Display for NewlineCounting<S> {
701    fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
702        self.run(dest, |f| write!(f, "{}", self.source))
703    }
704}
705
706impl<S: FastWritable> FastWritable for NewlineCounting<S> {
707    fn write_into(
708        &self,
709        dest: &mut dyn fmt::Write,
710        values: &dyn crate::Values,
711    ) -> crate::Result<()> {
712        self.run(dest, |f| self.source.write_into(f, values))
713    }
714}
715
716struct NewlineCountingFormatter<'a, W: ?Sized> {
717    dest: &'a mut W,
718    counter: isize,
719    one: &'static str,
720}
721
722impl<W: fmt::Write + ?Sized> fmt::Write for NewlineCountingFormatter<'_, W> {
723    fn write_str(&mut self, s: &str) -> fmt::Result {
724        if s.is_empty() {
725            return Ok(());
726        }
727        for (has_eol, line) in split_lines(s) {
728            if !line.is_empty() {
729                match replace(&mut self.counter, if has_eol { 1 } else { 0 }) {
730                    ..=0 => {}
731                    1 => self.dest.write_str(self.one)?,
732                    2.. => self.dest.write_str("</p><p>")?,
733                }
734                self.dest.write_str(line)?;
735            } else if has_eol && self.counter >= 0 {
736                self.counter += 1;
737            }
738        }
739        Ok(())
740    }
741}
742
743/// Converts all newlines in a piece of plain text to HTML line breaks.
744///
745/// ```
746/// # #[cfg(feature = "code-in-doc")] {
747/// # use askama::Template;
748/// /// ```jinja
749/// /// <div>{{ lines|linebreaksbr }}</div>
750/// /// ```
751/// #[derive(Template)]
752/// #[template(ext = "html", in_doc = true)]
753/// struct Example<'a> {
754///     lines: &'a str,
755/// }
756///
757/// assert_eq!(
758///     Example { lines: "a\nb\nc" }.to_string(),
759///     "<div>a<br/>b<br/>c</div>"
760/// );
761/// # }
762/// ```
763#[inline]
764pub fn linebreaksbr<S: fmt::Display>(
765    source: S,
766) -> Result<HtmlSafeOutput<Linebreaksbr<S>>, Infallible> {
767    Ok(HtmlSafeOutput(Linebreaksbr(source)))
768}
769
770pub struct Linebreaksbr<S>(S);
771
772impl<S: fmt::Display> fmt::Display for Linebreaksbr<S> {
773    #[inline]
774    fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
775        write!(LinebreaksbrFormatter(dest), "{}", self.0)
776    }
777}
778
779struct LinebreaksbrFormatter<'a, W: ?Sized>(&'a mut W);
780
781impl<S: FastWritable> FastWritable for Linebreaksbr<S> {
782    #[inline]
783    fn write_into(
784        &self,
785        dest: &mut dyn fmt::Write,
786        values: &dyn crate::Values,
787    ) -> crate::Result<()> {
788        self.0.write_into(&mut LinebreaksbrFormatter(dest), values)
789    }
790}
791
792impl<W: fmt::Write + ?Sized> fmt::Write for LinebreaksbrFormatter<'_, W> {
793    fn write_str(&mut self, s: &str) -> fmt::Result {
794        if s.is_empty() {
795            return Ok(());
796        }
797        for (has_eol, line) in split_lines(s) {
798            self.0.write_str(line)?;
799            if has_eol {
800                self.0.write_str("<br/>")?;
801            }
802        }
803        Ok(())
804    }
805}
806
807/// Splits the input at `/\r?\n/g``; returns whether a newline suffix was stripped and the
808/// (maybe stripped) line.
809fn split_lines(s: &str) -> impl Iterator<Item = (bool, &str)> {
810    s.split_inclusive('\n').map(|line| {
811        if let Some(line) = line.strip_suffix('\n') {
812            (true, line.strip_suffix('\r').unwrap_or(line))
813        } else {
814            (false, line)
815        }
816    })
817}
818
819#[cfg(all(test, feature = "alloc"))]
820mod tests {
821    use alloc::string::{String, ToString};
822    use alloc::vec::Vec;
823
824    use super::*;
825    use crate::NO_VALUES;
826
827    #[allow(clippy::needless_borrow)]
828    #[test]
829    fn test_join() {
830        assert_eq!(
831            join((&["hello", "world"]).iter(), ", ")
832                .unwrap()
833                .to_string(),
834            "hello, world"
835        );
836        assert_eq!(
837            join((&["hello"]).iter(), ", ").unwrap().to_string(),
838            "hello"
839        );
840
841        let empty: &[&str] = &[];
842        assert_eq!(join(empty.iter(), ", ").unwrap().to_string(), "");
843
844        let input: Vec<String> = alloc::vec!["foo".into(), "bar".into(), "bazz".into()];
845        assert_eq!(join(input.iter(), ":").unwrap().to_string(), "foo:bar:bazz");
846
847        let input: &[String] = &["foo".into(), "bar".into()];
848        assert_eq!(join(input.iter(), ":").unwrap().to_string(), "foo:bar");
849
850        let real: String = "blah".into();
851        let input: Vec<&str> = alloc::vec![&real];
852        assert_eq!(join(input.iter(), ";").unwrap().to_string(), "blah");
853
854        assert_eq!(
855            join((&&&&&["foo", "bar"]).iter(), ", ")
856                .unwrap()
857                .to_string(),
858            "foo, bar"
859        );
860    }
861
862    #[test]
863    fn test_center() {
864        assert_eq!(center("f", 3).unwrap().to_string(), " f ".to_string());
865        assert_eq!(center("f", 4).unwrap().to_string(), " f  ".to_string());
866        assert_eq!(center("foo", 1).unwrap().to_string(), "foo".to_string());
867        assert_eq!(
868            center("foo bar", 8).unwrap().to_string(),
869            "foo bar ".to_string()
870        );
871        assert_eq!(
872            center("foo", 111_669_149_696).unwrap().to_string(),
873            "foo".to_string()
874        );
875    }
876
877    #[test]
878    fn test_wordcount() {
879        for &(word, count) in &[
880            ("", 0),
881            (" \n\t", 0),
882            ("foo", 1),
883            ("foo bar", 2),
884            ("foo  bar", 2),
885        ] {
886            let w = wordcount(word);
887            let _ = w.to_string();
888            assert_eq!(w.into_count(), count, "fmt: {word:?}");
889
890            let w = wordcount(word);
891            w.write_into(&mut String::new(), NO_VALUES).unwrap();
892            assert_eq!(w.into_count(), count, "FastWritable: {word:?}");
893        }
894    }
895
896    #[test]
897    fn test_wordcount_on_partial_input() {
898        #[derive(Clone, Copy)]
899        struct Chunked<'a>(&'a str);
900
901        impl<'a> fmt::Display for Chunked<'a> {
902            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
903                for chunk in self.0.chars() {
904                    write!(f, "{chunk}")?;
905                }
906                Ok(())
907            }
908        }
909
910        fn wrap(s: &str) -> usize {
911            let w = wordcount(Chunked(s));
912            // Needed to actually count the words.
913            w.to_string();
914            w.into_count()
915        }
916
917        // This test ensures that if `wordcount` returned value's `Display` impl was not called,
918        // it will always return 0.
919        assert_eq!(wordcount(Chunked("hello")).into_count(), 0);
920
921        assert_eq!(wrap("hello"), 1);
922        assert_eq!(wrap("hello\n"), 1);
923        assert_eq!(wrap("hello\nfoo"), 2);
924        assert_eq!(wrap("hello\nfoo\n bar"), 3);
925
926        assert_eq!(wrap("hello\n\n bar"), 2);
927        assert_eq!(wrap("  hello\n\n bar  "), 2);
928    }
929
930    #[test]
931    fn test_linebreaks() {
932        assert_eq!(
933            linebreaks("Foo\nBar Baz").unwrap().to_string(),
934            "<p>Foo<br/>Bar Baz</p>"
935        );
936        assert_eq!(
937            linebreaks("Foo\nBar\n\nBaz").unwrap().to_string(),
938            "<p>Foo<br/>Bar</p><p>Baz</p>"
939        );
940    }
941
942    #[test]
943    fn test_paragraphbreaks() {
944        assert_eq!(
945            paragraphbreaks("Foo\nBar Baz").unwrap().to_string(),
946            "<p>Foo\nBar Baz</p>"
947        );
948        assert_eq!(
949            paragraphbreaks("Foo\nBar\n\nBaz").unwrap().to_string(),
950            "<p>Foo\nBar</p><p>Baz</p>"
951        );
952        assert_eq!(
953            paragraphbreaks("Foo\n\n\n\n\nBar\n\nBaz")
954                .unwrap()
955                .to_string(),
956            "<p>Foo</p><p>Bar</p><p>Baz</p>"
957        );
958    }
959
960    #[test]
961    fn test_linebreaksbr() {
962        assert_eq!(linebreaksbr("Foo\nBar").unwrap().to_string(), "Foo<br/>Bar");
963        assert_eq!(
964            linebreaksbr("Foo\nBar\n\nBaz").unwrap().to_string(),
965            "Foo<br/>Bar<br/><br/>Baz"
966        );
967    }
968}