Skip to main content

std/os/unix/fs/
dirs.rs

1use crate::env::{self, SplitPathsRef};
2use crate::ffi::{OsStr, OsString};
3use crate::fs::{self, HomeDirs, MediaDirs};
4use crate::io::{self, ErrorKind, const_error};
5use crate::path::{Path, PathBuf};
6
7#[derive(Debug, Default, Clone)]
8#[non_exhaustive]
9pub struct ExtraHomeDirs {
10    runtime: Option<PathBuf>,
11    config_path: Option<OsString>,
12    data_path: Option<OsString>,
13}
14
15#[derive(Debug, Default, Clone)]
16#[non_exhaustive]
17pub struct ExtraMediaDirs {
18    templates: Option<PathBuf>,
19}
20
21/// XDG-specific extensions to [`fs::HomeDirs`](HomeDirs).
22///
23/// The XDG conventions are defined by the Freedesktop.org project in the
24/// [XDG Base Directory Specification][xdg-basedir]. These conventions have
25/// been largely adopted by Linux distributions.
26///
27/// The XDG conventions are written to be usable on any Unix-like filesystem,
28/// thus this extension being provided in `os::unix` rather than `os::linux`.
29/// However, while some tooling does use XDG conventions on macOS, note that
30/// macOS has its own separate conventions for user directories. Consider
31/// carefully what conventions your users will expect your application to
32/// follow along with any legacy path compatibility you might need to support.
33///
34/// [xdg-basedir]: https://specifications.freedesktop.org/basedir/
35#[unstable(feature = "fs_home_dirs", issue = "162082")]
36pub impl(self) trait HomeDirsExt: Sized {
37    /// Load the user directory paths according to the
38    /// [XDG Base Directory Specification][xdg-basedir].
39    ///
40    /// Each base directory path is set to the value of its corresponding
41    /// `XDG_*` environment variable (if it is set and non-empty), else to
42    /// the default value defined by the specification.
43    ///
44    /// | Field | Environment Variable | Default Value |
45    /// | ----- | -------------------- | ------------- |
46    /// | [`cache_home`] | `XDG_CACHE_HOME` | `$HOME/.cache` |
47    /// | [`config_home`] | `XDG_CONFIG_HOME` | `$HOME/.config` |
48    /// | [`data_home`] | `XDG_DATA_HOME` | `$HOME/.local/share` |
49    /// | [`state_home`] | `XDG_STATE_HOME` | `$HOME/.local/state` |
50    /// | [`runtime_home`] | `XDG_RUNTIME_DIR` | (see method docs) |
51    /// | [`config_dirs`] | `XDG_CONFIG_DIRS` | `/etc/xdg` |
52    /// | [`data_dirs`] | `XDG_DATA_DIRS` | `/usr/local/share/`, `/usr/share/` |
53    ///
54    /// Note that `$HOME` here means [`env::home_dir`], which uses
55    /// `$HOME` if set and non-empty, but falls back to the system password
56    /// database if it isn't set.
57    ///
58    /// All paths are required to be absolute. If a relative path is configured
59    /// by the environment, it is ignored and the default value is used instead.
60    ///
61    /// `config_dirs` and `data_dirs` are a list of delimited paths using the
62    /// [`env::split_paths`] delimiter. If some but not all paths in the list are
63    /// relative, those relative paths are ignored and the remaining absolute
64    /// paths are used. If there are no valid absolute paths, the default value
65    /// is used instead.
66    ///
67    /// # Errors
68    ///
69    /// Errors if the user's home directory cannot be determined.
70    ///
71    /// [xdg-basedir]: https://specifications.freedesktop.org/basedir/
72    /// [`cache_home`]: HomeDirs::cache_home
73    /// [`config_home`]: HomeDirs::config_home
74    /// [`data_home`]: HomeDirs::data_home
75    /// [`state_home`]: HomeDirs::state_home
76    /// [`runtime_home`]: HomeDirsExt::runtime_home
77    /// [`config_dirs`]: HomeDirsExt::config_dirs
78    /// [`data_dirs`]: HomeDirsExt::data_dirs
79    /// [`env::split_paths`]: crate::env::split_paths
80    #[unstable(feature = "fs_home_dirs", issue = "162082")]
81    fn xdg() -> io::Result<Self>;
82
83    /// A base directory relative to which user-specific runtime files
84    /// (such as sockets, named pipes, etc) should be stored.
85    ///
86    /// Files in this directory may be subjected to periodic clean-up.
87    /// Larger files should not be placed here, since it might reside in
88    /// runtime memory and cannot necessarily be swapped out to disk.
89    ///
90    /// This path does not have a default if not set. If it isn't set,
91    /// applications should fall back to a replacement directory with
92    /// similar capabilities and print a warning message.
93    #[unstable(feature = "fs_home_dirs", issue = "162082")]
94    fn runtime_home(&self) -> Option<&Path>;
95
96    /// A preference-ordered list of base directories to search for config
97    /// files *in addition to* [`config_home`].
98    ///
99    /// The order of directories denotes their importance; the first directory
100    /// is the most important. Information defined relative to the more
101    /// important base directory takes precedent. [`config_home`] is not
102    /// necessarily present in this list, and is considered more important
103    /// than any base directory in this list.
104    ///
105    /// [`config_home`]: HomeDirs::config_home
106    #[unstable(feature = "fs_home_dirs", issue = "162082")]
107    fn config_dirs(&self) -> Option<XdgDirs<'_>>;
108
109    /// A preference-ordered list of base directories to search for data
110    /// files *in addition to* [`data_home`].
111    ///
112    /// The order of directories denotes their importance; the first directory
113    /// is the most important. Information defined relative to the more
114    /// important base directory takes precedent. [`data_home`] is not
115    /// necessarily present in this list, and is considered more important
116    /// than any base directory in this list.
117    ///
118    /// [`data_home`]: HomeDirs::data_home
119    #[unstable(feature = "fs_home_dirs", issue = "162082")]
120    fn data_dirs(&self) -> Option<XdgDirs<'_>>;
121
122    /// Set the path for [Self::runtime_home].
123    ///
124    /// # Panics
125    ///
126    /// Panics if the provided path is not absolute.
127    #[unstable(feature = "fs_home_dirs", issue = "162082")]
128    fn set_runtime_home(&mut self, path: PathBuf) -> &mut Self;
129
130    /// Set the paths for [Self::config_dirs].
131    ///
132    /// Takes one or more paths joined appropriately for the `PATH` environment
133    /// variable, as by [`env::join_paths`].
134    ///
135    /// # Panics
136    ///
137    /// Panics if any of the provided paths are not absolute.
138    #[unstable(feature = "fs_home_dirs", issue = "162082")]
139    fn set_config_dirs(&mut self, paths: OsString) -> &mut Self;
140
141    /// Set the paths for [Self::data_dirs].
142    ///
143    /// Takes one or more paths joined appropriately for the `PATH` environment
144    /// variable, as by [`env::join_paths`].
145    ///
146    /// # Panics
147    ///
148    /// Panics if any of the provided paths are not absolute.
149    #[unstable(feature = "fs_home_dirs", issue = "162082")]
150    fn set_data_dirs(&mut self, paths: OsString) -> &mut Self;
151}
152
153/// XDG-specific extensions to [`fs::MediaDirs`](MediaDirs).
154///
155/// The XDG conventions are defined by the Freedesktop.org project through the
156/// [xdg-user-dirs] tool. This configuration is generally present on desktop
157/// Linux distributions, although adoption is less widespread than the base
158/// directory specification.
159///
160/// The XDG conventions are written to be usable on any Unix-like filesystem,
161/// thus this extension being provided in `os::unix` rather than `os::linux`.
162/// However, while some tooling does use XDG conventions on macOS, note that
163/// macOS has its own separate conventions for user directories. Consider
164/// carefully what conventions your users will expect your application to
165/// follow along with any legacy path compatibility you might need to support.
166///
167/// [xdg-user-dirs]: https://www.freedesktop.org/wiki/Software/xdg-user-dirs/
168#[unstable(feature = "fs_media_dirs", issue = "162083")]
169pub impl(self) trait MediaDirsExt: Sized {
170    /// Load the user directory paths according to the [xdg-user-dirs] tool.
171    ///
172    /// This directly reads and parses the `$XDG_CONFIG_HOME/user-dirs.dirs`
173    /// file as defined and maintained by the [xdg-user-dirs] tool.
174    ///
175    /// # Errors
176    ///
177    /// Errors if the user's home directory cannot be determined or if the
178    /// `$XDG_CONFIG_HOME/user-dirs.dirs` file cannot be read.
179    ///
180    /// # Implementation-specific behavior
181    ///
182    /// Only the format maintained by xdg-user-dirs-update is supported. Any
183    /// configuration that does not match the expected format will result in
184    /// loading an unspecified path or `None` for that directory. To be more
185    /// specific:
186    ///
187    /// - Any line not in the format of `XDG_{NAME}_DIR={path}` where `{NAME}`
188    ///   is one of `DESKTOP`, `DOWNLOAD`, `TEMPLATES`, `PUBLICSHARE`,
189    ///   `DOCUMENTS`, `MUSIC`, `PICTURES`, or `VIDEOS` is ignored.
190    /// - `{path}` must be a `"`-quoted shell-escaped path.
191    /// - `{path}` may only start with `/` or `$HOME/`. A home-relative path
192    ///   is returned relative to [`env::home_dir`]; shell expansion is not
193    ///   performed.
194    /// - A directory set to just `$HOME` marks it as removed, and results in
195    ///   a `None` value for that path.
196    /// - If shell expansion syntax other than a leading `$HOME` is present,
197    ///   the produced directory path is unspecified. This is invalid config
198    ///   according to the xdg-user-dirs tooling.
199    ///
200    /// This behavior may change in the future. One example change that we
201    /// explicitly reserve the right to make is to load paths that we currently
202    /// ignore, such as path formats that are not canonically supported by
203    /// xdg-user-dirs but which may occur in manually-edited `user-dirs.dirs`.
204    ///
205    /// [xdg-user-dirs]: https://www.freedesktop.org/wiki/Software/xdg-user-dirs/
206    #[unstable(feature = "fs_media_dirs", issue = "162083")]
207    fn xdg() -> io::Result<Self>;
208
209    /// The OS-privileged user "Templates" directory, often the `Templates`
210    /// folder in the user's home directory.
211    ///
212    /// As a media directory, this should typically be used as a default path
213    /// for file selection dialogs, not for automatically accessed file paths.
214    #[unstable(feature = "fs_media_dirs", issue = "162083")]
215    fn templates(&self) -> Option<&Path>;
216
217    /// Set the paths for [Self::templates].
218    ///
219    /// # Panics
220    ///
221    /// Panics if the provided path is not absolute.
222    #[unstable(feature = "fs_media_dirs", issue = "162083")]
223    fn set_templates(&mut self, path: PathBuf) -> &mut Self;
224}
225
226#[unstable(feature = "fs_home_dirs", issue = "162082")]
227#[derive(Debug)]
228pub struct XdgDirs<'a>(SplitPathsRef<'a>);
229
230#[unstable(feature = "fs_home_dirs", issue = "162082")]
231impl HomeDirsExt for HomeDirs {
232    fn xdg() -> io::Result<Self> {
233        let mut dirs = HomeDirs::empty();
234        let user_home = xdg::user_home()?;
235
236        dirs.set_cache_home(xdg::dir_or_else(|| user_home.join(".cache"), "XDG_CACHE_HOME"));
237        dirs.set_config_home(xdg::dir_or_else(|| user_home.join(".config"), "XDG_CONFIG_HOME"));
238        dirs.set_data_home(xdg::dir_or_else(|| user_home.join(".local/share"), "XDG_DATA_HOME"));
239        dirs.set_state_home(xdg::dir_or_else(|| user_home.join(".local/state"), "XDG_STATE_HOME"));
240        if let Some(runtime) = xdg::dir("XDG_RUNTIME_DIR") {
241            dirs.set_runtime_home(runtime);
242        }
243
244        dirs.set_config_dirs(xdg::dirs_or("/etc/xdg", "XDG_CONFIG_DIRS"));
245        dirs.set_data_dirs(xdg::dirs_or("/usr/local/share/:/usr/share/", "XDG_DATA_DIRS"));
246
247        Ok(dirs)
248    }
249
250    fn runtime_home(&self) -> Option<&Path> {
251        self.extra.runtime.as_deref()
252    }
253
254    fn config_dirs(&self) -> Option<XdgDirs<'_>> {
255        self.extra.config_path.as_deref().map(XdgDirs::new)
256    }
257
258    fn data_dirs(&self) -> Option<XdgDirs<'_>> {
259        self.extra.data_path.as_deref().map(XdgDirs::new)
260    }
261
262    fn set_runtime_home(&mut self, path: PathBuf) -> &mut Self {
263        assert!(path.is_absolute(), "runtime directory path must be absolute");
264        self.extra.runtime = Some(path);
265        self
266    }
267
268    fn set_config_dirs(&mut self, paths: OsString) -> &mut Self {
269        for path in split_paths_ref(&paths) {
270            assert!(path.is_absolute(), "config directory paths must be absolute");
271        }
272        self.extra.config_path = Some(paths);
273        self
274    }
275
276    fn set_data_dirs(&mut self, paths: OsString) -> &mut Self {
277        for path in split_paths_ref(&paths) {
278            assert!(path.is_absolute(), "data directory paths must be absolute");
279        }
280        self.extra.data_path = Some(paths);
281        self
282    }
283}
284
285#[unstable(feature = "fs_media_dirs", issue = "162083")]
286impl MediaDirsExt for MediaDirs {
287    fn xdg() -> io::Result<Self> {
288        let user_home = xdg::user_home()?;
289        let config_home = xdg::dir_or_else(|| user_home.join(".config"), "XDG_CONFIG_HOME");
290
291        let spec = match fs::read(config_home.join("user-dirs.dirs")) {
292            Ok(spec) => spec,
293            Err(e) if e.kind() == ErrorKind::NotFound => {
294                return Err(const_error!(
295                    ErrorKind::NotFound,
296                    "missing `$XDG_CONFIG_HOME/user-dirs.dirs`",
297                ));
298            }
299            Err(e) => return Err(e),
300        };
301
302        Ok(xdg::parse_user_dirs(&spec, &user_home))
303    }
304
305    fn templates(&self) -> Option<&Path> {
306        self.extra.templates.as_deref()
307    }
308
309    fn set_templates(&mut self, path: PathBuf) -> &mut Self {
310        assert!(path.is_absolute(), "templates directory path must be absolute");
311        self.extra.templates = Some(path);
312        self
313    }
314}
315
316impl<'a> XdgDirs<'a> {
317    fn new(paths: &'a OsStr) -> Self {
318        XdgDirs(split_paths_ref(paths))
319    }
320}
321
322#[unstable(feature = "fs_home_dirs", issue = "162082")]
323impl<'a> Iterator for XdgDirs<'a> {
324    type Item = &'a Path;
325
326    fn next(&mut self) -> Option<Self::Item> {
327        self.0.next()
328    }
329}
330
331fn split_paths_ref<'a>(paths: &'a OsStr) -> SplitPathsRef<'a> {
332    // returns Some on cfg(unix)
333    env::split_paths_ref(paths).unwrap()
334}
335
336mod xdg {
337    use super::{MediaDirs, split_paths_ref};
338    use crate::env::{self, join_paths, var_os};
339    use crate::ffi::OsString;
340    use crate::io::{self, ErrorKind, const_error};
341    use crate::ops::Deref;
342    use crate::os::unix::ffi::{OsStrExt, OsStringExt};
343    use crate::path::{Path, PathBuf};
344
345    pub fn user_home() -> io::Result<PathBuf> {
346        env::home_dir()
347            .filter(|p| p.is_absolute())
348            .ok_or(const_error!(ErrorKind::InvalidData, "user home directory path not absolute"))
349    }
350
351    pub fn dir(env: &str) -> Option<PathBuf> {
352        var_os(env).filter(|s| !s.is_empty()).map(PathBuf::from).filter(|path| path.is_absolute())
353    }
354
355    pub fn dir_or_else(fallback: impl FnOnce() -> PathBuf, env: &str) -> PathBuf {
356        dir(env).unwrap_or_else(fallback)
357    }
358
359    fn dirs(env: &str) -> Option<OsString> {
360        let dirs = var_os(env).filter(|s| !s.is_empty())?;
361        if split_paths_ref(&dirs).all(|p| p.is_absolute()) {
362            return Some(dirs);
363        }
364
365        struct DerefAsRef<T>(pub T);
366        impl<T: Deref<Target: AsRef<U>>, U: ?Sized> AsRef<U> for DerefAsRef<T> {
367            fn as_ref(&self) -> &U {
368                (*self.0).as_ref()
369            }
370        }
371
372        let paths = split_paths_ref(&dirs).filter(|p| p.is_absolute()).map(DerefAsRef);
373        join_paths(paths).ok().filter(|s| !s.is_empty())
374    }
375
376    pub fn dirs_or(fallback: impl Into<OsString>, env: &str) -> OsString {
377        dirs(env).unwrap_or_else(|| fallback.into())
378    }
379
380    pub fn parse_user_dirs<'a>(spec: &'a [u8], user_home: &Path) -> MediaDirs {
381        let mut dirs = MediaDirs::empty();
382
383        for (xdg, path) in
384            spec.split(|&b| b == b'\n').flat_map(|line| parse_user_dirs_line(line, user_home))
385        {
386            // load the known user directories
387            match xdg {
388                b"XDG_DESKTOP_DIR" => dirs.desktop = path,
389                b"XDG_DOCUMENTS_DIR" => dirs.documents = path,
390                b"XDG_DOWNLOAD_DIR" => dirs.downloads = path,
391                b"XDG_MUSIC_DIR" => dirs.music = path,
392                b"XDG_PICTURES_DIR" => dirs.pictures = path,
393                b"XDG_VIDEOS_DIR" => dirs.videos = path,
394                b"XDG_TEMPLATES_DIR" => dirs.extra.templates = path,
395                b"XDG_PUBLICSHARE_DIR" => {
396                    // we don't expose this directory yet as Windows also has a "public" directory
397                    // and it's not yet clear if we want to expose this at the target-agnostic level
398                }
399                _ => {
400                    // ignore unknown variable assignment
401                }
402            }
403        }
404
405        dirs
406    }
407
408    fn parse_user_dirs_line<'a>(
409        line: &'a [u8],
410        user_home: &Path,
411    ) -> Option<(&'a [u8], Option<PathBuf>)> {
412        // trim whitespace
413        let trimmed = line.trim_ascii();
414        // skip empty lines and comments
415        if trimmed.is_empty() || trimmed.starts_with(&[b'#']) {
416            return None;
417        }
418
419        // only variable assignment lines are allowed; split on `=`
420        let mut split = trimmed.splitn(2, |&b| b == b'=');
421        // extract assignment parts; ignore lines not in this format
422        let var = split.next()?;
423        let val = split.next()?;
424        debug_assert_eq!(split.next(), None);
425
426        // the path value is quoted; unquote it
427        let path = unquote(val, user_home)?;
428
429        let path = Some(path)
430            // ignore non-absolute paths
431            .filter(|path| path.is_absolute())
432            // setting to the home dir disables the directory configuration
433            .filter(|path| path != user_home);
434
435        Some((var, path))
436    }
437
438    fn unquote(bytes: &[u8], user_home: &Path) -> Option<PathBuf> {
439        let [b'"', rest @ .., b'"'] = bytes else { return None };
440
441        // setting to the home dir disables the directory configuration;
442        // if done symbolically, return None here and bypass later work
443        if matches!(rest, b"$HOME" | b"$HOME/") {
444            return None;
445        }
446
447        let mut rest = rest;
448        let mut s = Vec::with_capacity(rest.len());
449
450        // expand leading $HOME
451        if rest.starts_with(b"$HOME/") {
452            s.extend_from_slice(user_home.as_os_str().as_bytes());
453            if !user_home.has_trailing_sep() {
454                s.push(b'/');
455            }
456            rest = &rest[6..];
457        }
458
459        loop {
460            let i = rest
461                .iter()
462                .position(|&b| matches!(b, b'"' | b'\\' | b'$' | b'`'))
463                .unwrap_or(rest.len());
464            s.extend_from_slice(&rest[..i]);
465            match &rest[i..] {
466                [] => break,
467                [b'\\', c @ (b'"' | b'\\' | b'$' | b'`'), tail @ ..] => {
468                    // supported escapes
469                    s.push(*c);
470                    rest = tail;
471                }
472                [b'"' | b'\\' | b'$' | b'`', ..] => {
473                    // unsupported shell syntax
474                    return None;
475                }
476                _ => {
477                    if cfg!(debug_assertions) {
478                        unreachable!()
479                    } else {
480                        return None;
481                    }
482                }
483            }
484        }
485
486        Some(PathBuf::from(OsString::from_vec(s)))
487    }
488}
489
490#[cfg(test)]
491mod tests {
492    use super::*;
493
494    #[test]
495    fn can_fetch_xdg_base_dirs() {
496        let dirs = HomeDirs::xdg().unwrap();
497
498        assert!(dirs.cache_home().is_some());
499        assert!(dirs.config_home().is_some());
500        assert!(dirs.data_home().is_some());
501        assert!(dirs.state_home().is_some());
502        // dirs.runtime() may not exist
503        assert!(dirs.config_dirs().is_some());
504        assert!(dirs.data_dirs().is_some());
505    }
506
507    #[test]
508    fn can_fetch_xdg_media_dirs() {
509        let dirs = match MediaDirs::xdg() {
510            Ok(dirs) => dirs,
511            Err(e) if e.kind() == ErrorKind::NotFound => {
512                // xdg-user-dirs not initialized on this system, skip the test
513                return;
514            }
515            Err(e) => panic!("failed to fetch xdg user dirs: {e:?}"),
516        };
517
518        // even when user-dirs.dirs is present, directories may be unset,
519        // so we can't assert anything about the dir paths here
520        let _ = dirs;
521    }
522
523    #[test]
524    fn test_well_user_dirs_parsing() {
525        const TEST_USERDIRS: &'static str = r#"
526# This file is written by xdg-user-dirs-update
527# If you want to change or add directories, just edit the line you're
528# interested in. All local changes will be retained on the next run.
529# Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped
530# homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an
531# absolute path. No other format is supported.
532#
533XDG_DESKTOP_DIR="$HOME/Desktop"
534XDG_DOWNLOAD_DIR="$HOME/Downloads"
535XDG_TEMPLATES_DIR="/pub/Templates"
536XDG_PUBLICSHARE_DIR="/pub"
537XDG_DOCUMENTS_DIR="$HOME/Documents"
538XDG_MUSIC_DIR="$HOME/My \"Music\""
539XDG_PICTURES_DIR="$HOME/Pictures"
540XDG_VIDEOS_DIR="$HOME"
541"#;
542
543        let dirs = xdg::parse_user_dirs(TEST_USERDIRS.as_bytes(), Path::new("/home/ferris"));
544
545        assert_eq!(dirs.desktop(), Some("/home/ferris/Desktop".as_ref()));
546        assert_eq!(dirs.downloads(), Some("/home/ferris/Downloads".as_ref()));
547        assert_eq!(dirs.templates(), Some("/pub/Templates".as_ref()));
548        assert_eq!(dirs.documents(), Some("/home/ferris/Documents".as_ref()));
549        assert_eq!(dirs.music(), Some("/home/ferris/My \"Music\"".as_ref()));
550        assert_eq!(dirs.pictures(), Some("/home/ferris/Pictures".as_ref()));
551        assert_eq!(dirs.videos(), None);
552    }
553
554    #[test]
555    fn test_ill_user_dirs_parsing() {
556        const TEST_USERDIRS: &'static str = r#"
557# unquoted
558XDG_DESKTOP_DIR=$HOME/Desktop
559# mixed quoting
560XDG_DOWNLOAD_DIR="$HOME"/"Downloads"
561# non-HOME env
562XDG_TEMPLATES_DIR="/home/$USER/Templates"
563# missing PUBLICSHARE
564# command substitution
565XDG_DOCUMENTS_DIR="`pwd`/Documents"
566# non-symbolic homedir disabled
567XDG_MUSIC_DIR="/home/ferris"
568# set multiple times
569XDG_PICTURES_DIR="$HOME/Pictures1"
570XDG_PICTURES_DIR="$HOME/Pictures2"
571# relative path
572XDG_VIDEOS_DIR="Videos"
573"#;
574
575        let dirs = xdg::parse_user_dirs(TEST_USERDIRS.as_bytes(), Path::new("/home/ferris"));
576
577        assert_eq!(dirs.desktop(), None);
578        assert_eq!(dirs.downloads(), None);
579        assert_eq!(dirs.templates(), None);
580        assert_eq!(dirs.documents(), None);
581        assert_eq!(dirs.music(), None);
582        assert_eq!(dirs.pictures(), Some("/home/ferris/Pictures2".as_ref()));
583        assert_eq!(dirs.videos(), None);
584    }
585}