use std::{
fs::OpenOptions,
io::{self, Read, Write},
marker::PhantomData,
str::FromStr,
};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error(transparent)]
IoError(#[from] io::Error),
#[error("Error while converting value")]
ConvError,
}
pub trait AttributeBase {
fn path(&self) -> &str;
fn from_path(path: impl Into<String>) -> Self;
}
pub trait RawRead: AttributeBase {
fn read_raw(&self) -> Result<String, Error> {
let path = self.path();
let mut file = OpenOptions::new().read(true).open(path)?;
let mut str = String::new();
file.read_to_string(&mut str)?;
Ok(str.trim().to_string())
}
}
pub trait TypedRead: RawRead {
type Repr;
fn read(&self) -> Result<Self::Repr, Error>;
}
pub trait RawWrite: AttributeBase {
fn write_raw(&self, value: impl Into<String>) -> Result<(), Error> {
let path = self.path();
OpenOptions::new()
.write(true)
.open(path)
.and_then(|mut f| write!(f, "{}", value.into()))?;
Ok(())
}
}
pub trait TypedWrite: RawWrite {
type Repr;
fn write(&self, value: Self::Repr) -> Result<(), Error>;
}
pub struct ReadOnly<T>
where
T: RawRead,
{
attribute: T,
}
impl<T: RawRead> AttributeBase for ReadOnly<T> {
fn path(&self) -> &str {
self.attribute.path()
}
fn from_path(path: impl Into<String>) -> Self {
Self {
attribute: T::from_path(path),
}
}
}
impl<T: RawRead> RawRead for ReadOnly<T> {
fn read_raw(&self) -> Result<String, Error> {
self.attribute.read_raw()
}
}
impl<T: TypedRead> TypedRead for ReadOnly<T> {
type Repr = T::Repr;
fn read(&self) -> Result<Self::Repr, Error> {
self.attribute.read()
}
}
impl<T: RawRead> From<T> for ReadOnly<T> {
fn from(attribute: T) -> Self {
Self { attribute }
}
}
pub struct WriteOnly<T>
where
T: RawWrite,
{
attribute: T,
}
impl<T: RawWrite> AttributeBase for WriteOnly<T> {
fn path(&self) -> &str {
self.attribute.path()
}
fn from_path(path: impl Into<String>) -> Self {
Self {
attribute: T::from_path(path),
}
}
}
impl<T: RawWrite> RawWrite for WriteOnly<T> {
fn write_raw(&self, value: impl Into<String>) -> Result<(), Error> {
self.attribute.write_raw(value)
}
}
impl<T: TypedWrite> TypedWrite for WriteOnly<T> {
type Repr = T::Repr;
fn write(&self, value: Self::Repr) -> Result<(), Error> {
self.attribute.write(value)
}
}
impl<T: RawWrite> From<T> for WriteOnly<T> {
fn from(attribute: T) -> Self {
Self { attribute }
}
}
pub struct Boolean {
pub path: String,
}
impl AttributeBase for Boolean {
fn path(&self) -> &str {
self.path.as_str()
}
fn from_path(path: impl Into<String>) -> Self {
Self { path: path.into() }
}
}
impl RawRead for Boolean {}
impl RawWrite for Boolean {}
impl TypedRead for Boolean {
type Repr = bool;
fn read(&self) -> Result<Self::Repr, Error> {
let repr = self.read_raw()?;
match repr.trim() {
"Y" | "1" => Ok(true),
"N" | "0" => Ok(false),
_ => Err(Error::ConvError),
}
}
}
impl TypedWrite for Boolean {
type Repr = bool;
fn write(&self, value: Self::Repr) -> Result<(), Error> {
self.write_raw(format!("{}", value as u8))
}
}
pub struct Generic<T> {
pub path: String,
_phantom: PhantomData<T>,
}
impl<T> AttributeBase for Generic<T> {
fn path(&self) -> &str {
self.path.as_str()
}
fn from_path(path: impl Into<String>) -> Self {
Self {
path: path.into(),
_phantom: Default::default(),
}
}
}
impl<T> RawRead for Generic<T> {}
impl<T> RawWrite for Generic<T> {}
impl<T> TypedRead for Generic<T>
where
T: FromStr,
{
type Repr = T;
fn read(&self) -> Result<Self::Repr, Error> {
self.read_raw()?.parse().map_err(|_| Error::ConvError)
}
}
impl<T> TypedWrite for Generic<T>
where
T: ToString,
{
type Repr = T;
fn write(&self, value: Self::Repr) -> Result<(), Error> {
self.write_raw(value.to_string())
}
}
#[allow(dead_code)]
pub type RBoolean = ReadOnly<Boolean>;
#[allow(dead_code)]
pub type WBoolean = WriteOnly<Boolean>;
pub type RGeneric<T> = ReadOnly<Generic<T>>;
pub type Int32 = Generic<i32>;
pub type RInt32 = ReadOnly<Generic<i32>>;