solar_data_structures/
fmt.rs

1use std::{cell::Cell, fmt};
2
3pub use fmt::*;
4
5/// Wrapper for [`fmt::from_fn`].
6#[cfg(feature = "nightly")]
7pub fn from_fn<F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result>(
8    f: F,
9) -> impl fmt::Debug + fmt::Display {
10    fmt::from_fn(f)
11}
12
13/// Polyfill for [`fmt::from_fn`].
14#[cfg(not(feature = "nightly"))]
15pub fn from_fn<F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result>(
16    f: F,
17) -> impl fmt::Debug + fmt::Display {
18    struct FromFn<F>(F);
19
20    impl<F> fmt::Debug for FromFn<F>
21    where
22        F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
23    {
24        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25            (self.0)(f)
26        }
27    }
28
29    impl<F> fmt::Display for FromFn<F>
30    where
31        F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
32    {
33        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34            (self.0)(f)
35        }
36    }
37
38    FromFn(f)
39}
40
41/// Returns `list` formatted as a comma-separated list with "or" before the last item.
42pub fn or_list<I>(list: I) -> impl fmt::Display
43where
44    I: IntoIterator<IntoIter: ExactSizeIterator, Item: fmt::Display>,
45{
46    let list = Cell::new(Some(list.into_iter()));
47    from_fn(move |f| {
48        let list = list.take().expect("or_list called twice");
49        let len = list.len();
50        for (i, t) in list.enumerate() {
51            if i > 0 {
52                let is_last = i == len - 1;
53                f.write_str(if len > 2 && is_last {
54                    ", or "
55                } else if len == 2 && is_last {
56                    " or "
57                } else {
58                    ", "
59                })?;
60            }
61            write!(f, "{t}")?;
62        }
63        Ok(())
64    })
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn test_or_list() {
73        let tests: &[(&[&str], &str)] = &[
74            (&[], ""),
75            (&["`<eof>`"], "`<eof>`"),
76            (&["integer", "identifier"], "integer or identifier"),
77            (&["path", "string literal", "`&&`"], "path, string literal, or `&&`"),
78            (&["`&&`", "`||`", "`&&`", "`||`"], "`&&`, `||`, `&&`, or `||`"),
79        ];
80        for &(tokens, expected) in tests {
81            assert_eq!(or_list(tokens).to_string(), expected, "{tokens:?}");
82        }
83    }
84}