git_adr/core/
config.rs

1//! Configuration management for git-adr.
2//!
3//! This module handles loading and saving configuration from git config.
4
5use crate::core::Git;
6use crate::Error;
7
8/// Configuration for git-adr.
9#[derive(Debug, Clone)]
10pub struct AdrConfig {
11    /// Prefix for ADR IDs (default: "ADR-").
12    pub prefix: String,
13    /// Number of digits in ADR ID (default: 4).
14    pub digits: u8,
15    /// Default template name.
16    pub template: String,
17    /// Default format (nygard, madr, etc.).
18    pub format: String,
19    /// Whether the repository is initialized for ADRs.
20    pub initialized: bool,
21}
22
23impl Default for AdrConfig {
24    fn default() -> Self {
25        Self {
26            prefix: "ADR-".to_string(),
27            digits: 4,
28            template: "default".to_string(),
29            format: "nygard".to_string(),
30            initialized: false,
31        }
32    }
33}
34
35/// Manager for ADR configuration.
36#[derive(Debug)]
37pub struct ConfigManager {
38    git: Git,
39}
40
41impl ConfigManager {
42    /// Create a new `ConfigManager`.
43    #[must_use]
44    pub const fn new(git: Git) -> Self {
45        Self { git }
46    }
47
48    /// Load configuration from git config.
49    ///
50    /// # Errors
51    ///
52    /// Returns an error if configuration cannot be loaded.
53    pub fn load(&self) -> Result<AdrConfig, Error> {
54        let mut config = AdrConfig::default();
55
56        // Check if initialized
57        if let Some(val) = self.git.config_get("adr.initialized")? {
58            config.initialized = val == "true";
59        }
60
61        // Load prefix
62        if let Some(val) = self.git.config_get("adr.prefix")? {
63            config.prefix = val;
64        }
65
66        // Load digits
67        if let Some(val) = self.git.config_get("adr.digits")? {
68            if let Ok(digits) = val.parse::<u8>() {
69                config.digits = digits;
70            }
71        }
72
73        // Load template
74        if let Some(val) = self.git.config_get("adr.template")? {
75            config.template = val;
76        }
77
78        // Load format
79        if let Some(val) = self.git.config_get("adr.format")? {
80            config.format = val;
81        }
82
83        Ok(config)
84    }
85
86    /// Save configuration to git config.
87    ///
88    /// # Errors
89    ///
90    /// Returns an error if configuration cannot be saved.
91    pub fn save(&self, config: &AdrConfig) -> Result<(), Error> {
92        self.git
93            .config_set("adr.initialized", &config.initialized.to_string())?;
94        self.git.config_set("adr.prefix", &config.prefix)?;
95        self.git
96            .config_set("adr.digits", &config.digits.to_string())?;
97        self.git.config_set("adr.template", &config.template)?;
98        self.git.config_set("adr.format", &config.format)?;
99
100        Ok(())
101    }
102
103    /// Initialize ADR in the repository.
104    ///
105    /// # Errors
106    ///
107    /// Returns an error if initialization fails.
108    pub fn initialize(&self, config: &AdrConfig) -> Result<(), Error> {
109        // Verify we're in a git repository
110        self.git.check_repository()?;
111
112        // Save configuration
113        let mut init_config = config.clone();
114        init_config.initialized = true;
115        self.save(&init_config)?;
116
117        Ok(())
118    }
119
120    /// Check if ADR is initialized in this repository.
121    ///
122    /// # Errors
123    ///
124    /// Returns an error if the check fails.
125    pub fn is_initialized(&self) -> Result<bool, Error> {
126        let config = self.load()?;
127        Ok(config.initialized)
128    }
129
130    /// Get a specific config value.
131    ///
132    /// # Errors
133    ///
134    /// Returns an error if the value cannot be retrieved.
135    pub fn get(&self, key: &str) -> Result<Option<String>, Error> {
136        self.git.config_get(&format!("adr.{key}"))
137    }
138
139    /// Set a specific config value.
140    ///
141    /// # Errors
142    ///
143    /// Returns an error if the value cannot be set.
144    pub fn set(&self, key: &str, value: &str) -> Result<(), Error> {
145        self.git.config_set(&format!("adr.{key}"), value)
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152    use std::process::Command as StdCommand;
153    use tempfile::TempDir;
154
155    fn setup_git_repo() -> TempDir {
156        let temp_dir = TempDir::new().expect("Failed to create temp directory");
157        let path = temp_dir.path();
158
159        StdCommand::new("git")
160            .args(["init"])
161            .current_dir(path)
162            .output()
163            .expect("Failed to init git repo");
164
165        StdCommand::new("git")
166            .args(["config", "user.email", "test@example.com"])
167            .current_dir(path)
168            .output()
169            .expect("Failed to set git user email");
170
171        StdCommand::new("git")
172            .args(["config", "user.name", "Test User"])
173            .current_dir(path)
174            .output()
175            .expect("Failed to set git user name");
176
177        std::fs::write(path.join("README.md"), "# Test Repo\n").expect("Failed to write README");
178        StdCommand::new("git")
179            .args(["add", "."])
180            .current_dir(path)
181            .output()
182            .expect("Failed to stage files");
183        StdCommand::new("git")
184            .args(["commit", "-m", "Initial commit"])
185            .current_dir(path)
186            .output()
187            .expect("Failed to create initial commit");
188
189        temp_dir
190    }
191
192    #[test]
193    fn test_default_config() {
194        let config = AdrConfig::default();
195        assert_eq!(config.prefix, "ADR-");
196        assert_eq!(config.digits, 4);
197        assert_eq!(config.template, "default");
198        assert_eq!(config.format, "nygard");
199        assert!(!config.initialized);
200    }
201
202    #[test]
203    fn test_custom_config() {
204        let config = AdrConfig {
205            prefix: "DECISION-".to_string(),
206            digits: 3,
207            template: "madr".to_string(),
208            format: "madr".to_string(),
209            initialized: true,
210        };
211        assert_eq!(config.prefix, "DECISION-");
212        assert_eq!(config.digits, 3);
213        assert_eq!(config.template, "madr");
214        assert!(config.initialized);
215    }
216
217    #[test]
218    fn test_config_clone() {
219        let config = AdrConfig::default();
220        let cloned = config.clone();
221        assert_eq!(config.prefix, cloned.prefix);
222        assert_eq!(config.digits, cloned.digits);
223    }
224
225    #[test]
226    fn test_config_manager_new() {
227        let git = Git::new();
228        let _manager = ConfigManager::new(git);
229        // Just verify it creates without panic
230    }
231
232    #[test]
233    fn test_config_initialize() {
234        let temp_dir = setup_git_repo();
235        let git = Git::with_work_dir(temp_dir.path());
236        let manager = ConfigManager::new(git);
237
238        let config = AdrConfig {
239            prefix: "TEST-".to_string(),
240            digits: 5,
241            template: "madr".to_string(),
242            format: "madr".to_string(),
243            initialized: false,
244        };
245
246        let result = manager.initialize(&config);
247        assert!(result.is_ok());
248
249        // Verify the config was saved with initialized=true
250        let loaded = manager.load().expect("Should load config");
251        assert!(loaded.initialized);
252        assert_eq!(loaded.prefix, "TEST-");
253        assert_eq!(loaded.digits, 5);
254    }
255
256    #[test]
257    fn test_config_load_save() {
258        let temp_dir = setup_git_repo();
259        let git = Git::with_work_dir(temp_dir.path());
260        let manager = ConfigManager::new(git);
261
262        let config = AdrConfig {
263            prefix: "CUSTOM-".to_string(),
264            digits: 6,
265            template: "nygard".to_string(),
266            format: "nygard".to_string(),
267            initialized: true,
268        };
269
270        manager.save(&config).expect("Should save config");
271        let loaded = manager.load().expect("Should load config");
272
273        assert_eq!(loaded.prefix, "CUSTOM-");
274        assert_eq!(loaded.digits, 6);
275        assert!(loaded.initialized);
276    }
277
278    #[test]
279    fn test_config_is_initialized() {
280        let temp_dir = setup_git_repo();
281        let git = Git::with_work_dir(temp_dir.path());
282        let manager = ConfigManager::new(git);
283
284        // Not initialized initially
285        assert!(!manager.is_initialized().expect("Should check"));
286
287        // Initialize
288        let config = AdrConfig::default();
289        manager.initialize(&config).expect("Should initialize");
290
291        // Now it should be initialized
292        assert!(manager.is_initialized().expect("Should check"));
293    }
294
295    #[test]
296    fn test_config_get_set() {
297        let temp_dir = setup_git_repo();
298        let git = Git::with_work_dir(temp_dir.path());
299        let manager = ConfigManager::new(git);
300
301        // Set a custom value
302        manager.set("custom", "value").expect("Should set");
303
304        // Get the value
305        let result = manager.get("custom").expect("Should get");
306        assert_eq!(result, Some("value".to_string()));
307
308        // Get non-existent value
309        let result = manager.get("nonexistent").expect("Should get");
310        assert_eq!(result, None);
311    }
312}