etcetera/base_strategy/
xdg.rs

1use std::path::Path;
2use std::path::PathBuf;
3
4use crate::HomeDirError;
5
6/// This strategy implements the [XDG Base Directories Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html). It is the most common on Linux, but is increasingly being adopted elsewhere.
7///
8/// This initial example removes all the XDG environment variables to show the strategy’s use of the XDG default directories.
9///
10/// ```
11/// use etcetera::base_strategy::BaseStrategy;
12/// use etcetera::base_strategy::Xdg;
13/// use std::path::Path;
14///
15/// // Remove the environment variables that the strategy reads from.
16/// unsafe {
17/// std::env::remove_var("XDG_CONFIG_HOME");
18/// std::env::remove_var("XDG_DATA_HOME");
19/// std::env::remove_var("XDG_CACHE_HOME");
20/// std::env::remove_var("XDG_STATE_HOME");
21/// std::env::remove_var("XDG_RUNTIME_DIR");
22/// }
23///
24/// let base_strategy = Xdg::new().unwrap();
25///
26/// let home_dir = etcetera::home_dir().unwrap();
27///
28/// assert_eq!(
29///     base_strategy.home_dir(),
30///     &home_dir
31/// );
32/// assert_eq!(
33///     base_strategy.config_dir().strip_prefix(&home_dir),
34///     Ok(Path::new(".config/"))
35/// );
36/// assert_eq!(
37///     base_strategy.data_dir().strip_prefix(&home_dir),
38///     Ok(Path::new(".local/share/"))
39/// );
40/// assert_eq!(
41///     base_strategy.cache_dir().strip_prefix(&home_dir),
42///     Ok(Path::new(".cache/"))
43/// );
44/// assert_eq!(
45///     base_strategy.state_dir().unwrap().strip_prefix(&home_dir),
46///     Ok(Path::new(".local/state"))
47/// );
48/// assert_eq!(
49///     base_strategy.runtime_dir(),
50///     None
51/// );
52/// ```
53///
54/// This next example gives the environment variables values:
55///
56/// ```
57/// use etcetera::base_strategy::BaseStrategy;
58/// use etcetera::base_strategy::Xdg;
59/// use std::path::Path;
60///
61/// // We need to conditionally set these to ensure that they are absolute paths both on Windows and other systems.
62/// let config_path = if cfg!(windows) {
63///     "C:\\foo\\"
64/// } else {
65///     "/foo/"
66/// };
67/// let data_path = if cfg!(windows) {
68///     "C:\\bar\\"
69/// } else {
70///     "/bar/"
71/// };
72/// let cache_path = if cfg!(windows) {
73///     "C:\\baz\\"
74/// } else {
75///     "/baz/"
76/// };
77/// let state_path = if cfg!(windows) {
78///     "C:\\foobar\\"
79/// } else {
80///     "/foobar/"
81/// };
82/// let runtime_path = if cfg!(windows) {
83///     "C:\\qux\\"
84/// } else {
85///     "/qux/"
86/// };
87///
88/// unsafe {
89/// std::env::set_var("XDG_CONFIG_HOME", config_path);
90/// std::env::set_var("XDG_DATA_HOME", data_path);
91/// std::env::set_var("XDG_CACHE_HOME", cache_path);
92/// std::env::set_var("XDG_STATE_HOME", state_path);
93/// std::env::set_var("XDG_RUNTIME_DIR", runtime_path);
94/// }
95///
96/// let base_strategy = Xdg::new().unwrap();
97///
98/// assert_eq!(
99///     base_strategy.config_dir(),
100///     Path::new(config_path)
101/// );
102/// assert_eq!(
103///     base_strategy.data_dir(),
104///     Path::new(data_path)
105/// );
106/// assert_eq!(
107///     base_strategy.cache_dir(),
108///     Path::new(cache_path)
109/// );
110/// assert_eq!(
111///     base_strategy.state_dir().unwrap(),
112///     Path::new(state_path)
113/// );
114/// assert_eq!(
115///     base_strategy.runtime_dir().unwrap(),
116///     Path::new(runtime_path)
117/// );
118/// ```
119///
120/// The XDG spec requires that when the environment variables’ values are not absolute paths, their values should be ignored. This example exemplifies this behaviour:
121///
122/// ```
123/// use etcetera::base_strategy::BaseStrategy;
124/// use etcetera::base_strategy::Xdg;
125/// use std::path::Path;
126///
127/// // Remove the environment variables that the strategy reads from.
128/// unsafe {
129/// std::env::set_var("XDG_CONFIG_HOME", "foo/");
130/// std::env::set_var("XDG_DATA_HOME", "bar/");
131/// std::env::set_var("XDG_CACHE_HOME", "baz/");
132/// std::env::set_var("XDG_STATE_HOME", "foobar/");
133/// std::env::set_var("XDG_RUNTIME_DIR", "qux/");
134/// }
135///
136/// let base_strategy = Xdg::new().unwrap();
137///
138/// let home_dir = etcetera::home_dir().unwrap();
139///
140/// // We still get the default values.
141/// assert_eq!(
142///     base_strategy.config_dir().strip_prefix(&home_dir),
143///     Ok(Path::new(".config/"))
144/// );
145/// assert_eq!(
146///     base_strategy.data_dir().strip_prefix(&home_dir),
147///     Ok(Path::new(".local/share/"))
148/// );
149/// assert_eq!(
150///     base_strategy.cache_dir().strip_prefix(&home_dir),
151///     Ok(Path::new(".cache/"))
152/// );
153/// assert_eq!(
154///     base_strategy.state_dir().unwrap().strip_prefix(&home_dir),
155///     Ok(Path::new(".local/state/"))
156/// );
157/// assert_eq!(
158///     base_strategy.runtime_dir(),
159///     None
160/// );
161/// ```
162#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
163pub struct Xdg {
164    home_dir: PathBuf,
165}
166
167impl Xdg {
168    /// Create a new Xdg BaseStrategy
169    pub fn new() -> Result<Self, HomeDirError> {
170        Ok(Self {
171            home_dir: crate::home_dir()?,
172        })
173    }
174
175    fn env_var_or_none(env_var: &str) -> Option<PathBuf> {
176        std::env::var_os(env_var).and_then(|path| {
177            let path = PathBuf::from(path);
178
179            // Return None if the path obtained from the environment variable isn’t absolute.
180            if path.is_absolute() { Some(path) } else { None }
181        })
182    }
183
184    fn env_var_or_default(&self, env_var: &str, default: impl AsRef<Path>) -> PathBuf {
185        Self::env_var_or_none(env_var).unwrap_or_else(|| self.home_dir.join(default))
186    }
187}
188
189impl super::BaseStrategy for Xdg {
190    fn home_dir(&self) -> &Path {
191        &self.home_dir
192    }
193
194    fn config_dir(&self) -> PathBuf {
195        self.env_var_or_default("XDG_CONFIG_HOME", ".config/")
196    }
197
198    fn data_dir(&self) -> PathBuf {
199        self.env_var_or_default("XDG_DATA_HOME", ".local/share/")
200    }
201
202    fn cache_dir(&self) -> PathBuf {
203        self.env_var_or_default("XDG_CACHE_HOME", ".cache/")
204    }
205
206    fn state_dir(&self) -> Option<PathBuf> {
207        Some(self.env_var_or_default("XDG_STATE_HOME", ".local/state/"))
208    }
209
210    fn runtime_dir(&self) -> Option<PathBuf> {
211        Self::env_var_or_none("XDG_RUNTIME_DIR")
212    }
213}