gitwatch_rs/
config_file.rs1use 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}