Skip to main content

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 = "macos")]
36mod macos;
37#[cfg(all(target_family = "unix", not(target_os = "macos")))]
38mod unix;
39#[cfg(target_os = "windows")]
40mod windows;
41pub(crate) mod xdg;
42
43use std::{env, path::PathBuf};
44
45#[cfg(target_os = "macos")]
46use macos as os;
47#[cfg(all(target_family = "unix", not(target_os = "macos")))]
48use unix 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/// Returns the XDG binary directory, or a custom default.
417///
418/// Checks `XDG_BIN_HOME` and returns its value if set to an absolute path.
419/// If unset or relative, returns the provided default instead.
420///
421/// Unlike [`bin_home()`], this does **not** fall back to platform-specific defaults —
422/// you control the fallback entirely.
423///
424/// # Examples
425///
426/// ```rust
427/// use dir_spec::xdg_bin_home_or;
428/// let bin_dir = xdg_bin_home_or("/usr/local/bin");
429/// println!("Bin directory: {}", bin_dir.display());
430/// ```
431pub fn xdg_bin_home_or(default: impl Into<PathBuf>) -> PathBuf {
432  xdg::resolve_path(xdg::BIN_HOME).unwrap_or_else(|| default.into())
433}
434
435/// Returns the XDG cache directory, or a custom default.
436///
437/// Checks `XDG_CACHE_HOME` and returns its value if set to an absolute path.
438/// If unset or relative, returns the provided default instead.
439///
440/// Unlike [`cache_home()`], this does **not** fall back to platform-specific defaults —
441/// you control the fallback entirely.
442///
443/// # Examples
444///
445/// ```rust
446/// use dir_spec::xdg_cache_home_or;
447/// let cache_dir = xdg_cache_home_or("/tmp/cache");
448/// println!("Cache directory: {}", cache_dir.display());
449/// ```
450pub fn xdg_cache_home_or(default: impl Into<PathBuf>) -> PathBuf {
451  xdg::resolve_path(xdg::CACHE_HOME).unwrap_or_else(|| default.into())
452}
453
454/// Returns the XDG configuration directory, or a custom default.
455///
456/// Checks `XDG_CONFIG_HOME` and returns its value if set to an absolute path.
457/// If unset or relative, returns the provided default instead.
458///
459/// Unlike [`config_home()`], this does **not** fall back to platform-specific defaults —
460/// you control the fallback entirely.
461///
462/// # Examples
463///
464/// ```rust
465/// use dir_spec::xdg_config_home_or;
466/// let config_dir = xdg_config_home_or("/etc/myapp");
467/// println!("Config directory: {}", config_dir.display());
468/// ```
469pub fn xdg_config_home_or(default: impl Into<PathBuf>) -> PathBuf {
470  xdg::resolve_path(xdg::CONFIG_HOME).unwrap_or_else(|| default.into())
471}
472
473/// Returns the XDG data directory, or a custom default.
474///
475/// Checks `XDG_DATA_HOME` and returns its value if set to an absolute path.
476/// If unset or relative, returns the provided default instead.
477///
478/// Unlike [`data_home()`], this does **not** fall back to platform-specific defaults —
479/// you control the fallback entirely.
480///
481/// # Examples
482///
483/// ```rust
484/// use dir_spec::xdg_data_home_or;
485/// let data_dir = xdg_data_home_or("/usr/share/myapp");
486/// println!("Data directory: {}", data_dir.display());
487/// ```
488pub fn xdg_data_home_or(default: impl Into<PathBuf>) -> PathBuf {
489  xdg::resolve_path(xdg::DATA_HOME).unwrap_or_else(|| default.into())
490}
491
492/// Returns the XDG desktop directory, or a custom default.
493///
494/// Checks `XDG_DESKTOP_DIR` and returns its value if set to an absolute path.
495/// If unset or relative, returns the provided default instead.
496///
497/// Unlike [`desktop()`], this does **not** fall back to platform-specific defaults —
498/// you control the fallback entirely.
499///
500/// # Examples
501///
502/// ```rust
503/// use dir_spec::xdg_desktop_dir_or;
504/// let desktop_dir = xdg_desktop_dir_or("/home/user/Desktop");
505/// println!("Desktop directory: {}", desktop_dir.display());
506/// ```
507pub fn xdg_desktop_dir_or(default: impl Into<PathBuf>) -> PathBuf {
508  xdg::resolve_path(xdg::DESKTOP_DIR).unwrap_or_else(|| default.into())
509}
510
511/// Returns the XDG documents directory, or a custom default.
512///
513/// Checks `XDG_DOCUMENTS_DIR` and returns its value if set to an absolute path.
514/// If unset or relative, returns the provided default instead.
515///
516/// Unlike [`documents()`], this does **not** fall back to platform-specific defaults —
517/// you control the fallback entirely.
518///
519/// # Examples
520///
521/// ```rust
522/// use dir_spec::xdg_documents_dir_or;
523/// let docs_dir = xdg_documents_dir_or("/home/user/Documents");
524/// println!("Documents directory: {}", docs_dir.display());
525/// ```
526pub fn xdg_documents_dir_or(default: impl Into<PathBuf>) -> PathBuf {
527  xdg::resolve_path(xdg::DOCUMENTS_DIR).unwrap_or_else(|| default.into())
528}
529
530/// Returns the XDG download directory, or a custom default.
531///
532/// Checks `XDG_DOWNLOAD_DIR` and returns its value if set to an absolute path.
533/// If unset or relative, returns the provided default instead.
534///
535/// Unlike [`downloads()`], this does **not** fall back to platform-specific defaults —
536/// you control the fallback entirely.
537///
538/// # Examples
539///
540/// ```rust
541/// use dir_spec::xdg_download_dir_or;
542/// let dl_dir = xdg_download_dir_or("/home/user/Downloads");
543/// println!("Download directory: {}", dl_dir.display());
544/// ```
545pub fn xdg_download_dir_or(default: impl Into<PathBuf>) -> PathBuf {
546  xdg::resolve_path(xdg::DOWNLOAD_DIR).unwrap_or_else(|| default.into())
547}
548
549/// Returns the XDG music directory, or a custom default.
550///
551/// Checks `XDG_MUSIC_DIR` and returns its value if set to an absolute path.
552/// If unset or relative, returns the provided default instead.
553///
554/// Unlike [`music()`], this does **not** fall back to platform-specific defaults —
555/// you control the fallback entirely.
556///
557/// # Examples
558///
559/// ```rust
560/// use dir_spec::xdg_music_dir_or;
561/// let music_dir = xdg_music_dir_or("/home/user/Music");
562/// println!("Music directory: {}", music_dir.display());
563/// ```
564pub fn xdg_music_dir_or(default: impl Into<PathBuf>) -> PathBuf {
565  xdg::resolve_path(xdg::MUSIC_DIR).unwrap_or_else(|| default.into())
566}
567
568/// Returns the XDG pictures directory, or a custom default.
569///
570/// Checks `XDG_PICTURES_DIR` and returns its value if set to an absolute path.
571/// If unset or relative, returns the provided default instead.
572///
573/// Unlike [`pictures()`], this does **not** fall back to platform-specific defaults —
574/// you control the fallback entirely.
575///
576/// # Examples
577///
578/// ```rust
579/// use dir_spec::xdg_pictures_dir_or;
580/// let pics_dir = xdg_pictures_dir_or("/home/user/Pictures");
581/// println!("Pictures directory: {}", pics_dir.display());
582/// ```
583pub fn xdg_pictures_dir_or(default: impl Into<PathBuf>) -> PathBuf {
584  xdg::resolve_path(xdg::PICTURES_DIR).unwrap_or_else(|| default.into())
585}
586
587/// Returns the XDG public share directory, or a custom default.
588///
589/// Checks `XDG_PUBLICSHARE_DIR` and returns its value if set to an absolute path.
590/// If unset or relative, returns the provided default instead.
591///
592/// Unlike [`publicshare()`], this does **not** fall back to platform-specific defaults —
593/// you control the fallback entirely.
594///
595/// # Examples
596///
597/// ```rust
598/// use dir_spec::xdg_publicshare_dir_or;
599/// let public_dir = xdg_publicshare_dir_or("/home/user/Public");
600/// println!("Public directory: {}", public_dir.display());
601/// ```
602pub fn xdg_publicshare_dir_or(default: impl Into<PathBuf>) -> PathBuf {
603  xdg::resolve_path(xdg::PUBLICSHARE_DIR).unwrap_or_else(|| default.into())
604}
605
606/// Returns the XDG runtime directory, or a custom default.
607///
608/// Checks `XDG_RUNTIME_DIR` and returns its value if set to an absolute path.
609/// If unset or relative, returns the provided default instead.
610///
611/// Unlike [`runtime()`], this does **not** fall back to platform-specific defaults —
612/// you control the fallback entirely.
613///
614/// # Examples
615///
616/// ```rust
617/// use dir_spec::xdg_runtime_dir_or;
618/// let runtime_dir = xdg_runtime_dir_or("/tmp/runtime");
619/// println!("Runtime directory: {}", runtime_dir.display());
620/// ```
621pub fn xdg_runtime_dir_or(default: impl Into<PathBuf>) -> PathBuf {
622  xdg::resolve_path(xdg::RUNTIME_DIR).unwrap_or_else(|| default.into())
623}
624
625/// Returns the XDG state directory, or a custom default.
626///
627/// Checks `XDG_STATE_HOME` and returns its value if set to an absolute path.
628/// If unset or relative, returns the provided default instead.
629///
630/// Unlike [`state_home()`], this does **not** fall back to platform-specific defaults —
631/// you control the fallback entirely.
632///
633/// # Examples
634///
635/// ```rust
636/// use dir_spec::xdg_state_home_or;
637/// let state_dir = xdg_state_home_or("/var/lib/myapp");
638/// println!("State directory: {}", state_dir.display());
639/// ```
640pub fn xdg_state_home_or(default: impl Into<PathBuf>) -> PathBuf {
641  xdg::resolve_path(xdg::STATE_HOME).unwrap_or_else(|| default.into())
642}
643
644/// Returns the XDG templates directory, or a custom default.
645///
646/// Checks `XDG_TEMPLATES_DIR` and returns its value if set to an absolute path.
647/// If unset or relative, returns the provided default instead.
648///
649/// Unlike [`templates()`], this does **not** fall back to platform-specific defaults —
650/// you control the fallback entirely.
651///
652/// # Examples
653///
654/// ```rust
655/// use dir_spec::xdg_templates_dir_or;
656/// let templates_dir = xdg_templates_dir_or("/home/user/Templates");
657/// println!("Templates directory: {}", templates_dir.display());
658/// ```
659pub fn xdg_templates_dir_or(default: impl Into<PathBuf>) -> PathBuf {
660  xdg::resolve_path(xdg::TEMPLATES_DIR).unwrap_or_else(|| default.into())
661}
662
663/// Returns the XDG videos directory, or a custom default.
664///
665/// Checks `XDG_VIDEOS_DIR` and returns its value if set to an absolute path.
666/// If unset or relative, returns the provided default instead.
667///
668/// Unlike [`videos()`], this does **not** fall back to platform-specific defaults —
669/// you control the fallback entirely.
670///
671/// # Examples
672///
673/// ```rust
674/// use dir_spec::xdg_videos_dir_or;
675/// let videos_dir = xdg_videos_dir_or("/home/user/Videos");
676/// println!("Videos directory: {}", videos_dir.display());
677/// ```
678pub fn xdg_videos_dir_or(default: impl Into<PathBuf>) -> PathBuf {
679  xdg::resolve_path(xdg::VIDEOS_DIR).unwrap_or_else(|| default.into())
680}
681
682#[cfg(test)]
683mod tests {
684  use super::*;
685
686  mod bin_home {
687    use temp_env::{with_var, with_var_unset};
688
689    use super::*;
690
691    #[test]
692    fn respects_xdg_bin_home() {
693      let test_path = if cfg!(windows) { "C:\\test\\bin" } else { "/test/bin" };
694      with_var("XDG_BIN_HOME", Some(test_path), || {
695        let result = bin_home();
696        assert_eq!(result, Some(PathBuf::from(test_path)));
697      });
698    }
699
700    #[test]
701    fn ignores_relative_xdg_bin_home() {
702      with_var("XDG_BIN_HOME", Some("relative/bin"), || {
703        let result = bin_home();
704
705        if let Some(path) = result {
706          assert!(path.is_absolute());
707        }
708      });
709    }
710
711    #[test]
712    fn uses_platform_default_when_xdg_unset() {
713      with_var_unset("XDG_BIN_HOME", || {
714        let result = bin_home();
715        if let Some(bin_path) = result {
716          assert!(bin_path.is_absolute());
717
718          #[cfg(any(target_os = "linux", target_os = "macos"))]
719          assert!(bin_path.to_string_lossy().ends_with(".local/bin"));
720
721          #[cfg(target_os = "windows")]
722          assert!(bin_path.to_string_lossy().contains("Programs"));
723        }
724      });
725    }
726  }
727
728  mod cache_home {
729    use temp_env::{with_var, with_var_unset};
730
731    use super::*;
732
733    #[test]
734    fn respects_xdg_cache_home() {
735      let test_path = if cfg!(windows) {
736        "C:\\test\\cache"
737      } else {
738        "/test/cache"
739      };
740      with_var("XDG_CACHE_HOME", Some(test_path), || {
741        let result = cache_home();
742        assert_eq!(result, Some(PathBuf::from(test_path)));
743      });
744    }
745
746    #[test]
747    fn ignores_relative_xdg_cache_home() {
748      with_var("XDG_CACHE_HOME", Some("relative/cache"), || {
749        let result = cache_home();
750        if let Some(path) = result {
751          assert!(path.is_absolute());
752        }
753      });
754    }
755
756    #[test]
757    fn uses_platform_default_when_xdg_unset() {
758      with_var_unset("XDG_CACHE_HOME", || {
759        let result = cache_home();
760        if let Some(cache_path) = result {
761          assert!(cache_path.is_absolute());
762
763          #[cfg(target_os = "linux")]
764          assert!(cache_path.to_string_lossy().ends_with(".cache"));
765
766          #[cfg(target_os = "macos")]
767          assert!(cache_path.to_string_lossy().contains("Library/Caches"));
768
769          #[cfg(target_os = "windows")]
770          {
771            if let Ok(localappdata) = env::var("LOCALAPPDATA") {
772              assert_eq!(cache_path, PathBuf::from(localappdata));
773            }
774          }
775        }
776      });
777    }
778  }
779
780  mod config_home {
781    use temp_env::{with_var, with_var_unset};
782
783    use super::*;
784
785    #[test]
786    fn respects_xdg_config_home() {
787      let test_path = if cfg!(windows) {
788        "C:\\test\\config"
789      } else {
790        "/test/config"
791      };
792      with_var("XDG_CONFIG_HOME", Some(test_path), || {
793        let result = config_home();
794        assert_eq!(result, Some(PathBuf::from(test_path)));
795      });
796    }
797
798    #[test]
799    fn ignores_relative_xdg_config_home() {
800      with_var("XDG_CONFIG_HOME", Some("relative/config"), || {
801        let result = config_home();
802        if let Some(path) = result {
803          assert!(path.is_absolute());
804        }
805      });
806    }
807
808    #[test]
809    fn uses_platform_default_when_xdg_unset() {
810      with_var_unset("XDG_CONFIG_HOME", || {
811        let result = config_home();
812        if let Some(config_path) = result {
813          assert!(config_path.is_absolute());
814
815          #[cfg(target_os = "linux")]
816          assert!(config_path.to_string_lossy().ends_with(".config"));
817
818          #[cfg(target_os = "macos")]
819          assert!(config_path.to_string_lossy().contains("Library/Application Support"));
820
821          #[cfg(target_os = "windows")]
822          {
823            if let Ok(appdata) = env::var("APPDATA") {
824              assert_eq!(config_path, PathBuf::from(appdata));
825            }
826          }
827        }
828      });
829    }
830  }
831
832  mod config_local {
833    use super::*;
834
835    #[test]
836    fn uses_localappdata_on_windows() {
837      let result = config_local();
838      if let Some(config_local_path) = result {
839        assert!(config_local_path.is_absolute());
840
841        #[cfg(target_os = "windows")]
842        {
843          if let Ok(localappdata) = env::var("LOCALAPPDATA") {
844            assert_eq!(config_local_path, PathBuf::from(localappdata));
845          }
846        }
847
848        #[cfg(not(target_os = "windows"))]
849        {
850          assert_eq!(Some(config_local_path), config_home());
851        }
852      }
853    }
854
855    #[test]
856    fn matches_config_home_on_non_windows() {
857      #[cfg(not(target_os = "windows"))]
858      {
859        let config_local = config_local();
860        let config_home = config_home();
861        assert_eq!(config_local, config_home);
862      }
863    }
864  }
865
866  mod data_home {
867    use temp_env::{with_var, with_var_unset};
868
869    use super::*;
870
871    #[test]
872    fn respects_xdg_data_home() {
873      let test_path = if cfg!(windows) { "C:\\test\\data" } else { "/test/data" };
874      with_var("XDG_DATA_HOME", Some(test_path), || {
875        let result = data_home();
876        assert_eq!(result, Some(PathBuf::from(test_path)));
877      });
878    }
879
880    #[test]
881    fn ignores_relative_xdg_data_home() {
882      with_var("XDG_DATA_HOME", Some("relative/data"), || {
883        let result = data_home();
884        if let Some(path) = result {
885          assert!(path.is_absolute());
886        }
887      });
888    }
889
890    #[test]
891    fn uses_platform_default_when_xdg_unset() {
892      with_var_unset("XDG_DATA_HOME", || {
893        let result = data_home();
894        if let Some(data_path) = result {
895          assert!(data_path.is_absolute());
896
897          #[cfg(target_os = "linux")]
898          assert!(data_path.to_string_lossy().ends_with(".local/share"));
899
900          #[cfg(target_os = "macos")]
901          assert!(data_path.to_string_lossy().contains("Library/Application Support"));
902
903          #[cfg(target_os = "windows")]
904          {
905            if let Ok(appdata) = env::var("APPDATA") {
906              assert_eq!(data_path, PathBuf::from(appdata));
907            }
908          }
909        }
910      });
911    }
912  }
913
914  mod data_local {
915    use super::*;
916
917    #[test]
918    fn uses_localappdata_on_windows() {
919      let result = data_local();
920      if let Some(data_local_path) = result {
921        assert!(data_local_path.is_absolute());
922
923        #[cfg(target_os = "windows")]
924        {
925          if let Ok(localappdata) = env::var("LOCALAPPDATA") {
926            assert_eq!(data_local_path, PathBuf::from(localappdata));
927          }
928        }
929
930        #[cfg(not(target_os = "windows"))]
931        {
932          assert_eq!(Some(data_local_path), data_home());
933        }
934      }
935    }
936
937    #[test]
938    fn matches_data_home_on_non_windows() {
939      #[cfg(not(target_os = "windows"))]
940      {
941        let data_local = data_local();
942        let data_home = data_home();
943        assert_eq!(data_local, data_home);
944      }
945    }
946  }
947
948  mod desktop {
949    use temp_env::{with_var, with_var_unset};
950
951    use super::*;
952
953    #[test]
954    fn respects_xdg_desktop_dir() {
955      let test_path = if cfg!(windows) {
956        "C:\\test\\desktop"
957      } else {
958        "/test/desktop"
959      };
960      with_var("XDG_DESKTOP_DIR", Some(test_path), || {
961        let result = desktop();
962        assert_eq!(result, Some(PathBuf::from(test_path)));
963      });
964    }
965
966    #[test]
967    fn ignores_relative_xdg_desktop_dir() {
968      with_var("XDG_DESKTOP_DIR", Some("relative/desktop"), || {
969        let result = desktop();
970        if let Some(path) = result {
971          assert!(path.is_absolute());
972        }
973      });
974    }
975
976    #[test]
977    fn uses_platform_default_when_xdg_unset() {
978      with_var_unset("XDG_DESKTOP_DIR", || {
979        let result = desktop();
980        if let Some(desktop_path) = result {
981          assert!(desktop_path.is_absolute());
982          assert!(desktop_path.to_string_lossy().ends_with("Desktop"));
983        }
984      });
985    }
986  }
987
988  mod documents {
989    use temp_env::{with_var, with_var_unset};
990
991    use super::*;
992
993    #[test]
994    fn respects_xdg_documents_dir() {
995      let test_path = if cfg!(windows) {
996        "C:\\test\\documents"
997      } else {
998        "/test/documents"
999      };
1000      with_var("XDG_DOCUMENTS_DIR", Some(test_path), || {
1001        let result = documents();
1002        assert_eq!(result, Some(PathBuf::from(test_path)));
1003      });
1004    }
1005
1006    #[test]
1007    fn ignores_relative_xdg_documents_dir() {
1008      with_var("XDG_DOCUMENTS_DIR", Some("relative/documents"), || {
1009        let result = documents();
1010        if let Some(path) = result {
1011          assert!(path.is_absolute());
1012        }
1013      });
1014    }
1015
1016    #[test]
1017    fn uses_platform_default_when_xdg_unset() {
1018      with_var_unset("XDG_DOCUMENTS_DIR", || {
1019        let result = documents();
1020        if let Some(documents_path) = result {
1021          assert!(documents_path.is_absolute());
1022          assert!(documents_path.to_string_lossy().ends_with("Documents"));
1023        }
1024      });
1025    }
1026  }
1027
1028  mod downloads {
1029    use temp_env::{with_var, with_var_unset};
1030
1031    use super::*;
1032
1033    #[test]
1034    fn respects_xdg_download_dir() {
1035      let test_path = if cfg!(windows) {
1036        "C:\\test\\downloads"
1037      } else {
1038        "/test/downloads"
1039      };
1040      with_var("XDG_DOWNLOAD_DIR", Some(test_path), || {
1041        let result = downloads();
1042        assert_eq!(result, Some(PathBuf::from(test_path)));
1043      });
1044    }
1045
1046    #[test]
1047    fn ignores_relative_xdg_download_dir() {
1048      with_var("XDG_DOWNLOAD_DIR", Some("relative/downloads"), || {
1049        let result = downloads();
1050        if let Some(path) = result {
1051          assert!(path.is_absolute());
1052        }
1053      });
1054    }
1055
1056    #[test]
1057    fn uses_platform_default_when_xdg_unset() {
1058      with_var_unset("XDG_DOWNLOAD_DIR", || {
1059        let result = downloads();
1060        if let Some(downloads_path) = result {
1061          assert!(downloads_path.is_absolute());
1062          assert!(downloads_path.to_string_lossy().ends_with("Downloads"));
1063        }
1064      });
1065    }
1066  }
1067
1068  mod fonts {
1069    use super::*;
1070
1071    #[test]
1072    fn returns_platform_specific_path() {
1073      let result = fonts();
1074
1075      #[cfg(target_os = "linux")]
1076      if let Some(fonts_path) = result {
1077        assert!(fonts_path.is_absolute());
1078        assert!(fonts_path.to_string_lossy().ends_with(".local/share/fonts"));
1079      }
1080
1081      #[cfg(target_os = "macos")]
1082      if let Some(fonts_path) = result {
1083        assert!(fonts_path.is_absolute());
1084        assert!(fonts_path.to_string_lossy().ends_with("Library/Fonts"));
1085      }
1086
1087      #[cfg(target_os = "windows")]
1088      assert_eq!(result, None);
1089    }
1090
1091    #[test]
1092    fn returns_none_on_windows() {
1093      #[cfg(target_os = "windows")]
1094      {
1095        let result = fonts();
1096        assert_eq!(result, None);
1097      }
1098    }
1099
1100    #[test]
1101    fn returns_some_on_unix() {
1102      #[cfg(any(target_os = "linux", target_os = "macos"))]
1103      {
1104        let result = fonts();
1105        assert!(result.is_some());
1106        if let Some(path) = result {
1107          assert!(path.is_absolute());
1108        }
1109      }
1110    }
1111  }
1112
1113  mod home {
1114    use super::*;
1115
1116    #[test]
1117    fn returns_absolute_path_when_available() {
1118      let result = home();
1119      if let Some(home_path) = result {
1120        assert!(home_path.is_absolute());
1121      }
1122    }
1123
1124    #[test]
1125    fn delegates_to_std_env_home_dir() {
1126      let std_result = std::env::home_dir();
1127      let our_result = home();
1128      assert_eq!(std_result, our_result);
1129    }
1130  }
1131
1132  mod music {
1133    use temp_env::{with_var, with_var_unset};
1134
1135    use super::*;
1136
1137    #[test]
1138    fn respects_xdg_music_dir() {
1139      let test_path = if cfg!(windows) {
1140        "C:\\test\\music"
1141      } else {
1142        "/test/music"
1143      };
1144      with_var("XDG_MUSIC_DIR", Some(test_path), || {
1145        let result = music();
1146        assert_eq!(result, Some(PathBuf::from(test_path)));
1147      });
1148    }
1149
1150    #[test]
1151    fn ignores_relative_xdg_music_dir() {
1152      with_var("XDG_MUSIC_DIR", Some("relative/music"), || {
1153        let result = music();
1154        if let Some(path) = result {
1155          assert!(path.is_absolute());
1156        }
1157      });
1158    }
1159
1160    #[test]
1161    fn uses_platform_default_when_xdg_unset() {
1162      with_var_unset("XDG_MUSIC_DIR", || {
1163        let result = music();
1164        if let Some(music_path) = result {
1165          assert!(music_path.is_absolute());
1166          assert!(music_path.to_string_lossy().ends_with("Music"));
1167        }
1168      });
1169    }
1170  }
1171
1172  mod pictures {
1173    use temp_env::{with_var, with_var_unset};
1174
1175    use super::*;
1176
1177    #[test]
1178    fn respects_xdg_pictures_dir() {
1179      let test_path = if cfg!(windows) {
1180        "C:\\test\\pictures"
1181      } else {
1182        "/test/pictures"
1183      };
1184      with_var("XDG_PICTURES_DIR", Some(test_path), || {
1185        let result = pictures();
1186        assert_eq!(result, Some(PathBuf::from(test_path)));
1187      });
1188    }
1189
1190    #[test]
1191    fn ignores_relative_xdg_pictures_dir() {
1192      with_var("XDG_PICTURES_DIR", Some("relative/pictures"), || {
1193        let result = pictures();
1194        if let Some(path) = result {
1195          assert!(path.is_absolute());
1196        }
1197      });
1198    }
1199
1200    #[test]
1201    fn uses_platform_default_when_xdg_unset() {
1202      with_var_unset("XDG_PICTURES_DIR", || {
1203        let result = pictures();
1204        if let Some(pictures_path) = result {
1205          assert!(pictures_path.is_absolute());
1206          assert!(pictures_path.to_string_lossy().ends_with("Pictures"));
1207        }
1208      });
1209    }
1210  }
1211
1212  mod preferences {
1213    use super::*;
1214
1215    #[test]
1216    fn returns_platform_specific_path() {
1217      let result = preferences();
1218      if let Some(preferences_path) = result {
1219        assert!(preferences_path.is_absolute());
1220
1221        #[cfg(target_os = "macos")]
1222        assert!(preferences_path.to_string_lossy().ends_with("Library/Preferences"));
1223
1224        #[cfg(not(target_os = "macos"))]
1225        assert_eq!(Some(preferences_path), config_home());
1226      }
1227    }
1228
1229    #[test]
1230    fn matches_config_home_on_non_macos() {
1231      #[cfg(not(target_os = "macos"))]
1232      {
1233        let preferences = preferences();
1234        let config_home = config_home();
1235        assert_eq!(preferences, config_home);
1236      }
1237    }
1238
1239    #[test]
1240    fn uses_library_preferences_on_macos() {
1241      #[cfg(target_os = "macos")]
1242      {
1243        let result = preferences();
1244        if let Some(path) = result {
1245          assert!(path.to_string_lossy().ends_with("Library/Preferences"));
1246        }
1247      }
1248    }
1249  }
1250
1251  mod publicshare {
1252    use temp_env::{with_var, with_var_unset};
1253
1254    use super::*;
1255
1256    #[test]
1257    fn respects_xdg_publicshare_dir() {
1258      let test_path = if cfg!(windows) {
1259        "C:\\test\\public"
1260      } else {
1261        "/test/public"
1262      };
1263      with_var("XDG_PUBLICSHARE_DIR", Some(test_path), || {
1264        let result = publicshare();
1265        assert_eq!(result, Some(PathBuf::from(test_path)));
1266      });
1267    }
1268
1269    #[test]
1270    fn ignores_relative_xdg_publicshare_dir() {
1271      with_var("XDG_PUBLICSHARE_DIR", Some("relative/public"), || {
1272        let result = publicshare();
1273        if let Some(path) = result {
1274          assert!(path.is_absolute());
1275        }
1276      });
1277    }
1278
1279    #[test]
1280    fn uses_platform_default_when_xdg_unset() {
1281      with_var_unset("XDG_PUBLICSHARE_DIR", || {
1282        let result = publicshare();
1283        if let Some(public_path) = result {
1284          assert!(public_path.is_absolute());
1285
1286          #[cfg(target_os = "windows")]
1287          assert_eq!(public_path, PathBuf::from("C:\\Users\\Public"));
1288
1289          #[cfg(any(target_os = "linux", target_os = "macos"))]
1290          assert!(public_path.to_string_lossy().ends_with("Public"));
1291        }
1292      });
1293    }
1294
1295    #[test]
1296    fn uses_system_public_on_windows() {
1297      #[cfg(target_os = "windows")]
1298      {
1299        with_var_unset("XDG_PUBLICSHARE_DIR", || {
1300          let result = publicshare();
1301          assert_eq!(result, Some(PathBuf::from("C:\\Users\\Public")));
1302        });
1303      }
1304    }
1305  }
1306
1307  mod runtime {
1308    use temp_env::{with_var, with_var_unset};
1309
1310    use super::*;
1311
1312    #[test]
1313    fn respects_xdg_runtime_dir() {
1314      let test_path = if cfg!(windows) {
1315        "C:\\test\\runtime"
1316      } else {
1317        "/test/runtime"
1318      };
1319      with_var("XDG_RUNTIME_DIR", Some(test_path), || {
1320        let result = runtime();
1321        assert_eq!(result, Some(PathBuf::from(test_path)));
1322      });
1323    }
1324
1325    #[test]
1326    fn ignores_relative_xdg_runtime_dir() {
1327      with_var("XDG_RUNTIME_DIR", Some("relative/runtime"), || {
1328        let result = runtime();
1329        if let Some(path) = result {
1330          assert!(path.is_absolute());
1331        }
1332      });
1333    }
1334
1335    #[test]
1336    fn uses_platform_default_when_xdg_unset() {
1337      with_var_unset("XDG_RUNTIME_DIR", || {
1338        let result = runtime();
1339        if let Some(runtime_path) = result {
1340          assert!(runtime_path.is_absolute());
1341
1342          #[cfg(any(target_os = "linux", target_os = "macos"))]
1343          {
1344            let path_str = runtime_path.to_string_lossy();
1345            assert!(path_str.contains("tmp") || path_str.starts_with("/var/folders"));
1346          }
1347
1348          #[cfg(target_os = "windows")]
1349          {
1350            if let Ok(temp) = env::var("TEMP") {
1351              assert_eq!(runtime_path, PathBuf::from(temp));
1352            }
1353          }
1354        }
1355      });
1356    }
1357
1358    #[test]
1359    fn falls_back_to_tmp_on_unix() {
1360      #[cfg(any(target_os = "linux", target_os = "macos"))]
1361      {
1362        with_var_unset("XDG_RUNTIME_DIR", || {
1363          with_var_unset("TMPDIR", || {
1364            let result = runtime();
1365            assert_eq!(result, Some(PathBuf::from("/tmp")));
1366          });
1367        });
1368      }
1369    }
1370  }
1371
1372  mod state_home {
1373    use temp_env::{with_var, with_var_unset};
1374
1375    use super::*;
1376
1377    #[test]
1378    fn respects_xdg_state_home() {
1379      let test_path = if cfg!(windows) {
1380        "C:\\test\\state"
1381      } else {
1382        "/test/state"
1383      };
1384      with_var("XDG_STATE_HOME", Some(test_path), || {
1385        let result = state_home();
1386        assert_eq!(result, Some(PathBuf::from(test_path)));
1387      });
1388    }
1389
1390    #[test]
1391    fn ignores_relative_xdg_state_home() {
1392      with_var("XDG_STATE_HOME", Some("relative/state"), || {
1393        let result = state_home();
1394        if let Some(path) = result {
1395          assert!(path.is_absolute());
1396        }
1397      });
1398    }
1399
1400    #[test]
1401    fn uses_platform_default_when_xdg_unset() {
1402      with_var_unset("XDG_STATE_HOME", || {
1403        let result = state_home();
1404        if let Some(state_path) = result {
1405          assert!(state_path.is_absolute());
1406
1407          #[cfg(target_os = "linux")]
1408          assert!(state_path.to_string_lossy().ends_with(".local/state"));
1409
1410          #[cfg(target_os = "macos")]
1411          assert!(state_path.to_string_lossy().contains("Library/Application Support"));
1412
1413          #[cfg(target_os = "windows")]
1414          {
1415            if let Ok(localappdata) = env::var("LOCALAPPDATA") {
1416              assert_eq!(state_path, PathBuf::from(localappdata));
1417            }
1418          }
1419        }
1420      });
1421    }
1422  }
1423
1424  mod templates {
1425    use temp_env::{with_var, with_var_unset};
1426
1427    use super::*;
1428
1429    #[test]
1430    fn respects_xdg_templates_dir() {
1431      let test_path = if cfg!(windows) {
1432        "C:\\test\\templates"
1433      } else {
1434        "/test/templates"
1435      };
1436      with_var("XDG_TEMPLATES_DIR", Some(test_path), || {
1437        let result = templates();
1438        assert_eq!(result, Some(PathBuf::from(test_path)));
1439      });
1440    }
1441
1442    #[test]
1443    fn ignores_relative_xdg_templates_dir() {
1444      with_var("XDG_TEMPLATES_DIR", Some("relative/templates"), || {
1445        let result = templates();
1446        if let Some(path) = result {
1447          assert!(path.is_absolute());
1448        }
1449      });
1450    }
1451
1452    #[test]
1453    fn uses_platform_default_when_xdg_unset() {
1454      with_var_unset("XDG_TEMPLATES_DIR", || {
1455        let result = templates();
1456        if let Some(templates_path) = result {
1457          assert!(templates_path.is_absolute());
1458          assert!(templates_path.to_string_lossy().ends_with("Templates"));
1459        }
1460      });
1461    }
1462  }
1463
1464  mod videos {
1465    use temp_env::{with_var, with_var_unset};
1466
1467    use super::*;
1468
1469    #[test]
1470    fn respects_xdg_videos_dir() {
1471      let test_path = if cfg!(windows) {
1472        "C:\\test\\videos"
1473      } else {
1474        "/test/videos"
1475      };
1476      with_var("XDG_VIDEOS_DIR", Some(test_path), || {
1477        let result = videos();
1478        assert_eq!(result, Some(PathBuf::from(test_path)));
1479      });
1480    }
1481
1482    #[test]
1483    fn ignores_relative_xdg_videos_dir() {
1484      with_var("XDG_VIDEOS_DIR", Some("relative/videos"), || {
1485        let result = videos();
1486        if let Some(path) = result {
1487          assert!(path.is_absolute());
1488        }
1489      });
1490    }
1491
1492    #[test]
1493    fn uses_platform_default_when_xdg_unset() {
1494      with_var_unset("XDG_VIDEOS_DIR", || {
1495        let result = videos();
1496        if let Some(videos_path) = result {
1497          assert!(videos_path.is_absolute());
1498
1499          #[cfg(target_os = "linux")]
1500          assert!(videos_path.to_string_lossy().ends_with("Videos"));
1501
1502          #[cfg(target_os = "macos")]
1503          assert!(videos_path.to_string_lossy().ends_with("Movies"));
1504
1505          #[cfg(target_os = "windows")]
1506          assert!(videos_path.to_string_lossy().ends_with("Videos"));
1507        }
1508      });
1509    }
1510  }
1511
1512  macro_rules! xdg_or_tests {
1513    ($mod_name:ident, $fn_name:ident, $xdg_var:expr) => {
1514      mod $mod_name {
1515        use temp_env::{with_var, with_var_unset};
1516
1517        use super::*;
1518
1519        #[test]
1520        fn returns_xdg_value_when_set() {
1521          let test_path = if cfg!(windows) {
1522            "C:\\test\\xdg_or"
1523          } else {
1524            "/test/xdg_or"
1525          };
1526          with_var($xdg_var, Some(test_path), || {
1527            let result = $fn_name(PathBuf::from("/fallback"));
1528            assert_eq!(result, PathBuf::from(test_path));
1529          });
1530        }
1531
1532        #[test]
1533        fn returns_default_when_xdg_unset() {
1534          let fallback = if cfg!(windows) {
1535            "C:\\fallback"
1536          } else {
1537            "/fallback"
1538          };
1539          with_var_unset($xdg_var, || {
1540            let result = $fn_name(fallback);
1541            assert_eq!(result, PathBuf::from(fallback));
1542          });
1543        }
1544
1545        #[test]
1546        fn returns_default_when_xdg_relative() {
1547          let fallback = if cfg!(windows) {
1548            "C:\\fallback"
1549          } else {
1550            "/fallback"
1551          };
1552          with_var($xdg_var, Some("relative/path"), || {
1553            let result = $fn_name(fallback);
1554            assert_eq!(result, PathBuf::from(fallback));
1555          });
1556        }
1557      }
1558    };
1559  }
1560
1561  xdg_or_tests!(xdg_bin_home_or, xdg_bin_home_or, "XDG_BIN_HOME");
1562  xdg_or_tests!(xdg_cache_home_or, xdg_cache_home_or, "XDG_CACHE_HOME");
1563  xdg_or_tests!(xdg_config_home_or, xdg_config_home_or, "XDG_CONFIG_HOME");
1564  xdg_or_tests!(xdg_data_home_or, xdg_data_home_or, "XDG_DATA_HOME");
1565  xdg_or_tests!(xdg_desktop_dir_or, xdg_desktop_dir_or, "XDG_DESKTOP_DIR");
1566  xdg_or_tests!(xdg_documents_dir_or, xdg_documents_dir_or, "XDG_DOCUMENTS_DIR");
1567  xdg_or_tests!(xdg_download_dir_or, xdg_download_dir_or, "XDG_DOWNLOAD_DIR");
1568  xdg_or_tests!(xdg_music_dir_or, xdg_music_dir_or, "XDG_MUSIC_DIR");
1569  xdg_or_tests!(xdg_pictures_dir_or, xdg_pictures_dir_or, "XDG_PICTURES_DIR");
1570  xdg_or_tests!(xdg_publicshare_dir_or, xdg_publicshare_dir_or, "XDG_PUBLICSHARE_DIR");
1571  xdg_or_tests!(xdg_runtime_dir_or, xdg_runtime_dir_or, "XDG_RUNTIME_DIR");
1572  xdg_or_tests!(xdg_state_home_or, xdg_state_home_or, "XDG_STATE_HOME");
1573  xdg_or_tests!(xdg_templates_dir_or, xdg_templates_dir_or, "XDG_TEMPLATES_DIR");
1574  xdg_or_tests!(xdg_videos_dir_or, xdg_videos_dir_or, "XDG_VIDEOS_DIR");
1575}