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#[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 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#[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
148pub 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#[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#[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
341pub trait PluralizeCount {
343 type Error: Into<Error>;
345
346 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 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 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 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
427pub enum Either<L, R> {
429 Left(L),
431 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#[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#[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#[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 return Ok(());
590 } else if s.trim().is_empty() {
591 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 self.0.count -= 1;
603 }
604 self.0.ends_with_whitespace = s.ends_with(char::is_whitespace);
607 Ok(())
608 }
609}
610
611#[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#[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#[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
807fn 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 w.to_string();
914 w.into_count()
915 }
916
917 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}