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 pub hooks: HooksConfig,
11}
12
13#[derive(Debug, Clone, Serialize, Deserialize, Default)]
14pub struct HooksConfig {
15 #[serde(rename = "pre:open", skip_serializing_if = "Option::is_none", default)]
16 pub pre_open: Option<String>,
17 #[serde(rename = "post:open", skip_serializing_if = "Option::is_none", default)]
18 pub post_open: Option<String>,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22#[serde(default)]
23pub struct EditorConfig {
24 pub command: Option<String>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29#[serde(default)]
30pub struct OpenConfig {
31 pub editor: bool,
32}
33
34impl Default for Config {
35 fn default() -> Self {
36 Self {
37 editor: EditorConfig::default(),
38 open: OpenConfig::default(),
39 hooks: HooksConfig::default(),
40 }
41 }
42}
43
44impl Default for EditorConfig {
45 fn default() -> Self {
46 Self { command: None }
47 }
48}
49
50impl Default for OpenConfig {
51 fn default() -> Self {
52 Self { editor: true }
53 }
54}
55
56impl Config {
57 pub fn path() -> Result<PathBuf> {
58 let config_dir = dirs::config_dir()
59 .context("Could not determine config directory")?;
60 Ok(config_dir.join("worktree").join("config.toml"))
61 }
62
63 pub fn load() -> Result<Self> {
64 let path = Self::path()?;
65 if !path.exists() {
66 return Ok(Self::default());
67 }
68 let content = std::fs::read_to_string(&path)
69 .with_context(|| format!("Failed to read config from {}", path.display()))?;
70 let config: Self = toml::from_str(&content)
71 .with_context(|| format!("Failed to parse config at {}", path.display()))?;
72 Ok(config)
73 }
74
75 pub fn save(&self) -> Result<()> {
76 let path = Self::path()?;
77 if let Some(parent) = path.parent() {
78 std::fs::create_dir_all(parent)
79 .with_context(|| format!("Failed to create config dir {}", parent.display()))?;
80 }
81 let content = toml::to_string_pretty(self)
82 .context("Failed to serialize config")?;
83 std::fs::write(&path, content)
84 .with_context(|| format!("Failed to write config to {}", path.display()))?;
85 Ok(())
86 }
87
88 pub fn get_value(&self, key: &str) -> Result<String> {
90 match key {
91 "editor.command" => Ok(self.editor.command.clone().unwrap_or_default()),
92 "open.editor" => Ok(self.open.editor.to_string()),
93 _ => anyhow::bail!("Unknown config key: {key}"),
94 }
95 }
96
97 pub fn set_value(&mut self, key: &str, value: &str) -> Result<()> {
99 match key {
100 "editor.command" => {
101 self.editor.command = if value.is_empty() { None } else { Some(value.to_string()) };
102 }
103 "open.editor" => {
104 self.open.editor = value.parse::<bool>()
105 .with_context(|| format!("Invalid boolean value: {value}"))?;
106 }
107 _ => anyhow::bail!("Unknown config key: {key}"),
108 }
109 Ok(())
110 }
111}