Skip to main content

solar_data_structures/
fmt.rs

1use std::{
2    cell::{Cell, RefCell},
3    fmt,
4};
5
6pub use fmt::*;
7
8/// Creates a formatter from a function.
9pub fn from_fn<F>(f: F) -> FromFn<F>
10where
11    F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
12{
13    FromFn(f)
14}
15
16/// Display adapter returned by [`from_fn`].
17pub struct FromFn<F>(F);
18
19impl<F> fmt::Display for FromFn<F>
20where
21    F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
22{
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        (self.0)(f)
25    }
26}
27
28impl<F> fmt::Debug for FromFn<F>
29where
30    F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
31{
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        (self.0)(f)
34    }
35}
36
37/// Iterator formatting helpers.
38pub trait FmtIteratorExt: Iterator + Sized {
39    /// Formats each item separated by `separator`.
40    fn format<'a>(self, separator: &'a str) -> Format<'a, Self>
41    where
42        Self::Item: fmt::Display,
43    {
44        Format { iter: Cell::new(Some(self)), separator }
45    }
46
47    /// Formats each item separated by `separator`, using `format` for each item.
48    fn format_with<'a, F>(self, separator: &'a str, format: F) -> FormatWith<'a, Self, F>
49    where
50        F: FnMut(&mut fmt::Formatter<'_>, Self::Item) -> fmt::Result,
51    {
52        FormatWith { inner: RefCell::new(Some((self, format))), separator }
53    }
54}
55
56impl<I: Iterator> FmtIteratorExt for I {}
57
58/// Display adapter returned by [`FmtIteratorExt::format`].
59pub struct Format<'a, I> {
60    iter: Cell<Option<I>>,
61    separator: &'a str,
62}
63
64impl<I> fmt::Display for Format<'_, I>
65where
66    I: Iterator,
67    I::Item: fmt::Display,
68{
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        let iter = self.iter.take().expect("format called twice");
71        for (i, item) in iter.enumerate() {
72            if i != 0 {
73                f.write_str(self.separator)?;
74            }
75            write!(f, "{item}")?;
76        }
77        Ok(())
78    }
79}
80
81/// Display adapter returned by [`FmtIteratorExt::format_with`].
82pub struct FormatWith<'a, I, F> {
83    inner: RefCell<Option<(I, F)>>,
84    separator: &'a str,
85}
86
87impl<I, F> fmt::Display for FormatWith<'_, I, F>
88where
89    I: Iterator,
90    F: FnMut(&mut fmt::Formatter<'_>, I::Item) -> fmt::Result,
91{
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        let (iter, mut format) = self.inner.borrow_mut().take().expect("format_with called twice");
94        for (i, item) in iter.enumerate() {
95            if i != 0 {
96                f.write_str(self.separator)?;
97            }
98            format(f, item)?;
99        }
100        Ok(())
101    }
102}
103
104/// Returns `list` formatted as a comma-separated list with "or" before the last item.
105pub fn or_list<I>(list: I) -> impl fmt::Display
106where
107    I: IntoIterator<IntoIter: ExactSizeIterator, Item: fmt::Display>,
108{
109    let list = Cell::new(Some(list.into_iter()));
110    from_fn(move |f| {
111        let list = list.take().expect("or_list called twice");
112        let len = list.len();
113        for (i, t) in list.enumerate() {
114            if i > 0 {
115                let is_last = i == len - 1;
116                f.write_str(if len > 2 && is_last {
117                    ", or "
118                } else if len == 2 && is_last {
119                    " or "
120                } else {
121                    ", "
122                })?;
123            }
124            write!(f, "{t}")?;
125        }
126        Ok(())
127    })
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_or_list() {
136        let tests: &[(&[&str], &str)] = &[
137            (&[], ""),
138            (&["`<eof>`"], "`<eof>`"),
139            (&["integer", "identifier"], "integer or identifier"),
140            (&["path", "string literal", "`&&`"], "path, string literal, or `&&`"),
141            (&["`&&`", "`||`", "`&&`", "`||`"], "`&&`, `||`, `&&`, or `||`"),
142        ];
143        for &(tokens, expected) in tests {
144            assert_eq!(or_list(tokens).to_string(), expected, "{tokens:?}");
145        }
146    }
147
148    #[test]
149    fn test_format() {
150        assert_eq!([1, 2, 3].iter().format(", ").to_string(), "1, 2, 3");
151    }
152
153    #[test]
154    fn test_format_with() {
155        let values = [1, 2, 3];
156        let formatted = values.iter().format_with(" | ", |f, value| write!(f, "#{value}"));
157        assert_eq!(formatted.to_string(), "#1 | #2 | #3");
158    }
159}