1use std::fs;
2use std::io;
3use std::path::PathBuf;
4
5use super::defaults;
6use super::ConfigError;
7
8pub fn create_config_file(path: Option<&str>) -> Result<PathBuf, ConfigError> {
10 let config_path = if let Some(path) = path {
11 PathBuf::from(path)
12 } else {
13 PathBuf::from(defaults::DEFAULT_CONFIG_FILENAME)
14 };
15
16 if config_path.exists() {
18 return Err(ConfigError::IoError(io::Error::new(
19 io::ErrorKind::AlreadyExists,
20 format!("Configuration file already exists at {:?}", config_path),
21 )));
22 }
23
24 if let Some(parent) = config_path.parent() {
26 if !parent.exists() {
27 fs::create_dir_all(parent)?;
28 }
29 }
30
31 fs::write(&config_path, defaults::example_config())?;
33
34 Ok(config_path)
35}
36
37pub fn global_config_dir() -> Option<PathBuf> {
39 if let Ok(home) = std::env::var("HOME") {
40 Some(PathBuf::from(home).join(defaults::GLOBAL_CONFIG_DIRNAME))
41 } else {
42 None
43 }
44}
45
46pub fn global_config_file() -> Option<PathBuf> {
48 global_config_dir().map(|dir| dir.join(defaults::GLOBAL_CONFIG_FILENAME))
49}
50
51pub fn create_global_config() -> Result<PathBuf, ConfigError> {
53 let global_dir = global_config_dir().ok_or_else(|| {
54 ConfigError::IoError(io::Error::new(
55 io::ErrorKind::NotFound,
56 "Could not determine home directory",
57 ))
58 })?;
59
60 if !global_dir.exists() {
62 fs::create_dir_all(&global_dir)?;
63 }
64
65 let global_file = global_dir.join(defaults::GLOBAL_CONFIG_FILENAME);
66
67 if !global_file.exists() {
69 fs::write(&global_file, defaults::example_config())?;
70 }
71
72 Ok(global_file)
73}
74
75pub fn find_project_config() -> Option<PathBuf> {
77 let current_dir = std::env::current_dir().ok()?;
78 let mut dir = current_dir.as_path();
79
80 loop {
81 let config_path = dir.join(defaults::DEFAULT_CONFIG_FILENAME);
82 if config_path.exists() {
83 return Some(config_path);
84 }
85
86 if let Some(parent) = dir.parent() {
87 dir = parent;
88 } else {
89 break;
90 }
91 }
92
93 None
94}
95
96pub fn template_dir() -> Option<PathBuf> {
98 global_config_dir().map(|dir| dir.join("templates"))
99}
100
101pub fn create_template_dir() -> Result<PathBuf, ConfigError> {
103 let template_dir = template_dir().ok_or_else(|| {
104 ConfigError::IoError(io::Error::new(
105 io::ErrorKind::NotFound,
106 "Could not determine template directory",
107 ))
108 })?;
109
110 if !template_dir.exists() {
112 fs::create_dir_all(&template_dir)?;
113 }
114
115 let simple_path = template_dir.join("simple.hbs");
117 if !simple_path.exists() {
118 fs::write(&simple_path, defaults::simple_template())?;
119 }
120
121 let conventional_path = template_dir.join("conventional.hbs");
122 if !conventional_path.exists() {
123 fs::write(&conventional_path, defaults::conventional_template())?;
124 }
125
126 let detailed_path = template_dir.join("detailed.hbs");
127 if !detailed_path.exists() {
128 fs::write(&detailed_path, defaults::detailed_template())?;
129 }
130
131 Ok(template_dir)
132}
133
134pub fn list_templates() -> Result<Vec<String>, ConfigError> {
136 let template_dir = template_dir().ok_or_else(|| {
137 ConfigError::IoError(io::Error::new(
138 io::ErrorKind::NotFound,
139 "Could not determine template directory",
140 ))
141 })?;
142
143 if !template_dir.exists() {
144 return Ok(Vec::new());
145 }
146
147 let entries = fs::read_dir(template_dir)?;
148 let mut templates = Vec::new();
149
150 for entry in entries {
151 let entry = entry?;
152 let path = entry.path();
153
154 if path.is_file() {
155 if let Some(extension) = path.extension() {
156 if extension == "hbs" {
157 if let Some(name) = path.file_stem() {
158 if let Some(name_str) = name.to_str() {
159 templates.push(name_str.to_string());
160 }
161 }
162 }
163 }
164 }
165 }
166
167 Ok(templates)
168}
169
170pub fn get_template_path(name: &str) -> Result<PathBuf, ConfigError> {
172 if let Some(template_dir) = template_dir() {
174 let template_path = template_dir.join(format!("{}.hbs", name));
175 if template_path.exists() {
176 return Ok(template_path);
177 }
178 }
179
180 match name {
182 "simple" | "conventional" | "detailed" => {
183 Ok(PathBuf::from(format!("__builtin__/{}.hbs", name)))
186 }
187 _ => Err(ConfigError::IoError(io::Error::new(
188 io::ErrorKind::NotFound,
189 format!("Template '{}' not found", name),
190 ))),
191 }
192}
193
194pub fn get_template(name: &str) -> Result<String, ConfigError> {
196 let template_path = get_template_path(name)?;
198
199 if let Some(path_str) = template_path.to_str() {
201 if path_str.starts_with("__builtin__/") {
202 match name {
204 "simple" => return Ok(defaults::simple_template()),
205 "conventional" => return Ok(defaults::conventional_template()),
206 "detailed" => return Ok(defaults::detailed_template()),
207 _ => {} }
209 }
210 }
211
212 match fs::read_to_string(&template_path) {
214 Ok(content) => Ok(content),
215 Err(e) => Err(ConfigError::IoError(e)),
216 }
217}
218
219pub fn save_template(name: &str, content: &str) -> Result<(), ConfigError> {
221 let template_dir = template_dir().ok_or_else(|| {
222 ConfigError::IoError(io::Error::new(
223 io::ErrorKind::NotFound,
224 "Could not determine template directory",
225 ))
226 })?;
227
228 if !template_dir.exists() {
230 fs::create_dir_all(&template_dir)?;
231 }
232
233 let template_path = template_dir.join(format!("{}.hbs", name));
234 fs::write(template_path, content)?;
235
236 Ok(())
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242 use std::env;
243 use std::fs;
244 use tempfile::TempDir;
245
246 #[test]
247 #[serial_test::serial]
248 fn test_get_template_from_file() {
249 let temp_dir = TempDir::new().unwrap();
251
252 let original_home = env::var("HOME").unwrap_or_default();
254 env::set_var("HOME", temp_dir.path());
255
256 let config_dir = temp_dir.path().join(defaults::GLOBAL_CONFIG_DIRNAME);
258 let template_dir = config_dir.join("templates");
259 fs::create_dir_all(&template_dir).unwrap();
260
261 let template_name = "custom-test-template";
263 let template_content = "Test template content";
264 let template_path = template_dir.join(format!("{}.hbs", template_name));
265 fs::write(&template_path, template_content).unwrap();
266
267 let result = get_template(template_name);
269 assert!(result.is_ok());
270 assert_eq!(result.unwrap(), template_content);
271
272 if original_home.is_empty() {
274 env::remove_var("HOME");
275 } else {
276 env::set_var("HOME", original_home);
277 }
278 }
279
280 #[test]
281 #[serial_test::serial]
282 fn test_get_builtin_template() {
283 let temp_dir = TempDir::new().unwrap();
285
286 let original_home = env::var("HOME").unwrap_or_default();
289 env::set_var("HOME", temp_dir.path());
290
291 let simple_result = get_template("simple");
293 assert!(simple_result.is_ok());
294 assert_eq!(simple_result.unwrap(), defaults::simple_template());
295
296 let conventional_result = get_template("conventional");
297 assert!(conventional_result.is_ok());
298 assert_eq!(
299 conventional_result.unwrap(),
300 defaults::conventional_template()
301 );
302
303 let detailed_result = get_template("detailed");
304 assert!(detailed_result.is_ok());
305 assert_eq!(detailed_result.unwrap(), defaults::detailed_template());
306
307 if original_home.is_empty() {
309 env::remove_var("HOME");
310 } else {
311 env::set_var("HOME", original_home);
312 }
313 }
314
315 #[test]
316 #[serial_test::serial]
317 fn test_get_nonexistent_template() {
318 let temp_dir = TempDir::new().unwrap();
320
321 let original_home = env::var("HOME").unwrap_or_default();
323 env::set_var("HOME", temp_dir.path());
324
325 let result = get_template("nonexistent-template");
327 assert!(result.is_err());
328 let error = result.unwrap_err();
329 match error {
330 ConfigError::IoError(e) => {
331 assert_eq!(e.kind(), io::ErrorKind::NotFound);
332 assert!(e
333 .to_string()
334 .contains("Template 'nonexistent-template' not found"));
335 }
336 _ => panic!("Expected IoError, got {:?}", error),
337 }
338
339 if original_home.is_empty() {
341 env::remove_var("HOME");
342 } else {
343 env::set_var("HOME", original_home);
344 }
345 }
346}