wlk 0.1.0

File-centric, event-sourced version control system with implicit branching
Documentation
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::Path;

/// WLK configuration
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Config {
    #[serde(default)]
    pub wlk: WlkConfig,

    #[serde(default)]
    pub user: UserConfig,

    #[serde(default)]
    pub alias: HashMap<String, String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WlkConfig {
    #[serde(default = "default_version")]
    pub version: String,

    #[serde(default)]
    pub auto_snapshot: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct UserConfig {
    pub name: Option<String>,
    pub email: Option<String>,
}

fn default_version() -> String {
    "0.1.0".to_string()
}

impl Default for WlkConfig {
    fn default() -> Self {
        WlkConfig {
            version: default_version(),
            auto_snapshot: false,
        }
    }
}

impl Config {
    /// Load config from .wlk/config file
    pub fn load(wlk_root: &Path) -> Result<Self> {
        let config_path = wlk_root.join("config");

        if !config_path.exists() {
            return Ok(Config::default());
        }

        let content = fs::read_to_string(&config_path).context("Failed to read config file")?;

        let config: Config = toml::from_str(&content).context("Failed to parse config file")?;

        Ok(config)
    }

    /// Save config to .wlk/config file
    pub fn save(&self, wlk_root: &Path) -> Result<()> {
        let config_path = wlk_root.join("config");
        let content = toml::to_string_pretty(self).context("Failed to serialize config")?;

        fs::write(&config_path, content).context("Failed to write config file")?;

        Ok(())
    }

    /// Get a config value by key
    pub fn get(&self, key: &str) -> Option<String> {
        match key {
            "wlk.version" => Some(self.wlk.version.clone()),
            "wlk.auto_snapshot" => Some(self.wlk.auto_snapshot.to_string()),
            "user.name" => self.user.name.clone(),
            "user.email" => self.user.email.clone(),
            _ if key.starts_with("alias.") => {
                let alias_key = &key[6..];
                self.alias.get(alias_key).cloned()
            }
            _ => None,
        }
    }

    /// Set a config value by key
    pub fn set(&mut self, key: &str, value: &str) -> Result<()> {
        match key {
            "wlk.version" => self.wlk.version = value.to_string(),
            "wlk.auto_snapshot" => {
                self.wlk.auto_snapshot = value.parse().context("Invalid boolean value")?;
            }
            "user.name" => self.user.name = Some(value.to_string()),
            "user.email" => self.user.email = Some(value.to_string()),
            _ if key.starts_with("alias.") => {
                let alias_key = &key[6..];
                self.alias.insert(alias_key.to_string(), value.to_string());
            }
            _ => return Err(anyhow::anyhow!("Unknown config key: {}", key)),
        }
        Ok(())
    }

    /// Resolve an alias
    pub fn resolve_alias(&self, command: &str) -> Option<String> {
        self.alias.get(command).cloned()
    }
}

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

    #[test]
    fn test_default_config() {
        let config = Config::default();
        assert_eq!(config.wlk.version, "0.1.0");
        assert!(!config.wlk.auto_snapshot);
    }

    #[test]
    fn test_save_load_config() {
        let temp_dir = TempDir::new().unwrap();
        let mut config = Config::default();
        config.user.name = Some("Test User".to_string());
        config.user.email = Some("test@example.com".to_string());
        config.alias.insert("st".to_string(), "status".to_string());

        config.save(temp_dir.path()).unwrap();

        let loaded = Config::load(temp_dir.path()).unwrap();
        assert_eq!(loaded.user.name, Some("Test User".to_string()));
        assert_eq!(loaded.user.email, Some("test@example.com".to_string()));
        assert_eq!(loaded.alias.get("st"), Some(&"status".to_string()));
    }

    #[test]
    fn test_get_set() {
        let mut config = Config::default();

        config.set("user.name", "Alice").unwrap();
        assert_eq!(config.get("user.name"), Some("Alice".to_string()));

        config.set("alias.co", "show").unwrap();
        assert_eq!(config.get("alias.co"), Some("show".to_string()));
    }

    #[test]
    fn test_resolve_alias() {
        let mut config = Config::default();
        config.alias.insert("st".to_string(), "status".to_string());

        assert_eq!(config.resolve_alias("st"), Some("status".to_string()));
        assert_eq!(config.resolve_alias("status"), None);
    }
}