use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::Path;
#[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 {
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)
}
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(())
}
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,
}
}
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(())
}
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);
}
}