#![allow(clippy::let_unit_value)]
use core::fmt::{self, Write};
use crate::{BitsPrimitive, Flags};
#[cfg(feature = "alloc")]
use alloc::string::{String, ToString};
#[cfg(not(feature = "alloc"))]
use fmt::Display as ToString;
pub fn to_writer<B: Flags>(flags: &B, mut writer: impl Write) -> Result<(), fmt::Error> {
let mut first = true;
let mut iter = flags.iter_names();
for (name, _) in &mut iter {
if !first {
writer.write_str(" | ")?;
}
first = false;
writer.write_str(name)?;
}
let remaining = iter.remaining().bits();
if remaining != B::Bits::EMPTY {
if !first {
writer.write_str(" | ")?;
}
write!(writer, "{remaining:#X}")?;
}
fmt::Result::Ok(())
}
pub fn from_text<B: Flags>(input: &str) -> Result<B, ParseError>
where
B::Bits: ParseHex,
{
let mut parsed_flags = B::empty();
if input.trim().is_empty() {
return Ok(parsed_flags);
}
for flag in input.split('|') {
let flag = flag.trim();
if flag.is_empty() {
return Err(ParseError::empty_flag());
}
let parsed_flag = if let Some(flag) = flag.strip_prefix("0x") {
let bits =
<B::Bits>::parse_hex(flag).map_err(|_| ParseError::invalid_hex_flag(flag))?;
B::from_bits_retain(bits)
}
else {
B::from_name(flag).ok_or_else(|| ParseError::invalid_named_flag(flag))?
};
parsed_flags.set(parsed_flag);
}
Ok(parsed_flags)
}
pub fn to_writer_truncate<B: Flags>(flags: &B, writer: impl Write) -> Result<(), fmt::Error> {
to_writer(&B::from_bits_truncate(flags.bits()), writer)
}
pub fn from_text_truncate<B: Flags>(input: &str) -> Result<B, ParseError>
where
B::Bits: ParseHex,
{
Ok(B::from_bits_truncate(from_text::<B>(input)?.bits()))
}
pub fn to_writer_strict<B: Flags>(flags: &B, mut writer: impl Write) -> Result<(), fmt::Error> {
let mut first = true;
let mut iter = flags.iter_names();
for (name, _) in &mut iter {
if !first {
writer.write_str(" | ")?;
}
first = false;
writer.write_str(name)?;
}
fmt::Result::Ok(())
}
pub fn from_text_strict<B: Flags>(input: &str) -> Result<B, ParseError> {
let mut parsed_flags = B::empty();
if input.trim().is_empty() {
return Ok(parsed_flags);
}
for flag in input.split('|') {
let flag = flag.trim();
if flag.is_empty() {
return Err(ParseError::empty_flag());
}
if flag.starts_with("0x") {
return Err(ParseError::invalid_hex_flag("unsupported hex flag value"));
}
let parsed_flag = B::from_name(flag).ok_or_else(|| ParseError::invalid_named_flag(flag))?;
parsed_flags.set(parsed_flag);
}
Ok(parsed_flags)
}
pub trait ParseHex {
fn parse_hex(input: &str) -> Result<Self, ParseError>
where
Self: Sized;
}
#[derive(Debug)]
pub struct ParseError(ParseErrorKind);
#[derive(Debug)]
#[allow(clippy::enum_variant_names)]
enum ParseErrorKind {
EmptyFlag,
InvalidNamedFlag {
#[cfg(not(feature = "alloc"))]
got: (),
#[cfg(feature = "alloc")]
got: String,
},
InvalidHexFlag {
#[cfg(not(feature = "alloc"))]
got: (),
#[cfg(feature = "alloc")]
got: String,
},
}
impl ParseError {
pub fn invalid_hex_flag<T>(flag: T) -> Self
where
T: fmt::Display + ToString,
{
let _flag = flag;
let got = {
#[cfg(feature = "alloc")]
{
_flag.to_string()
}
};
ParseError(ParseErrorKind::InvalidHexFlag { got })
}
pub fn invalid_named_flag<T>(flag: T) -> Self
where
T: fmt::Display + ToString,
{
let _flag = flag;
let got = {
#[cfg(feature = "alloc")]
{
_flag.to_string()
}
};
ParseError(ParseErrorKind::InvalidNamedFlag { got })
}
pub const 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 = "alloc")]
{
write!(f, " `{}`", _got)?;
}
}
ParseErrorKind::InvalidHexFlag { got } => {
let _got = got;
write!(f, "invalid hex flag")?;
#[cfg(feature = "alloc")]
{
write!(f, " `{}`", _got)?;
}
}
ParseErrorKind::EmptyFlag => {
write!(f, "encountered empty flag")?;
}
}
Ok(())
}
}
impl core::error::Error for ParseError {}