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};
67#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ExtraHomeDirs {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field3_finish(f, "ExtraHomeDirs",
"runtime", &self.runtime, "config_path", &self.config_path,
"data_path", &&self.data_path)
}
}Debug, #[automatically_derived]
impl ::core::default::Default for ExtraHomeDirs {
#[inline]
fn default() -> Self {
ExtraHomeDirs {
runtime: ::core::default::Default::default(),
config_path: ::core::default::Default::default(),
data_path: ::core::default::Default::default(),
}
}
}Default, #[automatically_derived]
impl ::core::clone::Clone for ExtraHomeDirs {
#[inline]
fn clone(&self) -> Self {
ExtraHomeDirs {
runtime: ::core::clone::Clone::clone(&self.runtime),
config_path: ::core::clone::Clone::clone(&self.config_path),
data_path: ::core::clone::Clone::clone(&self.data_path),
}
}
}Clone)]
8#[non_exhaustive]
9pub struct ExtraHomeDirs {
10 runtime: Option<PathBuf>,
11 config_path: Option<OsString>,
12 data_path: Option<OsString>,
13}
1415#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ExtraMediaDirs {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field1_finish(f,
"ExtraMediaDirs", "templates", &&self.templates)
}
}Debug, #[automatically_derived]
impl ::core::default::Default for ExtraMediaDirs {
#[inline]
fn default() -> Self {
ExtraMediaDirs { templates: ::core::default::Default::default() }
}
}Default, #[automatically_derived]
impl ::core::clone::Clone for ExtraMediaDirs {
#[inline]
fn clone(&self) -> Self {
ExtraMediaDirs {
templates: ::core::clone::Clone::clone(&self.templates),
}
}
}Clone)]
16#[non_exhaustive]
17pub struct ExtraMediaDirs {
18 templates: Option<PathBuf>,
19}
2021/// 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")]
81fn xdg() -> io::Result<Self>;
8283/// 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")]
94fn runtime_home(&self) -> Option<&Path>;
9596/// 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")]
107fn config_dirs(&self) -> Option<XdgDirs<'_>>;
108109/// 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")]
120fn data_dirs(&self) -> Option<XdgDirs<'_>>;
121122/// 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")]
128fn set_runtime_home(&mut self, path: PathBuf) -> &mut Self;
129130/// 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")]
139fn set_config_dirs(&mut self, paths: OsString) -> &mut Self;
140141/// 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")]
150fn set_data_dirs(&mut self, paths: OsString) -> &mut Self;
151}
152153/// 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")]
207fn xdg() -> io::Result<Self>;
208209/// 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")]
215fn templates(&self) -> Option<&Path>;
216217/// 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")]
223fn set_templates(&mut self, path: PathBuf) -> &mut Self;
224}
225226#[unstable(feature = "fs_home_dirs", issue = "162082")]
227#[derive(#[automatically_derived]
#[unstable(feature = "fs_home_dirs", issue = "162082")]
impl<'a> ::core::fmt::Debug for XdgDirs<'a> {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_tuple_field1_finish(f, "XdgDirs",
&&self.0)
}
}Debug)]
228pub struct XdgDirs<'a>(SplitPathsRef<'a>);
229230#[unstable(feature = "fs_home_dirs", issue = "162082")]
231impl HomeDirsExtfor HomeDirs {
232fn xdg() -> io::Result<Self> {
233let mut dirs = HomeDirs::empty();
234let user_home = xdg::user_home()?;
235236dirs.set_cache_home(xdg::dir_or_else(|| user_home.join(".cache"), "XDG_CACHE_HOME"));
237dirs.set_config_home(xdg::dir_or_else(|| user_home.join(".config"), "XDG_CONFIG_HOME"));
238dirs.set_data_home(xdg::dir_or_else(|| user_home.join(".local/share"), "XDG_DATA_HOME"));
239dirs.set_state_home(xdg::dir_or_else(|| user_home.join(".local/state"), "XDG_STATE_HOME"));
240if let Some(runtime) = xdg::dir("XDG_RUNTIME_DIR") {
241dirs.set_runtime_home(runtime);
242 }
243244dirs.set_config_dirs(xdg::dirs_or("/etc/xdg", "XDG_CONFIG_DIRS"));
245dirs.set_data_dirs(xdg::dirs_or("/usr/local/share/:/usr/share/", "XDG_DATA_DIRS"));
246247Ok(dirs)
248 }
249250fn runtime_home(&self) -> Option<&Path> {
251self.extra.runtime.as_deref()
252 }
253254fn config_dirs(&self) -> Option<XdgDirs<'_>> {
255self.extra.config_path.as_deref().map(XdgDirs::new)
256 }
257258fn data_dirs(&self) -> Option<XdgDirs<'_>> {
259self.extra.data_path.as_deref().map(XdgDirs::new)
260 }
261262fn set_runtime_home(&mut self, path: PathBuf) -> &mut Self {
263if !path.is_absolute() {
{
::core::panicking::panic_fmt(format_args!("runtime directory path must be absolute"));
}
};assert!(path.is_absolute(), "runtime directory path must be absolute");
264self.extra.runtime = Some(path);
265self266 }
267268fn set_config_dirs(&mut self, paths: OsString) -> &mut Self {
269for path in split_paths_ref(&paths) {
270if !path.is_absolute() {
{
::core::panicking::panic_fmt(format_args!("config directory paths must be absolute"));
}
};assert!(path.is_absolute(), "config directory paths must be absolute");
271 }
272self.extra.config_path = Some(paths);
273self274 }
275276fn set_data_dirs(&mut self, paths: OsString) -> &mut Self {
277for path in split_paths_ref(&paths) {
278if !path.is_absolute() {
{
::core::panicking::panic_fmt(format_args!("data directory paths must be absolute"));
}
};assert!(path.is_absolute(), "data directory paths must be absolute");
279 }
280self.extra.data_path = Some(paths);
281self282 }
283}
284285#[unstable(feature = "fs_media_dirs", issue = "162083")]
286impl MediaDirsExtfor MediaDirs {
287fn xdg() -> io::Result<Self> {
288let user_home = xdg::user_home()?;
289let config_home = xdg::dir_or_else(|| user_home.join(".config"), "XDG_CONFIG_HOME");
290291let spec = match fs::read(config_home.join("user-dirs.dirs")) {
292Ok(spec) => spec,
293Err(e) if e.kind() == ErrorKind::NotFound => {
294return Err(::core::hint::must_use(::core::io::Error::from_static_message(const {
&::core::io::SimpleMessage {
kind: ErrorKind::NotFound,
message: "missing `$XDG_CONFIG_HOME/user-dirs.dirs`",
}
}))const_error!(
295 ErrorKind::NotFound,
296"missing `$XDG_CONFIG_HOME/user-dirs.dirs`",
297 ));
298 }
299Err(e) => return Err(e),
300 };
301302Ok(xdg::parse_user_dirs(&spec, &user_home))
303 }
304305fn templates(&self) -> Option<&Path> {
306self.extra.templates.as_deref()
307 }
308309fn set_templates(&mut self, path: PathBuf) -> &mut Self {
310if !path.is_absolute() {
{
::core::panicking::panic_fmt(format_args!("templates directory path must be absolute"));
}
};assert!(path.is_absolute(), "templates directory path must be absolute");
311self.extra.templates = Some(path);
312self313 }
314}
315316impl<'a> XdgDirs<'a> {
317fn new(paths: &'a OsStr) -> Self {
318XdgDirs(split_paths_ref(paths))
319 }
320}
321322#[unstable(feature = "fs_home_dirs", issue = "162082")]
323impl<'a> Iteratorfor XdgDirs<'a> {
324type Item = &'a Path;
325326fn next(&mut self) -> Option<Self::Item> {
327self.0.next()
328 }
329}
330331fn split_paths_ref<'a>(paths: &'a OsStr) -> SplitPathsRef<'a> {
332// returns Some on cfg(unix)
333env::split_paths_ref(paths).unwrap()
334}
335336mod xdg {
337use super::{MediaDirs, split_paths_ref};
338use crate::env::{self, join_paths, var_os};
339use crate::ffi::OsString;
340use crate::io::{self, ErrorKind, const_error};
341use crate::ops::Deref;
342use crate::os::unix::ffi::{OsStrExt, OsStringExt};
343use crate::path::{Path, PathBuf};
344345pub fn user_home() -> io::Result<PathBuf> {
346 env::home_dir()
347 .filter(|p| p.is_absolute())
348 .ok_or(::core::hint::must_use(::core::io::Error::from_static_message(const {
&::core::io::SimpleMessage {
kind: ErrorKind::InvalidData,
message: "user home directory path not absolute",
}
}))const_error!(ErrorKind::InvalidData, "user home directory path not absolute"))
349 }
350351pub fn dir(env: &str) -> Option<PathBuf> {
352var_os(env).filter(|s| !s.is_empty()).map(PathBuf::from).filter(|path| path.is_absolute())
353 }
354355pub fn dir_or_else(fallback: impl FnOnce() -> PathBuf, env: &str) -> PathBuf {
356dir(env).unwrap_or_else(fallback)
357 }
358359fn dirs(env: &str) -> Option<OsString> {
360let dirs = var_os(env).filter(|s| !s.is_empty())?;
361if split_paths_ref(&dirs).all(|p| p.is_absolute()) {
362return Some(dirs);
363 }
364365struct DerefAsRef<T>(pub T);
366impl<T: Deref<Target: AsRef<U>>, U: ?Sized> AsRef<U> for DerefAsRef<T> {
367fn as_ref(&self) -> &U {
368 (*self.0).as_ref()
369 }
370 }
371372let paths = split_paths_ref(&dirs).filter(|p| p.is_absolute()).map(DerefAsRef);
373join_paths(paths).ok().filter(|s| !s.is_empty())
374 }
375376pub fn dirs_or(fallback: impl Into<OsString>, env: &str) -> OsString {
377dirs(env).unwrap_or_else(|| fallback.into())
378 }
379380pub fn parse_user_dirs<'a>(spec: &'a [u8], user_home: &Path) -> MediaDirs {
381let mut dirs = MediaDirs::empty();
382383for (xdg, path) in
384spec.split(|&b| b == b'\n').flat_map(|line| parse_user_dirs_line(line, user_home))
385 {
386// load the known user directories
387match xdg {
388b"XDG_DESKTOP_DIR" => dirs.desktop = path,
389b"XDG_DOCUMENTS_DIR" => dirs.documents = path,
390b"XDG_DOWNLOAD_DIR" => dirs.downloads = path,
391b"XDG_MUSIC_DIR" => dirs.music = path,
392b"XDG_PICTURES_DIR" => dirs.pictures = path,
393b"XDG_VIDEOS_DIR" => dirs.videos = path,
394b"XDG_TEMPLATES_DIR" => dirs.extra.templates = path,
395b"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 }
404405dirs406 }
407408fn parse_user_dirs_line<'a>(
409 line: &'a [u8],
410 user_home: &Path,
411 ) -> Option<(&'a [u8], Option<PathBuf>)> {
412// trim whitespace
413let trimmed = line.trim_ascii();
414// skip empty lines and comments
415if trimmed.is_empty() || trimmed.starts_with(&[b'#']) {
416return None;
417 }
418419// only variable assignment lines are allowed; split on `=`
420let mut split = trimmed.splitn(2, |&b| b == b'=');
421// extract assignment parts; ignore lines not in this format
422let var = split.next()?;
423let val = split.next()?;
424if true {
{
match (&split.next(), &None) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
let kind = ::core::panicking::AssertKind::Eq;
::core::panicking::assert_failed(kind, &*left_val,
&*right_val, ::core::option::Option::None);
}
}
}
};
};debug_assert_eq!(split.next(), None);
425426// the path value is quoted; unquote it
427let path = unquote(val, user_home)?;
428429let 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);
434435Some((var, path))
436 }
437438fn unquote(bytes: &[u8], user_home: &Path) -> Option<PathBuf> {
439let [b'"', rest @ .., b'"'] = byteselse { return None };
440441// setting to the home dir disables the directory configuration;
442 // if done symbolically, return None here and bypass later work
443if #[allow(non_exhaustive_omitted_patterns)] match rest {
b"$HOME" | b"$HOME/" => true,
_ => false,
}matches!(rest, b"$HOME" | b"$HOME/") {
444return None;
445 }
446447let mut rest = rest;
448let mut s = Vec::with_capacity(rest.len());
449450// expand leading $HOME
451if rest.starts_with(b"$HOME/") {
452s.extend_from_slice(user_home.as_os_str().as_bytes());
453if !user_home.has_trailing_sep() {
454s.push(b'/');
455 }
456rest = &rest[6..];
457 }
458459loop {
460let i = rest461 .iter()
462 .position(|&b| #[allow(non_exhaustive_omitted_patterns)] match b {
b'"' | b'\\' | b'$' | b'`' => true,
_ => false,
}matches!(b, b'"' | b'\\' | b'$' | b'`'))
463 .unwrap_or(rest.len());
464s.extend_from_slice(&rest[..i]);
465match &rest[i..] {
466 [] => break,
467 [b'\\', c @ (b'"' | b'\\' | b'$' | b'`'), tail @ ..] => {
468// supported escapes
469s.push(*c);
470rest = tail;
471 }
472 [b'"' | b'\\' | b'$' | b'`', ..] => {
473// unsupported shell syntax
474return None;
475 }
476_ => {
477if truecfg!(debug_assertions) {
478::core::panicking::panic("internal error: entered unreachable code")unreachable!()479 } else {
480return None;
481 }
482 }
483 }
484 }
485486Some(PathBuf::from(OsString::from_vec(s)))
487 }
488}
489490#[cfg(test)]
491mod tests {
492use super::*;
493494#[test]
495fn can_fetch_xdg_base_dirs() {
496let dirs = HomeDirs::xdg().unwrap();
497498assert!(dirs.cache_home().is_some());
499assert!(dirs.config_home().is_some());
500assert!(dirs.data_home().is_some());
501assert!(dirs.state_home().is_some());
502// dirs.runtime() may not exist
503assert!(dirs.config_dirs().is_some());
504assert!(dirs.data_dirs().is_some());
505 }
506507#[test]
508fn can_fetch_xdg_media_dirs() {
509let dirs = match MediaDirs::xdg() {
510Ok(dirs) => dirs,
511Err(e) if e.kind() == ErrorKind::NotFound => {
512// xdg-user-dirs not initialized on this system, skip the test
513return;
514 }
515Err(e) => panic!("failed to fetch xdg user dirs: {e:?}"),
516 };
517518// even when user-dirs.dirs is present, directories may be unset,
519 // so we can't assert anything about the dir paths here
520let _ = dirs;
521 }
522523#[test]
524fn test_well_user_dirs_parsing() {
525const 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"#;
542543let dirs = xdg::parse_user_dirs(TEST_USERDIRS.as_bytes(), Path::new("/home/ferris"));
544545assert_eq!(dirs.desktop(), Some("/home/ferris/Desktop".as_ref()));
546assert_eq!(dirs.downloads(), Some("/home/ferris/Downloads".as_ref()));
547assert_eq!(dirs.templates(), Some("/pub/Templates".as_ref()));
548assert_eq!(dirs.documents(), Some("/home/ferris/Documents".as_ref()));
549assert_eq!(dirs.music(), Some("/home/ferris/My \"Music\"".as_ref()));
550assert_eq!(dirs.pictures(), Some("/home/ferris/Pictures".as_ref()));
551assert_eq!(dirs.videos(), None);
552 }
553554#[test]
555fn test_ill_user_dirs_parsing() {
556const 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"#;
574575let dirs = xdg::parse_user_dirs(TEST_USERDIRS.as_bytes(), Path::new("/home/ferris"));
576577assert_eq!(dirs.desktop(), None);
578assert_eq!(dirs.downloads(), None);
579assert_eq!(dirs.templates(), None);
580assert_eq!(dirs.documents(), None);
581assert_eq!(dirs.music(), None);
582assert_eq!(dirs.pictures(), Some("/home/ferris/Pictures2".as_ref()));
583assert_eq!(dirs.videos(), None);
584 }
585}