bitflags::bitflags! { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct OpenMode: u8 { /// `r` const READ = 1 << 0; /// `w` const WRITE = 1 << 1; /// `a` const APPEND = 1 << 2; /// `x` const CREATE = 1 << 3; /// `b` const BINARY = 1 << 4; /// `t` const TEXT = 1 << 5; /// `+` const PLUS = 1 << 6; /// `U` const UNIVERSAL_NEWLINES = 1 << 7; } } impl OpenMode { /// Parse an [`OpenMode`] from a sequence of characters. pub fn from_chars(chars: impl Iterator) -> Result { let mut open_mode = OpenMode::empty(); for c in chars { let flag = OpenMode::try_from(c)?; if flag.intersects(open_mode) { return Err(format!("Open mode contains duplicate flag: `{c}`")); } open_mode.insert(flag); } // Both text and binary mode cannot be set at the same time. if open_mode.contains(OpenMode::TEXT | OpenMode::BINARY) { return Err( "Open mode cannot contain both text (`t`) and binary (`b`) flags".to_string(), ); } // The `U` mode is only valid with `r`. if open_mode.contains(OpenMode::UNIVERSAL_NEWLINES) && open_mode.intersects(OpenMode::WRITE | OpenMode::APPEND | OpenMode::CREATE) { return Err("Open mode cannot contain the universal newlines (`U`) flag with write (`w`), append (`a`), or create (`x`) flags".to_string()); } // Otherwise, reading, writing, creating, and appending are mutually exclusive. if [ OpenMode::READ | OpenMode::UNIVERSAL_NEWLINES, OpenMode::WRITE, OpenMode::CREATE, OpenMode::APPEND, ] .into_iter() .filter(|flag| open_mode.intersects(*flag)) .count() != 1 { return Err("Open mode must contain exactly one of the following flags: read (`r`), write (`w`), create (`x`), or append (`a`)".to_string()); } Ok(open_mode) } /// Remove any redundant flags from the open mode. #[must_use] pub fn reduce(self) -> Self { let mut open_mode = self; // `t` is always redundant. open_mode.remove(Self::TEXT); // `U` is always redundant. open_mode.remove(Self::UNIVERSAL_NEWLINES); // `r` is redundant, unless `b` or `+` is also set, in which case, we need one of `w`, `a`, `r`, or `x`. if open_mode.intersects(Self::BINARY | Self::PLUS) { if !open_mode.intersects(Self::WRITE | Self::CREATE | Self::APPEND) { open_mode.insert(Self::READ); } } else { open_mode.remove(Self::READ); } open_mode } } /// Write the [`OpenMode`] as a string. impl std::fmt::Display for OpenMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.contains(OpenMode::READ) { write!(f, "r")?; } if self.contains(OpenMode::WRITE) { write!(f, "w")?; } if self.contains(OpenMode::APPEND) { write!(f, "a")?; } if self.contains(OpenMode::CREATE) { write!(f, "x")?; } if self.contains(OpenMode::UNIVERSAL_NEWLINES) { write!(f, "U")?; } if self.contains(OpenMode::BINARY) { write!(f, "b")?; } if self.contains(OpenMode::TEXT) { write!(f, "t")?; } if self.contains(OpenMode::PLUS) { write!(f, "+")?; } Ok(()) } } impl TryFrom for OpenMode { type Error = String; fn try_from(value: char) -> Result { match value { 'r' => Ok(Self::READ), 'w' => Ok(Self::WRITE), 'a' => Ok(Self::APPEND), 'x' => Ok(Self::CREATE), 'b' => Ok(Self::BINARY), 't' => Ok(Self::TEXT), '+' => Ok(Self::PLUS), 'U' => Ok(Self::UNIVERSAL_NEWLINES), _ => Err(format!("Invalid open mode flag: `{value}`")), } } } impl TryFrom<&str> for OpenMode { type Error = String; fn try_from(value: &str) -> Result { OpenMode::from_chars(value.chars()) } }