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 {}