context_builder/
config.rs1use serde::Deserialize;
2use std::fs;
3use std::path::Path;
4
5#[derive(Deserialize, Debug, Default, Clone)]
24pub struct Config {
25 pub output: Option<String>,
27
28 pub filter: Option<Vec<String>>,
30
31 pub ignore: Option<Vec<String>>,
33
34 pub line_numbers: Option<bool>,
36
37 pub preview: Option<bool>,
39
40 pub token_count: Option<bool>,
42
43 pub output_folder: Option<String>,
45
46 pub timestamped_output: Option<bool>,
48
49 pub yes: Option<bool>,
51
52 pub auto_diff: Option<bool>,
54
55 pub diff_context_lines: Option<usize>,
57
58 pub diff_only: Option<bool>,
66
67 pub encoding_strategy: Option<String>,
72}
73
74pub fn load_config() -> Option<Config> {
77 let config_path = Path::new("context-builder.toml");
78 if config_path.exists() {
79 let content = fs::read_to_string(config_path).ok()?;
80 toml::from_str(&content).ok()
81 } else {
82 None
83 }
84}
85
86pub fn load_config_from_path(project_root: &Path) -> Option<Config> {
89 let config_path = project_root.join("context-builder.toml");
90 if config_path.exists() {
91 let content = fs::read_to_string(config_path).ok()?;
92 toml::from_str(&content).ok()
93 } else {
94 None
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use std::fs;
102 use tempfile::tempdir;
103
104 #[test]
105 fn load_config_nonexistent_file() {
106 let temp_dir = tempdir().unwrap();
108 let original_dir = std::env::current_dir().unwrap();
109
110 std::env::set_current_dir(&temp_dir).unwrap();
112
113 let result = load_config();
114
115 std::env::set_current_dir(original_dir).unwrap();
117
118 assert!(result.is_none());
119 }
120
121 #[test]
122 fn load_config_from_path_nonexistent_file() {
123 let dir = tempdir().unwrap();
124 let result = load_config_from_path(dir.path());
125 assert!(result.is_none());
126 }
127
128 #[test]
129 fn load_config_from_path_valid_config() {
130 let dir = tempdir().unwrap();
131 let config_path = dir.path().join("context-builder.toml");
132
133 let config_content = r#"
134output = "test-output.md"
135filter = ["rs", "toml"]
136ignore = ["target", ".git"]
137line_numbers = true
138preview = false
139token_count = true
140timestamped_output = true
141yes = false
142auto_diff = true
143diff_context_lines = 5
144diff_only = false
145encoding_strategy = "detect"
146"#;
147
148 fs::write(&config_path, config_content).unwrap();
149
150 let config = load_config_from_path(dir.path()).unwrap();
151 assert_eq!(config.output.unwrap(), "test-output.md");
152 assert_eq!(config.filter.unwrap(), vec!["rs", "toml"]);
153 assert_eq!(config.ignore.unwrap(), vec!["target", ".git"]);
154 assert!(config.line_numbers.unwrap());
155 assert!(!config.preview.unwrap());
156 assert!(config.token_count.unwrap());
157 assert!(config.timestamped_output.unwrap());
158 assert!(!config.yes.unwrap());
159 assert!(config.auto_diff.unwrap());
160 assert_eq!(config.diff_context_lines.unwrap(), 5);
161 assert!(!config.diff_only.unwrap());
162 assert_eq!(config.encoding_strategy.unwrap(), "detect");
163 }
164
165 #[test]
166 fn load_config_from_path_partial_config() {
167 let dir = tempdir().unwrap();
168 let config_path = dir.path().join("context-builder.toml");
169
170 let config_content = r#"
171output = "minimal.md"
172filter = ["py"]
173"#;
174
175 fs::write(&config_path, config_content).unwrap();
176
177 let config = load_config_from_path(dir.path()).unwrap();
178 assert_eq!(config.output.unwrap(), "minimal.md");
179 assert_eq!(config.filter.unwrap(), vec!["py"]);
180 assert!(config.ignore.is_none());
181 assert!(config.line_numbers.is_none());
182 assert!(config.auto_diff.is_none());
183 }
184
185 #[test]
186 fn load_config_from_path_invalid_toml() {
187 let dir = tempdir().unwrap();
188 let config_path = dir.path().join("context-builder.toml");
189
190 let config_content = r#"
192output = "test.md"
193invalid_toml [
194"#;
195
196 fs::write(&config_path, config_content).unwrap();
197
198 let config = load_config_from_path(dir.path());
199 assert!(config.is_none());
200 }
201
202 #[test]
203 fn load_config_from_path_empty_config() {
204 let dir = tempdir().unwrap();
205 let config_path = dir.path().join("context-builder.toml");
206
207 fs::write(&config_path, "").unwrap();
208
209 let config = load_config_from_path(dir.path()).unwrap();
210 assert!(config.output.is_none());
211 assert!(config.filter.is_none());
212 assert!(config.ignore.is_none());
213 }
214
215 #[test]
216 fn config_default_implementation() {
217 let config = Config::default();
218 assert!(config.output.is_none());
219 assert!(config.filter.is_none());
220 assert!(config.ignore.is_none());
221 assert!(config.line_numbers.is_none());
222 assert!(config.preview.is_none());
223 assert!(config.token_count.is_none());
224 assert!(config.output_folder.is_none());
225 assert!(config.timestamped_output.is_none());
226 assert!(config.yes.is_none());
227 assert!(config.auto_diff.is_none());
228 assert!(config.diff_context_lines.is_none());
229 assert!(config.diff_only.is_none());
230 assert!(config.encoding_strategy.is_none());
231 }
232}