1#![allow(clippy::let_unit_value)]
32
33use core::fmt::{self, Write};
34
35use crate::{Bits, Flags};
36
37pub fn to_writer<B: Flags>(flags: &B, mut writer: impl Write) -> Result<(), fmt::Error>
43where
44 B::Bits: WriteHex,
45{
46 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 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
93pub 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 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 flag.is_empty() {
115 return Err(ParseError::empty_flag());
116 }
117
118 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 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
139pub 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
149pub 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
162pub fn to_writer_strict<B: Flags>(flags: &B, mut writer: impl Write) -> Result<(), fmt::Error> {
166 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
183pub fn from_str_strict<B: Flags>(input: &str) -> Result<B, ParseError> {
190 let mut parsed_flags = B::empty();
194
195 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 flag.is_empty() {
205 return Err(ParseError::empty_flag());
206 }
207
208 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
222pub trait WriteHex {
228 fn write_hex<W: fmt::Write>(&self, writer: W) -> fmt::Result;
230}
231
232pub trait ParseHex {
236 fn parse_hex(input: &str) -> Result<Self, ParseError>
238 where
239 Self: Sized;
240}
241
242#[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 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 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 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 {}