kxio 1.1.2

Provides injectable Filesystem and Network resources to make code more testable
Documentation
#![allow(deprecated)]

use std::{
    ops::Deref,
    path::PathBuf,
    sync::{Arc, Mutex},
};

use tempfile::{tempdir, TempDir};
use tracing::{debug, info};

pub fn real(cwd: Option<PathBuf>) -> FileSystem {
    let cwd = cwd.unwrap_or_default();
    FileSystem::Real(RealFileSystem::new(cwd))
}
pub fn temp() -> std::io::Result<FileSystem> {
    TempFileSystem::new().map(FileSystem::Temp)
}

#[derive(Clone, Debug)]
#[deprecated(since = "1.1.0", note = "Use [kxio::fs::FileSystem] instead")]
pub enum FileSystem {
    Real(RealFileSystem),
    Temp(TempFileSystem),
}
impl FileSystem {
    #[deprecated(since = "1.1.0", note = "Use [kxio::filesystem::real()] instead")]
    pub fn new_real(cwd: Option<PathBuf>) -> Self {
        real(cwd)
    }
    #[deprecated(since = "1.1.0", note = "Use [kxio::filesystem::temp()] instead")]
    pub fn new_temp() -> std::io::Result<Self> {
        temp()
    }
}
impl Deref for FileSystem {
    type Target = dyn FileSystemLike;

    fn deref(&self) -> &Self::Target {
        match self {
            Self::Real(env) => env,
            Self::Temp(env) => env,
        }
    }
}

pub trait FileSystemLike: Sync + Send + std::fmt::Debug {
    fn cwd(&self) -> &PathBuf;

    fn in_cwd(&self, name: &str) -> PathBuf {
        self.cwd().join(name)
    }

    fn write_file(&self, file_name: &str, content: &str) -> std::io::Result<PathBuf> {
        use std::fs::File;
        use std::io::{LineWriter, Write};

        let path = self.in_cwd(file_name);
        debug!("writing to {:?}", path);
        let file = File::create(path.clone())?;
        let mut file = LineWriter::new(file);
        file.write_all(content.as_bytes())?;
        Ok(path)
    }

    fn file_exists(&self, name: &PathBuf) -> bool {
        use std::fs::File;
        File::open(name).is_ok()
    }

    fn read_file(&self, file_name: &str) -> std::io::Result<String> {
        use std::fs::File;
        use std::io::Read;

        let path = self.in_cwd(file_name);
        debug!("reading from {:?}", path);
        let mut file = File::open(path)?;
        let mut content = String::new();
        file.read_to_string(&mut content)?;
        Ok(content)
    }
}

#[derive(Clone, Debug, Default)]
pub struct RealFileSystem {
    cwd: PathBuf,
}

#[derive(Clone, Debug)]
pub struct TempFileSystem {
    cwd: PathBuf,

    // Handle to the temporary directory
    // When this handle is dropped the directory is deleted
    _temp_dir: Arc<Mutex<TempDir>>,
}

impl FileSystemLike for TempFileSystem {
    fn cwd(&self) -> &PathBuf {
        &self.cwd
    }
}

impl FileSystemLike for RealFileSystem {
    fn cwd(&self) -> &PathBuf {
        &self.cwd
    }
}

impl RealFileSystem {
    const fn new(cwd: PathBuf) -> Self {
        Self { cwd }
    }
}

impl TempFileSystem {
    fn new() -> std::io::Result<Self> {
        let temp_dir = tempdir()?;
        info!("temp dir: {:?}", temp_dir.path());
        let cwd = temp_dir.path().to_path_buf();
        let temp_dir = Arc::new(Mutex::new(temp_dir));
        Ok(Self {
            cwd,
            _temp_dir: temp_dir,
        })
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    use std::path::PathBuf;

    #[test_log::test]
    fn test_cwd() {
        let cwd = PathBuf::from("/tmp");
        let env = RealFileSystem::new(cwd.clone());
        assert_eq!(env.cwd(), &cwd);
    }

    #[test_log::test]
    fn test_create_on_temp_fs() -> std::io::Result<()> {
        let env = TempFileSystem::new()?;
        assert!(env.cwd().exists());
        Ok(())
    }

    #[test_log::test]
    fn test_create_on_real_fs() {
        let cwd = PathBuf::from("/tmp");
        let env = RealFileSystem::new(cwd.clone());
        assert_eq!(env.cwd(), &cwd);
    }

    #[test_log::test]
    fn test_write_and_read_file() -> std::io::Result<()> {
        let env = TempFileSystem::new()?;
        let file_name = "test.txt";
        let content = "Hello, World!";
        let path = env.write_file(file_name, content)?;
        assert_eq!(env.read_file(file_name)?, content);
        assert!(path.exists());
        Ok(())
    }
}