ruff/crates/ruff_python_stdlib/src/open_mode.rs

146 lines
4.3 KiB
Rust

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<Item = char>) -> Result<Self, String> {
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<char> for OpenMode {
type Error = String;
fn try_from(value: char) -> Result<Self, Self::Error> {
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<Self, Self::Error> {
OpenMode::from_chars(value.chars())
}
}