defmt_parser/
display_hint.rs

1use std::str::FromStr;
2
3/// All display hints
4#[derive(Clone, Debug, Eq, PartialEq)]
5#[non_exhaustive]
6pub enum DisplayHint {
7    NoHint {
8        zero_pad: usize,
9    },
10    /// `:x` OR `:X`
11    Hexadecimal {
12        alternate: bool,
13        uppercase: bool,
14        zero_pad: usize,
15    },
16    /// `:o`
17    Octal {
18        alternate: bool,
19        zero_pad: usize,
20    },
21    /// `:b`
22    Binary {
23        alternate: bool,
24        zero_pad: usize,
25    },
26    /// `:a`
27    Ascii,
28    /// `:?`
29    Debug,
30    /// `:us` `:ms`, formats integers as timestamps in seconds
31    Seconds(TimePrecision),
32    /// `:tus` `:tms` `:ts`, formats integers as human-readable time
33    Time(TimePrecision),
34    /// `:iso8601{ms,s}`, formats integers as timestamp in ISO8601 date time format
35    ISO8601(TimePrecision),
36    /// `__internal_bitflags_NAME` instructs the decoder to print the flags that are set, instead of
37    /// the raw value.
38    Bitflags {
39        name: String,
40        package: String,
41        disambiguator: String,
42        crate_name: Option<String>,
43    },
44    /// `:cbor`: There is CBOR data encoded in those bytes, to be shown in diagnostic notation.
45    ///
46    /// Technically, the byte string interpreted as a CBOR sequence, and shown in the diagnostic
47    /// notation of a sequence. That is identical to processing a single CBOR item if there is just
48    /// one present, but also allows reporting multiple items from consecutive memory; diagnostic
49    /// notation turns those into comma separated items.
50    // Should we have a flag to say "do a more display style stringification" (like, if you
51    // recognize `54([64,h'20010db8'])`, don't show "IP'2001:db8::/64'" but just "2001:db8::64")?
52    // Should we allow additional params that give a CDDL that further guides processing (like,
53    // when data is not tagged but the shape is known for processing anyway)?
54    Cbor,
55    /// Display hints currently not supported / understood
56    Unknown(String),
57}
58
59impl DisplayHint {
60    /// Parses the display hint (e.g. the `#x` in `{=u8:#x}`)
61    pub(crate) fn parse(mut s: &str) -> Option<Self> {
62        const BITFLAGS_HINT_START: &str = "__internal_bitflags_";
63
64        // The `#` comes before any padding hints (I think this matches core::fmt).
65        // It is ignored for types that don't have an alternate representation.
66        let alternate = if let Some(rest) = s.strip_prefix('#') {
67            s = rest;
68            true
69        } else {
70            false
71        };
72
73        let zero_pad = if let Some(rest) = s.strip_prefix('0') {
74            let (rest, columns) = parse_integer::<usize>(rest)?;
75            s = rest;
76            columns
77        } else {
78            0 // default behavior is the same as no zero-padding.
79        };
80
81        if let Some(stripped) = s.strip_prefix(BITFLAGS_HINT_START) {
82            let parts = stripped.split('@').collect::<Vec<_>>();
83            if parts.len() < 3 || parts.len() > 4 {
84                return Some(DisplayHint::Unknown(s.into()));
85            }
86            return Some(DisplayHint::Bitflags {
87                name: parts[0].into(),
88                package: parts[1].into(),
89                disambiguator: parts[2].into(),
90                // crate_name was added in wire format version 4
91                crate_name: parts.get(3).map(|&s| s.to_string()),
92            });
93        }
94
95        Some(match s {
96            "" => DisplayHint::NoHint { zero_pad },
97            "us" => DisplayHint::Seconds(TimePrecision::Micros),
98            "ms" => DisplayHint::Seconds(TimePrecision::Millis),
99            "tus" => DisplayHint::Time(TimePrecision::Micros),
100            "tms" => DisplayHint::Time(TimePrecision::Millis),
101            "ts" => DisplayHint::Time(TimePrecision::Seconds),
102            "a" => DisplayHint::Ascii,
103            "b" => DisplayHint::Binary {
104                alternate,
105                zero_pad,
106            },
107            "o" => DisplayHint::Octal {
108                alternate,
109                zero_pad,
110            },
111            "x" => DisplayHint::Hexadecimal {
112                alternate,
113                uppercase: false,
114                zero_pad,
115            },
116            "X" => DisplayHint::Hexadecimal {
117                alternate,
118                uppercase: true,
119                zero_pad,
120            },
121            "iso8601ms" => DisplayHint::ISO8601(TimePrecision::Millis),
122            "iso8601s" => DisplayHint::ISO8601(TimePrecision::Seconds),
123            "cbor" => DisplayHint::Cbor,
124            "?" => DisplayHint::Debug,
125            _ => return None,
126        })
127    }
128}
129
130/// Precision of timestamp
131#[derive(Clone, Debug, Eq, PartialEq)]
132pub enum TimePrecision {
133    Micros,
134    Millis,
135    Seconds,
136}
137
138/// Parses an integer at the beginning of `s`.
139///
140/// Returns the integer and remaining text, if `s` started with an integer. Any errors parsing the
141/// number (which we already know only contains digits) are silently ignored.
142fn parse_integer<T: FromStr>(s: &str) -> Option<(&str, T)> {
143    let start_digits = s
144        .as_bytes()
145        .iter()
146        .copied()
147        .take_while(|b| b.is_ascii_digit())
148        .count();
149    let num = s[..start_digits].parse().ok()?;
150    Some((&s[start_digits..], num))
151}