/*! Parsing flags from text. Format and parse a flags value as text using the following grammar: - _Flags:_ (_Whitespace_ _Flag_ _Whitespace_)`|`* - _Flag:_ _Name_ | _Hex Number_ - _Name:_ The name of any defined flag - _Hex Number_: `0x`([0-9a-fA-F])* - _Whitespace_: (\s)* As an example, this is how `Flags::A | Flags::B | 0x0c` can be represented as text: ```text A | B | 0x0c ``` Alternatively, it could be represented without whitespace: ```text A|B|0x0C ``` Note that identifiers are *case-sensitive*, so the following is *not equivalent*: ```text a|b|0x0C ``` */ #![allow(clippy::let_unit_value)] use core::fmt::{self, Write}; use crate::{Bits, Flags}; /** Write a flags value as text. Any bits that aren't part of a contained flag will be formatted as a hex number. */ pub fn to_writer(flags: &B, mut writer: impl Write) -> Result<(), fmt::Error> where B::Bits: WriteHex, { // A formatter for bitflags that produces text output like: // // A | B | 0xf6 // // The names of set flags are written in a bar-separated-format, // followed by a hex number of any remaining bits that are set // but don't correspond to any flags. // Iterate over known flag values let mut first = true; let mut iter = flags.iter_names(); for (name, _) in &mut iter { if !first { writer.write_str(" | ")?; } first = false; writer.write_str(name)?; } // Append any extra bits that correspond to flags to the end of the format let remaining = iter.remaining().bits(); if remaining != B::Bits::EMPTY { if !first { writer.write_str(" | ")?; } writer.write_str("0x")?; remaining.write_hex(writer)?; } fmt::Result::Ok(()) } #[cfg(feature = "serde")] pub(crate) struct AsDisplay<'a, B>(pub(crate) &'a B); #[cfg(feature = "serde")] impl<'a, B: Flags> fmt::Display for AsDisplay<'a, B> where B::Bits: WriteHex, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { to_writer(self.0, f) } } /** Parse a flags value from text. This function will fail on any names that don't correspond to defined flags. Unknown bits will be retained. */ pub fn from_str(input: &str) -> Result where B::Bits: ParseHex, { let mut parsed_flags = B::empty(); // If the input is empty then return an empty set of flags if input.trim().is_empty() { return Ok(parsed_flags); } for flag in input.split('|') { let flag = flag.trim(); // If the flag is empty then we've got missing input if flag.is_empty() { return Err(ParseError::empty_flag()); } // If the flag starts with `0x` then it's a hex number // Parse it directly to the underlying bits type let parsed_flag = if let Some(flag) = flag.strip_prefix("0x") { let bits = ::parse_hex(flag).map_err(|_| ParseError::invalid_hex_flag(flag))?; B::from_bits_retain(bits) } // Otherwise the flag is a name // The generated flags type will determine whether // or not it's a valid identifier else { B::from_name(flag).ok_or_else(|| ParseError::invalid_named_flag(flag))? }; parsed_flags.insert(parsed_flag); } Ok(parsed_flags) } /** Write a flags value as text, ignoring any unknown bits. */ pub fn to_writer_truncate(flags: &B, writer: impl Write) -> Result<(), fmt::Error> where B::Bits: WriteHex, { to_writer(&B::from_bits_truncate(flags.bits()), writer) } /** Parse a flags value from text. This function will fail on any names that don't correspond to defined flags. Unknown bits will be ignored. */ pub fn from_str_truncate(input: &str) -> Result where B::Bits: ParseHex, { Ok(B::from_bits_truncate(from_str::(input)?.bits())) } /** Write only the contained, defined, named flags in a flags value as text. */ pub fn to_writer_strict(flags: &B, mut writer: impl Write) -> Result<(), fmt::Error> { // This is a simplified version of `to_writer` that ignores // any bits not corresponding to a named flag let mut first = true; let mut iter = flags.iter_names(); for (name, _) in &mut iter { if !first { writer.write_str(" | ")?; } first = false; writer.write_str(name)?; } fmt::Result::Ok(()) } /** Parse a flags value from text. This function will fail on any names that don't correspond to defined flags. This function will fail to parse hex values. */ pub fn from_str_strict(input: &str) -> Result { // This is a simplified version of `from_str` that ignores // any bits not corresponding to a named flag let mut parsed_flags = B::empty(); // If the input is empty then return an empty set of flags if input.trim().is_empty() { return Ok(parsed_flags); } for flag in input.split('|') { let flag = flag.trim(); // If the flag is empty then we've got missing input if flag.is_empty() { return Err(ParseError::empty_flag()); } // If the flag starts with `0x` then it's a hex number // These aren't supported in the strict parser if flag.starts_with("0x") { return Err(ParseError::invalid_hex_flag("unsupported hex flag value")); } let parsed_flag = B::from_name(flag).ok_or_else(|| ParseError::invalid_named_flag(flag))?; parsed_flags.insert(parsed_flag); } Ok(parsed_flags) } /** Encode a value as a hex string. Implementors of this trait should not write the `0x` prefix. */ pub trait WriteHex { /// Write the value as hex. fn write_hex(&self, writer: W) -> fmt::Result; } /** Parse a value from a hex string. */ pub trait ParseHex { /// Parse the value from hex. fn parse_hex(input: &str) -> Result where Self: Sized; } /// An error encountered while parsing flags from text. #[derive(Debug)] pub struct ParseError(ParseErrorKind); #[derive(Debug)] #[allow(clippy::enum_variant_names)] enum ParseErrorKind { EmptyFlag, InvalidNamedFlag { #[cfg(not(feature = "std"))] got: (), #[cfg(feature = "std")] got: String, }, InvalidHexFlag { #[cfg(not(feature = "std"))] got: (), #[cfg(feature = "std")] got: String, }, } impl ParseError { /// An invalid hex flag was encountered. pub fn invalid_hex_flag(flag: impl fmt::Display) -> Self { let _flag = flag; let got = { #[cfg(feature = "std")] { _flag.to_string() } }; ParseError(ParseErrorKind::InvalidHexFlag { got }) } /// A named flag that doesn't correspond to any on the flags type was encountered. pub fn invalid_named_flag(flag: impl fmt::Display) -> Self { let _flag = flag; let got = { #[cfg(feature = "std")] { _flag.to_string() } }; ParseError(ParseErrorKind::InvalidNamedFlag { got }) } /// A hex or named flag wasn't found between separators. pub const fn empty_flag() -> Self { ParseError(ParseErrorKind::EmptyFlag) } } impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.0 { ParseErrorKind::InvalidNamedFlag { got } => { let _got = got; write!(f, "unrecognized named flag")?; #[cfg(feature = "std")] { write!(f, " `{}`", _got)?; } } ParseErrorKind::InvalidHexFlag { got } => { let _got = got; write!(f, "invalid hex flag")?; #[cfg(feature = "std")] { write!(f, " `{}`", _got)?; } } ParseErrorKind::EmptyFlag => { write!(f, "encountered empty flag")?; } } Ok(()) } } #[cfg(feature = "std")] impl std::error::Error for ParseError {}