dias 0.2.0

Minimal cross-platform support for common platform specific things, intended for small games for web plus desktopy platforms.
Documentation
use std::borrow::Cow;
use std::error::Error;
use std::fmt;
use std::io::{Read, Write};

#[derive(Debug)]
pub enum OuterDirectoryError {
    NotAvailable,
}

impl fmt::Display for OuterDirectoryError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl Error for OuterDirectoryError {}

pub trait File {
    type ReadText: Read;
    type ReadBinary: Read;
    fn exists(&self) -> std::io::Result<bool>;
    fn read_text(&self) -> std::io::Result<Self::ReadText>;
    fn read_binary(&self) -> std::io::Result<Self::ReadBinary>;
}

pub trait WritableFile: File {
    type WriteText: Write;
    type WriteBinary: Write;

    fn remove(&mut self) -> std::io::Result<()>;
    fn write_text(&mut self) -> std::io::Result<Self::WriteText>;
    fn write_binary(&mut self) -> std::io::Result<Self::WriteBinary>;
}

pub trait Dir {
    type File: File;
    fn file(&self, name: Cow<'static, str>) -> Self::File;
}

pub trait WritableDir: Dir {
    type WritableFile: WritableFile;
    fn writable_file(&mut self, name: Cow<'static, str>) -> Self::WritableFile;
}

pub trait ParentDir: Dir {
    type LeafDir: Dir;
    fn subdir(&self, name: Cow<'static, str>) -> Self;
    fn into_leaf(self) -> Self::LeafDir;
}

pub trait WritableParentDir: WritableDir + ParentDir {
    type WritableLeafDir: WritableDir;
    fn writable_subdir(&mut self, name: Cow<'static, str>) -> Self;
    fn into_writable_leaf(self) -> Self::WritableLeafDir;
}

pub trait Storage {
    type Dir: ParentDir;
    type WritableDir: WritableParentDir;
    fn data(&self) -> Result<Self::Dir, OuterDirectoryError>;
    fn config(&self) -> Result<Self::Dir, OuterDirectoryError>;
    fn cache(&self) -> Result<Self::Dir, OuterDirectoryError>;
    fn writable_data(&mut self) -> Result<Self::WritableDir, OuterDirectoryError>;
    fn writable_config(&mut self) -> Result<Self::WritableDir, OuterDirectoryError>;
    fn writable_cache(&mut self) -> Result<Self::WritableDir, OuterDirectoryError>;
}

#[cfg(test)]
pub(super) mod tests {
    use super::*;
    use std::io::{Read, Write};

    pub fn text_file(mut storage: impl Storage) {
        let text = "hello world";

        let mut file = storage
            .writable_data()
            .unwrap()
            .writable_file("test".into());
        file.write_text()
            .unwrap()
            .write_all(text.as_bytes())
            .unwrap();
        assert!(file.exists().unwrap());
        let mut got = String::new();
        file.read_text().unwrap().read_to_string(&mut got).unwrap();
        assert_eq!(got, text);

        let file = storage.data().unwrap().file("test".into());
        assert!(file.exists().unwrap());
        let mut got = String::new();
        file.read_text().unwrap().read_to_string(&mut got).unwrap();
        assert_eq!(got, text);

        let mut file = storage
            .writable_data()
            .unwrap()
            .writable_file("test".into());
        assert!(file.exists().unwrap());
        file.remove().unwrap();
        assert!(!file.exists().unwrap());
        let file = storage.data().unwrap().file("test".into());
        assert!(!file.exists().unwrap());
    }

    pub fn binary_file(mut storage: impl Storage) {
        let text = "hello world";

        let mut file = storage
            .writable_data()
            .unwrap()
            .writable_file("test".into());
        file.write_text()
            .unwrap()
            .write_all(text.as_bytes())
            .unwrap();
        assert!(file.exists().unwrap());
        let mut got = String::new();
        file.read_text().unwrap().read_to_string(&mut got).unwrap();
        assert_eq!(got, text);

        let file = storage.data().unwrap().file("test".into());
        assert!(file.exists().unwrap());
        let mut got = String::new();
        file.read_text().unwrap().read_to_string(&mut got).unwrap();
        assert_eq!(got, text);

        let mut file = storage
            .writable_data()
            .unwrap()
            .writable_file("test".into());
        assert!(file.exists().unwrap());
        file.remove().unwrap();
        assert!(!file.exists().unwrap());
        let file = storage.data().unwrap().file("test".into());
        assert!(!file.exists().unwrap());
    }

    pub fn file_uniqueness(mut storage: impl Storage) {
        let to_check = vec![
            (storage.writable_data().unwrap(), "data", "one", "a"),
            (storage.writable_data().unwrap(), "data", "one", "b"),
            (storage.writable_data().unwrap(), "data", "two", "a"),
            (storage.writable_data().unwrap(), "data", "two", "b"),
            (storage.writable_config().unwrap(), "config", "one", "a"),
            (storage.writable_config().unwrap(), "config", "one", "b"),
            (storage.writable_config().unwrap(), "config", "two", "a"),
            (storage.writable_config().unwrap(), "config", "two", "b"),
            (storage.writable_cache().unwrap(), "cache", "one", "a"),
            (storage.writable_cache().unwrap(), "cache", "one", "b"),
            (storage.writable_cache().unwrap(), "cache", "two", "a"),
            (storage.writable_cache().unwrap(), "cache", "two", "b"),
        ];

        for (outer_dir, outer_dir_name, inner_dir_name, file_name) in to_check.iter() {
            let value = format!(
                "contents {} {} {}",
                outer_dir_name, inner_dir_name, file_name
            );
            outer_dir
                .subdir((*inner_dir_name).into())
                .writable_file((*file_name).into())
                .write_text()
                .unwrap()
                .write_all(value.as_bytes())
                .unwrap();
        }

        for (outer_dir, outer_dir_name, inner_dir_name, file_name) in to_check.iter() {
            let value = format!(
                "contents {} {} {}",
                outer_dir_name, inner_dir_name, file_name
            );
            let mut got = String::new();
            outer_dir
                .subdir((*inner_dir_name).into())
                .file((*file_name).into())
                .read_text()
                .unwrap()
                .read_to_string(&mut got)
                .unwrap();
            assert_eq!(got, value);
        }
    }
}