1 #![cfg(any(unix, target_os = "redox"))]
2
3 extern crate dirs;
4
5 use std::fmt;
6 use std::convert;
7 use std::error;
8 use std::io;
9 use std::env;
10 use std::fs;
11 use std::path::{Path, PathBuf};
12 use std::ffi::OsString;
13
14 use std::os::unix::fs::PermissionsExt;
15
16 use BaseDirectoriesErrorKind::*;
17 use BaseDirectoriesError as Error;
18
19 /// BaseDirectories allows to look up paths to configuration, data,
20 /// cache and runtime files in well-known locations according to
21 /// the [X Desktop Group Base Directory specification][xdg-basedir].
22 /// [xdg-basedir]: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
23 ///
24 /// The Base Directory specification defines five kinds of files:
25 ///
26 /// * **Configuration files** store the application's settings and
27 /// are often modified during runtime;
28 /// * **Data files** store supplementary data, such as graphic assets,
29 /// precomputed tables, documentation, or architecture-independent
30 /// source code;
31 /// * **Cache files** store non-essential, transient data that provides
32 /// a runtime speedup;
33 /// * **State files** store logs, history, recently used files and application
34 /// state (window size, open files, unsaved changes, …);
35 /// * **Runtime files** include filesystem objects such are sockets or
36 /// named pipes that are used for communication internal to the application.
37 /// Runtime files must not be accessible to anyone except current user.
38 ///
39 /// # Examples
40 ///
41 /// To configure paths for application `myapp`:
42 ///
43 /// ```
44 /// extern crate xdg;
45 /// let xdg_dirs = xdg::BaseDirectories::with_prefix("myapp").unwrap();
46 /// ```
47 ///
48 /// To store configuration:
49 ///
50 /// ```
51 /// let config_path = xdg_dirs.place_config_file("config.ini")
52 /// .expect("cannot create configuration directory");
53 /// let mut config_file = File::create(config_path)?;
54 /// write!(&mut config_file, "configured = 1")?;
55 /// ```
56 ///
57 /// The `config.ini` file will appear in the proper location for desktop
58 /// configuration files, most likely `~/.config/myapp/config.ini`.
59 /// The leading directories will be automatically created.
60 ///
61 /// To retrieve supplementary data:
62 ///
63 /// ```
64 /// let logo_path = xdg_dirs.find_data_file("logo.png")
65 /// .expect("application data not present");
66 /// let mut logo_file = File::open(logo_path)?;
67 /// let mut logo = Vec::new();
68 /// logo_file.read_to_end(&mut logo)?;
69 /// ```
70 ///
71 /// The `logo.png` will be searched in the proper locations for
72 /// supplementary data files, most likely `~/.local/share/myapp/logo.png`,
73 /// then `/usr/local/share/myapp/logo.png` and `/usr/share/myapp/logo.png`.
74 #[derive(Debug, Clone)]
75 pub struct BaseDirectories {
76 shared_prefix: PathBuf,
77 user_prefix: PathBuf,
78 data_home: PathBuf,
79 config_home: PathBuf,
80 cache_home: PathBuf,
81 state_home: PathBuf,
82 data_dirs: Vec<PathBuf>,
83 config_dirs: Vec<PathBuf>,
84 runtime_dir: Option<PathBuf>,
85 }
86
87 pub struct BaseDirectoriesError {
88 kind: BaseDirectoriesErrorKind,
89 }
90
91 impl BaseDirectoriesError {
new(kind: BaseDirectoriesErrorKind) -> BaseDirectoriesError92 fn new(kind: BaseDirectoriesErrorKind) -> BaseDirectoriesError {
93 BaseDirectoriesError {
94 kind: kind,
95 }
96 }
97 }
98
99 impl fmt::Debug for BaseDirectoriesError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result100 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
101 self.kind.fmt(f)
102 }
103 }
104
105 impl error::Error for BaseDirectoriesError {
description(&self) -> &str106 fn description(&self) -> &str {
107 match self.kind {
108 HomeMissing => "$HOME must be set",
109 XdgRuntimeDirInaccessible(_, _) =>
110 "$XDG_RUNTIME_DIR must be accessible by the current user",
111 XdgRuntimeDirInsecure(_, _) =>
112 "$XDG_RUNTIME_DIR must be secure: have permissions 0700",
113 XdgRuntimeDirMissing =>
114 "$XDG_RUNTIME_DIR is not set",
115 }
116 }
cause(&self) -> Option<&dyn error::Error>117 fn cause(&self) -> Option<&dyn error::Error> {
118 match self.kind {
119 XdgRuntimeDirInaccessible(_, ref e) => Some(e),
120 _ => None,
121 }
122 }
123 }
124
125 impl fmt::Display for BaseDirectoriesError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result126 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127 match self.kind {
128 HomeMissing => write!(f, "{}", self.to_string()),
129 XdgRuntimeDirInaccessible(ref dir, ref error) => {
130 write!(f, "$XDG_RUNTIME_DIR (`{}`) must be accessible \
131 by the current user (error: {})", dir.display(), error)
132 },
133 XdgRuntimeDirInsecure(ref dir, permissions) => {
134 write!(f, "$XDG_RUNTIME_DIR (`{}`) must be secure: must have \
135 permissions 0o700, got {}", dir.display(), permissions)
136 },
137 XdgRuntimeDirMissing => {
138 write!(f, "$XDG_RUNTIME_DIR must be set")
139 },
140 }
141 }
142 }
143
144 impl convert::From<BaseDirectoriesError> for io::Error {
from(error: BaseDirectoriesError) -> io::Error145 fn from(error: BaseDirectoriesError) -> io::Error {
146 match error.kind {
147 HomeMissing | XdgRuntimeDirMissing =>
148 io::Error::new(io::ErrorKind::NotFound, error),
149 _ => io::Error::new(io::ErrorKind::Other, error)
150 }
151 }
152
153 }
154
155 #[derive(Copy, Clone)]
156 struct Permissions(u32);
157
158 impl fmt::Debug for Permissions {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result159 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
160 let Permissions(p) = *self;
161 write!(f, "{:#05o}", p)
162 }
163 }
164
165 impl fmt::Display for Permissions {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result166 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
167 fmt::Debug::fmt(self, f)
168 }
169 }
170
171 #[derive(Debug)]
172 enum BaseDirectoriesErrorKind {
173 HomeMissing,
174 XdgRuntimeDirInaccessible(PathBuf, io::Error),
175 XdgRuntimeDirInsecure(PathBuf, Permissions),
176 XdgRuntimeDirMissing,
177 }
178
179 impl BaseDirectories {
180 /// Reads the process environment, determines the XDG base directories,
181 /// and returns a value that can be used for lookup.
182 /// The following environment variables are examined:
183 ///
184 /// * `HOME`; if not set: use the same fallback as `dirs::home_dir()`;
185 /// if still not available: return an error.
186 /// * `XDG_DATA_HOME`; if not set: assumed to be `$HOME/.local/share`.
187 /// * `XDG_CONFIG_HOME`; if not set: assumed to be `$HOME/.config`.
188 /// * `XDG_CACHE_HOME`; if not set: assumed to be `$HOME/.cache`.
189 /// * `XDG_STATE_HOME`; if not set: assumed to be `$HOME/.local/state`.
190 /// * `XDG_DATA_DIRS`; if not set: assumed to be `/usr/local/share:/usr/share`.
191 /// * `XDG_CONFIG_DIRS`; if not set: assumed to be `/etc/xdg`.
192 /// * `XDG_RUNTIME_DIR`; if not accessible or permissions are not `0700`:
193 /// record as inaccessible (can be queried with
194 /// [has_runtime_directory](method.has_runtime_directory)).
195 ///
196 /// As per specification, if an environment variable contains a relative path,
197 /// the behavior is the same as if it was not set.
new() -> Result<BaseDirectories, BaseDirectoriesError>198 pub fn new() -> Result<BaseDirectories, BaseDirectoriesError> {
199 BaseDirectories::with_env("", "", &|name| env::var_os(name))
200 }
201
202 /// Same as [`new()`](#method.new), but `prefix` is implicitly prepended to
203 /// every path that is looked up.
with_prefix<P>(prefix: P) -> Result<BaseDirectories, BaseDirectoriesError> where P: AsRef<Path>204 pub fn with_prefix<P>(prefix: P) -> Result<BaseDirectories, BaseDirectoriesError>
205 where P: AsRef<Path> {
206 BaseDirectories::with_env(prefix, "", &|name| env::var_os(name))
207 }
208
209 /// Same as [`with_prefix()`](#method.with_prefix),
210 /// with `profile` also implicitly prepended to every path that is looked up,
211 /// but only for user-specific directories.
212 ///
213 /// This allows each user to have mutliple "profiles" with different user-specific data.
214 ///
215 /// For example:
216 ///
217 /// ```rust
218 /// let dirs = BaseDirectories::with_profile("program-name", "profile-name")
219 /// .unwrap();
220 /// dirs.find_data_file("bar.jpg");
221 /// dirs.find_config_file("foo.conf");
222 /// ```
223 ///
224 /// will find `/usr/share/program-name/bar.jpg` (without `profile-name`)
225 /// and `~/.config/program-name/profile-name/foo.conf`.
with_profile<P1, P2>(prefix: P1, profile: P2) -> Result<BaseDirectories, BaseDirectoriesError> where P1: AsRef<Path>, P2: AsRef<Path>226 pub fn with_profile<P1, P2>(prefix: P1, profile: P2)
227 -> Result<BaseDirectories, BaseDirectoriesError>
228 where P1: AsRef<Path>, P2: AsRef<Path> {
229 BaseDirectories::with_env(prefix, profile, &|name| env::var_os(name))
230 }
231
with_env<P1, P2, T: ?Sized>(prefix: P1, profile: P2, env_var: &T) -> Result<BaseDirectories, BaseDirectoriesError> where P1: AsRef<Path>, P2: AsRef<Path>, T: Fn(&str) -> Option<OsString>232 fn with_env<P1, P2, T: ?Sized>(prefix: P1, profile: P2, env_var: &T)
233 -> Result<BaseDirectories, BaseDirectoriesError>
234 where P1: AsRef<Path>, P2: AsRef<Path>, T: Fn(&str) -> Option<OsString> {
235 BaseDirectories::with_env_impl(prefix.as_ref(), profile.as_ref(), env_var)
236 }
237
with_env_impl<T: ?Sized>(prefix: &Path, profile: &Path, env_var: &T) -> Result<BaseDirectories, BaseDirectoriesError> where T: Fn(&str) -> Option<OsString>238 fn with_env_impl<T: ?Sized>(prefix: &Path, profile: &Path, env_var: &T)
239 -> Result<BaseDirectories, BaseDirectoriesError>
240 where T: Fn(&str) -> Option<OsString> {
241 fn abspath(path: OsString) -> Option<PathBuf> {
242 let path = PathBuf::from(path);
243 if path.is_absolute() {
244 Some(path)
245 } else {
246 None
247 }
248 }
249
250 fn abspaths(paths: OsString) -> Option<Vec<PathBuf>> {
251 let paths = env::split_paths(&paths)
252 .map(PathBuf::from)
253 .filter(|ref path| path.is_absolute())
254 .collect::<Vec<_>>();
255 if paths.is_empty() {
256 None
257 } else {
258 Some(paths)
259 }
260 }
261
262 let home = dirs::home_dir().ok_or(Error::new(HomeMissing))?;
263
264 let data_home = env_var("XDG_DATA_HOME")
265 .and_then(abspath)
266 .unwrap_or(home.join(".local/share"));
267 let config_home = env_var("XDG_CONFIG_HOME")
268 .and_then(abspath)
269 .unwrap_or(home.join(".config"));
270 let cache_home = env_var("XDG_CACHE_HOME")
271 .and_then(abspath)
272 .unwrap_or(home.join(".cache"));
273 let state_home = env_var("XDG_STATE_HOME")
274 .and_then(abspath)
275 .unwrap_or(home.join(".local/state"));
276 let data_dirs = env_var("XDG_DATA_DIRS")
277 .and_then(abspaths)
278 .unwrap_or(vec![PathBuf::from("/usr/local/share"),
279 PathBuf::from("/usr/share")]);
280 let config_dirs = env_var("XDG_CONFIG_DIRS")
281 .and_then(abspaths)
282 .unwrap_or(vec![PathBuf::from("/etc/xdg")]);
283 let runtime_dir = env_var("XDG_RUNTIME_DIR")
284 .and_then(abspath); // optional
285
286 let prefix = PathBuf::from(prefix);
287 Ok(BaseDirectories {
288 user_prefix: prefix.join(profile),
289 shared_prefix: prefix,
290 data_home,
291 config_home,
292 cache_home,
293 state_home,
294 data_dirs,
295 config_dirs,
296 runtime_dir,
297 })
298 }
299
300 /// Returns the user-specific runtime directory (set by `XDG_RUNTIME_DIR`).
get_runtime_directory(&self) -> Result<&PathBuf, BaseDirectoriesError>301 pub fn get_runtime_directory(&self) -> Result<&PathBuf, BaseDirectoriesError> {
302 if let Some(ref runtime_dir) = self.runtime_dir {
303 // If XDG_RUNTIME_DIR is in the environment but not secure,
304 // do not allow recovery.
305 fs::read_dir(runtime_dir).map_err(|e| {
306 Error::new(XdgRuntimeDirInaccessible(runtime_dir.clone(), e))
307 })?;
308 let permissions = fs::metadata(runtime_dir).map_err(|e| {
309 Error::new(XdgRuntimeDirInaccessible(runtime_dir.clone(), e))
310 })?.permissions().mode() as u32;
311 if permissions & 0o077 != 0 {
312 Err(Error::new(XdgRuntimeDirInsecure(runtime_dir.clone(),
313 Permissions(permissions))))
314 } else {
315 Ok(&runtime_dir)
316 }
317 } else {
318 Err(Error::new(XdgRuntimeDirMissing))
319 }
320 }
321
322 /// Returns `true` if `XDG_RUNTIME_DIR` is available, `false` otherwise.
has_runtime_directory(&self) -> bool323 pub fn has_runtime_directory(&self) -> bool {
324 match self.get_runtime_directory() {
325 Ok(_) => true,
326 _ => false
327 }
328 }
329
330 /// Like [`place_config_file()`](#method.place_config_file), but does
331 /// not create any directories.
get_config_file<P>(&self, path: P) -> PathBuf where P: AsRef<Path>332 pub fn get_config_file<P>(&self, path: P) -> PathBuf
333 where P: AsRef<Path> {
334 self.config_home.join(self.user_prefix.join(path))
335 }
336
337 /// Like [`place_data_file()`](#method.place_data_file), but does
338 /// not create any directories.
get_data_file<P>(&self, path: P) -> PathBuf where P: AsRef<Path>339 pub fn get_data_file<P>(&self, path: P) -> PathBuf
340 where P: AsRef<Path> {
341 self.data_home.join(self.user_prefix.join(path))
342 }
343
344 /// Like [`place_cache_file()`](#method.place_cache_file), but does
345 /// not create any directories.
get_cache_file<P>(&self, path: P) -> PathBuf where P: AsRef<Path>346 pub fn get_cache_file<P>(&self, path: P) -> PathBuf
347 where P: AsRef<Path> {
348 self.cache_home.join(self.user_prefix.join(path))
349 }
350
351 /// Like [`place_state_file()`](#method.place_state_file), but does
352 /// not create any directories.
get_state_file<P>(&self, path: P) -> PathBuf where P: AsRef<Path>353 pub fn get_state_file<P>(&self, path: P) -> PathBuf
354 where P: AsRef<Path> {
355 self.state_home.join(self.user_prefix.join(path))
356 }
357
358 /// Like [`place_runtime_file()`](#method.place_runtime_file), but does
359 /// not create any directories.
360 /// If `XDG_RUNTIME_DIR` is not available, returns an error.
get_runtime_file<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>361 pub fn get_runtime_file<P>(&self, path: P) -> io::Result<PathBuf>
362 where P: AsRef<Path> {
363 let runtime_dir = self.get_runtime_directory()?;
364 Ok(runtime_dir.join(self.user_prefix.join(path)))
365 }
366
367 /// Given a relative path `path`, returns an absolute path in
368 /// `XDG_CONFIG_HOME` where a configuration file may be stored.
369 /// Leading directories in the returned path are pre-created;
370 /// if that is not possible, an error is returned.
place_config_file<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>371 pub fn place_config_file<P>(&self, path: P) -> io::Result<PathBuf>
372 where P: AsRef<Path> {
373 write_file(&self.config_home, self.user_prefix.join(path))
374 }
375
376 /// Like [`place_config_file()`](#method.place_config_file), but for
377 /// a data file in `XDG_DATA_HOME`.
place_data_file<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>378 pub fn place_data_file<P>(&self, path: P) -> io::Result<PathBuf>
379 where P: AsRef<Path> {
380 write_file(&self.data_home, self.user_prefix.join(path))
381 }
382
383 /// Like [`place_config_file()`](#method.place_config_file), but for
384 /// a cache file in `XDG_CACHE_HOME`.
place_cache_file<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>385 pub fn place_cache_file<P>(&self, path: P) -> io::Result<PathBuf>
386 where P: AsRef<Path> {
387 write_file(&self.cache_home, self.user_prefix.join(path))
388 }
389
390 /// Like [`place_config_file()`](#method.place_config_file), but for
391 /// an application state file in `XDG_STATE_HOME`.
place_state_file<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>392 pub fn place_state_file<P>(&self, path: P) -> io::Result<PathBuf>
393 where P: AsRef<Path> {
394 write_file(&self.state_home, self.user_prefix.join(path))
395 }
396
397 /// Like [`place_config_file()`](#method.place_config_file), but for
398 /// a runtime file in `XDG_RUNTIME_DIR`.
399 /// If `XDG_RUNTIME_DIR` is not available, returns an error.
place_runtime_file<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>400 pub fn place_runtime_file<P>(&self, path: P) -> io::Result<PathBuf>
401 where P: AsRef<Path> {
402 write_file(self.get_runtime_directory()?, self.user_prefix.join(path))
403 }
404
405 /// Given a relative path `path`, returns an absolute path to an existing
406 /// configuration file, or `None`. Searches `XDG_CONFIG_HOME` and then
407 /// `XDG_CONFIG_DIRS`.
find_config_file<P>(&self, path: P) -> Option<PathBuf> where P: AsRef<Path>408 pub fn find_config_file<P>(&self, path: P) -> Option<PathBuf>
409 where P: AsRef<Path> {
410 read_file(&self.config_home, &self.config_dirs,
411 &self.user_prefix, &self.shared_prefix, path.as_ref())
412 }
413
414 /// Given a relative path `path`, returns an iterator yielding absolute
415 /// paths to existing configuration files, in `XDG_CONFIG_DIRS` and
416 /// `XDG_CONFIG_HOME`. Paths are produced in order from lowest priority
417 /// to highest.
find_config_files<P>(&self, path: P) -> FileFindIterator where P: AsRef<Path>418 pub fn find_config_files<P>(&self, path: P) -> FileFindIterator
419 where P: AsRef<Path> {
420 FileFindIterator::new(&self.config_home, &self.config_dirs,
421 &self.user_prefix, &self.shared_prefix, path.as_ref())
422 }
423
424 /// Given a relative path `path`, returns an absolute path to an existing
425 /// data file, or `None`. Searches `XDG_DATA_HOME` and then
426 /// `XDG_DATA_DIRS`.
find_data_file<P>(&self, path: P) -> Option<PathBuf> where P: AsRef<Path>427 pub fn find_data_file<P>(&self, path: P) -> Option<PathBuf>
428 where P: AsRef<Path> {
429 read_file(&self.data_home, &self.data_dirs,
430 &self.user_prefix, &self.shared_prefix, path.as_ref())
431 }
432
433 /// Given a relative path `path`, returns an iterator yielding absolute
434 /// paths to existing data files, in `XDG_DATA_DIRS` and
435 /// `XDG_DATA_HOME`. Paths are produced in order from lowest priority
436 /// to highest.
find_data_files<P>(&self, path: P) -> FileFindIterator where P: AsRef<Path>437 pub fn find_data_files<P>(&self, path: P) -> FileFindIterator
438 where P: AsRef<Path> {
439 FileFindIterator::new(&self.data_home, &self.data_dirs,
440 &self.user_prefix, &self.shared_prefix, path.as_ref())
441 }
442
443 /// Given a relative path `path`, returns an absolute path to an existing
444 /// cache file, or `None`. Searches `XDG_CACHE_HOME`.
find_cache_file<P>(&self, path: P) -> Option<PathBuf> where P: AsRef<Path>445 pub fn find_cache_file<P>(&self, path: P) -> Option<PathBuf>
446 where P: AsRef<Path> {
447 read_file(&self.cache_home, &Vec::new(),
448 &self.user_prefix, &self.shared_prefix, path.as_ref())
449 }
450
451 /// Given a relative path `path`, returns an absolute path to an existing
452 /// application state file, or `None`. Searches `XDG_STATE_HOME`.
find_state_file<P>(&self, path: P) -> Option<PathBuf> where P: AsRef<Path>453 pub fn find_state_file<P>(&self, path: P) -> Option<PathBuf>
454 where P: AsRef<Path> {
455 read_file(&self.state_home, &Vec::new(),
456 &self.user_prefix, &self.shared_prefix, path.as_ref())
457 }
458
459 /// Given a relative path `path`, returns an absolute path to an existing
460 /// runtime file, or `None`. Searches `XDG_RUNTIME_DIR`.
461 /// If `XDG_RUNTIME_DIR` is not available, returns `None`.
find_runtime_file<P>(&self, path: P) -> Option<PathBuf> where P: AsRef<Path>462 pub fn find_runtime_file<P>(&self, path: P) -> Option<PathBuf>
463 where P: AsRef<Path> {
464 if let Ok(runtime_dir) = self.get_runtime_directory() {
465 read_file(runtime_dir, &Vec::new(),
466 &self.user_prefix, &self.shared_prefix, path.as_ref())
467 } else {
468 None
469 }
470 }
471
472 /// Given a relative path `path`, returns an absolute path to a configuration
473 /// directory in `XDG_CONFIG_HOME`. The directory and all directories
474 /// leading to it are created if they did not exist;
475 /// if that is not possible, an error is returned.
create_config_directory<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>476 pub fn create_config_directory<P>(&self, path: P) -> io::Result<PathBuf>
477 where P: AsRef<Path> {
478 create_directory(&self.config_home,
479 self.user_prefix.join(path))
480 }
481
482 /// Like [`create_config_directory()`](#method.create_config_directory),
483 /// but for a data directory in `XDG_DATA_HOME`.
create_data_directory<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>484 pub fn create_data_directory<P>(&self, path: P) -> io::Result<PathBuf>
485 where P: AsRef<Path> {
486 create_directory(&self.data_home,
487 self.user_prefix.join(path))
488 }
489
490 /// Like [`create_config_directory()`](#method.create_config_directory),
491 /// but for a cache directory in `XDG_CACHE_HOME`.
create_cache_directory<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>492 pub fn create_cache_directory<P>(&self, path: P) -> io::Result<PathBuf>
493 where P: AsRef<Path> {
494 create_directory(&self.cache_home,
495 self.user_prefix.join(path))
496 }
497
498 /// Like [`create_config_directory()`](#method.create_config_directory),
499 /// but for an application state directory in `XDG_STATE_HOME`.
create_state_directory<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>500 pub fn create_state_directory<P>(&self, path: P) -> io::Result<PathBuf>
501 where P: AsRef<Path> {
502 create_directory(&self.state_home,
503 self.user_prefix.join(path))
504 }
505
506 /// Like [`create_config_directory()`](#method.create_config_directory),
507 /// but for a runtime directory in `XDG_RUNTIME_DIR`.
508 /// If `XDG_RUNTIME_DIR` is not available, returns an error.
create_runtime_directory<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>509 pub fn create_runtime_directory<P>(&self, path: P) -> io::Result<PathBuf>
510 where P: AsRef<Path> {
511 create_directory(self.get_runtime_directory()?,
512 self.user_prefix.join(path))
513 }
514
515 /// Given a relative path `path`, list absolute paths to all files
516 /// in directories with path `path` in `XDG_CONFIG_HOME` and
517 /// `XDG_CONFIG_DIRS`.
list_config_files<P>(&self, path: P) -> Vec<PathBuf> where P: AsRef<Path>518 pub fn list_config_files<P>(&self, path: P) -> Vec<PathBuf>
519 where P: AsRef<Path> {
520 list_files(&self.config_home, &self.config_dirs,
521 &self.user_prefix, &self.shared_prefix, path.as_ref())
522 }
523
524 /// Like [`list_config_files`](#method.list_config_files), but
525 /// only the first occurence of every distinct filename is returned.
list_config_files_once<P>(&self, path: P) -> Vec<PathBuf> where P: AsRef<Path>526 pub fn list_config_files_once<P>(&self, path: P) -> Vec<PathBuf>
527 where P: AsRef<Path> {
528 list_files_once(&self.config_home, &self.config_dirs,
529 &self.user_prefix, &self.shared_prefix, path.as_ref())
530 }
531
532 /// Given a relative path `path`, lists absolute paths to all files
533 /// in directories with path `path` in `XDG_DATA_HOME` and
534 /// `XDG_DATA_DIRS`.
list_data_files<P>(&self, path: P) -> Vec<PathBuf> where P: AsRef<Path>535 pub fn list_data_files<P>(&self, path: P) -> Vec<PathBuf>
536 where P: AsRef<Path> {
537 list_files(&self.data_home, &self.data_dirs,
538 &self.user_prefix, &self.shared_prefix, path.as_ref())
539 }
540
541 /// Like [`list_data_files`](#method.list_data_files), but
542 /// only the first occurence of every distinct filename is returned.
list_data_files_once<P>(&self, path: P) -> Vec<PathBuf> where P: AsRef<Path>543 pub fn list_data_files_once<P>(&self, path: P) -> Vec<PathBuf>
544 where P: AsRef<Path> {
545 list_files_once(&self.data_home, &self.data_dirs,
546 &self.user_prefix, &self.shared_prefix, path.as_ref())
547 }
548
549 /// Given a relative path `path`, lists absolute paths to all files
550 /// in directories with path `path` in `XDG_CACHE_HOME`.
list_cache_files<P>(&self, path: P) -> Vec<PathBuf> where P: AsRef<Path>551 pub fn list_cache_files<P>(&self, path: P) -> Vec<PathBuf>
552 where P: AsRef<Path> {
553 list_files(&self.cache_home, &Vec::new(),
554 &self.user_prefix, &self.shared_prefix, path.as_ref())
555 }
556
557 /// Given a relative path `path`, lists absolute paths to all files
558 /// in directories with path `path` in `XDG_STATE_HOME`.
list_state_files<P>(&self, path: P) -> Vec<PathBuf> where P: AsRef<Path>559 pub fn list_state_files<P>(&self, path: P) -> Vec<PathBuf>
560 where P: AsRef<Path> {
561 list_files(&self.state_home, &Vec::new(),
562 &self.user_prefix, &self.shared_prefix, path.as_ref())
563 }
564
565 /// Given a relative path `path`, lists absolute paths to all files
566 /// in directories with path `path` in `XDG_RUNTIME_DIR`.
567 /// If `XDG_RUNTIME_DIR` is not available, returns an empty `Vec`.
list_runtime_files<P>(&self, path: P) -> Vec<PathBuf> where P: AsRef<Path>568 pub fn list_runtime_files<P>(&self, path: P) -> Vec<PathBuf>
569 where P: AsRef<Path> {
570 if let Ok(runtime_dir) = self.get_runtime_directory() {
571 list_files(runtime_dir, &Vec::new(),
572 &self.user_prefix, &self.shared_prefix, path.as_ref())
573 } else {
574 Vec::new()
575 }
576 }
577
578 /// Returns the user-specific data directory (set by `XDG_DATA_HOME`).
get_data_home(&self) -> PathBuf579 pub fn get_data_home(&self) -> PathBuf {
580 self.data_home.join(&self.user_prefix)
581 }
582
583 /// Returns the user-specific configuration directory (set by
584 /// `XDG_CONFIG_HOME`).
get_config_home(&self) -> PathBuf585 pub fn get_config_home(&self) -> PathBuf {
586 self.config_home.join(&self.user_prefix)
587 }
588
589 /// Returns the user-specific directory for non-essential (cached) data
590 /// (set by `XDG_CACHE_HOME`).
get_cache_home(&self) -> PathBuf591 pub fn get_cache_home(&self) -> PathBuf {
592 self.cache_home.join(&self.user_prefix)
593 }
594
595 /// Returns the user-specific directory for application state data
596 /// (set by `XDG_STATE_HOME`).
get_state_home(&self) -> PathBuf597 pub fn get_state_home(&self) -> PathBuf {
598 self.state_home.join(&self.user_prefix)
599 }
600
601 /// Returns a preference ordered (preferred to less preferred) list of
602 /// supplementary data directories, ordered by preference (set by
603 /// `XDG_DATA_DIRS`).
get_data_dirs(&self) -> Vec<PathBuf>604 pub fn get_data_dirs(&self) -> Vec<PathBuf> {
605 self.data_dirs.iter().map(|p| p.join(&self.shared_prefix)).collect()
606 }
607
608 /// Returns a preference ordered (preferred to less preferred) list of
609 /// supplementary configuration directories (set by `XDG_CONFIG_DIRS`).
get_config_dirs(&self) -> Vec<PathBuf>610 pub fn get_config_dirs(&self) -> Vec<PathBuf> {
611 self.config_dirs.iter().map(|p| p.join(&self.shared_prefix)).collect()
612 }
613 }
614
write_file<P>(home: &PathBuf, path: P) -> io::Result<PathBuf> where P: AsRef<Path>615 fn write_file<P>(home: &PathBuf, path: P) -> io::Result<PathBuf>
616 where P: AsRef<Path> {
617 match path.as_ref().parent() {
618 Some(parent) => fs::create_dir_all(home.join(parent))?,
619 None => fs::create_dir_all(home)?,
620 }
621 Ok(PathBuf::from(home.join(path.as_ref())))
622 }
623
create_directory<P>(home: &PathBuf, path: P) -> io::Result<PathBuf> where P: AsRef<Path>624 fn create_directory<P>(home: &PathBuf, path: P) -> io::Result<PathBuf>
625 where P: AsRef<Path> {
626 let full_path = home.join(path.as_ref());
627 fs::create_dir_all(&full_path)?;
628 Ok(full_path)
629 }
630
path_exists<P: ?Sized + AsRef<Path>>(path: &P) -> bool631 fn path_exists<P: ?Sized + AsRef<Path>>(path: &P) -> bool {
632 fn inner(path: &Path) -> bool {
633 fs::metadata(path).is_ok()
634 }
635 inner(path.as_ref())
636 }
637
638 #[cfg(test)]
path_is_dir<P: ?Sized + AsRef<Path>>(path: &P) -> bool639 fn path_is_dir<P: ?Sized + AsRef<Path>>(path: &P) -> bool {
640 fn inner(path: &Path) -> bool {
641 fs::metadata(path).map(|m| m.is_dir()).unwrap_or(false)
642 }
643 inner(path.as_ref())
644 }
645
read_file(home: &PathBuf, dirs: &Vec<PathBuf>, user_prefix: &Path, shared_prefix: &Path, path: &Path) -> Option<PathBuf>646 fn read_file(home: &PathBuf, dirs: &Vec<PathBuf>,
647 user_prefix: &Path, shared_prefix: &Path, path: &Path)
648 -> Option<PathBuf> {
649 let full_path = home.join(user_prefix).join(path);
650 if path_exists(&full_path) {
651 return Some(full_path)
652 }
653 for dir in dirs.iter() {
654 let full_path = dir.join(shared_prefix).join(path);
655 if path_exists(&full_path) {
656 return Some(full_path)
657 }
658 }
659 None
660 }
661
662 use std::vec::IntoIter as VecIter;
663 pub struct FileFindIterator {
664 search_dirs: VecIter<PathBuf>,
665 relpath: PathBuf,
666 }
667
668 impl FileFindIterator {
new(home: &PathBuf, dirs: &Vec<PathBuf>, user_prefix: &Path, shared_prefix: &Path, path: &Path) -> FileFindIterator669 fn new(home: &PathBuf, dirs: &Vec<PathBuf>,
670 user_prefix: &Path, shared_prefix: &Path, path: &Path)
671 -> FileFindIterator {
672 let mut search_dirs = Vec::new();
673 for dir in dirs.iter().rev() {
674 search_dirs.push(dir.join(shared_prefix));
675 }
676 search_dirs.push(home.join(user_prefix));
677 FileFindIterator {
678 search_dirs: search_dirs.into_iter(),
679 relpath: path.to_path_buf(),
680 }
681 }
682 }
683
684 impl Iterator for FileFindIterator {
685 type Item = PathBuf;
686
next(&mut self) -> Option<Self::Item>687 fn next(&mut self) -> Option<Self::Item> {
688 loop {
689 let dir = self.search_dirs.next()?;
690 let candidate = dir.join(self.relpath.clone());
691 if path_exists(&candidate) {
692 return Some(candidate)
693 }
694 }
695 }
696 }
697
698 impl DoubleEndedIterator for FileFindIterator {
next_back(&mut self) -> Option<Self::Item>699 fn next_back(&mut self) -> Option<Self::Item> {
700 loop {
701 let dir = self.search_dirs.next_back()?;
702 let candidate = dir.join(self.relpath.clone());
703 if path_exists(&candidate) {
704 return Some(candidate)
705 }
706 }
707 }
708 }
709
list_files(home: &Path, dirs: &[PathBuf], user_prefix: &Path, shared_prefix: &Path, path: &Path) -> Vec<PathBuf>710 fn list_files(home: &Path, dirs: &[PathBuf],
711 user_prefix: &Path, shared_prefix: &Path, path: &Path)
712 -> Vec<PathBuf> {
713 fn read_dir(dir: &Path, into: &mut Vec<PathBuf>) {
714 if let Ok(entries) = fs::read_dir(dir) {
715 into.extend(
716 entries
717 .filter_map(|entry| entry.ok())
718 .map(|entry| entry.path()))
719 }
720 }
721 let mut files = Vec::new();
722 read_dir(&home.join(user_prefix).join(path), &mut files);
723 for dir in dirs {
724 read_dir(&dir.join(shared_prefix).join(path), &mut files);
725 }
726 files
727 }
728
list_files_once(home: &Path, dirs: &[PathBuf], user_prefix: &Path, shared_prefix: &Path, path: &Path) -> Vec<PathBuf>729 fn list_files_once(home: &Path, dirs: &[PathBuf],
730 user_prefix: &Path, shared_prefix: &Path, path: &Path)
731 -> Vec<PathBuf> {
732 let mut seen = std::collections::HashSet::new();
733 list_files(home, dirs, user_prefix, shared_prefix, path).into_iter().filter(|path| {
734 match path.clone().file_name() {
735 None => false,
736 Some(filename) => {
737 if seen.contains(filename) {
738 false
739 } else {
740 seen.insert(filename.to_owned());
741 true
742 }
743 }
744 }
745 }).collect::<Vec<_>>()
746 }
747
748 #[cfg(test)]
make_absolute<P>(path: P) -> PathBuf where P: AsRef<Path>749 fn make_absolute<P>(path: P) -> PathBuf where P: AsRef<Path> {
750 env::current_dir().unwrap().join(path.as_ref())
751 }
752
753 #[cfg(test)]
iter_after<A, I, J>(mut iter: I, mut prefix: J) -> Option<I> where I: Iterator<Item=A> + Clone, J: Iterator<Item=A>, A: PartialEq754 fn iter_after<A, I, J>(mut iter: I, mut prefix: J) -> Option<I> where
755 I: Iterator<Item=A> + Clone, J: Iterator<Item=A>, A: PartialEq
756 {
757 loop {
758 let mut iter_next = iter.clone();
759 match (iter_next.next(), prefix.next()) {
760 (Some(x), Some(y)) => {
761 if x != y { return None }
762 }
763 (Some(_), None) => return Some(iter),
764 (None, None) => return Some(iter),
765 (None, Some(_)) => return None,
766 }
767 iter = iter_next;
768 }
769 }
770
771 #[cfg(test)]
make_relative<P>(path: P) -> PathBuf where P: AsRef<Path>772 fn make_relative<P>(path: P) -> PathBuf where P: AsRef<Path> {
773 iter_after(path.as_ref().components(), env::current_dir().unwrap().components())
774 .unwrap().as_path().to_owned()
775 }
776
777 #[cfg(test)]
make_env(vars: Vec<(&'static str, String)>) -> Box<dyn Fn(&str)->Option<OsString>>778 fn make_env(vars: Vec<(&'static str, String)>) ->
779 Box<dyn Fn(&str)->Option<OsString>> {
780 return Box::new(move |name| {
781 for &(key, ref value) in vars.iter() {
782 if key == name { return Some(OsString::from(value)) }
783 }
784 None
785 })
786 }
787
788 #[test]
test_files_exists()789 fn test_files_exists() {
790 assert!(path_exists("test_files"));
791 assert!(fs::metadata("test_files/runtime-bad")
792 .unwrap().permissions().mode() & 0o077 != 0);
793 }
794
795 #[test]
test_bad_environment()796 fn test_bad_environment() {
797 let xd = BaseDirectories::with_env("", "", &*make_env(vec![
798 ("HOME", "test_files/user".to_string()),
799 ("XDG_DATA_HOME", "test_files/user/data".to_string()),
800 ("XDG_CONFIG_HOME", "test_files/user/config".to_string()),
801 ("XDG_CACHE_HOME", "test_files/user/cache".to_string()),
802 ("XDG_DATA_DIRS", "test_files/user/data".to_string()),
803 ("XDG_CONFIG_DIRS", "test_files/user/config".to_string()),
804 ("XDG_RUNTIME_DIR", "test_files/runtime-bad".to_string())
805 ])).unwrap();
806 assert_eq!(xd.find_data_file("everywhere"), None);
807 assert_eq!(xd.find_config_file("everywhere"), None);
808 assert_eq!(xd.find_cache_file("everywhere"), None);
809 }
810
811 #[test]
test_good_environment()812 fn test_good_environment() {
813 let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
814 let xd = BaseDirectories::with_env("", "", &*make_env(vec![
815 ("HOME", format!("{}/test_files/user", cwd)),
816 ("XDG_DATA_HOME", format!("{}/test_files/user/data", cwd)),
817 ("XDG_CONFIG_HOME", format!("{}/test_files/user/config", cwd)),
818 ("XDG_CACHE_HOME", format!("{}/test_files/user/cache", cwd)),
819 ("XDG_DATA_DIRS", format!("{}/test_files/system0/data:{}/test_files/system1/data:{}/test_files/system2/data:{}/test_files/system3/data", cwd, cwd, cwd, cwd)),
820 ("XDG_CONFIG_DIRS", format!("{}/test_files/system0/config:{}/test_files/system1/config:{}/test_files/system2/config:{}/test_files/system3/config", cwd, cwd, cwd, cwd)),
821 // ("XDG_RUNTIME_DIR", format!("{}/test_files/runtime-bad", cwd)),
822 ])).unwrap();
823 assert!(xd.find_data_file("everywhere") != None);
824 assert!(xd.find_config_file("everywhere") != None);
825 assert!(xd.find_cache_file("everywhere") != None);
826
827 let mut config_files = xd.find_config_files("everywhere");
828 assert_eq!(config_files.next(),
829 Some(PathBuf::from(format!("{}/test_files/system2/config/everywhere", cwd))));
830 assert_eq!(config_files.next(),
831 Some(PathBuf::from(format!("{}/test_files/system1/config/everywhere", cwd))));
832 assert_eq!(config_files.next(),
833 Some(PathBuf::from(format!("{}/test_files/user/config/everywhere", cwd))));
834 assert_eq!(config_files.next(), None);
835
836 let mut data_files = xd.find_data_files("everywhere");
837 assert_eq!(data_files.next(),
838 Some(PathBuf::from(format!("{}/test_files/system2/data/everywhere", cwd))));
839 assert_eq!(data_files.next(),
840 Some(PathBuf::from(format!("{}/test_files/system1/data/everywhere", cwd))));
841 assert_eq!(data_files.next(),
842 Some(PathBuf::from(format!("{}/test_files/user/data/everywhere", cwd))));
843 assert_eq!(data_files.next(), None);
844 }
845
846 #[test]
test_runtime_bad()847 fn test_runtime_bad() {
848 let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
849 let xd = BaseDirectories::with_env("", "", &*make_env(vec![
850 ("HOME", format!("{}/test_files/user", cwd)),
851 ("XDG_RUNTIME_DIR", format!("{}/test_files/runtime-bad", cwd)),
852 ])).unwrap();
853 assert!(xd.has_runtime_directory() == false);
854 }
855
856 #[test]
test_runtime_good()857 fn test_runtime_good() {
858 use std::fs::File;
859
860 let test_runtime_dir = make_absolute(&"test_files/runtime-good");
861 let _ = fs::remove_dir_all(&test_runtime_dir);
862 fs::create_dir_all(&test_runtime_dir).unwrap();
863
864 let mut perms = fs::metadata(&test_runtime_dir).unwrap().permissions();
865 perms.set_mode(0o700);
866 fs::set_permissions(&test_runtime_dir, perms).unwrap();
867
868 let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
869 let xd = BaseDirectories::with_env("", "", &*make_env(vec![
870 ("HOME", format!("{}/test_files/user", cwd)),
871 ("XDG_RUNTIME_DIR", format!("{}/test_files/runtime-good", cwd)),
872 ])).unwrap();
873
874 xd.create_runtime_directory("foo").unwrap();
875 assert!(path_is_dir("test_files/runtime-good/foo"));
876 let w = xd.place_runtime_file("bar/baz").unwrap();
877 assert!(path_is_dir("test_files/runtime-good/bar"));
878 assert!(!path_exists("test_files/runtime-good/bar/baz"));
879 File::create(&w).unwrap();
880 assert!(path_exists("test_files/runtime-good/bar/baz"));
881 assert!(xd.find_runtime_file("bar/baz") == Some(w.clone()));
882 File::open(&w).unwrap();
883 fs::remove_file(&w).unwrap();
884 let root = xd.list_runtime_files(".");
885 let mut root = root.into_iter().map(|p| make_relative(&p)).collect::<Vec<_>>();
886 root.sort();
887 assert_eq!(root,
888 vec![PathBuf::from("test_files/runtime-good/bar"),
889 PathBuf::from("test_files/runtime-good/foo")]);
890 assert!(xd.list_runtime_files("bar").is_empty());
891 assert!(xd.find_runtime_file("foo/qux").is_none());
892 assert!(xd.find_runtime_file("qux/foo").is_none());
893 assert!(!path_exists("test_files/runtime-good/qux"));
894 }
895
896 #[test]
test_lists()897 fn test_lists() {
898 let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
899 let xd = BaseDirectories::with_env("", "", &*make_env(vec![
900 ("HOME", format!("{}/test_files/user", cwd)),
901 ("XDG_DATA_HOME", format!("{}/test_files/user/data", cwd)),
902 ("XDG_CONFIG_HOME", format!("{}/test_files/user/config", cwd)),
903 ("XDG_CACHE_HOME", format!("{}/test_files/user/cache", cwd)),
904 ("XDG_DATA_DIRS", format!("{}/test_files/system0/data:{}/test_files/system1/data:{}/test_files/system2/data:{}/test_files/system3/data", cwd, cwd, cwd, cwd)),
905 ("XDG_CONFIG_DIRS", format!("{}/test_files/system0/config:{}/test_files/system1/config:{}/test_files/system2/config:{}/test_files/system3/config", cwd, cwd, cwd, cwd)),
906 ])).unwrap();
907
908 let files = xd.list_config_files(".");
909 let mut files = files.into_iter().map(|p| make_relative(&p)).collect::<Vec<_>>();
910 files.sort();
911 assert_eq!(files,
912 [
913 "test_files/system1/config/both_system_config.file",
914 "test_files/system1/config/everywhere",
915 "test_files/system1/config/myapp",
916 "test_files/system1/config/system1_config.file",
917 "test_files/system2/config/both_system_config.file",
918 "test_files/system2/config/everywhere",
919 "test_files/system2/config/system2_config.file",
920 "test_files/user/config/everywhere",
921 "test_files/user/config/myapp",
922 "test_files/user/config/user_config.file",
923 ].iter().map(PathBuf::from).collect::<Vec<_>>());
924
925 let files = xd.list_config_files_once(".");
926 let mut files = files.into_iter().map(|p| make_relative(&p)).collect::<Vec<_>>();
927 files.sort();
928 assert_eq!(files,
929 [
930 "test_files/system1/config/both_system_config.file",
931 "test_files/system1/config/system1_config.file",
932 "test_files/system2/config/system2_config.file",
933 "test_files/user/config/everywhere",
934 "test_files/user/config/myapp",
935 "test_files/user/config/user_config.file",
936 ].iter().map(PathBuf::from).collect::<Vec<_>>());
937 }
938
939 #[test]
test_get_file()940 fn test_get_file() {
941 let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
942 let xd = BaseDirectories::with_env("", "", &*make_env(vec![
943 ("HOME", format!("{}/test_files/user", cwd)),
944 ("XDG_DATA_HOME", format!("{}/test_files/user/data", cwd)),
945 ("XDG_CONFIG_HOME", format!("{}/test_files/user/config", cwd)),
946 ("XDG_CACHE_HOME", format!("{}/test_files/user/cache", cwd)),
947 ("XDG_RUNTIME_DIR", format!("{}/test_files/user/runtime", cwd)),
948 ])).unwrap();
949
950 let path = format!("{}/test_files/user/runtime/", cwd);
951 let metadata = fs::metadata(&path).expect("Could not read metadata for runtime directory");
952 let mut perms = metadata.permissions();
953 perms.set_mode(0o700);
954 fs::set_permissions(&path, perms).expect("Could not set permissions for runtime directory");
955
956 let file = xd.get_config_file("myapp/user_config.file");
957 assert_eq!(file, PathBuf::from(&format!("{}/test_files/user/config/myapp/user_config.file", cwd)));
958
959 let file = xd.get_data_file("user_data.file");
960 assert_eq!(file, PathBuf::from(&format!("{}/test_files/user/data/user_data.file", cwd)));
961
962 let file = xd.get_cache_file("user_cache.file");
963 assert_eq!(file, PathBuf::from(&format!("{}/test_files/user/cache/user_cache.file", cwd)));
964
965 let file = xd.get_runtime_file("user_runtime.file").unwrap();
966 assert_eq!(file, PathBuf::from(&format!("{}/test_files/user/runtime/user_runtime.file", cwd)));
967 }
968
969 #[test]
test_prefix()970 fn test_prefix() {
971 let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
972 let xd = BaseDirectories::with_env("myapp", "", &*make_env(vec![
973 ("HOME", format!("{}/test_files/user", cwd)),
974 ("XDG_CACHE_HOME", format!("{}/test_files/user/cache", cwd)),
975 ])).unwrap();
976 assert_eq!(xd.get_cache_file("cache.db"),
977 PathBuf::from(&format!("{}/test_files/user/cache/myapp/cache.db", cwd)));
978 assert_eq!(xd.place_cache_file("cache.db").unwrap(),
979 PathBuf::from(&format!("{}/test_files/user/cache/myapp/cache.db", cwd)));
980 }
981
982 #[test]
test_profile()983 fn test_profile() {
984 let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
985 let xd = BaseDirectories::with_env("myapp", "default_profile", &*make_env(vec![
986 ("HOME", format!("{}/test_files/user", cwd)),
987 ("XDG_CONFIG_HOME", format!("{}/test_files/user/config", cwd)),
988 ("XDG_CONFIG_DIRS", format!("{}/test_files/system1/config", cwd)),
989 ])).unwrap();
990 assert_eq!(xd.find_config_file("system1_config.file").unwrap(),
991 // Does *not* include default_profile
992 PathBuf::from(&format!("{}/test_files/system1/config/myapp/system1_config.file", cwd)));
993 assert_eq!(xd.find_config_file("user_config.file").unwrap(),
994 // Includes default_profile
995 PathBuf::from(&format!("{}/test_files/user/config/myapp/default_profile/user_config.file", cwd)));
996 }
997
998 /// Ensure that entries in XDG_CONFIG_DIRS can be replaced with symlinks.
999 #[test]
test_symlinks()1000 fn test_symlinks() {
1001 let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
1002 let symlinks_dir = format!("{}/test_files/symlinks", cwd);
1003 let config_dir = format!("{}/config", symlinks_dir);
1004 let myapp_dir = format!("{}/myapp", config_dir);
1005
1006 assert!(path_exists(&myapp_dir));
1007 assert!(path_exists(&config_dir));
1008 assert!(path_exists(&symlinks_dir));
1009
1010 let xd = BaseDirectories::with_env(
1011 "myapp", "", &*make_env(vec![
1012 ("HOME", symlinks_dir),
1013 ("XDG_CONFIG_HOME", config_dir),
1014 ])
1015 ).unwrap();
1016 assert_eq!(xd.find_config_file("user_config.file").unwrap(),
1017 PathBuf::from(&format!("{}/user_config.file", myapp_dir)));
1018 }
1019