plumber-cli 0.4.1

unix process pipelines made easy
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};

pub enum PlumberFileError {
    NotFound,
    TomlError(toml::de::Error),
    IoError(std::io::Error),
    #[allow(dead_code)]
    MissingRequiredConfig(String),
}

impl From<std::io::Error> for PlumberFileError {
    fn from(error: std::io::Error) -> Self {
        match error.kind() {
            std::io::ErrorKind::NotFound => PlumberFileError::NotFound,
            _ => PlumberFileError::IoError(error),
        }
    }
}

impl From<toml::de::Error> for PlumberFileError {
    fn from(error: toml::de::Error) -> Self {
        PlumberFileError::TomlError(error)
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct PlumberFileConfig {
    pub exec: String,
    pub run: Option<PlumberFileConfigRun>,
    pub metadata: Option<PlumberFileConfigMeta>,
    pub extra: Option<toml::Table>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct PlumberFileConfigRun {
    instances: Option<u32>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct PlumberFileConfigMeta {
    pub name: Option<String>,
    pub logging_dir: Option<String>,
    pub metadata_dir: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct PlumberFile {
    pub path: PathBuf,
    pub name: String,
    pub config: PlumberFileConfig,
}

impl TryFrom<PathBuf> for PlumberFile {
    type Error = PlumberFileError;

    fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
        let data = fs::read_to_string(&path)?;
        let config = toml::from_str::<PlumberFileConfig>(&data)?;
        let name = match config.metadata.as_ref().and_then(|m| m.name.clone()) {
            Some(name) => name,
            None => {
                let name = path.file_stem().ok_or(PlumberFileError::NotFound)?;
                let name = name.to_str().ok_or(PlumberFileError::NotFound)?.to_string();
                name
            }
        };
        Ok(PlumberFile { path, name, config })
    }
}

impl PlumberFile {
    pub fn save_to(&self, path: &Path) {
        log::debug!("saving PlumberFile struct to {}", path.display());
        fs::create_dir_all(path).unwrap();

        let pf_raw = serde_json::to_vec(self)
            .expect("failed to serialize PlumberFile struct");

        let mut f = fs::File::create(path.join(".data"))
            .expect("failed to create file to store PlumberFile struct");

        f.write_all(&pf_raw).unwrap();
    }
}

#[cfg(test)]
mod tests {
    use std::path::PathBuf;

    use super::*;

    fn init() {
        let _ = env_logger::builder().is_test(true).try_init();
    }

    #[test]
    fn plumber_file_not_exist() {
        init();
        let nonexistent_path = PathBuf::from("./thisdoesntexist/definetly");
        assert!(PlumberFile::try_from(nonexistent_path).is_err());
    }

    #[test]
    fn test_toml() {
        init();
        let t: PlumberFileConfig = toml::from_str(r#"
        exec = "test | test | test"

        [metadata]
        name = "test"

        [run]
        instances = 3

        [extra]
        pizza = 'yes'

        "#).unwrap();

        log::info!("{:#?}", t);
    }
}