gitwatch_rs/
config_file.rs

1use std::path::{Path, PathBuf};
2
3use anyhow::Result;
4use figment::{
5    providers::{Env, Format, Yaml},
6    Figment,
7};
8
9use log::debug;
10use regex::Regex;
11use serde::Deserialize;
12
13#[derive(Debug, Deserialize, Default)]
14pub struct ConfigFile {
15    pub commit_message: Option<String>,
16    pub commit_message_script: Option<PathBuf>,
17    pub commit_on_start: Option<bool>,
18    pub debounce_seconds: Option<u64>,
19    pub dry_run: Option<bool>,
20    #[serde(default, with = "serde_regex")]
21    pub ignore_regex: Option<Regex>,
22    pub remote: Option<String>,
23    pub retries: Option<i32>,
24    pub watch: Option<bool>,
25}
26
27impl ConfigFile {
28    pub fn load(repo_path: &Path) -> Result<Self> {
29        let config_path = repo_path.join("gitwatch.yaml");
30        if config_path.exists() {
31            debug!("Using config file '{}'", config_path.display());
32            Ok(Figment::new()
33                .merge(Yaml::file(config_path))
34                .merge(Env::prefixed("GITWATCH_"))
35                .extract()?)
36        } else {
37            Ok(ConfigFile::default())
38        }
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45    use std::{fs, path::PathBuf};
46    use tempfile::TempDir;
47    use testresult::TestResult;
48
49    impl PartialEq for ConfigFile {
50        fn eq(&self, other: &Self) -> bool {
51            self.ignore_regex.as_ref().map(|r| r.as_str())
52                == other.ignore_regex.as_ref().map(|r| r.as_str())
53                && self.commit_message == other.commit_message
54                && self.commit_message_script == other.commit_message_script
55                && self.debounce_seconds == other.debounce_seconds
56                && self.dry_run == other.dry_run
57                && self.retries == other.retries
58                && self.commit_on_start == other.commit_on_start
59                && self.watch == other.watch
60        }
61    }
62
63    fn create_config_file(dir: &TempDir, content: &str) -> Result<()> {
64        fs::write(dir.path().join("gitwatch.yaml"), content)?;
65        Ok(())
66    }
67
68    #[test]
69    fn test_load_config_file() -> TestResult {
70        let temp_dir = TempDir::new()?;
71
72        let config_content = r#"
73        commit_message: "test commit"
74        commit_message_script: "script.sh"
75        commit_on_start: true
76        debounce_seconds: 5
77        dry_run: true
78        ignore_regex: "test.*"
79        remote: "origin"
80        retries: 3
81        watch: true
82        "#;
83
84        create_config_file(&temp_dir, config_content)?;
85
86        let config = ConfigFile::load(temp_dir.path())?;
87
88        assert_eq!(config.commit_message, Some("test commit".to_string()));
89        assert_eq!(
90            config.commit_message_script,
91            Some(PathBuf::from("script.sh"))
92        );
93        assert_eq!(config.commit_on_start, Some(true));
94        assert_eq!(config.debounce_seconds, Some(5));
95        assert_eq!(config.dry_run, Some(true));
96        assert_eq!(config.ignore_regex.as_ref().unwrap().as_str(), "test.*");
97        assert_eq!(config.remote, Some("origin".to_string()));
98        assert_eq!(config.retries, Some(3));
99        assert_eq!(config.watch, Some(true));
100
101        Ok(())
102    }
103
104    #[test]
105    fn test_load_empty_config() -> TestResult {
106        let temp_dir = TempDir::new()?;
107        let config = ConfigFile::load(temp_dir.path())?;
108        assert_eq!(config, ConfigFile::default());
109        Ok(())
110    }
111
112    #[test]
113    fn test_load_invalid_config() -> TestResult {
114        let temp_dir = TempDir::new()?;
115
116        let invalid_content = r#"
117        commit_message: 42  # should be string
118        "#;
119
120        create_config_file(&temp_dir, invalid_content)?;
121
122        let result = ConfigFile::load(temp_dir.path());
123        assert!(result.is_err());
124        let err = format!("{:#}", result.unwrap_err());
125        assert!(
126            err.contains("invalid type"),
127            "Unexpected error message: {err}"
128        );
129
130        Ok(())
131    }
132}