diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f00ed6c2..9fd66642 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -40,17 +40,13 @@ jobs: uses: actions/checkout@v2 - name: Install Rust Toolchain - uses: actions-rs/toolchain@v1 - with: - override: true - profile: minimal - toolchain: ${{ matrix.channel }}-${{ matrix.rust_target }} + run: rustup default ${{ matrix.channel }}-${{ matrix.rust_target }} - name: Install cargo-hack run: cargo install cargo-hack - name: Powerset - run: cargo hack test --feature-powerset --lib --optional-deps "serde" --depth 3 --skip rustc-dep-of-std + run: cargo hack test --feature-powerset --lib --optional-deps "std serde" --depth 3 --skip rustc-dep-of-std - name: Docs run: cargo doc --features example_generated @@ -58,6 +54,22 @@ jobs: - name: Smoke test run: cargo run --manifest-path tests/smoke-test/Cargo.toml + benches: + name: Benches + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install Rust toolchain + run: rustup default nightly + + - name: Default features + uses: actions-rs/cargo@v1 + with: + command: bench + args: --no-run + embedded: name: Build (embedded) runs-on: ubuntu-latest @@ -66,12 +78,9 @@ jobs: uses: actions/checkout@v2 - name: Install Rust toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - target: thumbv6m-none-eabi - override: true + run: | + rustup default nightly + rustup target add thumbv6m-none-eabi - name: Default features uses: actions-rs/cargo@v1 diff --git a/Cargo.toml b/Cargo.toml index 04941307..05c469e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,8 +28,10 @@ trybuild = "1.0" rustversion = "1.0" serde_derive = "1.0" serde_json = "1.0" +serde_test = "1.0" [features] +std = [] example_generated = [] rustc-dep-of-std = ["core", "compiler_builtins"] diff --git a/benches/parse.rs b/benches/parse.rs new file mode 100644 index 00000000..caa92034 --- /dev/null +++ b/benches/parse.rs @@ -0,0 +1,96 @@ +#![feature(test)] + +extern crate test; + +use std::{ + fmt::{self, Display}, + str::FromStr, +}; + +bitflags::bitflags! { + struct Flags10: u32 { + const A = 0b0000_0000_0000_0001; + const B = 0b0000_0000_0000_0010; + const C = 0b0000_0000_0000_0100; + const D = 0b0000_0000_0000_1000; + const E = 0b0000_0000_0001_0000; + const F = 0b0000_0000_0010_0000; + const G = 0b0000_0000_0100_0000; + const H = 0b0000_0000_1000_0000; + const I = 0b0000_0001_0000_0000; + const J = 0b0000_0010_0000_0000; + } +} + +impl FromStr for Flags10 { + type Err = bitflags::parser::ParseError; + + fn from_str(flags: &str) -> Result { + Ok(Flags10(flags.parse()?)) + } +} + +impl Display for Flags10 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Display::fmt(&self.0, f) + } +} + +#[bench] +fn format_flags_1_present(b: &mut test::Bencher) { + b.iter(|| Flags10::J.to_string()) +} + +#[bench] +fn format_flags_5_present(b: &mut test::Bencher) { + b.iter(|| (Flags10::F | Flags10::G | Flags10::H | Flags10::I | Flags10::J).to_string()) +} + +#[bench] +fn format_flags_10_present(b: &mut test::Bencher) { + b.iter(|| { + (Flags10::A + | Flags10::B + | Flags10::C + | Flags10::D + | Flags10::E + | Flags10::F + | Flags10::G + | Flags10::H + | Flags10::I + | Flags10::J) + .to_string() + }) +} + +#[bench] +fn parse_flags_1_10(b: &mut test::Bencher) { + b.iter(|| { + let flags: Flags10 = "J".parse().unwrap(); + flags + }) +} + +#[bench] +fn parse_flags_5_10(b: &mut test::Bencher) { + b.iter(|| { + let flags: Flags10 = "F | G | H | I | J".parse().unwrap(); + flags + }) +} + +#[bench] +fn parse_flags_10_10(b: &mut test::Bencher) { + b.iter(|| { + let flags: Flags10 = "A | B | C | D | E | F | G | H | I | J".parse().unwrap(); + flags + }) +} + +#[bench] +fn parse_flags_1_10_hex(b: &mut test::Bencher) { + b.iter(|| { + let flags: Flags10 = "0xFF".parse().unwrap(); + flags + }) +} diff --git a/examples/fmt.rs b/examples/fmt.rs new file mode 100644 index 00000000..3bb9b8c4 --- /dev/null +++ b/examples/fmt.rs @@ -0,0 +1,49 @@ +//! An example of implementing Rust's standard formatting and parsing traits for flags types. + +use core::{fmt, str}; + +fn main() -> Result<(), bitflags::parser::ParseError> { + bitflags::bitflags! { + // You can `#[derive]` the `Debug` trait, but implementing it manually + // can produce output like `A | B` instead of `Flags(A | B)`. + // #[derive(Debug)] + #[derive(PartialEq, Eq)] + pub struct Flags: u32 { + const A = 1; + const B = 2; + const C = 4; + const D = 8; + } + } + + impl fmt::Debug for Flags { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.0, f) + } + } + + impl fmt::Display for Flags { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } + } + + impl str::FromStr for Flags { + type Err = bitflags::parser::ParseError; + + fn from_str(flags: &str) -> Result { + Ok(Self(flags.parse()?)) + } + } + + let flags = Flags::A | Flags::B; + + println!("{}", flags); + + let formatted = flags.to_string(); + let parsed: Flags = formatted.parse()?; + + assert_eq!(flags, parsed); + + Ok(()) +} diff --git a/examples/serde.rs b/examples/serde.rs new file mode 100644 index 00000000..22eae2db --- /dev/null +++ b/examples/serde.rs @@ -0,0 +1,36 @@ +//! An example of implementing `serde::Serialize` and `serde::Deserialize`. +//! The `#[serde(transparent)]` attribute is recommended to serialize directly +//! to the underlying bits type without wrapping it in a `serde` newtype. + +#[cfg(feature = "serde")] +fn main() { + use serde_derive::*; + + bitflags::bitflags! { + #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] + #[serde(transparent)] + pub struct Flags: u32 { + const A = 1; + const B = 2; + const C = 4; + const D = 8; + } + } + + let flags = Flags::A | Flags::B; + + let serialized = serde_json::to_string(&flags).unwrap(); + + println!("{:?} -> {}", flags, serialized); + + assert_eq!(serialized, r#""A | B""#); + + let deserialized: Flags = serde_json::from_str(&serialized).unwrap(); + + println!("{} -> {:?}", serialized, flags); + + assert_eq!(deserialized, flags); +} + +#[cfg(not(feature = "serde"))] +fn main() {} diff --git a/src/external.rs b/src/external.rs index 3076710f..4b696acb 100644 --- a/src/external.rs +++ b/src/external.rs @@ -48,9 +48,8 @@ macro_rules! __impl_external_bitflags_serde { &self, serializer: S, ) -> $crate::__private::core::result::Result { - $crate::__private::serde_support::serialize_bits_default( - $crate::__private::core::stringify!($InternalBitFlags), - &self.bits, + $crate::__private::serde_support::serialize_bits_default::<$InternalBitFlags, $T, S>( + &self, serializer, ) } @@ -60,14 +59,9 @@ macro_rules! __impl_external_bitflags_serde { fn deserialize>( deserializer: D, ) -> $crate::__private::core::result::Result { - let bits = $crate::__private::serde_support::deserialize_bits_default( - $crate::__private::core::stringify!($InternalBitFlags), + $crate::__private::serde_support::deserialize_bits_default::<$InternalBitFlags, $T, D>( deserializer, - )?; - - $crate::__private::core::result::Result::Ok($InternalBitFlags::from_bits_retain( - bits, - )) + ) } } }; diff --git a/src/external/serde_support.rs b/src/external/serde_support.rs index 5d30d1c3..e1a460b9 100644 --- a/src/external/serde_support.rs +++ b/src/external/serde_support.rs @@ -1,62 +1,68 @@ -use core::fmt; +use core::{fmt, str}; use serde::{ - de::{Error, MapAccess, Visitor}, - ser::SerializeStruct, + de::{Error, Visitor}, Deserialize, Deserializer, Serialize, Serializer, }; -// These methods are compatible with the result of `#[derive(Serialize, Deserialize)]` on bitflags `1.0` types - -pub fn serialize_bits_default( - name: &'static str, - bits: &B, +pub fn serialize_bits_default, B: Serialize, S: Serializer>( + flags: &T, serializer: S, ) -> Result { - let mut serialize_struct = serializer.serialize_struct(name, 1)?; - serialize_struct.serialize_field("bits", bits)?; - serialize_struct.end() + // Serialize human-readable flags as a string like `"A | B"` + if serializer.is_human_readable() { + serializer.collect_str(flags) + } + // Serialize non-human-readable flags directly as the underlying bits + else { + flags.as_ref().serialize(serializer) + } } -pub fn deserialize_bits_default<'de, B: Deserialize<'de>, D: Deserializer<'de>>( - name: &'static str, +pub fn deserialize_bits_default< + 'de, + T: str::FromStr + From, + B: Deserialize<'de>, + D: Deserializer<'de>, +>( deserializer: D, -) -> Result { - struct BitsVisitor(core::marker::PhantomData); - - impl<'de, T: Deserialize<'de>> Visitor<'de> for BitsVisitor { - type Value = T; +) -> Result +where + ::Err: fmt::Display, +{ + if deserializer.is_human_readable() { + // Deserialize human-readable flags by parsing them from strings like `"A | B"` + struct FlagsVisitor(core::marker::PhantomData); + + impl<'de, T: str::FromStr> Visitor<'de> for FlagsVisitor + where + ::Err: fmt::Display, + { + type Value = T; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string value of `|` separated flags") + } - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a primitive bitflags value wrapped in a struct") + fn visit_str(self, flags: &str) -> Result { + flags.parse().map_err(|e| E::custom(e)) + } } - fn visit_map>(self, mut map: A) -> Result { - let mut bits = None; - - while let Some(key) = map.next_key()? { - match key { - "bits" => { - if bits.is_some() { - return Err(Error::duplicate_field("bits")); - } + deserializer.deserialize_str(FlagsVisitor(Default::default())) + } else { + // Deserialize non-human-readable flags directly from the underlying bits + let bits = B::deserialize(deserializer)?; - bits = Some(map.next_value()?); - } - v => return Err(Error::unknown_field(v, &["bits"])), - } - } - - bits.ok_or_else(|| Error::missing_field("bits")) - } + Ok(bits.into()) } - - deserializer.deserialize_struct(name, &["bits"], BitsVisitor(Default::default())) } #[cfg(test)] mod tests { + use serde_test::{assert_tokens, Configure, Token::*}; bitflags! { - #[derive(serde_derive::Serialize, serde_derive::Deserialize)] + #[derive(serde_derive::Serialize, serde_derive::Deserialize, Debug, PartialEq, Eq)] + #[serde(transparent)] struct SerdeFlags: u32 { const A = 1; const B = 2; @@ -66,30 +72,13 @@ mod tests { } #[test] - fn test_serde_bitflags_default_serialize() { - let flags = SerdeFlags::A | SerdeFlags::B; - - let serialized = serde_json::to_string(&flags).unwrap(); - - assert_eq!(serialized, r#"{"bits":3}"#); - } - - #[test] - fn test_serde_bitflags_default_deserialize() { - let deserialized: SerdeFlags = serde_json::from_str(r#"{"bits":12}"#).unwrap(); - - let expected = SerdeFlags::C | SerdeFlags::D; + fn test_serde_bitflags_default() { + assert_tokens(&SerdeFlags::empty().readable(), &[Str("")]); - assert_eq!(deserialized.bits(), expected.bits()); - } - - #[test] - fn test_serde_bitflags_default_roundtrip() { - let flags = SerdeFlags::A | SerdeFlags::B; + assert_tokens(&SerdeFlags::empty().compact(), &[U32(0)]); - let deserialized: SerdeFlags = - serde_json::from_str(&serde_json::to_string(&flags).unwrap()).unwrap(); + assert_tokens(&(SerdeFlags::A | SerdeFlags::B).readable(), &[Str("A | B")]); - assert_eq!(deserialized.bits(), flags.bits()); + assert_tokens(&(SerdeFlags::A | SerdeFlags::B).compact(), &[U32(1 | 2)]); } } diff --git a/src/internal.rs b/src/internal.rs index fa63b8e1..272b08eb 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -58,6 +58,32 @@ macro_rules! __impl_internal_bitflags { impl $crate::__private::core::fmt::Debug for $InternalBitFlags { fn fmt(&self, f: &mut $crate::__private::core::fmt::Formatter) -> $crate::__private::core::fmt::Result { + if self.is_empty() { + // If no flags are set then write an empty hex flag to avoid + // writing an empty string. In some contexts, like serialization, + // an empty string is preferrable, but it may be unexpected in + // others for a format not to produce any output. + // + // We can remove this `0x0` and remain compatible with `FromStr`, + // because an empty string will still parse to an empty set of flags, + // just like `0x0` does. + $crate::__private::core::write!(f, "{:#x}", <$T as $crate::__private::Bits>::EMPTY) + } else { + $crate::__private::core::fmt::Display::fmt(self, f) + } + } + } + + impl $crate::__private::core::fmt::Display for $InternalBitFlags { + fn fmt(&self, f: &mut $crate::__private::core::fmt::Formatter) -> $crate::__private::core::fmt::Result { + // 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 the valid flags let mut first = true; for (name, _) in self.iter_names() { @@ -76,15 +102,55 @@ macro_rules! __impl_internal_bitflags { if !first { f.write_str(" | ")?; } - first = false; + $crate::__private::core::write!(f, "{:#x}", extra_bits)?; } - if first { - f.write_str("empty")?; + $crate::__private::core::fmt::Result::Ok(()) + } + } + + // The impl for `FromStr` should parse anything produced by `Display` + impl $crate::__private::core::str::FromStr for $InternalBitFlags { + type Err = $crate::parser::ParseError; + + fn from_str(s: &str) -> $crate::__private::core::result::Result { + let s = s.trim(); + + let mut parsed_flags = Self::empty(); + + // If the input is empty then return an empty set of flags + if s.is_empty() { + return $crate::__private::core::result::Result::Ok(parsed_flags); + } + + for flag in s.split('|') { + let flag = flag.trim(); + + // If the flag is empty then we've got missing input + if flag.is_empty() { + return $crate::__private::core::result::Result::Err($crate::parser::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 flag.starts_with("0x") { + let flag = &flag[2..]; + let bits = <$T>::from_str_radix(flag, 16).map_err(|_| $crate::parser::ParseError::invalid_hex_flag(flag))?; + + Self::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 { + Self::from_name(flag).ok_or_else(|| $crate::parser::ParseError::invalid_named_flag(flag))? + }; + + parsed_flags.insert(parsed_flag); } - $crate::__private::core::fmt::Result::Ok(()) + $crate::__private::core::result::Result::Ok(parsed_flags) } } @@ -267,6 +333,18 @@ macro_rules! __impl_internal_bitflags { } } + impl $crate::__private::core::convert::AsRef<$T> for $InternalBitFlags { + fn as_ref(&self) -> &$T { + &self.bits + } + } + + impl $crate::__private::core::convert::From<$T> for $InternalBitFlags { + fn from(bits: $T) -> Self { + Self::from_bits_retain(bits) + } + } + impl $crate::__private::core::iter::Iterator for $Iter { type Item = $BitFlags; diff --git a/src/lib.rs b/src/lib.rs index 85aec33f..69da4fd6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -154,6 +154,9 @@ //! - `LowerHex` and `UpperHex`. //! - `Octal`. //! +//! Also see the _Debug and Display_ section for details about standard text +//! representations for flags types. +//! //! ## Operators //! //! The following operator traits are implemented for the generated `struct`s: @@ -267,7 +270,9 @@ //! //! ## `Debug` and `Display` //! -//! The `Debug` trait can be derived for a reasonable implementation. +//! The `Debug` trait can be derived for a reasonable implementation. This library defines a standard +//! text-based representation for flags that generated flags types can use. For details on the exact +//! grammar, see the [`parser`] module. //! //! ## `PartialEq` and `PartialOrd` //! @@ -338,13 +343,14 @@ //! assert_eq!(2, count_unset_flags(&Flags::B)); //! ``` -#![cfg_attr(not(test), no_std)] +#![cfg_attr(not(any(feature = "std", test)), no_std)] #![doc(html_root_url = "https://docs.rs/bitflags/2.0.0-rc.1")] #![forbid(unsafe_code)] #[doc(inline)] pub use traits::BitFlags; +pub mod parser; mod traits; #[doc(hidden)] @@ -360,7 +366,7 @@ pub mod __private { /* How does the bitflags crate work? -This library generates `struct`s in the end-user's crate with a bunch of constants on it that represent flags. +This library generates a `struct` in the end-user's crate with a bunch of constants on it that represent flags. The difference between `bitflags` and a lot of other libraries is that we don't actually control the generated `struct` in the end. It's part of the end-user's crate, so it belongs to them. That makes it difficult to extend `bitflags` with new functionality because we could end up breaking valid code that was already written. @@ -555,8 +561,12 @@ pub mod example_generated; #[cfg(test)] mod tests { - use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; + use std::{ + collections::hash_map::DefaultHasher, + fmt, + hash::{Hash, Hasher}, + str, + }; bitflags! { #[doc = "> The first principle is that you must not fool yourself — and"] @@ -596,6 +606,30 @@ mod tests { } } + bitflags! { + #[derive(Debug, PartialEq, Eq)] + struct FmtFlags: u16 { + const 고양이 = 0b0000_0001; + const 개 = 0b0000_0010; + const 물고기 = 0b0000_0100; + const 물고기_고양이 = Self::고양이.bits() | Self::물고기.bits(); + } + } + + impl str::FromStr for FmtFlags { + type Err = crate::parser::ParseError; + + fn from_str(flags: &str) -> Result { + Ok(Self(flags.parse()?)) + } + } + + impl fmt::Display for FmtFlags { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } + } + bitflags! { #[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] struct EmptyFlags: u32 { @@ -1106,7 +1140,7 @@ mod tests { #[test] fn test_debug() { assert_eq!(format!("{:?}", Flags::A | Flags::B), "Flags(A | B)"); - assert_eq!(format!("{:?}", Flags::empty()), "Flags(empty)"); + assert_eq!(format!("{:?}", Flags::empty()), "Flags(0x0)"); assert_eq!(format!("{:?}", Flags::ABC), "Flags(A | B | C)"); let extra = Flags::from_bits_retain(0xb8); @@ -1119,7 +1153,64 @@ mod tests { "Flags(A | B | C | ABC | 0xb8)" ); - assert_eq!(format!("{:?}", EmptyFlags::empty()), "EmptyFlags(empty)"); + assert_eq!(format!("{:?}", EmptyFlags::empty()), "EmptyFlags(0x0)"); + } + + #[test] + fn test_display_from_str_roundtrip() { + fn format_parse_case(flags: FmtFlags) { + assert_eq!(flags, { + match flags.to_string().parse::() { + Ok(flags) => flags, + Err(e) => panic!("failed to parse `{}`: {}", flags, e), + } + }); + } + + fn parse_case(expected: FmtFlags, flags: &str) { + assert_eq!(expected, flags.parse::().unwrap()); + } + + format_parse_case(FmtFlags::empty()); + format_parse_case(FmtFlags::all()); + format_parse_case(FmtFlags::고양이); + format_parse_case(FmtFlags::고양이 | FmtFlags::개); + format_parse_case(FmtFlags::물고기_고양이); + format_parse_case(FmtFlags::from_bits_retain(0xb8)); + format_parse_case(FmtFlags::from_bits_retain(0x20)); + + parse_case(FmtFlags::empty(), ""); + parse_case(FmtFlags::empty(), " \r\n\t"); + parse_case(FmtFlags::empty(), "0x0"); + parse_case(FmtFlags::empty(), "0x0"); + + parse_case(FmtFlags::고양이, "고양이"); + parse_case(FmtFlags::고양이, " 고양이 "); + parse_case(FmtFlags::고양이, "고양이 | 고양이 | 고양이"); + parse_case(FmtFlags::고양이, "0x01"); + + parse_case(FmtFlags::고양이 | FmtFlags::개, "고양이 | 개"); + parse_case(FmtFlags::고양이 | FmtFlags::개, "고양이|개"); + parse_case(FmtFlags::고양이 | FmtFlags::개, "\n고양이|개 "); + + parse_case(FmtFlags::고양이 | FmtFlags::물고기, "물고기_고양이"); + } + + #[test] + fn test_from_str_err() { + fn parse_case(pat: &str, flags: &str) { + let err = flags.parse::().unwrap_err().to_string(); + assert!(err.contains(pat), "`{}` not found in error `{}`", pat, err); + } + + parse_case("empty flag", "|"); + parse_case("empty flag", "|||"); + parse_case("empty flag", "고양이 |"); + parse_case("unrecognized named flag", "NOT_A_FLAG"); + parse_case("unrecognized named flag", "고양이 개"); + parse_case("unrecognized named flag", "고양이 | NOT_A_FLAG"); + parse_case("invalid hex flag", "0xhi"); + parse_case("invalid hex flag", "고양이 | 0xhi"); } #[test] @@ -1283,7 +1374,6 @@ mod tests { assert!(Flags::SOME.contains(Flags::NONE)); assert!(Flags::NONE.is_empty()); - assert_eq!(format!("{:?}", Flags::empty()), "Flags(empty)"); assert_eq!(format!("{:?}", Flags::SOME), "Flags(NONE | SOME)"); } diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 00000000..4c54c843 --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,108 @@ +//! Parsing flags from text. +//! +//! `bitflags` defines the following whitespace-insensitive grammar for flags formatted +//! as text: +//! +//! - _Flags:_ (_Flag_)`|`* +//! - _Flag:_ _Identifier_ | _HexNumber_ +//! - _Identifier:_ Any Rust identifier +//! - _HexNumber_: `0x`([0-9a-zA-Z])* +//! +//! As an example, this is how `Flags::A | Flags::B | 0x0c` can be represented as text: +//! +//! ```text +//! A | B | 0x0c +//! ``` + +use core::fmt; + +/// An error encountered while parsing flags from text. +#[derive(Debug)] +pub struct ParseError(ParseErrorKind); + +#[derive(Debug)] +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 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 {}