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
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: Option<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<_>>();
if parts.len() < 3 || parts.len() > 4 {
return Some(DisplayHint::Unknown(s.into()));
}
return Some(DisplayHint::Bitflags {
name: parts[0].into(),
package: parts[1].into(),
disambiguator: parts[2].into(),
// crate_name was added in wire format version 4
crate_name: parts.get(3).map(|&s| s.to_string()),
});
}
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, T)> {
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))
}