1use std::path::{Path, PathBuf};
2
3pub struct BaseDirs {
5 home: PathBuf,
6 config_home: PathBuf,
7 cache_home: PathBuf,
8 data_home: PathBuf,
9 state_home: PathBuf,
10 runtime_dir: Option<PathBuf>,
11}
12
13impl BaseDirs {
14 pub fn new() -> Result<Self, std::io::Error> {
18 Self::internal_new(None)
19 }
20
21 pub fn with_prefix(prefix: impl Into<String>) -> Result<Self, std::io::Error> {
27 Self::internal_new(Some(prefix.into()))
28 }
29
30 pub fn home(&self) -> &Path {
33 &self.home
34 }
35
36 pub fn config_home(&self) -> &Path {
54 &self.config_home
55 }
56
57 pub fn cache_home(&self) -> &Path {
60 &self.cache_home
61 }
62
63 pub fn data_home(&self) -> &Path {
66 &self.data_home
67 }
68
69 pub fn state_home(&self) -> &Path {
72 &self.state_home
73 }
74
75 pub fn runtime_dir(&self) -> Option<&Path> {
78 self.runtime_dir.as_deref()
79 }
80
81 fn internal_new(prefix: Option<String>) -> Result<Self, std::io::Error> {
82 #[allow(deprecated)]
85 let tmp_home = std::env::home_dir().ok_or(std::io::Error::new(
86 std::io::ErrorKind::NotFound,
87 "Could not find home directory",
88 ))?;
89
90 Ok(Self {
91 config_home: Self::get_config_home(&tmp_home, &prefix),
92 cache_home: Self::get_cache_home(&tmp_home, &prefix),
93 data_home: Self::get_data_home(&tmp_home, &prefix),
94 state_home: Self::get_state_home(&tmp_home, &prefix),
95 runtime_dir: Self::get_runtime_dir(&prefix),
96 home: tmp_home,
97 })
98 }
99
100 fn get_cache_home(home: &Path, prefix: &Option<String>) -> PathBuf {
101 Self::get_dir(home, "XDG_CACHE_HOME", ".cache", prefix)
102 }
103
104 fn get_data_home(home: &Path, prefix: &Option<String>) -> PathBuf {
105 Self::get_dir(home, "XDG_DATA_HOME", ".local/share", prefix)
106 }
107
108 fn get_state_home(home: &Path, prefix: &Option<String>) -> PathBuf {
109 Self::get_dir(home, "XDG_STATE_HOME", ".local/state", prefix)
110 }
111
112 fn get_runtime_dir(prefix: &Option<String>) -> Option<PathBuf> {
113 let dir = std::env::var("XDG_RUNTIME_DIR").map(PathBuf::from).ok();
114 match (&dir, prefix) {
115 (Some(dir), Some(prefix)) => Some(dir.join(prefix)),
116 _ => dir, }
118 }
119
120 fn get_config_home(home: &Path, prefix: &Option<String>) -> PathBuf {
121 Self::get_dir(home, "XDG_CONFIG_HOME", ".config", prefix)
122 }
123
124 fn get_dir(home: &Path, xdg_var: &str, default: &str, prefix: &Option<String>) -> PathBuf {
125 let xdg_dir = std::env::var(xdg_var).ok();
126
127 let dir = if let Some(dir) = xdg_dir {
128 PathBuf::from(dir)
129 } else {
130 home.join(default)
131 };
132
133 match prefix {
134 Some(prefix) => dir.join(prefix),
135 None => dir,
136 }
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use crate::test_helper::{get_home, set_var, unset_var};
144 use serial_test::serial;
145
146 mod without_prefix {
147
148 use super::*;
149
150 #[test]
151 #[serial]
152 fn config_home_no_xdg_set_returns_correct_path() {
153 unset_var("XDG_CONFIG_HOME");
154 let base_dir = BaseDirs::new().unwrap();
155 assert_eq!(base_dir.config_home(), &get_home().join(".config"));
156 }
157
158 #[test]
159 #[serial]
160 fn config_home_xdg_set_returns_correct_path() {
161 set_var("XDG_CONFIG_HOME", "/tmp/config");
162 let base_dir = BaseDirs::new().unwrap();
163 assert_eq!(base_dir.config_home(), Path::new("/tmp/config"));
164 }
165
166 #[test]
167 #[serial]
168 fn cache_home_no_xdg_set_returns_correct_path() {
169 unset_var("XDG_CACHE_HOME");
170 let base_dir = BaseDirs::new().unwrap();
171 assert_eq!(base_dir.cache_home(), &get_home().join(".cache"));
172 }
173
174 #[test]
175 #[serial]
176 fn cache_home_xdg_set_returns_correct_path() {
177 set_var("XDG_CACHE_HOME", "/tmp/cache");
178 let base_dir = BaseDirs::new().unwrap();
179 assert_eq!(base_dir.cache_home(), Path::new("/tmp/cache"));
180 }
181
182 #[test]
183 #[serial]
184 fn data_home_no_xdg_set_returns_correct_path() {
185 unset_var("XDG_DATA_HOME");
186 let base_dir = BaseDirs::new().unwrap();
187 assert_eq!(base_dir.data_home(), &get_home().join(".local/share"));
188 }
189
190 #[test]
191 #[serial]
192 fn data_home_xdg_set_returns_correct_path() {
193 set_var("XDG_DATA_HOME", "/tmp/data");
194 let base_dir = BaseDirs::new().unwrap();
195 assert_eq!(base_dir.data_home(), Path::new("/tmp/data"));
196 }
197
198 #[test]
199 #[serial]
200 fn state_home_no_xdg_set_returns_correct_path() {
201 unset_var("XDG_STATE_HOME");
202 let base_dir = BaseDirs::new().unwrap();
203 assert_eq!(base_dir.state_home(), &get_home().join(".local/state"));
204 }
205
206 #[test]
207 #[serial]
208 fn state_home_xdg_set_returns_correct_path() {
209 set_var("XDG_STATE_HOME", "/tmp/state");
210 let base_dir = BaseDirs::new().unwrap();
211 assert_eq!(base_dir.state_home(), Path::new("/tmp/state"));
212 }
213
214 #[test]
215 #[serial]
216 fn runtime_dir_no_xdg_set_returns_none() {
217 unset_var("XDG_RUNTIME_DIR");
218 let base_dir = BaseDirs::new().unwrap();
219 assert_eq!(base_dir.runtime_dir(), None);
220 }
221
222 #[test]
223 #[serial]
224 fn runtime_dir_xdg_set_returns_correct_path() {
225 set_var("XDG_RUNTIME_DIR", "/tmp/runtime");
226 let base_dir = BaseDirs::new().unwrap();
227 assert_eq!(base_dir.runtime_dir(), Some(Path::new("/tmp/runtime")));
228 }
229 }
230
231 mod with_prefix {
232 use super::*;
233
234 #[test]
235 #[serial]
236 fn config_home_no_xdg_set_returns_correct_path() {
237 unset_var("XDG_CONFIG_HOME");
238 let base_dir = BaseDirs::with_prefix("prefix").unwrap();
239 assert_eq!(base_dir.config_home(), &get_home().join(".config/prefix"));
240 }
241
242 #[test]
243 #[serial]
244 fn config_home_xdg_set_returns_correct_path() {
245 set_var("XDG_CONFIG_HOME", "/tmp/config");
246 let base_dir = BaseDirs::with_prefix("prefix").unwrap();
247 assert_eq!(base_dir.config_home(), Path::new("/tmp/config/prefix"));
248 }
249
250 #[test]
251 #[serial]
252 fn cache_home_no_xdg_set_returns_correct_path() {
253 unset_var("XDG_CACHE_HOME");
254 let base_dir = BaseDirs::with_prefix("prefix").unwrap();
255 assert_eq!(base_dir.cache_home(), &get_home().join(".cache/prefix"));
256 }
257
258 #[test]
259 #[serial]
260 fn cache_home_xdg_set_returns_correct_path() {
261 set_var("XDG_CACHE_HOME", "/tmp/cache");
262 let base_dir = BaseDirs::with_prefix("prefix").unwrap();
263 assert_eq!(base_dir.cache_home(), Path::new("/tmp/cache/prefix"));
264 }
265
266 #[test]
267 #[serial]
268 fn data_home_no_xdg_set_returns_correct_path() {
269 unset_var("XDG_DATA_HOME");
270 let base_dir = BaseDirs::with_prefix("prefix").unwrap();
271 assert_eq!(
272 base_dir.data_home(),
273 &get_home().join(".local/share/prefix")
274 );
275 }
276
277 #[test]
278 #[serial]
279 fn data_home_xdg_set_returns_correct_path() {
280 set_var("XDG_DATA_HOME", "/tmp/data");
281 let base_dir = BaseDirs::with_prefix("prefix").unwrap();
282 assert_eq!(base_dir.data_home(), Path::new("/tmp/data/prefix"));
283 }
284
285 #[test]
286 #[serial]
287 fn state_home_no_xdg_set_returns_correct_path() {
288 unset_var("XDG_STATE_HOME");
289 let base_dir = BaseDirs::with_prefix("prefix").unwrap();
290 assert_eq!(
291 base_dir.state_home(),
292 &get_home().join(".local/state/prefix")
293 );
294 }
295
296 #[test]
297 #[serial]
298 fn state_home_xdg_set_returns_correct_path() {
299 set_var("XDG_STATE_HOME", "/tmp/state");
300 let base_dir = BaseDirs::with_prefix("prefix").unwrap();
301 assert_eq!(base_dir.state_home(), Path::new("/tmp/state/prefix"));
302 }
303
304 #[test]
305 #[serial]
306 fn runtime_dir_no_xdg_set_returns_none() {
307 unset_var("XDG_RUNTIME_DIR");
308 let base_dir = BaseDirs::with_prefix("prefix").unwrap();
309 assert_eq!(base_dir.runtime_dir(), None);
310 }
311
312 #[test]
313 #[serial]
314 fn runtime_dir_xdg_set_returns_correct_path() {
315 set_var("XDG_RUNTIME_DIR", "/tmp/runtime");
316 let base_dir = BaseDirs::with_prefix("prefix").unwrap();
317 assert_eq!(
318 base_dir.runtime_dir(),
319 Some(Path::new("/tmp/runtime/prefix"))
320 );
321 }
322 }
323}