tam 1.0.12

A tasks manager cli tool
use anyhow::Context;
use env_home::env_home_dir;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;

pub const TAM_DIR: &str = ".tam";
pub const CONFIG_FILE: &str = "tam.config.toml";

pub const LOG_FILE_PATH: &str = "logs/tam.log";
pub const LOG_FILE_PATTERN: &str = "logs/tam.{}.log";
pub const DB_FILE: &str = "tam.db3";

static CONFIG: Lazy<Config> = Lazy::new(|| {
    let config = match Config::load() {
        Ok(c) => c,
        Err(e) => {
            eprintln!("Failed to load config: {}", e);
            std::process::exit(1);
        }
    };
    config
});

pub fn get_config() -> Config {
    CONFIG.clone()
}

#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
pub struct Config {
    pub db_file: String,
    pub current_project: Option<String>,
    pub logfile_path: String,
    pub logfile_pattern: String,
    pub theme: String,
}

impl Default for Config {
    fn default() -> Self {
        let tam_dir = match Self::config_dir() {
            Ok(p) => p,
            Err(e) => {
                eprintln!("Failed to tam dir: {}", e);
                std::process::exit(1);
            }
        };
        let db_file = tam_dir.join(DB_FILE);
        let log_file = tam_dir.join(LOG_FILE_PATH);
        let log_file_pattern = tam_dir.join(LOG_FILE_PATTERN);

        Config {
            db_file: db_file.display().to_string(),
            current_project: None,
            logfile_path: log_file.display().to_string(),
            logfile_pattern: log_file_pattern.display().to_string(),
            theme: "dark".to_string(),
        }
    }
}

impl Config {
    fn load() -> anyhow::Result<Self> {
        let config_dir = Self::config_dir()?;
        let config_file = config_dir.join(CONFIG_FILE);
        if !config_file.exists() {
            log::info!("config file not exists, create default config");
            let config = Self::default();
            config.save()?;
            return Ok(config);
        }
        let content = std::fs::read_to_string(config_file)?;
        let config = toml::from_str(&content)?;
        Ok(config)
    }
    fn config_dir() -> anyhow::Result<PathBuf> {
        let home_dir = env_home_dir().with_context(|| format!("fail to get home dir"))?;
        let tam_dir = home_dir.join(TAM_DIR);
        if !tam_dir.exists() {
            fs::create_dir_all(&tam_dir).with_context(|| format!("fail to create tam dir"))?;
        }
        Ok(tam_dir)
    }
    pub fn save(&self) -> anyhow::Result<bool> {
        let config_dir = Self::config_dir()?;
        let config_file = config_dir.join(CONFIG_FILE);
        let content = toml::to_string(&self)?;
        std::fs::write(config_file, content)?;
        Ok(true)
    }
    pub fn set_theme(&mut self, theme: String) {
        self.theme = theme;
        match self.save() {
            Ok(_) => (),
            Err(err) => {
                log::info!("set theme error: {}", err);
            }
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_config_dir() {
        let result = Config::config_dir();
        assert!(result.is_ok());
        assert_eq!(
            result.unwrap().display().to_string(),
            "/Users/wstreet7/.tam".to_string()
        );
    }
    #[test]
    fn test_config_dir_error_path() {
        let result = Config::config_dir();
        assert!(result.is_ok());
        assert_ne!(
            result.unwrap().display().to_string(),
            "/Users/xxx/.tam".to_string()
        );
    }

    #[test]
    fn test_load() {
        let result = Config::load();
        assert!(result.is_ok());
        let mut c = result.unwrap();
        c.current_project = None;
        assert_eq!(c, Config::default());
    }

    #[test]
    fn test_edit_config() {
        let mut config = Config::default();
        config.current_project = Some(String::from("myproject"));
        let result = config.save();
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), true)
    }
    #[test]
    fn test_load_edit_config() {
        let result = Config::load();
        assert!(result.is_ok());

        let mut c = result.unwrap();
        c.current_project = Some(String::from("myproject2"));
        assert_ne!(c, Config::default());
    }
}