defmt_parser/
display_hint.rs

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