1use super::Config;
4use crate::error::{DevSweepError, Result};
5use std::path::{Path, PathBuf};
6
7pub fn default_config_path() -> Result<PathBuf> {
9 let config_dir = dirs::config_dir().ok_or_else(|| {
10 DevSweepError::Config("Cannot determine config directory".into())
11 })?;
12
13 Ok(config_dir.join("devsweep").join("config.toml"))
14}
15
16pub fn load_config(path: &Path) -> Result<Config> {
18 if !path.exists() {
19 return Ok(Config::default());
20 }
21
22 let content = std::fs::read_to_string(path).map_err(|e| {
23 DevSweepError::ConfigParse {
24 path: path.to_path_buf(),
25 reason: e.to_string(),
26 }
27 })?;
28
29 let config: Config = toml::from_str(&content).map_err(|e| {
30 DevSweepError::ConfigParse {
31 path: path.to_path_buf(),
32 reason: e.to_string(),
33 }
34 })?;
35
36 Ok(config)
37}
38
39pub fn load_default_config() -> Result<Config> {
41 let path = default_config_path()?;
42 load_config(&path)
43}
44
45pub fn save_config(config: &Config, path: &Path) -> Result<()> {
47 if let Some(parent) = path.parent() {
49 std::fs::create_dir_all(parent)?;
50 }
51
52 let content = toml::to_string_pretty(config)?;
53 std::fs::write(path, content)?;
54
55 Ok(())
56}
57
58pub fn save_default_config(config: &Config) -> Result<()> {
60 let path = default_config_path()?;
61 save_config(config, &path)
62}
63
64pub fn generate_sample_config() -> String {
66 r#"# DevSweep Configuration
67# Location: ~/.config/devsweep/config.toml
68
69[general]
70# Default directories to scan (leave empty to require explicit path)
71default_paths = [
72 "~/projects",
73 "~/code",
74]
75
76# Paths to always exclude
77exclude_paths = [
78 "~/projects/important-project",
79]
80
81# Log level: error, warn, info, debug, trace
82log_level = "info"
83
84# Enable verbose output
85verbose = false
86
87[scan]
88# Maximum depth to scan (null = unlimited)
89# max_depth = 10
90
91# Skip hidden directories (starting with .)
92skip_hidden = true
93
94# Respect .gitignore files
95respect_gitignore = true
96
97# Minimum artifact size to report in bytes (null = no minimum)
98# min_size = 1000000 # 1 MB
99
100# Custom ignore patterns (glob syntax)
101ignore_patterns = []
102
103# Number of parallel threads (null = auto based on CPU)
104# parallelism = 4
105
106# Check git status for each project
107check_git_status = true
108
109[clean]
110# Delete method: trash, permanent, dry-run
111delete_method = "trash"
112
113# Protection level: none, warn, block, paranoid
114protection_level = "warn"
115
116# Continue on errors
117continue_on_error = true
118
119# Auto-confirm without prompts (use with caution!)
120auto_confirm = false
121
122# Dry run by default
123dry_run = false
124
125[ui]
126# Color theme: dark, light, auto
127theme = "auto"
128
129# Show file counts for artifacts
130show_file_counts = true
131
132# Show last modified dates
133show_dates = true
134
135# Sort results by: size, name, date, kind
136sort_by = "size"
137
138# Reverse sort order
139sort_reverse = false
140
141# Use icons/emojis
142use_icons = true
143
144[plugins]
145# Enabled plugins (empty = all)
146enabled = []
147
148# Disabled plugins
149disabled = []
150"#.to_string()
151}
152
153pub fn init_config() -> Result<PathBuf> {
155 let path = default_config_path()?;
156
157 if path.exists() {
158 return Err(DevSweepError::Config(format!(
159 "Config file already exists at {}",
160 path.display()
161 )));
162 }
163
164 if let Some(parent) = path.parent() {
166 std::fs::create_dir_all(parent)?;
167 }
168
169 let sample = generate_sample_config();
171 std::fs::write(&path, sample)?;
172
173 Ok(path)
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use tempfile::TempDir;
180
181 #[test]
182 fn test_load_nonexistent_config() {
183 let temp = TempDir::new().unwrap();
184 let path = temp.path().join("nonexistent.toml");
185
186 let config = load_config(&path).unwrap();
187 assert_eq!(config.clean.delete_method, crate::trash::DeleteMethod::Trash);
189 }
190
191 #[test]
192 fn test_save_and_load_config() {
193 let temp = TempDir::new().unwrap();
194 let path = temp.path().join("config.toml");
195
196 let mut config = Config::default();
197 config.general.verbose = true;
198 config.scan.max_depth = Some(5);
199
200 save_config(&config, &path).unwrap();
201
202 let loaded = load_config(&path).unwrap();
203 assert!(loaded.general.verbose);
204 assert_eq!(loaded.scan.max_depth, Some(5));
205 }
206
207 #[test]
208 fn test_sample_config_is_valid() {
209 let sample = generate_sample_config();
210 let result: std::result::Result<Config, _> = toml::from_str(&sample);
211 assert!(result.is_ok(), "Sample config should be valid TOML");
212 }
213}