Skip to main content

pidge_core/
paths.rs

1//! Where the tool keeps its files.
2//!
3//! Linux and macOS share one XDG layout, the same as Ailloy and every MKLab
4//! tool: config in `$XDG_CONFIG_HOME/<tool>` (default `~/.config/<tool>`) and
5//! cache in `$XDG_CACHE_HOME/<tool>` (default `~/.cache/<tool>`). Windows uses
6//! its native folders (`%APPDATA%`, `%LOCALAPPDATA%`). Deliberately not
7//! `dirs::config_dir()` on macOS, which is `~/Library/Application Support`.
8
9use std::path::PathBuf;
10
11/// Folder name under the config and cache roots.
12pub const TOOL: &str = "pidge";
13
14/// The tool's config directory (holds `config.yaml`).
15#[cfg(not(windows))]
16pub fn config_dir() -> Option<PathBuf> {
17    xdg_dir(
18        std::env::var_os("XDG_CONFIG_HOME"),
19        dirs::home_dir(),
20        ".config",
21    )
22}
23
24/// The tool's config directory (holds `config.yaml`): `%APPDATA%\<tool>`.
25#[cfg(windows)]
26pub fn config_dir() -> Option<PathBuf> {
27    dirs::config_dir().map(|d| d.join(TOOL))
28}
29
30/// The tool's cache directory (update check, other disposable state).
31#[cfg(not(windows))]
32pub fn cache_dir() -> Option<PathBuf> {
33    xdg_dir(
34        std::env::var_os("XDG_CACHE_HOME"),
35        dirs::home_dir(),
36        ".cache",
37    )
38}
39
40/// The tool's cache directory: `%LOCALAPPDATA%\<tool>`.
41#[cfg(windows)]
42pub fn cache_dir() -> Option<PathBuf> {
43    dirs::cache_dir().map(|d| d.join(TOOL))
44}
45
46/// `$XDG_*_HOME/<tool>` when that variable is an absolute path, else
47/// `~/<fallback>/<tool>`.
48#[cfg(not(windows))]
49fn xdg_dir(
50    xdg: Option<std::ffi::OsString>,
51    home: Option<PathBuf>,
52    fallback: &str,
53) -> Option<PathBuf> {
54    match xdg.map(PathBuf::from) {
55        Some(dir) if dir.is_absolute() => Some(dir.join(TOOL)),
56        _ => home.map(|h| h.join(fallback).join(TOOL)),
57    }
58}
59
60#[cfg(all(test, not(windows)))]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn an_absolute_xdg_variable_wins() {
66        let dir = xdg_dir(Some("/xdg".into()), Some("/home/k".into()), ".config");
67        assert_eq!(dir, Some(PathBuf::from("/xdg").join(TOOL)));
68    }
69
70    #[test]
71    fn otherwise_the_dot_folder_in_home() {
72        let config = xdg_dir(None, Some("/Users/k".into()), ".config");
73        assert_eq!(config, Some(PathBuf::from("/Users/k/.config").join(TOOL)));
74        let cache = xdg_dir(None, Some("/Users/k".into()), ".cache");
75        assert_eq!(cache, Some(PathBuf::from("/Users/k/.cache").join(TOOL)));
76    }
77
78    #[test]
79    fn empty_or_relative_xdg_variables_are_ignored() {
80        // The XDG Base Directory spec says to ignore relative paths.
81        for xdg in ["", "relative/dir"] {
82            let dir = xdg_dir(Some(xdg.into()), Some("/home/k".into()), ".config");
83            assert_eq!(
84                dir,
85                Some(PathBuf::from("/home/k/.config").join(TOOL)),
86                "{xdg:?}"
87            );
88        }
89    }
90
91    #[test]
92    fn no_home_and_no_xdg_is_none() {
93        assert_eq!(xdg_dir(None, None, ".config"), None);
94    }
95}