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 pub max_tokens: Option<usize>,
75}
76
77pub fn load_config() -> Option<Config> {
80 let config_path = Path::new("context-builder.toml");
81 if config_path.exists() {
82 let content = fs::read_to_string(config_path).ok()?;
83 toml::from_str(&content).ok()
84 } else {
85 None
86 }
87}
88
89pub fn load_config_from_path(project_root: &Path) -> Option<Config> {
92 let config_path = project_root.join("context-builder.toml");
93 if config_path.exists() {
94 let content = fs::read_to_string(config_path).ok()?;
95 toml::from_str(&content).ok()
96 } else {
97 None
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use std::fs;
105 use tempfile::tempdir;
106
107 #[test]
108 fn load_config_nonexistent_file() {
109 let temp_dir = tempdir().unwrap();
111 let original_dir = std::env::current_dir().unwrap();
112
113 std::env::set_current_dir(&temp_dir).unwrap();
115
116 let result = load_config();
117
118 std::env::set_current_dir(original_dir).unwrap();
120
121 assert!(result.is_none());
122 }
123
124 #[test]
125 fn load_config_from_path_nonexistent_file() {
126 let dir = tempdir().unwrap();
127 let result = load_config_from_path(dir.path());
128 assert!(result.is_none());
129 }
130
131 #[test]
132 fn load_config_from_path_valid_config() {
133 let dir = tempdir().unwrap();
134 let config_path = dir.path().join("context-builder.toml");
135
136 let config_content = r#"
137output = "test-output.md"
138filter = ["rs", "toml"]
139ignore = ["target", ".git"]
140line_numbers = true
141preview = false
142token_count = true
143timestamped_output = true
144yes = false
145auto_diff = true
146diff_context_lines = 5
147diff_only = false
148encoding_strategy = "detect"
149"#;
150
151 fs::write(&config_path, config_content).unwrap();
152
153 let config = load_config_from_path(dir.path()).unwrap();
154 assert_eq!(config.output.unwrap(), "test-output.md");
155 assert_eq!(config.filter.unwrap(), vec!["rs", "toml"]);
156 assert_eq!(config.ignore.unwrap(), vec!["target", ".git"]);
157 assert!(config.line_numbers.unwrap());
158 assert!(!config.preview.unwrap());
159 assert!(config.token_count.unwrap());
160 assert!(config.timestamped_output.unwrap());
161 assert!(!config.yes.unwrap());
162 assert!(config.auto_diff.unwrap());
163 assert_eq!(config.diff_context_lines.unwrap(), 5);
164 assert!(!config.diff_only.unwrap());
165 assert_eq!(config.encoding_strategy.unwrap(), "detect");
166 }
167
168 #[test]
169 fn load_config_from_path_partial_config() {
170 let dir = tempdir().unwrap();
171 let config_path = dir.path().join("context-builder.toml");
172
173 let config_content = r#"
174output = "minimal.md"
175filter = ["py"]
176"#;
177
178 fs::write(&config_path, config_content).unwrap();
179
180 let config = load_config_from_path(dir.path()).unwrap();
181 assert_eq!(config.output.unwrap(), "minimal.md");
182 assert_eq!(config.filter.unwrap(), vec!["py"]);
183 assert!(config.ignore.is_none());
184 assert!(config.line_numbers.is_none());
185 assert!(config.auto_diff.is_none());
186 }
187
188 #[test]
189 fn load_config_from_path_invalid_toml() {
190 let dir = tempdir().unwrap();
191 let config_path = dir.path().join("context-builder.toml");
192
193 let config_content = r#"
195output = "test.md"
196invalid_toml [
197"#;
198
199 fs::write(&config_path, config_content).unwrap();
200
201 let config = load_config_from_path(dir.path());
202 assert!(config.is_none());
203 }
204
205 #[test]
206 fn load_config_from_path_empty_config() {
207 let dir = tempdir().unwrap();
208 let config_path = dir.path().join("context-builder.toml");
209
210 fs::write(&config_path, "").unwrap();
211
212 let config = load_config_from_path(dir.path()).unwrap();
213 assert!(config.output.is_none());
214 assert!(config.filter.is_none());
215 assert!(config.ignore.is_none());
216 }
217
218 #[test]
219 fn config_default_implementation() {
220 let config = Config::default();
221 assert!(config.output.is_none());
222 assert!(config.filter.is_none());
223 assert!(config.ignore.is_none());
224 assert!(config.line_numbers.is_none());
225 assert!(config.preview.is_none());
226 assert!(config.token_count.is_none());
227 assert!(config.output_folder.is_none());
228 assert!(config.timestamped_output.is_none());
229 assert!(config.yes.is_none());
230 assert!(config.auto_diff.is_none());
231 assert!(config.diff_context_lines.is_none());
232 assert!(config.diff_only.is_none());
233 assert!(config.encoding_strategy.is_none());
234 }
235}