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_os("XDG_CONFIG_HOME")
23 .filter(|s| !s.is_empty())
24 .map(PathBuf::from)
25 .or_else(|| {
26 std::env::home_dir().map(|mut base| {
27 base.push(CONFIG_DIR);
28 base
29 })
30 })
31 } else if cfg!(target_os = "macos") {
32 std::env::home_dir().map(|mut home| {
35 if cfg!(feature = "favor-xdg-style") {
36 home.push(CONFIG_DIR);
37 return home;
38 }
39 home.push("Library");
40 home.push("Application Support");
41 home
42 })
43 } else if cfg!(target_os = "windows") {
44 env::var_os("APPDATA")
46 .filter(|s| !s.is_empty())
47 .map(PathBuf::from)
48 } else {
49 None
51 }
52}
53
54pub fn data_dir() -> Option<PathBuf> {
66 if cfg!(target_os = "linux") {
67 env::var_os("XDG_DATA_HOME")
69 .filter(|s| !s.is_empty())
70 .map(PathBuf::from)
71 .or_else(|| {
72 std::env::home_dir().map(|mut home| {
73 home.push(DATA_DIR);
74 home
75 })
76 })
77 } else if cfg!(target_os = "macos") {
78 std::env::home_dir().map(|mut home| {
81 if cfg!(feature = "favor-xdg-style") {
82 home.push(DATA_DIR);
83 return home;
84 }
85 home.push("Library");
86 home.push("Application Support");
87 home
88 })
89 } else if cfg!(target_os = "windows") {
90 env::var_os("LOCALAPPDATA")
92 .filter(|s| !s.is_empty())
93 .map(PathBuf::from)
94 } else {
95 None
97 }
98}
99
100pub fn cache_dir() -> Option<PathBuf> {
112 if cfg!(target_os = "linux") {
113 env::var_os("XDG_CACHE_HOME")
115 .filter(|s| !s.is_empty())
116 .map(PathBuf::from)
117 .or_else(|| {
118 std::env::home_dir().map(|mut home| {
119 home.push(CACHE_DIR);
120 home
121 })
122 })
123 } else if cfg!(target_os = "macos") {
124 std::env::home_dir().map(|mut home| {
127 if cfg!(feature = "favor-xdg-style") {
128 home.push(CACHE_DIR);
129 return home;
130 }
131 home.push("Library");
132 home.push("Caches");
133 home
134 })
135 } else if cfg!(target_os = "windows") {
136 env::var_os("LOCALAPPDATA")
138 .filter(|s| !s.is_empty())
139 .map(PathBuf::from)
140 } else {
141 None
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 unsafe fn set_var(key: &str, value: &str) {
151 unsafe { env::set_var(key, value) };
152 }
153
154 unsafe fn remove_var(key: &str) {
155 unsafe { env::remove_var(key) };
156 }
157
158 fn restore_var(key: &str, original: Option<String>) {
159 unsafe {
161 match original {
162 Some(val) => set_var(key, &val),
163 None => remove_var(key),
164 }
165 }
166 }
167
168 #[cfg(any(target_os = "linux", target_os = "macos"))]
169 fn restore_var_os(key: &str, original: Option<std::ffi::OsString>) {
170 unsafe {
172 match original {
173 Some(val) => env::set_var(key, val),
174 None => env::remove_var(key),
175 }
176 }
177 }
178
179 #[test]
180 fn config_dir_returns_some() {
181 let result = config_dir();
182 assert!(
183 result.is_some(),
184 "config_dir should return Some on supported platforms"
185 );
186 }
187
188 #[test]
189 #[cfg(target_os = "linux")]
190 fn linux_uses_xdg_config_home_when_set() {
191 let original = env::var("XDG_CONFIG_HOME").ok();
192 unsafe { set_var("XDG_CONFIG_HOME", "/custom/.config") };
194
195 let result = config_dir();
196 assert_eq!(result, Some(PathBuf::from("/custom/.config")));
197
198 restore_var("XDG_CONFIG_HOME", original);
199 }
200
201 #[test]
202 #[cfg(target_os = "linux")]
203 fn linux_falls_back_to_home_when_xdg_unset() {
204 let original_xdg = env::var("XDG_CONFIG_HOME").ok();
205 let original_home = env::var("HOME").ok();
206
207 unsafe {
209 remove_var("XDG_CONFIG_HOME");
210 set_var("HOME", "/home/testuser");
211 }
212
213 let result = config_dir();
214 assert_eq!(result, Some(PathBuf::from("/home/testuser/.config")));
215
216 restore_var("XDG_CONFIG_HOME", original_xdg);
217 restore_var("HOME", original_home);
218 }
219
220 #[test]
221 #[cfg(all(target_os = "macos", not(feature = "favor-xdg-style")))]
222 fn macos_config_dir_uses_library_application_support() {
223 let original = env::var("HOME").ok();
224 unsafe { set_var("HOME", "/Users/testuser") };
226
227 let result = config_dir();
228 assert_eq!(
229 result,
230 Some(PathBuf::from("/Users/testuser/Library/Application Support"))
231 );
232
233 restore_var("HOME", original);
234 }
235
236 #[test]
237 #[cfg(all(target_os = "macos", feature = "favor-xdg-style"))]
238 fn macos_config_dir_uses_xdg_style() {
239 let original = env::var("HOME").ok();
240 unsafe { set_var("HOME", "/Users/testuser") };
242
243 let result = config_dir();
244 assert_eq!(result, Some(PathBuf::from("/Users/testuser/.config")));
245
246 restore_var("HOME", original);
247 }
248
249 #[test]
250 #[cfg(target_os = "windows")]
251 fn windows_uses_appdata() {
252 let original = env::var("APPDATA").ok();
253 unsafe { set_var("APPDATA", "C:\\Users\\testuser\\AppData\\Roaming") };
255
256 let result = config_dir();
257 assert_eq!(
258 result,
259 Some(PathBuf::from("C:\\Users\\testuser\\AppData\\Roaming"))
260 );
261
262 restore_var("APPDATA", original);
263 }
264
265 #[test]
266 fn config_dir_path_is_absolute() {
267 let result = config_dir();
268 if let Some(path) = result {
269 assert!(
270 path.is_absolute(),
271 "config_dir should return an absolute path"
272 );
273 }
274 }
275
276 #[test]
277 fn data_dir_returns_some() {
278 let result = data_dir();
279 assert!(
280 result.is_some(),
281 "data_dir should return Some on supported platforms"
282 );
283 }
284
285 #[test]
286 #[cfg(target_os = "linux")]
287 fn linux_data_dir_uses_xdg_data_home_when_set() {
288 let original = env::var("XDG_DATA_HOME").ok();
289 unsafe { set_var("XDG_DATA_HOME", "/custom/data") };
291
292 let result = data_dir();
293 assert_eq!(result, Some(PathBuf::from("/custom/data")));
294
295 restore_var("XDG_DATA_HOME", original);
296 }
297
298 #[test]
299 #[cfg(target_os = "linux")]
300 fn linux_data_dir_falls_back_to_home_when_xdg_unset() {
301 let original_xdg = env::var("XDG_DATA_HOME").ok();
302 let original_home = env::var("HOME").ok();
303
304 unsafe {
306 remove_var("XDG_DATA_HOME");
307 set_var("HOME", "/home/testuser");
308 }
309
310 let result = data_dir();
311 assert_eq!(result, Some(PathBuf::from("/home/testuser/.local/share")));
312
313 restore_var("XDG_DATA_HOME", original_xdg);
314 restore_var("HOME", original_home);
315 }
316
317 #[test]
318 #[cfg(all(target_os = "macos", not(feature = "favor-xdg-style")))]
319 fn macos_data_dir_uses_library_application_support() {
320 let original = env::var("HOME").ok();
321 unsafe { set_var("HOME", "/Users/testuser") };
323
324 let result = data_dir();
325 assert_eq!(
326 result,
327 Some(PathBuf::from("/Users/testuser/Library/Application Support"))
328 );
329
330 restore_var("HOME", original);
331 }
332
333 #[test]
334 #[cfg(all(target_os = "macos", feature = "favor-xdg-style"))]
335 fn macos_data_dir_uses_xdg_style() {
336 let original = env::var("HOME").ok();
337 unsafe { set_var("HOME", "/Users/testuser") };
339
340 let result = data_dir();
341 assert_eq!(result, Some(PathBuf::from("/Users/testuser/.local/share")));
342
343 restore_var("HOME", original);
344 }
345
346 #[test]
347 #[cfg(target_os = "windows")]
348 fn windows_data_dir_uses_localappdata() {
349 let original = env::var("LOCALAPPDATA").ok();
350 unsafe { set_var("LOCALAPPDATA", "C:\\Users\\runneradmin\\AppData\\Local") };
352
353 let result = data_dir();
354 assert_eq!(
355 result,
356 Some(PathBuf::from("C:\\Users\\runneradmin\\AppData\\Local"))
357 );
358
359 restore_var("LOCALAPPDATA", original);
360 }
361
362 #[test]
363 fn data_dir_path_is_absolute() {
364 let result = data_dir();
365 if let Some(path) = result {
366 assert!(
367 path.is_absolute(),
368 "data_dir should return an absolute path"
369 );
370 }
371 }
372
373 #[test]
374 fn cache_dir_returns_some() {
375 let result = cache_dir();
376 assert!(
377 result.is_some(),
378 "cache_dir should return Some on supported platforms"
379 );
380 }
381
382 #[test]
383 #[cfg(target_os = "linux")]
384 fn linux_cache_dir_uses_xdg_cache_home_when_set() {
385 let original = env::var("XDG_CACHE_HOME").ok();
386 unsafe { set_var("XDG_CACHE_HOME", "/custom/cache") };
388
389 let result = cache_dir();
390 assert_eq!(result, Some(PathBuf::from("/custom/cache")));
391
392 restore_var("XDG_CACHE_HOME", original);
393 }
394
395 #[test]
396 #[cfg(target_os = "linux")]
397 fn linux_cache_dir_falls_back_to_home_when_xdg_unset() {
398 let original_xdg = env::var("XDG_CACHE_HOME").ok();
399 let original_home = env::var("HOME").ok();
400
401 unsafe {
403 remove_var("XDG_CACHE_HOME");
404 set_var("HOME", "/home/testuser");
405 }
406
407 let result = cache_dir();
408 assert_eq!(result, Some(PathBuf::from("/home/testuser/.cache")));
409
410 restore_var("XDG_CACHE_HOME", original_xdg);
411 restore_var("HOME", original_home);
412 }
413
414 #[test]
415 #[cfg(all(target_os = "macos", not(feature = "favor-xdg-style")))]
416 fn macos_cache_dir_uses_library_caches() {
417 let original = env::var("HOME").ok();
418 unsafe { set_var("HOME", "/Users/testuser") };
420
421 let result = cache_dir();
422 assert_eq!(
423 result,
424 Some(PathBuf::from("/Users/testuser/Library/Caches"))
425 );
426
427 restore_var("HOME", original);
428 }
429
430 #[test]
431 #[cfg(all(target_os = "macos", feature = "favor-xdg-style"))]
432 fn macos_cache_dir_uses_xdg_style() {
433 let original = env::var("HOME").ok();
434 unsafe { set_var("HOME", "/Users/testuser") };
436
437 let result = cache_dir();
438 assert_eq!(result, Some(PathBuf::from("/Users/testuser/.cache")));
439
440 restore_var("HOME", original);
441 }
442
443 #[test]
444 #[cfg(target_os = "windows")]
445 fn windows_cache_dir_uses_localappdata() {
446 let original = env::var("LOCALAPPDATA").ok();
447 unsafe { set_var("LOCALAPPDATA", "C:\\Users\\testuser\\AppData\\Local") };
449
450 let result = cache_dir();
451 assert_eq!(
452 result,
453 Some(PathBuf::from("C:\\Users\\testuser\\AppData\\Local"))
454 );
455
456 restore_var("LOCALAPPDATA", original);
457 }
458
459 #[test]
460 fn cache_dir_path_is_absolute() {
461 let result = cache_dir();
462 if let Some(path) = result {
463 assert!(
464 path.is_absolute(),
465 "cache_dir should return an absolute path"
466 );
467 }
468 }
469
470 #[test]
471 #[cfg(target_os = "linux")]
472 fn linux_config_dir_handles_non_utf8_xdg() {
473 use std::ffi::OsStr;
474 use std::os::unix::ffi::OsStrExt;
475
476 let original = env::var_os("XDG_CONFIG_HOME");
477 let non_utf8 = OsStr::from_bytes(b"/tmp/\xff\xfe");
478 unsafe { env::set_var("XDG_CONFIG_HOME", non_utf8) };
480
481 let result = config_dir();
482 let expected = PathBuf::from(non_utf8);
483 assert_eq!(result, Some(expected));
484
485 restore_var_os("XDG_CONFIG_HOME", original);
486 }
487
488 #[test]
489 #[cfg(target_os = "linux")]
490 fn linux_data_dir_handles_non_utf8_xdg() {
491 use std::ffi::OsStr;
492 use std::os::unix::ffi::OsStrExt;
493
494 let original = env::var_os("XDG_DATA_HOME");
495 let non_utf8 = OsStr::from_bytes(b"/tmp/\xff\xfe/data");
496 unsafe { env::set_var("XDG_DATA_HOME", non_utf8) };
498
499 let result = data_dir();
500 assert_eq!(result, Some(PathBuf::from(non_utf8)));
501
502 restore_var_os("XDG_DATA_HOME", original);
503 }
504
505 #[test]
506 #[cfg(target_os = "linux")]
507 fn linux_cache_dir_handles_non_utf8_xdg() {
508 use std::ffi::OsStr;
509 use std::os::unix::ffi::OsStrExt;
510
511 let original = env::var_os("XDG_CACHE_HOME");
512 let non_utf8 = OsStr::from_bytes(b"/tmp/\xff\xfe/cache");
513 unsafe { env::set_var("XDG_CACHE_HOME", non_utf8) };
515
516 let result = cache_dir();
517 assert_eq!(result, Some(PathBuf::from(non_utf8)));
518
519 restore_var_os("XDG_CACHE_HOME", original);
520 }
521
522 #[test]
523 #[cfg(all(target_os = "macos", not(feature = "favor-xdg-style")))]
524 fn macos_config_dir_handles_non_utf8_home() {
525 use std::ffi::OsStr;
526 use std::os::unix::ffi::OsStrExt;
527
528 let original = env::var_os("HOME");
529 let non_utf8_home = OsStr::from_bytes(b"/Users/\xff\xfe");
530 unsafe { env::set_var("HOME", non_utf8_home) };
532
533 let result = config_dir();
534 let mut expected = PathBuf::from(non_utf8_home);
535 expected.push("Library");
536 expected.push("Application Support");
537 assert_eq!(result, Some(expected));
538
539 restore_var_os("HOME", original);
540 }
541
542 #[test]
543 #[cfg(all(target_os = "macos", not(feature = "favor-xdg-style")))]
544 fn macos_data_dir_handles_non_utf8_home() {
545 use std::ffi::OsStr;
546 use std::os::unix::ffi::OsStrExt;
547
548 let original = env::var_os("HOME");
549 let non_utf8_home = OsStr::from_bytes(b"/Users/\xff\xfe");
550 unsafe { env::set_var("HOME", non_utf8_home) };
552
553 let result = data_dir();
554 let mut expected = PathBuf::from(non_utf8_home);
555 expected.push("Library");
556 expected.push("Application Support");
557 assert_eq!(result, Some(expected));
558
559 restore_var_os("HOME", original);
560 }
561
562 #[test]
563 #[cfg(all(target_os = "macos", not(feature = "favor-xdg-style")))]
564 fn macos_cache_dir_handles_non_utf8_home() {
565 use std::ffi::OsStr;
566 use std::os::unix::ffi::OsStrExt;
567
568 let original = env::var_os("HOME");
569 let non_utf8_home = OsStr::from_bytes(b"/Users/\xff\xfe");
570 unsafe { env::set_var("HOME", non_utf8_home) };
572
573 let result = cache_dir();
574 let mut expected = PathBuf::from(non_utf8_home);
575 expected.push("Library");
576 expected.push("Caches");
577 assert_eq!(result, Some(expected));
578
579 restore_var_os("HOME", original);
580 }
581
582 #[test]
583 #[cfg(target_os = "linux")]
584 fn linux_config_dir_ignores_empty_xdg() {
585 let original_xdg = env::var("XDG_CONFIG_HOME").ok();
586 let original_home = env::var("HOME").ok();
587 unsafe {
589 set_var("XDG_CONFIG_HOME", "");
590 set_var("HOME", "/home/testuser");
591 }
592
593 let result = config_dir();
594 assert_eq!(result, Some(PathBuf::from("/home/testuser/.config")));
595
596 restore_var("XDG_CONFIG_HOME", original_xdg);
597 restore_var("HOME", original_home);
598 }
599
600 #[test]
601 #[cfg(target_os = "linux")]
602 fn linux_data_dir_ignores_empty_xdg() {
603 let original_xdg = env::var("XDG_DATA_HOME").ok();
604 let original_home = env::var("HOME").ok();
605 unsafe {
607 set_var("XDG_DATA_HOME", "");
608 set_var("HOME", "/home/testuser");
609 }
610
611 let result = data_dir();
612 assert_eq!(result, Some(PathBuf::from("/home/testuser/.local/share")));
613
614 restore_var("XDG_DATA_HOME", original_xdg);
615 restore_var("HOME", original_home);
616 }
617
618 #[test]
619 #[cfg(target_os = "linux")]
620 fn linux_cache_dir_ignores_empty_xdg() {
621 let original_xdg = env::var("XDG_CACHE_HOME").ok();
622 let original_home = env::var("HOME").ok();
623 unsafe {
625 set_var("XDG_CACHE_HOME", "");
626 set_var("HOME", "/home/testuser");
627 }
628
629 let result = cache_dir();
630 assert_eq!(result, Some(PathBuf::from("/home/testuser/.cache")));
631
632 restore_var("XDG_CACHE_HOME", original_xdg);
633 restore_var("HOME", original_home);
634 }
635}