1 /*!
2 Parsing flags from text.
3 
4 Format and parse a flags value as text using the following grammar:
5 
6 - _Flags:_ (_Whitespace_ _Flag_ _Whitespace_)`|`*
7 - _Flag:_ _Name_ | _Hex Number_
8 - _Name:_ The name of any defined flag
9 - _Hex Number_: `0x`([0-9a-fA-F])*
10 - _Whitespace_: (\s)*
11 
12 As an example, this is how `Flags::A | Flags::B | 0x0c` can be represented as text:
13 
14 ```text
15 A | B | 0x0c
16 ```
17 
18 Alternatively, it could be represented without whitespace:
19 
20 ```text
21 A|B|0x0C
22 ```
23 
24 Note that identifiers are *case-sensitive*, so the following is *not equivalent*:
25 
26 ```text
27 a|b|0x0C
28 ```
29 */
30 
31 #![allow(clippy::let_unit_value)]
32 
33 use core::fmt::{self, Write};
34 
35 use crate::{Bits, Flags};
36 
37 /**
38 Write a flags value as text.
39 
40 Any bits that aren't part of a contained flag will be formatted as a hex number.
41 */
to_writer<B: Flags>(flags: &B, mut writer: impl Write) -> Result<(), fmt::Error> where B::Bits: WriteHex,42 pub fn to_writer<B: Flags>(flags: &B, mut writer: impl Write) -> Result<(), fmt::Error>
43 where
44     B::Bits: WriteHex,
45 {
46     // A formatter for bitflags that produces text output like:
47     //
48     // A | B | 0xf6
49     //
50     // The names of set flags are written in a bar-separated-format,
51     // followed by a hex number of any remaining bits that are set
52     // but don't correspond to any flags.
53 
54     // Iterate over known flag values
55     let mut first = true;
56     let mut iter = flags.iter_names();
57     for (name, _) in &mut iter {
58         if !first {
59             writer.write_str(" | ")?;
60         }
61 
62         first = false;
63         writer.write_str(name)?;
64     }
65 
66     // Append any extra bits that correspond to flags to the end of the format
67     let remaining = iter.remaining().bits();
68     if remaining != B::Bits::EMPTY {
69         if !first {
70             writer.write_str(" | ")?;
71         }
72 
73         writer.write_str("0x")?;
74         remaining.write_hex(writer)?;
75     }
76 
77     fmt::Result::Ok(())
78 }
79 
80 #[cfg(feature = "serde")]
81 pub(crate) struct AsDisplay<'a, B>(pub(crate) &'a B);
82 
83 #[cfg(feature = "serde")]
84 impl<'a, B: Flags> fmt::Display for AsDisplay<'a, B>
85 where
86     B::Bits: WriteHex,
87 {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result88     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89         to_writer(self.0, f)
90     }
91 }
92 
93 /**
94 Parse a flags value from text.
95 
96 This function will fail on any names that don't correspond to defined flags.
97 Unknown bits will be retained.
98 */
from_str<B: Flags>(input: &str) -> Result<B, ParseError> where B::Bits: ParseHex,99 pub fn from_str<B: Flags>(input: &str) -> Result<B, ParseError>
100 where
101     B::Bits: ParseHex,
102 {
103     let mut parsed_flags = B::empty();
104 
105     // If the input is empty then return an empty set of flags
106     if input.trim().is_empty() {
107         return Ok(parsed_flags);
108     }
109 
110     for flag in input.split('|') {
111         let flag = flag.trim();
112 
113         // If the flag is empty then we've got missing input
114         if flag.is_empty() {
115             return Err(ParseError::empty_flag());
116         }
117 
118         // If the flag starts with `0x` then it's a hex number
119         // Parse it directly to the underlying bits type
120         let parsed_flag = if let Some(flag) = flag.strip_prefix("0x") {
121             let bits =
122                 <B::Bits>::parse_hex(flag).map_err(|_| ParseError::invalid_hex_flag(flag))?;
123 
124             B::from_bits_retain(bits)
125         }
126         // Otherwise the flag is a name
127         // The generated flags type will determine whether
128         // or not it's a valid identifier
129         else {
130             B::from_name(flag).ok_or_else(|| ParseError::invalid_named_flag(flag))?
131         };
132 
133         parsed_flags.insert(parsed_flag);
134     }
135 
136     Ok(parsed_flags)
137 }
138 
139 /**
140 Write a flags value as text, ignoring any unknown bits.
141 */
to_writer_truncate<B: Flags>(flags: &B, writer: impl Write) -> Result<(), fmt::Error> where B::Bits: WriteHex,142 pub fn to_writer_truncate<B: Flags>(flags: &B, writer: impl Write) -> Result<(), fmt::Error>
143 where
144     B::Bits: WriteHex,
145 {
146     to_writer(&B::from_bits_truncate(flags.bits()), writer)
147 }
148 
149 /**
150 Parse a flags value from text.
151 
152 This function will fail on any names that don't correspond to defined flags.
153 Unknown bits will be ignored.
154 */
from_str_truncate<B: Flags>(input: &str) -> Result<B, ParseError> where B::Bits: ParseHex,155 pub fn from_str_truncate<B: Flags>(input: &str) -> Result<B, ParseError>
156 where
157     B::Bits: ParseHex,
158 {
159     Ok(B::from_bits_truncate(from_str::<B>(input)?.bits()))
160 }
161 
162 /**
163 Write only the contained, defined, named flags in a flags value as text.
164 */
to_writer_strict<B: Flags>(flags: &B, mut writer: impl Write) -> Result<(), fmt::Error>165 pub fn to_writer_strict<B: Flags>(flags: &B, mut writer: impl Write) -> Result<(), fmt::Error> {
166     // This is a simplified version of `to_writer` that ignores
167     // any bits not corresponding to a named flag
168 
169     let mut first = true;
170     let mut iter = flags.iter_names();
171     for (name, _) in &mut iter {
172         if !first {
173             writer.write_str(" | ")?;
174         }
175 
176         first = false;
177         writer.write_str(name)?;
178     }
179 
180     fmt::Result::Ok(())
181 }
182 
183 /**
184 Parse a flags value from text.
185 
186 This function will fail on any names that don't correspond to defined flags.
187 This function will fail to parse hex values.
188 */
from_str_strict<B: Flags>(input: &str) -> Result<B, ParseError>189 pub fn from_str_strict<B: Flags>(input: &str) -> Result<B, ParseError> {
190     // This is a simplified version of `from_str` that ignores
191     // any bits not corresponding to a named flag
192 
193     let mut parsed_flags = B::empty();
194 
195     // If the input is empty then return an empty set of flags
196     if input.trim().is_empty() {
197         return Ok(parsed_flags);
198     }
199 
200     for flag in input.split('|') {
201         let flag = flag.trim();
202 
203         // If the flag is empty then we've got missing input
204         if flag.is_empty() {
205             return Err(ParseError::empty_flag());
206         }
207 
208         // If the flag starts with `0x` then it's a hex number
209         // These aren't supported in the strict parser
210         if flag.starts_with("0x") {
211             return Err(ParseError::invalid_hex_flag("unsupported hex flag value"));
212         }
213 
214         let parsed_flag = B::from_name(flag).ok_or_else(|| ParseError::invalid_named_flag(flag))?;
215 
216         parsed_flags.insert(parsed_flag);
217     }
218 
219     Ok(parsed_flags)
220 }
221 
222 /**
223 Encode a value as a hex string.
224 
225 Implementors of this trait should not write the `0x` prefix.
226 */
227 pub trait WriteHex {
228     /// Write the value as hex.
write_hex<W: fmt::Write>(&self, writer: W) -> fmt::Result229     fn write_hex<W: fmt::Write>(&self, writer: W) -> fmt::Result;
230 }
231 
232 /**
233 Parse a value from a hex string.
234 */
235 pub trait ParseHex {
236     /// Parse the value from hex.
parse_hex(input: &str) -> Result<Self, ParseError> where Self: Sized237     fn parse_hex(input: &str) -> Result<Self, ParseError>
238     where
239         Self: Sized;
240 }
241 
242 /// An error encountered while parsing flags from text.
243 #[derive(Debug)]
244 pub struct ParseError(ParseErrorKind);
245 
246 #[derive(Debug)]
247 #[allow(clippy::enum_variant_names)]
248 enum ParseErrorKind {
249     EmptyFlag,
250     InvalidNamedFlag {
251         #[cfg(not(feature = "std"))]
252         got: (),
253         #[cfg(feature = "std")]
254         got: String,
255     },
256     InvalidHexFlag {
257         #[cfg(not(feature = "std"))]
258         got: (),
259         #[cfg(feature = "std")]
260         got: String,
261     },
262 }
263 
264 impl ParseError {
265     /// An invalid hex flag was encountered.
invalid_hex_flag(flag: impl fmt::Display) -> Self266     pub fn invalid_hex_flag(flag: impl fmt::Display) -> Self {
267         let _flag = flag;
268 
269         let got = {
270             #[cfg(feature = "std")]
271             {
272                 _flag.to_string()
273             }
274         };
275 
276         ParseError(ParseErrorKind::InvalidHexFlag { got })
277     }
278 
279     /// A named flag that doesn't correspond to any on the flags type was encountered.
invalid_named_flag(flag: impl fmt::Display) -> Self280     pub fn invalid_named_flag(flag: impl fmt::Display) -> Self {
281         let _flag = flag;
282 
283         let got = {
284             #[cfg(feature = "std")]
285             {
286                 _flag.to_string()
287             }
288         };
289 
290         ParseError(ParseErrorKind::InvalidNamedFlag { got })
291     }
292 
293     /// A hex or named flag wasn't found between separators.
empty_flag() -> Self294     pub const fn empty_flag() -> Self {
295         ParseError(ParseErrorKind::EmptyFlag)
296     }
297 }
298 
299 impl fmt::Display for ParseError {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result300     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301         match &self.0 {
302             ParseErrorKind::InvalidNamedFlag { got } => {
303                 let _got = got;
304 
305                 write!(f, "unrecognized named flag")?;
306 
307                 #[cfg(feature = "std")]
308                 {
309                     write!(f, " `{}`", _got)?;
310                 }
311             }
312             ParseErrorKind::InvalidHexFlag { got } => {
313                 let _got = got;
314 
315                 write!(f, "invalid hex flag")?;
316 
317                 #[cfg(feature = "std")]
318                 {
319                     write!(f, " `{}`", _got)?;
320                 }
321             }
322             ParseErrorKind::EmptyFlag => {
323                 write!(f, "encountered empty flag")?;
324             }
325         }
326 
327         Ok(())
328     }
329 }
330 
331 #[cfg(feature = "std")]
332 impl std::error::Error for ParseError {}
333