Skip to main content

worktree_io/
config.rs

1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use std::path::PathBuf;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6#[serde(default)]
7pub struct Config {
8    pub editor: EditorConfig,
9    pub open: OpenConfig,
10}
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13#[serde(default)]
14pub struct EditorConfig {
15    /// Command to launch the editor, e.g. "code ." or "nvim ."
16    pub command: Option<String>,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20#[serde(default)]
21pub struct OpenConfig {
22    pub editor: bool,
23}
24
25impl Default for Config {
26    fn default() -> Self {
27        Self {
28            editor: EditorConfig::default(),
29            open: OpenConfig::default(),
30        }
31    }
32}
33
34impl Default for EditorConfig {
35    fn default() -> Self {
36        Self { command: None }
37    }
38}
39
40impl Default for OpenConfig {
41    fn default() -> Self {
42        Self { editor: true }
43    }
44}
45
46impl Config {
47    pub fn path() -> Result<PathBuf> {
48        let config_dir = dirs::config_dir()
49            .context("Could not determine config directory")?;
50        Ok(config_dir.join("worktree").join("config.toml"))
51    }
52
53    pub fn load() -> Result<Self> {
54        let path = Self::path()?;
55        if !path.exists() {
56            return Ok(Self::default());
57        }
58        let content = std::fs::read_to_string(&path)
59            .with_context(|| format!("Failed to read config from {}", path.display()))?;
60        let config: Self = toml::from_str(&content)
61            .with_context(|| format!("Failed to parse config at {}", path.display()))?;
62        Ok(config)
63    }
64
65    pub fn save(&self) -> Result<()> {
66        let path = Self::path()?;
67        if let Some(parent) = path.parent() {
68            std::fs::create_dir_all(parent)
69                .with_context(|| format!("Failed to create config dir {}", parent.display()))?;
70        }
71        let content = toml::to_string_pretty(self)
72            .context("Failed to serialize config")?;
73        std::fs::write(&path, content)
74            .with_context(|| format!("Failed to write config to {}", path.display()))?;
75        Ok(())
76    }
77
78    /// Get a config value by dot-separated key path
79    pub fn get_value(&self, key: &str) -> Result<String> {
80        match key {
81            "editor.command" => Ok(self.editor.command.clone().unwrap_or_default()),
82            "open.editor" => Ok(self.open.editor.to_string()),
83            _ => anyhow::bail!("Unknown config key: {key}"),
84        }
85    }
86
87    /// Set a config value by dot-separated key path
88    pub fn set_value(&mut self, key: &str, value: &str) -> Result<()> {
89        match key {
90            "editor.command" => {
91                self.editor.command = if value.is_empty() { None } else { Some(value.to_string()) };
92            }
93            "open.editor" => {
94                self.open.editor = value.parse::<bool>()
95                    .with_context(|| format!("Invalid boolean value: {value}"))?;
96            }
97            _ => anyhow::bail!("Unknown config key: {key}"),
98        }
99        Ok(())
100    }
101}