1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
use std::str::FromStr;

/// All display hints
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DisplayHint {
    NoHint {
        zero_pad: usize,
    },
    /// `:x` OR `:X`
    Hexadecimal {
        alternate: bool,
        uppercase: bool,
        zero_pad: usize,
    },
    /// `:b`
    Binary {
        alternate: bool,
        zero_pad: usize,
    },
    /// `:a`
    Ascii,
    /// `:?`
    Debug,
    /// `:us`, formats integers as timestamps in microseconds
    Microseconds,
    /// `:iso8601{ms,s}`, formats integers as timestamp in ISO8601 date time format
    ISO8601(TimePrecision),
    /// `__internal_bitflags_NAME` instructs the decoder to print the flags that are set, instead of
    /// the raw value.
    Bitflags {
        name: String,
        package: String,
        disambiguator: String,
        crate_name: String,
    },
    /// Display hints currently not supported / understood
    Unknown(String),
}

impl DisplayHint {
    /// Parses the display hint (e.g. the `#x` in `{=u8:#x}`)
    pub(crate) fn parse(mut s: &str) -> Option<Self> {
        const BITFLAGS_HINT_START: &str = "__internal_bitflags_";

        // The `#` comes before any padding hints (I think this matches core::fmt).
        // It is ignored for types that don't have an alternate representation.
        let alternate = if let Some(rest) = s.strip_prefix('#') {
            s = rest;
            true
        } else {
            false
        };

        let zero_pad = if let Some(rest) = s.strip_prefix('0') {
            let (rest, columns) = parse_integer::<usize>(rest)?;
            s = rest;
            columns
        } else {
            0 // default behavior is the same as no zero-padding.
        };

        if let Some(stripped) = s.strip_prefix(BITFLAGS_HINT_START) {
            let parts = stripped.split('@').collect::<Vec<_>>();
            match *parts {
                [bitflags_name, package, disambiguator, crate_name] => {
                    return Some(DisplayHint::Bitflags {
                        name: bitflags_name.into(),
                        package: package.into(),
                        disambiguator: disambiguator.into(),
                        crate_name: crate_name.into(),
                    });
                }
                _ => return Some(DisplayHint::Unknown(s.into())),
            }
        }

        Some(match s {
            "" => DisplayHint::NoHint { zero_pad },
            "us" => DisplayHint::Microseconds,
            "a" => DisplayHint::Ascii,
            "b" => DisplayHint::Binary {
                alternate,
                zero_pad,
            },
            "x" => DisplayHint::Hexadecimal {
                alternate,
                uppercase: false,
                zero_pad,
            },
            "X" => DisplayHint::Hexadecimal {
                alternate,
                uppercase: true,
                zero_pad,
            },
            "iso8601ms" => DisplayHint::ISO8601(TimePrecision::Millis),
            "iso8601s" => DisplayHint::ISO8601(TimePrecision::Seconds),
            "?" => DisplayHint::Debug,
            _ => return None,
        })
    }
}

/// Precision of ISO8601 datetime
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum TimePrecision {
    Millis,
    Seconds,
}

/// Parses an integer at the beginning of `s`.
///
/// Returns the integer and remaining text, if `s` started with an integer. Any errors parsing the
/// number (which we already know only contains digits) are silently ignored.
fn parse_integer<T: FromStr>(s: &str) -> Option<(&str, usize)> {
    let start_digits = s
        .as_bytes()
        .iter()
        .copied()
        .take_while(|b| b.is_ascii_digit())
        .count();
    let num = s[..start_digits].parse().ok()?;
    Some((&s[start_digits..], num))
}