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}