use std::{borrow::Cow, str::FromStr};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum FileMode {
Read(bool),
ReadUpdate(bool),
Write(bool),
WriteUpdate(bool),
Append(bool),
AppendUpdate(bool),
}
impl FileMode {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::Read(false) => "r",
Self::Read(true) => "rb",
Self::ReadUpdate(false) => "r+",
Self::ReadUpdate(true) => "rb+",
Self::Write(false) => "w",
Self::Write(true) => "wb",
Self::WriteUpdate(false) => "w+",
Self::WriteUpdate(true) => "wb+",
Self::Append(false) => "a",
Self::Append(true) => "ab",
Self::AppendUpdate(false) => "a+",
Self::AppendUpdate(true) => "ab+",
}
}
#[must_use]
pub fn is_binary(&self) -> bool {
let (Self::Read(b)
| Self::ReadUpdate(b)
| Self::Write(b)
| Self::WriteUpdate(b)
| Self::Append(b)
| Self::AppendUpdate(b)) = self;
*b
}
#[must_use]
pub fn readable(&self) -> bool {
matches!(
self,
Self::Read(_) | Self::ReadUpdate(_) | Self::WriteUpdate(_) | Self::AppendUpdate(_)
)
}
#[must_use]
pub fn writable(&self) -> bool {
matches!(
self,
Self::Write(_) | Self::WriteUpdate(_) | Self::Append(_) | Self::AppendUpdate(_) | Self::ReadUpdate(_)
)
}
#[must_use]
pub fn is_append(&self) -> bool {
matches!(self, Self::Append(_) | Self::AppendUpdate(_))
}
#[must_use]
pub fn truncate(&self) -> bool {
matches!(self, Self::Write(_) | Self::WriteUpdate(_))
}
#[must_use]
pub fn create(&self) -> bool {
matches!(
self,
Self::Write(_) | Self::WriteUpdate(_) | Self::Append(_) | Self::AppendUpdate(_)
)
}
#[must_use]
pub fn type_name(&self) -> &'static str {
match self {
_ if !self.is_binary() => "TextIOWrapper",
Self::ReadUpdate(_) | Self::WriteUpdate(_) | Self::AppendUpdate(_) => "BufferedRandom",
Self::Read(_) => "BufferedReader",
Self::Write(_) | Self::Append(_) => "BufferedWriter",
}
}
#[must_use]
pub fn file_type_name(&self) -> &'static str {
match self {
_ if !self.is_binary() => "_io.TextIOWrapper",
Self::ReadUpdate(_) | Self::WriteUpdate(_) | Self::AppendUpdate(_) => "_io.BufferedRandom",
Self::Read(_) => "_io.BufferedReader",
Self::Write(_) | Self::Append(_) => "_io.BufferedWriter",
}
}
}
impl FromStr for FileMode {
type Err = Cow<'static, str>;
fn from_str(mode: &str) -> Result<Self, Self::Err> {
let mut action = None;
let mut binary = false;
let mut text = false;
for ch in mode.chars() {
match ch {
'r' | 'w' | 'a' => {
if action.replace(ch).is_some() {
return Err("must have exactly one of create/read/write/append mode".into());
}
}
'x' => return Err("exclusive creation mode is not supported".into()),
'b' => {
if binary {
return Err("invalid mode: binary mode specified twice".into());
}
binary = true;
}
't' => {
if text {
return Err("invalid mode: text mode specified twice".into());
}
text = true;
}
'+' => return Err("update modes ('+') are not yet supported".into()),
_ => return Err(format!("invalid mode: {ch:?}").into()),
}
}
if binary && text {
return Err("can't have text and binary mode at once".into());
}
let Some(action) = action else {
return Err("Must have exactly one of create/read/write/append mode and at most one plus".into());
};
Ok(match action {
'w' => Self::Write(binary),
'a' => Self::Append(binary),
_ => Self::Read(binary),
})
}
}