solar_data_structures/
fmt.rs

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