1/*!
2Parsing flags from text.
3
4Format 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
12As an example, this is how `Flags::A | Flags::B | 0x0c` can be represented as text:
13
14```text
15A | B | 0x0c
16```
17
18Alternatively, it could be represented without whitespace:
19
20```text
21A|B|0x0C
22```
23
24Note that identifiers are *case-sensitive*, so the following is *not equivalent*:
25
26```text
27a|b|0x0C
28```
29*/
30
31#![allow(clippy::let_unit_value)]
32
33use core::fmt::{self, Write};
34
35use crate::{Bits, Flags};
36
37/**
38Write a flags value as text.
39
40Any bits that aren't part of a contained flag will be formatted as a hex number.
41*/
42pub fn to_writer<B: Flags>(flags: &B, mut writer: impl Write) -> Result<(), fmt::Error>
43where
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")]
81pub(crate) struct AsDisplay<'a, B>(pub(crate) &'a B);
82
83#[cfg(feature = "serde")]
84impl<'a, B: Flags> fmt::Display for AsDisplay<'a, B>
85where
86 B::Bits: WriteHex,
87{
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 to_writer(self.0, f)
90 }
91}
92
93/**
94Parse a flags value from text.
95
96This function will fail on any names that don't correspond to defined flags.
97Unknown bits will be retained.
98*/
99pub fn from_str<B: Flags>(input: &str) -> Result<B, ParseError>
100where
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/**
140Write a flags value as text, ignoring any unknown bits.
141*/
142pub fn to_writer_truncate<B: Flags>(flags: &B, writer: impl Write) -> Result<(), fmt::Error>
143where
144 B::Bits: WriteHex,
145{
146 to_writer(&B::from_bits_truncate(flags.bits()), writer)
147}
148
149/**
150Parse a flags value from text.
151
152This function will fail on any names that don't correspond to defined flags.
153Unknown bits will be ignored.
154*/
155pub fn from_str_truncate<B: Flags>(input: &str) -> Result<B, ParseError>
156where
157 B::Bits: ParseHex,
158{
159 Ok(B::from_bits_truncate(from_str::<B>(input)?.bits()))
160}
161
162/**
163Write only the contained, defined, named flags in a flags value as text.
164*/
165pub 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: bool = true;
170 let mut iter: IterNames = flags.iter_names();
171 for (name: &str, _) 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/**
184Parse a flags value from text.
185
186This function will fail on any names that don't correspond to defined flags.
187This function will fail to parse hex values.
188*/
189pub 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/**
223Encode a value as a hex string.
224
225Implementors of this trait should not write the `0x` prefix.
226*/
227pub trait WriteHex {
228 /// Write the value as hex.
229 fn write_hex<W: fmt::Write>(&self, writer: W) -> fmt::Result;
230}
231
232/**
233Parse a value from a hex string.
234*/
235pub trait ParseHex {
236 /// Parse the value from hex.
237 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)]
244pub struct ParseError(ParseErrorKind);
245
246#[derive(Debug)]
247#[allow(clippy::enum_variant_names)]
248enum 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
264impl ParseError {
265 /// An invalid hex flag was encountered.
266 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.
280 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.
294 pub const fn empty_flag() -> Self {
295 ParseError(ParseErrorKind::EmptyFlag)
296 }
297}
298
299impl fmt::Display for ParseError {
300 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")]
332impl std::error::Error for ParseError {}
333