use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use serde::Deserialize;
const DEFAULT_GLOBAL_ROOT_REL: &str = "worktrees";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Config {
pub global_root: PathBuf,
}
impl Default for Config {
fn default() -> Self {
Self {
global_root: default_global_root(),
}
}
}
#[derive(Debug, Default, Deserialize)]
struct FileConfig {
#[serde(default)]
worktree: WorktreeSection,
}
#[derive(Debug, Default, Deserialize)]
struct WorktreeSection {
global_root: Option<String>,
}
pub fn load() -> Result<Config> {
let path = config_path();
load_from(&path)
}
pub fn load_from(path: &Path) -> Result<Config> {
if !path.exists() {
return Ok(Config::default());
}
let raw = fs::read_to_string(path)
.with_context(|| format!("读取配置失败:{}", path.display()))?;
parse_toml(&raw)
}
fn parse_toml(raw: &str) -> Result<Config> {
let file: FileConfig = toml::from_str(raw).context("解析 vkit 配置失败")?;
let global_root = match file.worktree.global_root {
Some(s) => expand_tilde(&s),
None => default_global_root(),
};
Ok(Config { global_root })
}
pub fn config_path() -> PathBuf {
dirs::config_dir()
.unwrap_or_else(|| PathBuf::from(".").join(".config"))
.join("vkit")
.join("config.toml")
}
fn default_global_root() -> PathBuf {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("/"))
.join(DEFAULT_GLOBAL_ROOT_REL)
}
fn expand_tilde(s: &str) -> PathBuf {
if let Some(rest) = s.strip_prefix("~/") {
return dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("/"))
.join(rest);
}
if s == "~" {
return dirs::home_dir().unwrap_or_else(|| PathBuf::from("/"));
}
PathBuf::from(s)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn missing_file_uses_default() {
let cfg = load_from(Path::new("/nonexistent/vkit-config.toml")).unwrap();
assert!(cfg.global_root.ends_with("worktrees"));
}
#[test]
fn parses_global_root_override() {
let cfg = parse_toml(
r#"
[worktree]
global_root = "/tmp/my-worktrees"
"#,
)
.unwrap();
assert_eq!(cfg.global_root, PathBuf::from("/tmp/my-worktrees"));
}
#[test]
fn empty_toml_uses_default() {
let cfg = parse_toml("").unwrap();
assert!(cfg.global_root.ends_with("worktrees"));
}
}