enumflags2/
formatting.rs

1use crate::{BitFlag, BitFlags};
2use core::fmt::{self, Binary, Debug};
3
4impl<T> fmt::Debug for BitFlags<T>
5where
6    T: BitFlag + fmt::Debug,
7{
8    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
9        let name = T::BITFLAGS_TYPE_NAME;
10        let bits = DebugBinaryFormatter(&self.val);
11        let iter = if !self.is_empty() {
12            Some(FlagFormatter(self.iter()))
13        } else {
14            None
15        };
16
17        if !fmt.alternate() {
18            // Concise tuple formatting is a better default
19            let mut debug = fmt.debug_tuple(name);
20            debug.field(&bits);
21            if let Some(iter) = iter {
22                debug.field(&iter);
23            }
24            debug.finish()
25        } else {
26            // Pretty-printed tuples are ugly and hard to read, so use struct format
27            let mut debug = fmt.debug_struct(name);
28            debug.field("bits", &bits);
29            if let Some(iter) = iter {
30                debug.field("flags", &iter);
31            }
32            debug.finish()
33        }
34    }
35}
36
37impl<T> fmt::Display for BitFlags<T>
38where
39    T: BitFlag + fmt::Debug,
40{
41    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
42        fmt::Debug::fmt(&FlagFormatter(self.iter()), fmt)
43    }
44}
45
46impl<T> fmt::Binary for BitFlags<T>
47where
48    T: BitFlag,
49    T::Numeric: fmt::Binary,
50{
51    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
52        fmt::Binary::fmt(&self.bits(), fmt)
53    }
54}
55
56impl<T> fmt::Octal for BitFlags<T>
57where
58    T: BitFlag,
59    T::Numeric: fmt::Octal,
60{
61    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
62        fmt::Octal::fmt(&self.bits(), fmt)
63    }
64}
65
66impl<T> fmt::LowerHex for BitFlags<T>
67where
68    T: BitFlag,
69    T::Numeric: fmt::LowerHex,
70{
71    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
72        fmt::LowerHex::fmt(&self.bits(), fmt)
73    }
74}
75
76impl<T> fmt::UpperHex for BitFlags<T>
77where
78    T: BitFlag,
79    T::Numeric: fmt::UpperHex,
80{
81    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
82        fmt::UpperHex::fmt(&self.bits(), fmt)
83    }
84}
85
86// Format an iterator of flags into "A | B | etc"
87struct FlagFormatter<I>(I);
88
89impl<T: Debug, I: Clone + Iterator<Item = T>> Debug for FlagFormatter<I> {
90    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
91        let mut iter = self.0.clone();
92        if let Some(val) = iter.next() {
93            Debug::fmt(&val, fmt)?;
94            for val in iter {
95                fmt.write_str(" | ")?;
96                Debug::fmt(&val, fmt)?;
97            }
98            Ok(())
99        } else {
100            fmt.write_str("<empty>")
101        }
102    }
103}
104
105// A formatter that obeys format arguments but falls back to binary when
106// no explicit format is requested. Supports {:08?}, {:08x?}, etc.
107struct DebugBinaryFormatter<'a, F>(&'a F);
108
109impl<'a, F: Debug + Binary + 'a> Debug for DebugBinaryFormatter<'a, F> {
110    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
111        // Check if {:x?} or {:X?} was used; this is determined via the
112        // discriminator of core::fmt::FlagV1::{DebugLowerHex, DebugUpperHex},
113        // which is not an accessible type: https://github.com/rust-lang/rust/blob/d65e272a9fe3e61aa5f229c5358e35a909435575/src/libcore/fmt/mod.rs#L306
114        // See also: https://github.com/rust-lang/rfcs/pull/2226
115        #[allow(deprecated)]
116        let format_hex = fmt.flags() >> 4;
117        let width = fmt.width().unwrap_or(0);
118
119        if format_hex & 1 != 0 {
120            // FlagV1::DebugLowerHex
121            write!(fmt, "{:#0width$x?}", &self.0, width = width)
122        } else if format_hex & 2 != 0 {
123            // FlagV1::DebugUpperHex
124            write!(fmt, "{:#0width$X?}", &self.0, width = width)
125        } else {
126            // Fall back to binary otheriwse
127            write!(fmt, "{:#0width$b}", &self.0, width = width)
128        }
129    }
130}
131
132#[test]
133fn flag_formatter() {
134    use core::iter;
135
136    macro_rules! assert_fmt {
137        ($fmt:expr, $expr:expr, $expected:expr) => {
138            assert_eq!(format!($fmt, FlagFormatter($expr)), $expected)
139        };
140    }
141
142    assert_fmt!("{:?}", iter::empty::<u8>(), "<empty>");
143    assert_fmt!("{:?}", iter::once(1), "1");
144    assert_fmt!("{:?}", [1, 2].iter(), "1 | 2");
145    assert_fmt!("{:?}", [1, 2, 10].iter(), "1 | 2 | 10");
146    assert_fmt!("{:02x?}", [1, 2, 10].iter(), "01 | 02 | 0a");
147    assert_fmt!("{:#04X?}", [1, 2, 10].iter(), "0x01 | 0x02 | 0x0A");
148}
149
150#[test]
151fn debug_binary_formatter() {
152    macro_rules! assert_fmt {
153        ($fmt:expr, $expr:expr, $expected:expr) => {
154            assert_eq!(format!($fmt, DebugBinaryFormatter(&$expr)), $expected)
155        };
156    }
157
158    assert_fmt!("{:?}", 10, "0b1010");
159    assert_fmt!("{:#?}", 10, "0b1010");
160    assert_fmt!("{:010?}", 10, "0b00001010");
161    assert_fmt!("{:010x?}", 10, "0x0000000a");
162    assert_fmt!("{:#010X?}", 10, "0x0000000A");
163}