dir_spec/
lib.rs

1//! # `dir_spec`
2//!
3//! Cross-platform directory resolver with XDG compliance.
4//!
5//! This crate provides functions to resolve standard directories across Linux, macOS, and Windows
6//! while respecting XDG Base Directory Specification environment variables when set.
7//!
8//! ## XDG Compliance
9//!
10//! All functions check for corresponding XDG environment variables first (e.g., `XDG_CONFIG_HOME`),
11//! and only fall back to platform-specific defaults if the XDG variable is not set or contains
12//! a relative path (which the XDG spec requires to be ignored).
13//!
14//! ## Examples
15//!
16//! ```rust
17//! use dir_spec::{config_home, cache_home, videos};
18//!
19//! // Get config directory (respects XDG_CONFIG_HOME if set)
20//! if let Some(config) = config_home() {
21//!     println!("Config directory: {}", config.display());
22//! }
23//!
24//! // Get cache directory (respects XDG_CACHE_HOME if set)
25//! if let Some(cache) = cache_home() {
26//!     println!("Cache directory: {}", cache.display());
27//! }
28//!
29//! // Get videos directory
30//! if let Some(videos) = videos() {
31//!     println!("Videos directory: {}", videos.display());
32//! }
33//! ```
34
35#[cfg(target_os = "linux")]
36mod linux;
37#[cfg(target_os = "macos")]
38mod macos;
39#[cfg(target_os = "windows")]
40mod windows;
41pub(crate) mod xdg;
42
43use std::{env, path::PathBuf};
44
45#[cfg(target_os = "linux")]
46use linux as os;
47#[cfg(target_os = "macos")]
48use macos as os;
49#[cfg(target_os = "windows")]
50use windows as os;
51
52/// Returns the user's binary directory.
53///
54/// Checks `XDG_BIN_HOME` first, then falls back to platform defaults:
55/// - **Linux/macOS**: `~/.local/bin`
56/// - **Windows**: `%LOCALAPPDATA%\Programs`
57///
58/// # Examples
59///
60/// ```rust
61/// use dir_spec::bin_home;
62/// if let Some(bin_dir) = bin_home() {
63///     println!("Bin directory: {}", bin_dir.display());
64/// }
65/// ```
66pub fn bin_home() -> Option<PathBuf> {
67  os::bin_home()
68}
69
70/// Returns the user's cache directory.
71///
72/// Checks `XDG_CACHE_HOME` first, then falls back to platform defaults:
73/// - **Linux**: `~/.cache`
74/// - **macOS**: `~/Library/Caches`
75/// - **Windows**: `%LOCALAPPDATA%`
76///
77/// # Examples
78///
79/// ```rust
80/// use dir_spec::cache_home;
81/// if let Some(cache_dir) = cache_home() {
82///     println!("Cache directory: {}", cache_dir.display());
83/// }
84/// ```
85pub fn cache_home() -> Option<PathBuf> {
86  os::cache_home()
87}
88
89/// Returns the user's configuration directory.
90///
91/// Checks `XDG_CONFIG_HOME` first, then falls back to platform defaults:
92/// - **Linux**: `~/.config`
93/// - **macOS**: `~/Library/Application Support`
94/// - **Windows**: `%APPDATA%`
95///
96/// # Examples
97///
98/// ```rust
99/// use dir_spec::config_home;
100/// if let Some(config_dir) = config_home() {
101///     println!("Config directory: {}", config_dir.display());
102/// }
103/// ```
104pub fn config_home() -> Option<PathBuf> {
105  os::config_home()
106}
107
108/// Returns the user's local configuration directory (non-roaming).
109///
110/// This is primarily useful on Windows where it returns the local (non-roaming) config directory.
111/// On other platforms, it behaves identically to `config_home()`.
112///
113/// Platform defaults:
114/// - **Linux**: `~/.config` (same as `config_home()`)
115/// - **macOS**: `~/Library/Application Support` (same as `config_home()`)
116/// - **Windows**: `%LOCALAPPDATA%` (non-roaming)
117///
118/// # Examples
119///
120/// ```rust
121/// use dir_spec::config_local;
122/// if let Some(config_local) = config_local() {
123///     println!("Local config directory: {}", config_local.display());
124/// }
125/// ```
126pub fn config_local() -> Option<PathBuf> {
127  os::config_local()
128}
129
130/// Returns the user's data directory.
131///
132/// Checks `XDG_DATA_HOME` first, then falls back to platform defaults:
133/// - **Linux**: `~/.local/share`
134/// - **macOS**: `~/Library/Application Support`
135/// - **Windows**: `%APPDATA%`
136///
137/// # Examples
138///
139/// ```rust
140/// use dir_spec::data_home;
141/// if let Some(data_dir) = data_home() {
142///     println!("Data directory: {}", data_dir.display());
143/// }
144/// ```
145pub fn data_home() -> Option<PathBuf> {
146  os::data_home()
147}
148
149/// Returns the user's local data directory (non-roaming).
150///
151/// This is primarily useful on Windows where it returns the local (non-roaming) data directory.
152/// On other platforms, it behaves identically to `data_home()`.
153///
154/// Platform defaults:
155/// - **Linux**: `~/.local/share` (same as `data_home()`)
156/// - **macOS**: `~/Library/Application Support` (same as `data_home()`)
157/// - **Windows**: `%LOCALAPPDATA%` (non-roaming)
158///
159/// # Examples
160///
161/// ```rust
162/// use dir_spec::data_local;
163/// if let Some(data_local) = data_local() {
164///     println!("Local data directory: {}", data_local.display());
165/// }
166/// ```
167pub fn data_local() -> Option<PathBuf> {
168  os::data_local()
169}
170
171/// Returns the user's desktop directory.
172///
173/// Checks `XDG_DESKTOP_DIR` first, then falls back to platform defaults:
174/// - **Linux/macOS**: `~/Desktop`
175/// - **Windows**: `%USERPROFILE%\Desktop`
176///
177/// # Examples
178///
179/// ```rust
180/// use dir_spec::desktop;
181/// if let Some(desktop) = desktop() {
182///     println!("Desktop directory: {}", desktop.display());
183/// }
184/// ```
185pub fn desktop() -> Option<PathBuf> {
186  os::desktop()
187}
188
189/// Returns the user's documents directory.
190///
191/// Checks `XDG_DOCUMENTS_DIR` first, then falls back to platform defaults:
192/// - **Linux/macOS**: `~/Documents`
193/// - **Windows**: `%USERPROFILE%\Documents`
194///
195/// # Examples
196///
197/// ```rust
198/// use dir_spec::documents;
199/// if let Some(documents) = documents() {
200///     println!("Documents directory: {}", documents.display());
201/// }
202/// ```
203pub fn documents() -> Option<PathBuf> {
204  os::documents()
205}
206
207/// Returns the user's downloads directory.
208///
209/// Checks `XDG_DOWNLOAD_DIR` first, then falls back to platform defaults:
210/// - **Linux/macOS**: `~/Downloads`
211/// - **Windows**: `%USERPROFILE%\Downloads`
212///
213/// # Examples
214///
215/// ```rust
216/// use dir_spec::downloads;
217/// if let Some(downloads) = downloads() {
218///     println!("Downloads directory: {}", downloads.display());
219/// }
220/// ```
221pub fn downloads() -> Option<PathBuf> {
222  os::downloads()
223}
224
225/// Returns the user's fonts directory.
226///
227/// This directory is used for user-installed fonts.
228/// Note: Returns `None` on Windows as there is no standard user fonts directory.
229///
230/// Platform defaults:
231/// - **Linux**: `~/.local/share/fonts`
232/// - **macOS**: `~/Library/Fonts`
233/// - **Windows**: `None` (no standard user fonts directory)
234///
235/// # Examples
236///
237/// ```rust
238/// use dir_spec::fonts;
239/// if let Some(fonts) = fonts() {
240///     println!("Fonts directory: {}", fonts.display());
241/// } else {
242///     println!("No user fonts directory on this platform");
243/// }
244/// ```
245pub fn fonts() -> Option<PathBuf> {
246  os::fonts()
247}
248
249/// Returns the user's home directory.
250///
251/// Uses the standard library's `std::env::home_dir()` function.
252///
253/// # Examples
254///
255/// ```rust
256/// use dir_spec::home;
257/// if let Some(home_dir) = home() {
258///     println!("Home directory: {}", home_dir.display());
259/// }
260/// ```
261pub fn home() -> Option<PathBuf> {
262  env::home_dir()
263}
264
265/// Returns the user's music directory.
266///
267/// Checks `XDG_MUSIC_DIR` first, then falls back to platform defaults:
268/// - **Linux/macOS**: `~/Music`
269/// - **Windows**: `%USERPROFILE%\Music`
270///
271/// # Examples
272///
273/// ```rust
274/// use dir_spec::music;
275/// if let Some(music) = music() {
276///     println!("Music directory: {}", music.display());
277/// }
278/// ```
279pub fn music() -> Option<PathBuf> {
280  os::music()
281}
282
283/// Returns the user's pictures directory.
284///
285/// Checks `XDG_PICTURES_DIR` first, then falls back to platform defaults:
286/// - **Linux/macOS**: `~/Pictures`
287/// - **Windows**: `%USERPROFILE%\Pictures`
288///
289/// # Examples
290///
291/// ```rust
292/// use dir_spec::pictures;
293/// if let Some(pictures) = pictures() {
294///     println!("Pictures directory: {}", pictures.display());
295/// }
296/// ```
297pub fn pictures() -> Option<PathBuf> {
298  os::pictures()
299}
300
301/// Returns the user's preferences directory.
302///
303/// This is primarily used on macOS for storing .plist files using Apple's proprietary APIs.
304/// On other platforms, it behaves identically to `config_home()`.
305///
306/// Platform defaults:
307/// - **Linux**: `~/.config` (same as `config_home()`)
308/// - **macOS**: `~/Library/Preferences` (for .plist files)
309/// - **Windows**: `%APPDATA%` (same as `config_home()`)
310///
311/// # Examples
312///
313/// ```rust
314/// use dir_spec::preferences;
315/// if let Some(preferences) = preferences() {
316///     println!("Preferences directory: {}", preferences.display());
317/// }
318/// ```
319pub fn preferences() -> Option<PathBuf> {
320  os::preferences()
321}
322
323/// Returns the user's public share directory.
324///
325/// Checks `XDG_PUBLICSHARE_DIR` first, then falls back to platform defaults:
326/// - **Linux/macOS**: `~/Public`
327/// - **Windows**: `C:\Users\Public` (system-wide public folder)
328///
329/// # Examples
330///
331/// ```rust
332/// use dir_spec::publicshare;
333/// if let Some(public) = publicshare() {
334///     println!("Public directory: {}", public.display());
335/// }
336/// ```
337pub fn publicshare() -> Option<PathBuf> {
338  os::publicshare()
339}
340
341/// Returns the user's runtime directory.
342///
343/// Checks `XDG_RUNTIME_DIR` first, then falls back to platform defaults:
344/// - **Linux**: Attempts to use `$TMPDIR`, then falls back to `/tmp`
345/// - **macOS**: `$TMPDIR` or `/tmp`
346/// - **Windows**: `%TEMP%`
347///
348/// # Examples
349///
350/// ```rust
351/// use dir_spec::runtime;
352/// if let Some(runtime) = runtime() {
353///     println!("Runtime directory: {}", runtime.display());
354/// }
355/// ```
356pub fn runtime() -> Option<PathBuf> {
357  os::runtime()
358}
359
360/// Returns the user's state directory.
361///
362/// Checks `XDG_STATE_HOME` first, then falls back to platform defaults:
363/// - **Linux**: `~/.local/state`
364/// - **macOS**: `~/Library/Application Support`
365/// - **Windows**: `%LOCALAPPDATA%`
366///
367/// # Examples
368///
369/// ```rust
370/// use dir_spec::state_home;
371/// if let Some(state_dir) = state_home() {
372///     println!("State directory: {}", state_dir.display());
373/// }
374/// ```
375pub fn state_home() -> Option<PathBuf> {
376  os::state_home()
377}
378
379/// Returns the user's templates directory.
380///
381/// Checks `XDG_TEMPLATES_DIR` first, then falls back to platform defaults:
382/// - **Linux/macOS**: `~/Templates`
383/// - **Windows**: `%USERPROFILE%\Templates`
384///
385/// # Examples
386///
387/// ```rust
388/// use dir_spec::templates;
389/// if let Some(templates) = templates() {
390///     println!("Templates directory: {}", templates.display());
391/// }
392/// ```
393pub fn templates() -> Option<PathBuf> {
394  os::templates()
395}
396
397/// Returns the user's videos directory.
398///
399/// Checks `XDG_VIDEOS_DIR` first, then falls back to platform defaults:
400/// - **Linux**: `~/Videos`
401/// - **macOS**: `~/Movies` (following macOS convention)
402/// - **Windows**: `%USERPROFILE%\Videos`
403///
404/// # Examples
405///
406/// ```rust
407/// use dir_spec::videos;
408/// if let Some(videos) = videos() {
409///     println!("Videos directory: {}", videos.display());
410/// }
411/// ```
412pub fn videos() -> Option<PathBuf> {
413  os::videos()
414}
415
416#[cfg(test)]
417mod tests {
418  use super::*;
419
420  mod bin_home {
421    use temp_env::{with_var, with_var_unset};
422
423    use super::*;
424
425    #[test]
426    fn respects_xdg_bin_home() {
427      let test_path = if cfg!(windows) { "C:\\test\\bin" } else { "/test/bin" };
428      with_var("XDG_BIN_HOME", Some(test_path), || {
429        let result = bin_home();
430        assert_eq!(result, Some(PathBuf::from(test_path)));
431      });
432    }
433
434    #[test]
435    fn ignores_relative_xdg_bin_home() {
436      with_var("XDG_BIN_HOME", Some("relative/bin"), || {
437        let result = bin_home();
438
439        if let Some(path) = result {
440          assert!(path.is_absolute());
441        }
442      });
443    }
444
445    #[test]
446    fn uses_platform_default_when_xdg_unset() {
447      with_var_unset("XDG_BIN_HOME", || {
448        let result = bin_home();
449        if let Some(bin_path) = result {
450          assert!(bin_path.is_absolute());
451
452          #[cfg(any(target_os = "linux", target_os = "macos"))]
453          assert!(bin_path.to_string_lossy().ends_with(".local/bin"));
454
455          #[cfg(target_os = "windows")]
456          assert!(bin_path.to_string_lossy().contains("Programs"));
457        }
458      });
459    }
460  }
461
462  mod cache_home {
463    use temp_env::{with_var, with_var_unset};
464
465    use super::*;
466
467    #[test]
468    fn respects_xdg_cache_home() {
469      let test_path = if cfg!(windows) { "C:\\test\\cache" } else { "/test/cache" };
470      with_var("XDG_CACHE_HOME", Some(test_path), || {
471        let result = cache_home();
472        assert_eq!(result, Some(PathBuf::from(test_path)));
473      });
474    }
475
476    #[test]
477    fn ignores_relative_xdg_cache_home() {
478      with_var("XDG_CACHE_HOME", Some("relative/cache"), || {
479        let result = cache_home();
480        if let Some(path) = result {
481          assert!(path.is_absolute());
482        }
483      });
484    }
485
486    #[test]
487    fn uses_platform_default_when_xdg_unset() {
488      with_var_unset("XDG_CACHE_HOME", || {
489        let result = cache_home();
490        if let Some(cache_path) = result {
491          assert!(cache_path.is_absolute());
492
493          #[cfg(target_os = "linux")]
494          assert!(cache_path.to_string_lossy().ends_with(".cache"));
495
496          #[cfg(target_os = "macos")]
497          assert!(cache_path.to_string_lossy().contains("Library/Caches"));
498
499          #[cfg(target_os = "windows")]
500          {
501            if let Ok(localappdata) = env::var("LOCALAPPDATA") {
502              assert_eq!(cache_path, PathBuf::from(localappdata));
503            }
504          }
505        }
506      });
507    }
508  }
509
510  mod config_home {
511    use temp_env::{with_var, with_var_unset};
512
513    use super::*;
514
515    #[test]
516    fn respects_xdg_config_home() {
517      let test_path = if cfg!(windows) { "C:\\test\\config" } else { "/test/config" };
518      with_var("XDG_CONFIG_HOME", Some(test_path), || {
519        let result = config_home();
520        assert_eq!(result, Some(PathBuf::from(test_path)));
521      });
522    }
523
524    #[test]
525    fn ignores_relative_xdg_config_home() {
526      with_var("XDG_CONFIG_HOME", Some("relative/config"), || {
527        let result = config_home();
528        if let Some(path) = result {
529          assert!(path.is_absolute());
530        }
531      });
532    }
533
534    #[test]
535    fn uses_platform_default_when_xdg_unset() {
536      with_var_unset("XDG_CONFIG_HOME", || {
537        let result = config_home();
538        if let Some(config_path) = result {
539          assert!(config_path.is_absolute());
540
541          #[cfg(target_os = "linux")]
542          assert!(config_path.to_string_lossy().ends_with(".config"));
543
544          #[cfg(target_os = "macos")]
545          assert!(config_path.to_string_lossy().contains("Library/Application Support"));
546
547          #[cfg(target_os = "windows")]
548          {
549            if let Ok(appdata) = env::var("APPDATA") {
550              assert_eq!(config_path, PathBuf::from(appdata));
551            }
552          }
553        }
554      });
555    }
556  }
557
558  mod config_local {
559    use super::*;
560
561    #[test]
562    fn uses_localappdata_on_windows() {
563      let result = config_local();
564      if let Some(config_local_path) = result {
565        assert!(config_local_path.is_absolute());
566
567        #[cfg(target_os = "windows")]
568        {
569          if let Ok(localappdata) = env::var("LOCALAPPDATA") {
570            assert_eq!(config_local_path, PathBuf::from(localappdata));
571          }
572        }
573
574        #[cfg(not(target_os = "windows"))]
575        {
576          assert_eq!(Some(config_local_path), config_home());
577        }
578      }
579    }
580
581    #[test]
582    fn matches_config_home_on_non_windows() {
583      #[cfg(not(target_os = "windows"))]
584      {
585        let config_local = config_local();
586        let config_home = config_home();
587        assert_eq!(config_local, config_home);
588      }
589    }
590  }
591
592  mod data_home {
593    use temp_env::{with_var, with_var_unset};
594
595    use super::*;
596
597    #[test]
598    fn respects_xdg_data_home() {
599      let test_path = if cfg!(windows) { "C:\\test\\data" } else { "/test/data" };
600      with_var("XDG_DATA_HOME", Some(test_path), || {
601        let result = data_home();
602        assert_eq!(result, Some(PathBuf::from(test_path)));
603      });
604    }
605
606    #[test]
607    fn ignores_relative_xdg_data_home() {
608      with_var("XDG_DATA_HOME", Some("relative/data"), || {
609        let result = data_home();
610        if let Some(path) = result {
611          assert!(path.is_absolute());
612        }
613      });
614    }
615
616    #[test]
617    fn uses_platform_default_when_xdg_unset() {
618      with_var_unset("XDG_DATA_HOME", || {
619        let result = data_home();
620        if let Some(data_path) = result {
621          assert!(data_path.is_absolute());
622
623          #[cfg(target_os = "linux")]
624          assert!(data_path.to_string_lossy().ends_with(".local/share"));
625
626          #[cfg(target_os = "macos")]
627          assert!(data_path.to_string_lossy().contains("Library/Application Support"));
628
629          #[cfg(target_os = "windows")]
630          {
631            if let Ok(appdata) = env::var("APPDATA") {
632              assert_eq!(data_path, PathBuf::from(appdata));
633            }
634          }
635        }
636      });
637    }
638  }
639
640  mod data_local {
641    use super::*;
642
643    #[test]
644    fn uses_localappdata_on_windows() {
645      let result = data_local();
646      if let Some(data_local_path) = result {
647        assert!(data_local_path.is_absolute());
648
649        #[cfg(target_os = "windows")]
650        {
651          if let Ok(localappdata) = env::var("LOCALAPPDATA") {
652            assert_eq!(data_local_path, PathBuf::from(localappdata));
653          }
654        }
655
656        #[cfg(not(target_os = "windows"))]
657        {
658          assert_eq!(Some(data_local_path), data_home());
659        }
660      }
661    }
662
663    #[test]
664    fn matches_data_home_on_non_windows() {
665      #[cfg(not(target_os = "windows"))]
666      {
667        let data_local = data_local();
668        let data_home = data_home();
669        assert_eq!(data_local, data_home);
670      }
671    }
672  }
673
674  mod desktop {
675    use temp_env::{with_var, with_var_unset};
676
677    use super::*;
678
679    #[test]
680    fn respects_xdg_desktop_dir() {
681      let test_path = if cfg!(windows) { "C:\\test\\desktop" } else { "/test/desktop" };
682      with_var("XDG_DESKTOP_DIR", Some(test_path), || {
683        let result = desktop();
684        assert_eq!(result, Some(PathBuf::from(test_path)));
685      });
686    }
687
688    #[test]
689    fn ignores_relative_xdg_desktop_dir() {
690      with_var("XDG_DESKTOP_DIR", Some("relative/desktop"), || {
691        let result = desktop();
692        if let Some(path) = result {
693          assert!(path.is_absolute());
694        }
695      });
696    }
697
698    #[test]
699    fn uses_platform_default_when_xdg_unset() {
700      with_var_unset("XDG_DESKTOP_DIR", || {
701        let result = desktop();
702        if let Some(desktop_path) = result {
703          assert!(desktop_path.is_absolute());
704          assert!(desktop_path.to_string_lossy().ends_with("Desktop"));
705        }
706      });
707    }
708  }
709
710  mod documents {
711    use temp_env::{with_var, with_var_unset};
712
713    use super::*;
714
715    #[test]
716    fn respects_xdg_documents_dir() {
717      let test_path = if cfg!(windows) { "C:\\test\\documents" } else { "/test/documents" };
718      with_var("XDG_DOCUMENTS_DIR", Some(test_path), || {
719        let result = documents();
720        assert_eq!(result, Some(PathBuf::from(test_path)));
721      });
722    }
723
724    #[test]
725    fn ignores_relative_xdg_documents_dir() {
726      with_var("XDG_DOCUMENTS_DIR", Some("relative/documents"), || {
727        let result = documents();
728        if let Some(path) = result {
729          assert!(path.is_absolute());
730        }
731      });
732    }
733
734    #[test]
735    fn uses_platform_default_when_xdg_unset() {
736      with_var_unset("XDG_DOCUMENTS_DIR", || {
737        let result = documents();
738        if let Some(documents_path) = result {
739          assert!(documents_path.is_absolute());
740          assert!(documents_path.to_string_lossy().ends_with("Documents"));
741        }
742      });
743    }
744  }
745
746  mod downloads {
747    use temp_env::{with_var, with_var_unset};
748
749    use super::*;
750
751    #[test]
752    fn respects_xdg_download_dir() {
753      let test_path = if cfg!(windows) { "C:\\test\\downloads" } else { "/test/downloads" };
754      with_var("XDG_DOWNLOAD_DIR", Some(test_path), || {
755        let result = downloads();
756        assert_eq!(result, Some(PathBuf::from(test_path)));
757      });
758    }
759
760    #[test]
761    fn ignores_relative_xdg_download_dir() {
762      with_var("XDG_DOWNLOAD_DIR", Some("relative/downloads"), || {
763        let result = downloads();
764        if let Some(path) = result {
765          assert!(path.is_absolute());
766        }
767      });
768    }
769
770    #[test]
771    fn uses_platform_default_when_xdg_unset() {
772      with_var_unset("XDG_DOWNLOAD_DIR", || {
773        let result = downloads();
774        if let Some(downloads_path) = result {
775          assert!(downloads_path.is_absolute());
776          assert!(downloads_path.to_string_lossy().ends_with("Downloads"));
777        }
778      });
779    }
780  }
781
782  mod fonts {
783    use super::*;
784
785    #[test]
786    fn returns_platform_specific_path() {
787      let result = fonts();
788
789      #[cfg(target_os = "linux")]
790      if let Some(fonts_path) = result {
791        assert!(fonts_path.is_absolute());
792        assert!(fonts_path.to_string_lossy().ends_with(".local/share/fonts"));
793      }
794
795      #[cfg(target_os = "macos")]
796      if let Some(fonts_path) = result {
797        assert!(fonts_path.is_absolute());
798        assert!(fonts_path.to_string_lossy().ends_with("Library/Fonts"));
799      }
800
801      #[cfg(target_os = "windows")]
802      assert_eq!(result, None);
803    }
804
805    #[test]
806    fn returns_none_on_windows() {
807      #[cfg(target_os = "windows")]
808      {
809        let result = fonts();
810        assert_eq!(result, None);
811      }
812    }
813
814    #[test]
815    fn returns_some_on_unix() {
816      #[cfg(any(target_os = "linux", target_os = "macos"))]
817      {
818        let result = fonts();
819        assert!(result.is_some());
820        if let Some(path) = result {
821          assert!(path.is_absolute());
822        }
823      }
824    }
825  }
826
827  mod home {
828    use super::*;
829
830    #[test]
831    fn returns_absolute_path_when_available() {
832      let result = home();
833      if let Some(home_path) = result {
834        assert!(home_path.is_absolute());
835      }
836    }
837
838    #[test]
839    fn delegates_to_std_env_home_dir() {
840      let std_result = std::env::home_dir();
841      let our_result = home();
842      assert_eq!(std_result, our_result);
843    }
844  }
845
846  mod music {
847    use temp_env::{with_var, with_var_unset};
848
849    use super::*;
850
851    #[test]
852    fn respects_xdg_music_dir() {
853      let test_path = if cfg!(windows) { "C:\\test\\music" } else { "/test/music" };
854      with_var("XDG_MUSIC_DIR", Some(test_path), || {
855        let result = music();
856        assert_eq!(result, Some(PathBuf::from(test_path)));
857      });
858    }
859
860    #[test]
861    fn ignores_relative_xdg_music_dir() {
862      with_var("XDG_MUSIC_DIR", Some("relative/music"), || {
863        let result = music();
864        if let Some(path) = result {
865          assert!(path.is_absolute());
866        }
867      });
868    }
869
870    #[test]
871    fn uses_platform_default_when_xdg_unset() {
872      with_var_unset("XDG_MUSIC_DIR", || {
873        let result = music();
874        if let Some(music_path) = result {
875          assert!(music_path.is_absolute());
876          assert!(music_path.to_string_lossy().ends_with("Music"));
877        }
878      });
879    }
880  }
881
882  mod pictures {
883    use temp_env::{with_var, with_var_unset};
884
885    use super::*;
886
887    #[test]
888    fn respects_xdg_pictures_dir() {
889      let test_path = if cfg!(windows) { "C:\\test\\pictures" } else { "/test/pictures" };
890      with_var("XDG_PICTURES_DIR", Some(test_path), || {
891        let result = pictures();
892        assert_eq!(result, Some(PathBuf::from(test_path)));
893      });
894    }
895
896    #[test]
897    fn ignores_relative_xdg_pictures_dir() {
898      with_var("XDG_PICTURES_DIR", Some("relative/pictures"), || {
899        let result = pictures();
900        if let Some(path) = result {
901          assert!(path.is_absolute());
902        }
903      });
904    }
905
906    #[test]
907    fn uses_platform_default_when_xdg_unset() {
908      with_var_unset("XDG_PICTURES_DIR", || {
909        let result = pictures();
910        if let Some(pictures_path) = result {
911          assert!(pictures_path.is_absolute());
912          assert!(pictures_path.to_string_lossy().ends_with("Pictures"));
913        }
914      });
915    }
916  }
917
918  mod preferences {
919    use super::*;
920
921    #[test]
922    fn returns_platform_specific_path() {
923      let result = preferences();
924      if let Some(preferences_path) = result {
925        assert!(preferences_path.is_absolute());
926
927        #[cfg(target_os = "macos")]
928        assert!(preferences_path.to_string_lossy().ends_with("Library/Preferences"));
929
930        #[cfg(not(target_os = "macos"))]
931        assert_eq!(Some(preferences_path), config_home());
932      }
933    }
934
935    #[test]
936    fn matches_config_home_on_non_macos() {
937      #[cfg(not(target_os = "macos"))]
938      {
939        let preferences = preferences();
940        let config_home = config_home();
941        assert_eq!(preferences, config_home);
942      }
943    }
944
945    #[test]
946    fn uses_library_preferences_on_macos() {
947      #[cfg(target_os = "macos")]
948      {
949        let result = preferences();
950        if let Some(path) = result {
951          assert!(path.to_string_lossy().ends_with("Library/Preferences"));
952        }
953      }
954    }
955  }
956
957  mod publicshare {
958    use temp_env::{with_var, with_var_unset};
959
960    use super::*;
961
962    #[test]
963    fn respects_xdg_publicshare_dir() {
964      let test_path = if cfg!(windows) { "C:\\test\\public" } else { "/test/public" };
965      with_var("XDG_PUBLICSHARE_DIR", Some(test_path), || {
966        let result = publicshare();
967        assert_eq!(result, Some(PathBuf::from(test_path)));
968      });
969    }
970
971    #[test]
972    fn ignores_relative_xdg_publicshare_dir() {
973      with_var("XDG_PUBLICSHARE_DIR", Some("relative/public"), || {
974        let result = publicshare();
975        if let Some(path) = result {
976          assert!(path.is_absolute());
977        }
978      });
979    }
980
981    #[test]
982    fn uses_platform_default_when_xdg_unset() {
983      with_var_unset("XDG_PUBLICSHARE_DIR", || {
984        let result = publicshare();
985        if let Some(public_path) = result {
986          assert!(public_path.is_absolute());
987
988          #[cfg(target_os = "windows")]
989          assert_eq!(public_path, PathBuf::from("C:\\Users\\Public"));
990
991          #[cfg(any(target_os = "linux", target_os = "macos"))]
992          assert!(public_path.to_string_lossy().ends_with("Public"));
993        }
994      });
995    }
996
997    #[test]
998    fn uses_system_public_on_windows() {
999      #[cfg(target_os = "windows")]
1000      {
1001        with_var_unset("XDG_PUBLICSHARE_DIR", || {
1002          let result = publicshare();
1003          assert_eq!(result, Some(PathBuf::from("C:\\Users\\Public")));
1004        });
1005      }
1006    }
1007  }
1008
1009  mod runtime {
1010    use temp_env::{with_var, with_var_unset};
1011
1012    use super::*;
1013
1014    #[test]
1015    fn respects_xdg_runtime_dir() {
1016      let test_path = if cfg!(windows) { "C:\\test\\runtime" } else { "/test/runtime" };
1017      with_var("XDG_RUNTIME_DIR", Some(test_path), || {
1018        let result = runtime();
1019        assert_eq!(result, Some(PathBuf::from(test_path)));
1020      });
1021    }
1022
1023    #[test]
1024    fn ignores_relative_xdg_runtime_dir() {
1025      with_var("XDG_RUNTIME_DIR", Some("relative/runtime"), || {
1026        let result = runtime();
1027        if let Some(path) = result {
1028          assert!(path.is_absolute());
1029        }
1030      });
1031    }
1032
1033    #[test]
1034    fn uses_platform_default_when_xdg_unset() {
1035      with_var_unset("XDG_RUNTIME_DIR", || {
1036        let result = runtime();
1037        if let Some(runtime_path) = result {
1038          assert!(runtime_path.is_absolute());
1039
1040          #[cfg(any(target_os = "linux", target_os = "macos"))]
1041          {
1042            let path_str = runtime_path.to_string_lossy();
1043            assert!(path_str.contains("tmp") || path_str.starts_with("/var/folders"));
1044          }
1045
1046          #[cfg(target_os = "windows")]
1047          {
1048            if let Ok(temp) = env::var("TEMP") {
1049              assert_eq!(runtime_path, PathBuf::from(temp));
1050            }
1051          }
1052        }
1053      });
1054    }
1055
1056    #[test]
1057    fn falls_back_to_tmp_on_unix() {
1058      #[cfg(any(target_os = "linux", target_os = "macos"))]
1059      {
1060        with_var_unset("XDG_RUNTIME_DIR", || {
1061          with_var_unset("TMPDIR", || {
1062            let result = runtime();
1063            assert_eq!(result, Some(PathBuf::from("/tmp")));
1064          });
1065        });
1066      }
1067    }
1068  }
1069
1070  mod state_home {
1071    use temp_env::{with_var, with_var_unset};
1072
1073    use super::*;
1074
1075    #[test]
1076    fn respects_xdg_state_home() {
1077      let test_path = if cfg!(windows) { "C:\\test\\state" } else { "/test/state" };
1078      with_var("XDG_STATE_HOME", Some(test_path), || {
1079        let result = state_home();
1080        assert_eq!(result, Some(PathBuf::from(test_path)));
1081      });
1082    }
1083
1084    #[test]
1085    fn ignores_relative_xdg_state_home() {
1086      with_var("XDG_STATE_HOME", Some("relative/state"), || {
1087        let result = state_home();
1088        if let Some(path) = result {
1089          assert!(path.is_absolute());
1090        }
1091      });
1092    }
1093
1094    #[test]
1095    fn uses_platform_default_when_xdg_unset() {
1096      with_var_unset("XDG_STATE_HOME", || {
1097        let result = state_home();
1098        if let Some(state_path) = result {
1099          assert!(state_path.is_absolute());
1100
1101          #[cfg(target_os = "linux")]
1102          assert!(state_path.to_string_lossy().ends_with(".local/state"));
1103
1104          #[cfg(target_os = "macos")]
1105          assert!(state_path.to_string_lossy().contains("Library/Application Support"));
1106
1107          #[cfg(target_os = "windows")]
1108          {
1109            if let Ok(localappdata) = env::var("LOCALAPPDATA") {
1110              assert_eq!(state_path, PathBuf::from(localappdata));
1111            }
1112          }
1113        }
1114      });
1115    }
1116  }
1117
1118  mod templates {
1119    use temp_env::{with_var, with_var_unset};
1120
1121    use super::*;
1122
1123    #[test]
1124    fn respects_xdg_templates_dir() {
1125      let test_path = if cfg!(windows) { "C:\\test\\templates" } else { "/test/templates" };
1126      with_var("XDG_TEMPLATES_DIR", Some(test_path), || {
1127        let result = templates();
1128        assert_eq!(result, Some(PathBuf::from(test_path)));
1129      });
1130    }
1131
1132    #[test]
1133    fn ignores_relative_xdg_templates_dir() {
1134      with_var("XDG_TEMPLATES_DIR", Some("relative/templates"), || {
1135        let result = templates();
1136        if let Some(path) = result {
1137          assert!(path.is_absolute());
1138        }
1139      });
1140    }
1141
1142    #[test]
1143    fn uses_platform_default_when_xdg_unset() {
1144      with_var_unset("XDG_TEMPLATES_DIR", || {
1145        let result = templates();
1146        if let Some(templates_path) = result {
1147          assert!(templates_path.is_absolute());
1148          assert!(templates_path.to_string_lossy().ends_with("Templates"));
1149        }
1150      });
1151    }
1152  }
1153
1154  mod videos {
1155    use temp_env::{with_var, with_var_unset};
1156
1157    use super::*;
1158
1159    #[test]
1160    fn respects_xdg_videos_dir() {
1161      let test_path = if cfg!(windows) { "C:\\test\\videos" } else { "/test/videos" };
1162      with_var("XDG_VIDEOS_DIR", Some(test_path), || {
1163        let result = videos();
1164        assert_eq!(result, Some(PathBuf::from(test_path)));
1165      });
1166    }
1167
1168    #[test]
1169    fn ignores_relative_xdg_videos_dir() {
1170      with_var("XDG_VIDEOS_DIR", Some("relative/videos"), || {
1171        let result = videos();
1172        if let Some(path) = result {
1173          assert!(path.is_absolute());
1174        }
1175      });
1176    }
1177
1178    #[test]
1179    fn uses_platform_default_when_xdg_unset() {
1180      with_var_unset("XDG_VIDEOS_DIR", || {
1181        let result = videos();
1182        if let Some(videos_path) = result {
1183          assert!(videos_path.is_absolute());
1184
1185          #[cfg(target_os = "linux")]
1186          assert!(videos_path.to_string_lossy().ends_with("Videos"));
1187
1188          #[cfg(target_os = "macos")]
1189          assert!(videos_path.to_string_lossy().ends_with("Movies"));
1190
1191          #[cfg(target_os = "windows")]
1192          assert!(videos_path.to_string_lossy().ends_with("Videos"));
1193        }
1194      });
1195    }
1196  }
1197}