o7 0.1.1

O7 workflow DSL runner
Documentation
//! Global user configuration — ~/.config/o7/config.toml

use serde::{Deserialize, Serialize};

/// Global user configuration (loaded from ~/.config/o7/config.toml).
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GlobalConfig {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub default_project_root: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub default_harness: Option<String>,
}

/// Return the path to the global config file.
/// If `home_dir` is None, reads the HOME environment variable.
pub fn global_config_path_for(home_dir: Option<&std::path::Path>) -> std::path::PathBuf {
    let home = match home_dir {
        Some(p) => p.to_path_buf(),
        None => std::path::PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| ".".to_string())),
    };
    home.join(".config").join("o7").join("config.toml")
}

/// Return the path to the global config file (production — uses HOME env var).
pub fn global_config_path() -> std::path::PathBuf {
    global_config_path_for(None)
}

/// Load the global config, returning a default if the file doesn't exist.
pub fn load_global_config() -> Result<GlobalConfig, String> {
    let path = global_config_path();
    if !path.exists() {
        return Ok(GlobalConfig::default());
    }
    let raw = std::fs::read_to_string(&path)
        .map_err(|e| format!("Failed to read global config: {}", e))?;
    toml::from_str(&raw).map_err(|e| format!("Invalid global config: {}", e))
}

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

    #[test]
    fn test_load_missing_global_config_returns_default() {
        let dir = TempDir::new().unwrap();
        // Use path directly — no env mutation needed.
        let path = global_config_path_for(Some(dir.path()));
        assert!(!path.exists());
        // Call the logic inline (config file doesn't exist → return default).
        let cfg: GlobalConfig = if path.exists() {
            let raw = std::fs::read_to_string(&path).unwrap();
            toml::from_str(&raw).unwrap()
        } else {
            GlobalConfig::default()
        };
        assert!(cfg.default_project_root.is_none());
        assert!(cfg.default_harness.is_none());
    }

    #[test]
    fn test_load_existing_global_config() {
        let dir = TempDir::new().unwrap();
        let config_path = global_config_path_for(Some(dir.path()));
        let config_dir = config_path.parent().unwrap();
        std::fs::create_dir_all(config_dir).unwrap();
        std::fs::write(
            &config_path,
            "default_harness = \"claude\"\ndefault_project_root = \"/tmp/proj\"\n",
        )
        .unwrap();
        let raw = std::fs::read_to_string(&config_path).unwrap();
        let cfg: GlobalConfig = toml::from_str(&raw).unwrap();
        assert_eq!(cfg.default_harness.as_deref(), Some("claude"));
        assert_eq!(cfg.default_project_root.as_deref(), Some("/tmp/proj"));
    }
}