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(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_os("APPDATA")
45 .filter(|s| !s.is_empty())
46 .map(PathBuf::from)
47 } else {
48 None
50 }
51}
52
53pub fn data_dir() -> Option<PathBuf> {
65 if cfg!(target_os = "linux") {
66 env::var_os("XDG_DATA_HOME")
68 .filter(|s| !s.is_empty())
69 .map(PathBuf::from)
70 .or_else(|| {
71 std::env::home_dir().map(|mut home| {
72 home.push(DATA_DIR);
73 home
74 })
75 })
76 } else if cfg!(target_os = "macos") {
77 std::env::home_dir().map(|mut home| {
80 if cfg!(feature = "favor-xdg-style") {
81 home.push(DATA_DIR);
82 return home;
83 }
84 home.push("Library");
85 home.push("Application Support");
86 home
87 })
88 } else if cfg!(target_os = "windows") {
89 env::var_os("LOCALAPPDATA")
91 .filter(|s| !s.is_empty())
92 .map(PathBuf::from)
93 } else {
94 None
96 }
97}
98
99pub fn cache_dir() -> Option<PathBuf> {
111 if cfg!(target_os = "linux") {
112 env::var_os("XDG_CACHE_HOME")
114 .filter(|s| !s.is_empty())
115 .map(PathBuf::from)
116 .or_else(|| {
117 std::env::home_dir().map(|mut home| {
118 home.push(CACHE_DIR);
119 home
120 })
121 })
122 } else if cfg!(target_os = "macos") {
123 std::env::home_dir().map(|mut home| {
126 if cfg!(feature = "favor-xdg-style") {
127 home.push(CACHE_DIR);
128 return home;
129 }
130 home.push("Library");
131 home.push("Caches");
132 home
133 })
134 } else if cfg!(target_os = "windows") {
135 env::var_os("LOCALAPPDATA")
137 .filter(|s| !s.is_empty())
138 .map(PathBuf::from)
139 } else {
140 None
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 unsafe fn set_var(key: &str, value: &str) {
150 unsafe { env::set_var(key, value) };
151 }
152
153 unsafe fn remove_var(key: &str) {
154 unsafe { env::remove_var(key) };
155 }
156
157 fn restore_var(key: &str, original: Option<String>) {
158 unsafe {
160 match original {
161 Some(val) => set_var(key, &val),
162 None => remove_var(key),
163 }
164 }
165 }
166
167 #[cfg(any(target_os = "linux", target_os = "macos"))]
168 fn restore_var_os(key: &str, original: Option<std::ffi::OsString>) {
169 unsafe {
171 match original {
172 Some(val) => env::set_var(key, val),
173 None => env::remove_var(key),
174 }
175 }
176 }
177
178 #[test]
179 fn config_dir_returns_some() {
180 let result = config_dir();
181 assert!(
182 result.is_some(),
183 "config_dir should return Some on supported platforms"
184 );
185 }
186
187 #[test]
188 #[cfg(target_os = "linux")]
189 fn linux_uses_xdg_config_home_when_set() {
190 let original = env::var("XDG_CONFIG_HOME").ok();
191 unsafe { set_var("XDG_CONFIG_HOME", "/custom/config") };
193
194 let result = config_dir();
195 assert_eq!(result, Some(PathBuf::from("/custom/config/.config")));
196
197 restore_var("XDG_CONFIG_HOME", original);
198 }
199
200 #[test]
201 #[cfg(target_os = "linux")]
202 fn linux_falls_back_to_home_when_xdg_unset() {
203 let original_xdg = env::var("XDG_CONFIG_HOME").ok();
204 let original_home = env::var("HOME").ok();
205
206 unsafe {
208 remove_var("XDG_CONFIG_HOME");
209 set_var("HOME", "/home/testuser");
210 }
211
212 let result = config_dir();
213 assert_eq!(result, Some(PathBuf::from("/home/testuser/.config")));
214
215 restore_var("XDG_CONFIG_HOME", original_xdg);
216 restore_var("HOME", original_home);
217 }
218
219 #[test]
220 #[cfg(all(target_os = "macos", not(feature = "favor-xdg-style")))]
221 fn macos_config_dir_uses_library_application_support() {
222 let original = env::var("HOME").ok();
223 unsafe { set_var("HOME", "/Users/testuser") };
225
226 let result = config_dir();
227 assert_eq!(
228 result,
229 Some(PathBuf::from("/Users/testuser/Library/Application Support"))
230 );
231
232 restore_var("HOME", original);
233 }
234
235 #[test]
236 #[cfg(all(target_os = "macos", feature = "favor-xdg-style"))]
237 fn macos_config_dir_uses_xdg_style() {
238 let original = env::var("HOME").ok();
239 unsafe { set_var("HOME", "/Users/testuser") };
241
242 let result = config_dir();
243 assert_eq!(result, Some(PathBuf::from("/Users/testuser/.config")));
244
245 restore_var("HOME", original);
246 }
247
248 #[test]
249 #[cfg(target_os = "windows")]
250 fn windows_uses_appdata() {
251 let original = env::var("APPDATA").ok();
252 unsafe { set_var("APPDATA", "C:\\Users\\testuser\\AppData\\Roaming") };
254
255 let result = config_dir();
256 assert_eq!(
257 result,
258 Some(PathBuf::from("C:\\Users\\testuser\\AppData\\Roaming"))
259 );
260
261 restore_var("APPDATA", original);
262 }
263
264 #[test]
265 fn config_dir_path_is_absolute() {
266 let result = config_dir();
267 if let Some(path) = result {
268 assert!(
269 path.is_absolute(),
270 "config_dir should return an absolute path"
271 );
272 }
273 }
274
275 #[test]
276 fn data_dir_returns_some() {
277 let result = data_dir();
278 assert!(
279 result.is_some(),
280 "data_dir should return Some on supported platforms"
281 );
282 }
283
284 #[test]
285 #[cfg(target_os = "linux")]
286 fn linux_data_dir_uses_xdg_data_home_when_set() {
287 let original = env::var("XDG_DATA_HOME").ok();
288 unsafe { set_var("XDG_DATA_HOME", "/custom/data") };
290
291 let result = data_dir();
292 assert_eq!(result, Some(PathBuf::from("/custom/data")));
293
294 restore_var("XDG_DATA_HOME", original);
295 }
296
297 #[test]
298 #[cfg(target_os = "linux")]
299 fn linux_data_dir_falls_back_to_home_when_xdg_unset() {
300 let original_xdg = env::var("XDG_DATA_HOME").ok();
301 let original_home = env::var("HOME").ok();
302
303 unsafe {
305 remove_var("XDG_DATA_HOME");
306 set_var("HOME", "/home/testuser");
307 }
308
309 let result = data_dir();
310 assert_eq!(result, Some(PathBuf::from("/home/testuser/.local/share")));
311
312 restore_var("XDG_DATA_HOME", original_xdg);
313 restore_var("HOME", original_home);
314 }
315
316 #[test]
317 #[cfg(all(target_os = "macos", not(feature = "favor-xdg-style")))]
318 fn macos_data_dir_uses_library_application_support() {
319 let original = env::var("HOME").ok();
320 unsafe { set_var("HOME", "/Users/testuser") };
322
323 let result = data_dir();
324 assert_eq!(
325 result,
326 Some(PathBuf::from("/Users/testuser/Library/Application Support"))
327 );
328
329 restore_var("HOME", original);
330 }
331
332 #[test]
333 #[cfg(all(target_os = "macos", feature = "favor-xdg-style"))]
334 fn macos_data_dir_uses_xdg_style() {
335 let original = env::var("HOME").ok();
336 unsafe { set_var("HOME", "/Users/testuser") };
338
339 let result = data_dir();
340 assert_eq!(result, Some(PathBuf::from("/Users/testuser/.local/share")));
341
342 restore_var("HOME", original);
343 }
344
345 #[test]
346 #[cfg(target_os = "windows")]
347 fn windows_data_dir_uses_localappdata() {
348 let original = env::var("LOCALAPPDATA").ok();
349 unsafe { set_var("LOCALAPPDATA", "C:\\Users\\runneradmin\\AppData\\Local") };
351
352 let result = data_dir();
353 assert_eq!(
354 result,
355 Some(PathBuf::from("C:\\Users\\runneradmin\\AppData\\Local"))
356 );
357
358 restore_var("LOCALAPPDATA", original);
359 }
360
361 #[test]
362 fn data_dir_path_is_absolute() {
363 let result = data_dir();
364 if let Some(path) = result {
365 assert!(
366 path.is_absolute(),
367 "data_dir should return an absolute path"
368 );
369 }
370 }
371
372 #[test]
373 fn cache_dir_returns_some() {
374 let result = cache_dir();
375 assert!(
376 result.is_some(),
377 "cache_dir should return Some on supported platforms"
378 );
379 }
380
381 #[test]
382 #[cfg(target_os = "linux")]
383 fn linux_cache_dir_uses_xdg_cache_home_when_set() {
384 let original = env::var("XDG_CACHE_HOME").ok();
385 unsafe { set_var("XDG_CACHE_HOME", "/custom/cache") };
387
388 let result = cache_dir();
389 assert_eq!(result, Some(PathBuf::from("/custom/cache")));
390
391 restore_var("XDG_CACHE_HOME", original);
392 }
393
394 #[test]
395 #[cfg(target_os = "linux")]
396 fn linux_cache_dir_falls_back_to_home_when_xdg_unset() {
397 let original_xdg = env::var("XDG_CACHE_HOME").ok();
398 let original_home = env::var("HOME").ok();
399
400 unsafe {
402 remove_var("XDG_CACHE_HOME");
403 set_var("HOME", "/home/testuser");
404 }
405
406 let result = cache_dir();
407 assert_eq!(result, Some(PathBuf::from("/home/testuser/.cache")));
408
409 restore_var("XDG_CACHE_HOME", original_xdg);
410 restore_var("HOME", original_home);
411 }
412
413 #[test]
414 #[cfg(all(target_os = "macos", not(feature = "favor-xdg-style")))]
415 fn macos_cache_dir_uses_library_caches() {
416 let original = env::var("HOME").ok();
417 unsafe { set_var("HOME", "/Users/testuser") };
419
420 let result = cache_dir();
421 assert_eq!(
422 result,
423 Some(PathBuf::from("/Users/testuser/Library/Caches"))
424 );
425
426 restore_var("HOME", original);
427 }
428
429 #[test]
430 #[cfg(all(target_os = "macos", feature = "favor-xdg-style"))]
431 fn macos_cache_dir_uses_xdg_style() {
432 let original = env::var("HOME").ok();
433 unsafe { set_var("HOME", "/Users/testuser") };
435
436 let result = cache_dir();
437 assert_eq!(result, Some(PathBuf::from("/Users/testuser/.cache")));
438
439 restore_var("HOME", original);
440 }
441
442 #[test]
443 #[cfg(target_os = "windows")]
444 fn windows_cache_dir_uses_localappdata() {
445 let original = env::var("LOCALAPPDATA").ok();
446 unsafe { set_var("LOCALAPPDATA", "C:\\Users\\testuser\\AppData\\Local") };
448
449 let result = cache_dir();
450 assert_eq!(
451 result,
452 Some(PathBuf::from("C:\\Users\\testuser\\AppData\\Local"))
453 );
454
455 restore_var("LOCALAPPDATA", original);
456 }
457
458 #[test]
459 fn cache_dir_path_is_absolute() {
460 let result = cache_dir();
461 if let Some(path) = result {
462 assert!(
463 path.is_absolute(),
464 "cache_dir should return an absolute path"
465 );
466 }
467 }
468
469 #[test]
470 #[cfg(target_os = "linux")]
471 fn linux_config_dir_handles_non_utf8_xdg() {
472 use std::ffi::OsStr;
473 use std::os::unix::ffi::OsStrExt;
474
475 let original = env::var_os("XDG_CONFIG_HOME");
476 let non_utf8 = OsStr::from_bytes(b"/tmp/\xff\xfe");
477 unsafe { env::set_var("XDG_CONFIG_HOME", non_utf8) };
479
480 let result = config_dir();
481 let mut expected = PathBuf::from(non_utf8);
482 expected.push(".config");
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}