1use std::env;
2use std::path::PathBuf;
3
4const CONFIG_DIR: &str = ".config";
5const DATA_DIR: &str = ".local/share";
6const CACHE_DIR: &str = ".cache";
7
8pub fn config_dir() -> Option<PathBuf> {
20 if cfg!(target_os = "linux") {
21 env::var("XDG_CONFIG_HOME")
23 .ok()
24 .map(PathBuf::from)
25 .or_else(std::env::home_dir)
26 .map(|mut base| {
27 base.push(CONFIG_DIR);
28 base
29 })
30 } else if cfg!(target_os = "macos") {
31 std::env::home_dir().map(|mut home| {
34 if cfg!(feature = "favor-xdg-style") {
35 home.push(CONFIG_DIR);
36 return home;
37 }
38 home.push("Library");
39 home.push("Application Support");
40 home
41 })
42 } else if cfg!(target_os = "windows") {
43 env::var("APPDATA").ok().map(PathBuf::from)
45 } else {
46 None
48 }
49}
50
51pub fn data_dir() -> Option<PathBuf> {
63 if cfg!(target_os = "linux") {
64 env::var("XDG_DATA_HOME")
66 .ok()
67 .map(PathBuf::from)
68 .or_else(|| {
69 std::env::home_dir().map(|mut home| {
70 home.push(DATA_DIR);
71 home
72 })
73 })
74 } else if cfg!(target_os = "macos") {
75 std::env::home_dir().map(|mut home| {
78 if cfg!(feature = "favor-xdg-style") {
79 home.push(DATA_DIR);
80 return home;
81 }
82 home.push("Library");
83 home.push("Application Support");
84 home
85 })
86 } else if cfg!(target_os = "windows") {
87 env::var("LOCALAPPDATA").ok().map(PathBuf::from)
89 } else {
90 None
92 }
93}
94
95pub fn cache_dir() -> Option<PathBuf> {
107 if cfg!(target_os = "linux") {
108 env::var("XDG_CACHE_HOME")
110 .ok()
111 .map(PathBuf::from)
112 .or_else(|| {
113 std::env::home_dir().map(|mut home| {
114 home.push(CACHE_DIR);
115 home
116 })
117 })
118 } else if cfg!(target_os = "macos") {
119 std::env::home_dir().map(|mut home| {
122 if cfg!(feature = "favor-xdg-style") {
123 home.push(CACHE_DIR);
124 return home;
125 }
126 home.push("Library");
127 home.push("Caches");
128 home
129 })
130 } else if cfg!(target_os = "windows") {
131 env::var("LOCALAPPDATA").ok().map(PathBuf::from)
133 } else {
134 None
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 unsafe fn set_var(key: &str, value: &str) {
144 unsafe { env::set_var(key, value) };
145 }
146
147 unsafe fn remove_var(key: &str) {
148 unsafe { env::remove_var(key) };
149 }
150
151 fn restore_var(key: &str, original: Option<String>) {
152 unsafe {
154 match original {
155 Some(val) => set_var(key, &val),
156 None => remove_var(key),
157 }
158 }
159 }
160
161 #[test]
162 fn config_dir_returns_some() {
163 let result = config_dir();
164 assert!(
165 result.is_some(),
166 "config_dir should return Some on supported platforms"
167 );
168 }
169
170 #[test]
171 #[cfg(target_os = "linux")]
172 fn linux_uses_xdg_config_home_when_set() {
173 let original = env::var("XDG_CONFIG_HOME").ok();
174 unsafe { set_var("XDG_CONFIG_HOME", "/custom/config") };
176
177 let result = config_dir();
178 assert_eq!(result, Some(PathBuf::from("/custom/config/.config")));
179
180 restore_var("XDG_CONFIG_HOME", original);
181 }
182
183 #[test]
184 #[cfg(target_os = "linux")]
185 fn linux_falls_back_to_home_when_xdg_unset() {
186 let original_xdg = env::var("XDG_CONFIG_HOME").ok();
187 let original_home = env::var("HOME").ok();
188
189 unsafe {
191 remove_var("XDG_CONFIG_HOME");
192 set_var("HOME", "/home/testuser");
193 }
194
195 let result = config_dir();
196 assert_eq!(result, Some(PathBuf::from("/home/testuser/.config")));
197
198 restore_var("XDG_CONFIG_HOME", original_xdg);
199 restore_var("HOME", original_home);
200 }
201
202 #[test]
203 #[cfg(all(target_os = "macos", not(feature = "favor-xdg-style")))]
204 fn macos_config_dir_uses_library_application_support() {
205 let original = env::var("HOME").ok();
206 unsafe { set_var("HOME", "/Users/testuser") };
208
209 let result = config_dir();
210 assert_eq!(
211 result,
212 Some(PathBuf::from("/Users/testuser/Library/Application Support"))
213 );
214
215 restore_var("HOME", original);
216 }
217
218 #[test]
219 #[cfg(all(target_os = "macos", feature = "favor-xdg-style"))]
220 fn macos_config_dir_uses_xdg_style() {
221 let original = env::var("HOME").ok();
222 unsafe { set_var("HOME", "/Users/testuser") };
224
225 let result = config_dir();
226 assert_eq!(result, Some(PathBuf::from("/Users/testuser/.config")));
227
228 restore_var("HOME", original);
229 }
230
231 #[test]
232 #[cfg(target_os = "windows")]
233 fn windows_uses_appdata() {
234 let original = env::var("APPDATA").ok();
235 unsafe { set_var("APPDATA", "C:\\Users\\testuser\\AppData\\Roaming") };
237
238 let result = config_dir();
239 assert_eq!(
240 result,
241 Some(PathBuf::from("C:\\Users\\testuser\\AppData\\Roaming"))
242 );
243
244 restore_var("APPDATA", original);
245 }
246
247 #[test]
248 fn config_dir_path_is_absolute() {
249 let result = config_dir();
250 if let Some(path) = result {
251 assert!(
252 path.is_absolute(),
253 "config_dir should return an absolute path"
254 );
255 }
256 }
257
258 #[test]
259 fn data_dir_returns_some() {
260 let result = data_dir();
261 assert!(
262 result.is_some(),
263 "data_dir should return Some on supported platforms"
264 );
265 }
266
267 #[test]
268 #[cfg(target_os = "linux")]
269 fn linux_data_dir_uses_xdg_data_home_when_set() {
270 let original = env::var("XDG_DATA_HOME").ok();
271 unsafe { set_var("XDG_DATA_HOME", "/custom/data") };
273
274 let result = data_dir();
275 assert_eq!(result, Some(PathBuf::from("/custom/data")));
276
277 restore_var("XDG_DATA_HOME", original);
278 }
279
280 #[test]
281 #[cfg(target_os = "linux")]
282 fn linux_data_dir_falls_back_to_home_when_xdg_unset() {
283 let original_xdg = env::var("XDG_DATA_HOME").ok();
284 let original_home = env::var("HOME").ok();
285
286 unsafe {
288 remove_var("XDG_DATA_HOME");
289 set_var("HOME", "/home/testuser");
290 }
291
292 let result = data_dir();
293 assert_eq!(result, Some(PathBuf::from("/home/testuser/.local/share")));
294
295 restore_var("XDG_DATA_HOME", original_xdg);
296 restore_var("HOME", original_home);
297 }
298
299 #[test]
300 #[cfg(all(target_os = "macos", not(feature = "favor-xdg-style")))]
301 fn macos_data_dir_uses_library_application_support() {
302 let original = env::var("HOME").ok();
303 unsafe { set_var("HOME", "/Users/testuser") };
305
306 let result = data_dir();
307 assert_eq!(
308 result,
309 Some(PathBuf::from("/Users/testuser/Library/Application Support"))
310 );
311
312 restore_var("HOME", original);
313 }
314
315 #[test]
316 #[cfg(all(target_os = "macos", feature = "favor-xdg-style"))]
317 fn macos_data_dir_uses_xdg_style() {
318 let original = env::var("HOME").ok();
319 unsafe { set_var("HOME", "/Users/testuser") };
321
322 let result = data_dir();
323 assert_eq!(result, Some(PathBuf::from("/Users/testuser/.local/share")));
324
325 restore_var("HOME", original);
326 }
327
328 #[test]
329 #[cfg(target_os = "windows")]
330 fn windows_data_dir_uses_localappdata() {
331 let original = env::var("LOCALAPPDATA").ok();
332 unsafe { set_var("LOCALAPPDATA", "C:\\Users\\runneradmin\\AppData\\Local") };
334
335 let result = data_dir();
336 assert_eq!(
337 result,
338 Some(PathBuf::from("C:\\Users\\runneradmin\\AppData\\Local"))
339 );
340
341 restore_var("LOCALAPPDATA", original);
342 }
343
344 #[test]
345 fn data_dir_path_is_absolute() {
346 let result = data_dir();
347 if let Some(path) = result {
348 assert!(
349 path.is_absolute(),
350 "data_dir should return an absolute path"
351 );
352 }
353 }
354
355 #[test]
356 fn cache_dir_returns_some() {
357 let result = cache_dir();
358 assert!(
359 result.is_some(),
360 "cache_dir should return Some on supported platforms"
361 );
362 }
363
364 #[test]
365 #[cfg(target_os = "linux")]
366 fn linux_cache_dir_uses_xdg_cache_home_when_set() {
367 let original = env::var("XDG_CACHE_HOME").ok();
368 unsafe { set_var("XDG_CACHE_HOME", "/custom/cache") };
370
371 let result = cache_dir();
372 assert_eq!(result, Some(PathBuf::from("/custom/cache")));
373
374 restore_var("XDG_CACHE_HOME", original);
375 }
376
377 #[test]
378 #[cfg(target_os = "linux")]
379 fn linux_cache_dir_falls_back_to_home_when_xdg_unset() {
380 let original_xdg = env::var("XDG_CACHE_HOME").ok();
381 let original_home = env::var("HOME").ok();
382
383 unsafe {
385 remove_var("XDG_CACHE_HOME");
386 set_var("HOME", "/home/testuser");
387 }
388
389 let result = cache_dir();
390 assert_eq!(result, Some(PathBuf::from("/home/testuser/.cache")));
391
392 restore_var("XDG_CACHE_HOME", original_xdg);
393 restore_var("HOME", original_home);
394 }
395
396 #[test]
397 #[cfg(all(target_os = "macos", not(feature = "favor-xdg-style")))]
398 fn macos_cache_dir_uses_library_caches() {
399 let original = env::var("HOME").ok();
400 unsafe { set_var("HOME", "/Users/testuser") };
402
403 let result = cache_dir();
404 assert_eq!(
405 result,
406 Some(PathBuf::from("/Users/testuser/Library/Caches"))
407 );
408
409 restore_var("HOME", original);
410 }
411
412 #[test]
413 #[cfg(all(target_os = "macos", feature = "favor-xdg-style"))]
414 fn macos_cache_dir_uses_xdg_style() {
415 let original = env::var("HOME").ok();
416 unsafe { set_var("HOME", "/Users/testuser") };
418
419 let result = cache_dir();
420 assert_eq!(result, Some(PathBuf::from("/Users/testuser/.cache")));
421
422 restore_var("HOME", original);
423 }
424
425 #[test]
426 #[cfg(target_os = "windows")]
427 fn windows_cache_dir_uses_localappdata() {
428 let original = env::var("LOCALAPPDATA").ok();
429 unsafe { set_var("LOCALAPPDATA", "C:\\Users\\testuser\\AppData\\Local") };
431
432 let result = cache_dir();
433 assert_eq!(
434 result,
435 Some(PathBuf::from("C:\\Users\\testuser\\AppData\\Local"))
436 );
437
438 restore_var("LOCALAPPDATA", original);
439 }
440
441 #[test]
442 fn cache_dir_path_is_absolute() {
443 let result = cache_dir();
444 if let Some(path) = result {
445 assert!(
446 path.is_absolute(),
447 "cache_dir should return an absolute path"
448 );
449 }
450 }
451}