1mod file;
10
11pub use file::*;
12
13use crate::git::ProtectionLevel;
14use crate::trash::DeleteMethod;
15use serde::{Deserialize, Serialize};
16use std::path::PathBuf;
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20#[serde(default)]
21pub struct Config {
22 pub general: GeneralConfig,
24 pub scan: ScanSettings,
26 pub clean: CleanSettings,
28 pub ui: UiSettings,
30 pub plugins: PluginSettings,
32}
33
34impl Default for Config {
35 fn default() -> Self {
36 Self {
37 general: GeneralConfig::default(),
38 scan: ScanSettings::default(),
39 clean: CleanSettings::default(),
40 ui: UiSettings::default(),
41 plugins: PluginSettings::default(),
42 }
43 }
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48#[serde(default)]
49pub struct GeneralConfig {
50 pub default_paths: Vec<PathBuf>,
52 pub exclude_paths: Vec<PathBuf>,
54 pub log_level: String,
56 pub verbose: bool,
58}
59
60impl Default for GeneralConfig {
61 fn default() -> Self {
62 let mut default_paths = Vec::new();
63
64 if let Some(home) = dirs::home_dir() {
66 default_paths.push(home.join("projects"));
67 default_paths.push(home.join("Projects"));
68 default_paths.push(home.join("code"));
69 default_paths.push(home.join("Code"));
70 default_paths.push(home.join("dev"));
71 default_paths.push(home.join("Developer"));
72 default_paths.push(home.join("src"));
73 default_paths.push(home.join("workspace"));
74 }
75
76 Self {
77 default_paths,
78 exclude_paths: vec![],
79 log_level: "info".into(),
80 verbose: false,
81 }
82 }
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87#[serde(default)]
88pub struct ScanSettings {
89 pub max_depth: Option<usize>,
91 pub skip_hidden: bool,
93 pub respect_gitignore: bool,
95 pub min_size: Option<u64>,
97 pub ignore_patterns: Vec<String>,
99 pub parallelism: Option<usize>,
101 pub check_git_status: bool,
103}
104
105impl Default for ScanSettings {
106 fn default() -> Self {
107 Self {
108 max_depth: None,
109 skip_hidden: true,
110 respect_gitignore: true,
111 min_size: None, ignore_patterns: vec![],
113 parallelism: None,
114 check_git_status: true,
115 }
116 }
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
121#[serde(default)]
122pub struct CleanSettings {
123 #[serde(with = "delete_method_serde")]
125 pub delete_method: DeleteMethod,
126 #[serde(with = "protection_level_serde")]
128 pub protection_level: ProtectionLevel,
129 pub continue_on_error: bool,
131 pub auto_confirm: bool,
133 pub dry_run: bool,
135}
136
137impl Default for CleanSettings {
138 fn default() -> Self {
139 Self {
140 delete_method: DeleteMethod::Trash,
141 protection_level: ProtectionLevel::Warn,
142 continue_on_error: true,
143 auto_confirm: false,
144 dry_run: false,
145 }
146 }
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
151#[serde(default)]
152pub struct UiSettings {
153 pub theme: String,
155 pub show_file_counts: bool,
157 pub show_dates: bool,
159 pub sort_by: String,
161 pub sort_reverse: bool,
163 pub use_icons: bool,
165}
166
167impl Default for UiSettings {
168 fn default() -> Self {
169 Self {
170 theme: "auto".into(),
171 show_file_counts: true,
172 show_dates: true,
173 sort_by: "size".into(),
174 sort_reverse: false,
175 use_icons: true,
176 }
177 }
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
182#[serde(default)]
183pub struct PluginSettings {
184 pub enabled: Vec<String>,
186 pub disabled: Vec<String>,
188}
189
190impl Default for PluginSettings {
191 fn default() -> Self {
192 Self {
193 enabled: vec![],
194 disabled: vec![],
195 }
196 }
197}
198
199mod delete_method_serde {
202 use super::*;
203 use serde::{Deserializer, Serializer};
204
205 pub fn serialize<S>(method: &DeleteMethod, serializer: S) -> Result<S::Ok, S::Error>
206 where
207 S: Serializer,
208 {
209 let s = match method {
210 DeleteMethod::Trash => "trash",
211 DeleteMethod::Permanent => "permanent",
212 DeleteMethod::DryRun => "dry-run",
213 };
214 serializer.serialize_str(s)
215 }
216
217 pub fn deserialize<'de, D>(deserializer: D) -> Result<DeleteMethod, D::Error>
218 where
219 D: Deserializer<'de>,
220 {
221 let s = String::deserialize(deserializer)?;
222 DeleteMethod::from_str(&s).ok_or_else(|| {
223 serde::de::Error::custom(format!("invalid delete method: {}", s))
224 })
225 }
226}
227
228mod protection_level_serde {
229 use super::*;
230 use serde::{Deserializer, Serializer};
231
232 pub fn serialize<S>(level: &ProtectionLevel, serializer: S) -> Result<S::Ok, S::Error>
233 where
234 S: Serializer,
235 {
236 serializer.serialize_str(level.as_str())
237 }
238
239 pub fn deserialize<'de, D>(deserializer: D) -> Result<ProtectionLevel, D::Error>
240 where
241 D: Deserializer<'de>,
242 {
243 let s = String::deserialize(deserializer)?;
244 ProtectionLevel::from_str(&s).ok_or_else(|| {
245 serde::de::Error::custom(format!("invalid protection level: {}", s))
246 })
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 #[test]
255 fn test_default_config() {
256 let config = Config::default();
257 assert!(!config.general.default_paths.is_empty());
258 assert!(config.scan.skip_hidden);
259 assert_eq!(config.clean.delete_method, DeleteMethod::Trash);
260 }
261
262 #[test]
263 fn test_config_serialization() {
264 let config = Config::default();
265 let toml_str = toml::to_string_pretty(&config).unwrap();
266 assert!(toml_str.contains("[general]"));
267 assert!(toml_str.contains("[scan]"));
268 assert!(toml_str.contains("[clean]"));
269 }
270
271 #[test]
272 fn test_config_deserialization() {
273 let toml_str = r#"
274[general]
275verbose = true
276log_level = "debug"
277
278[scan]
279max_depth = 10
280skip_hidden = false
281
282[clean]
283delete_method = "permanent"
284protection_level = "block"
285"#;
286
287 let config: Config = toml::from_str(toml_str).unwrap();
288 assert!(config.general.verbose);
289 assert_eq!(config.general.log_level, "debug");
290 assert_eq!(config.scan.max_depth, Some(10));
291 assert!(!config.scan.skip_hidden);
292 assert_eq!(config.clean.delete_method, DeleteMethod::Permanent);
293 assert_eq!(config.clean.protection_level, ProtectionLevel::Block);
294 }
295}