Skip to content

Commit 9e48634

Browse files
committed
Add xdg_basedir API
1 parent 8954863 commit 9e48634

3 files changed

Lines changed: 167 additions & 0 deletions

File tree

library/std/src/env.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,7 @@ impl Error for JoinPathsError {
600600
/// For example, [XDG Base Directories] on Unix or the `LOCALAPPDATA` and `APPDATA` environment variables on Windows.
601601
///
602602
/// [XDG Base Directories]: https://specifications.freedesktop.org/basedir-spec/latest/
603+
// feature(xdg_basedir): This should link to std::os::unix::xdg once it's stabilized
603604
///
604605
/// # Unix
605606
///

library/std/src/os/unix/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ pub mod net;
9494
pub mod process;
9595
pub mod raw;
9696
pub mod thread;
97+
pub mod xdg;
9798

9899
/// A prelude for conveniently writing platform-specific code.
99100
///

library/std/src/os/unix/xdg.rs

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
//! XDG (X Desktop Group) related functionality for Unix platforms.
2+
//!
3+
//! The [XDG Base Directory Specification][basedir] defines where user-specific
4+
//! files should be looked for relative to a set of base directories. The
5+
//! functions in this module provide those directory paths as configured by
6+
//! the environment.
7+
//!
8+
//! Note that the use of these functions is not enforced by the system, and as
9+
//! such, not all programs will necessarily respect all details of the XDG path
10+
//! environment. This is a set of guidelines, and each program is ultimately
11+
//! responsible for defining where and how it both reads and writes files.
12+
//!
13+
//! Use of XDG paths can be generally considered the conventional expectation
14+
//! on Linux-based systems. Other Unix-based systems may or may not play well
15+
//! with the XDG conventions.
16+
//!
17+
//! Directories returned by this module are not guaranteed to exist yet. If the
18+
//! directory does not exist, an application should attempt to create it with
19+
//! [permissions mode][super::super::fs::PermissionsExt::from_mode] `0o700`.
20+
//!
21+
//! [basedir]: https://specifications.freedesktop.org/basedir/latest/
22+
#![unstable(feature = "xdg_basedir", issue = "157515")]
23+
24+
use crate::env::{SplitPaths, home_dir, split_paths, var_os};
25+
use crate::ffi::{OsStr, OsString};
26+
use crate::path::{Path, PathBuf};
27+
28+
fn xdg_home_dir() -> PathBuf {
29+
// Note: home_dir can return `Some("")` in some cases. We assume that in
30+
// this case the expected behavior is for `$HOME/path` to become `/path`,
31+
// i.e. the home directory is effectively `/`.
32+
match home_dir() {
33+
None => panic!("an XDG environment should have a home directory"),
34+
Some(home) if home.is_empty() => PathBuf::from("/"),
35+
Some(home) => home,
36+
}
37+
}
38+
39+
fn xdg_dir(env: &str, fallback_home_subdir: impl AsRef<Path>) -> PathBuf {
40+
var_os(env)
41+
.filter(|s| !s.is_empty())
42+
.map(PathBuf::from)
43+
.unwrap_or_else(|| xdg_home_dir().join(fallback_home_subdir))
44+
}
45+
46+
/// A base directory relative to which user-specific data files should be written.
47+
///
48+
/// An application `appid` would typically be expected to write its data files
49+
/// to `{data_home_dir}/{appid}/**/*`.
50+
pub fn data_home_dir() -> PathBuf {
51+
xdg_dir("XDG_DATA_HOME", ".local/share")
52+
}
53+
54+
/// A base directory relative to which user-specific configuration files should be written.
55+
///
56+
/// An application `appid` would typically be expected to write its configuration
57+
/// files to `{config_home_dir}/{appid}/**/*`.
58+
pub fn config_home_dir() -> PathBuf {
59+
xdg_dir("XDG_CONFIG_HOME", ".config")
60+
}
61+
62+
/// A base directory relative to which user-specific state data should be written.
63+
///
64+
/// An application `appid` would typically be expected to write its state data to
65+
/// `{state_home_dir}/{appid}/**/*`.
66+
///
67+
/// Common kinds of state data include actions history (such as logs, history,
68+
/// recently used files, etc.) and state of the application that can be reused
69+
/// after application restart (such as view, layout, open files, undo history,
70+
/// etc.).
71+
pub fn state_home_dir() -> PathBuf {
72+
xdg_dir("XDG_STATE_HOME", ".local/state")
73+
}
74+
75+
/// A base directory relative to which user-specific non-essential caches should be written.
76+
///
77+
/// An application `appid` would typically be expected to write its cache data to
78+
/// `{cache_home_dir}/{appid}/**/*`.
79+
pub fn cache_home_dir() -> PathBuf {
80+
xdg_dir("XDG_CACHE_HOME", ".cache")
81+
}
82+
83+
/// An iterator that produces directory paths from XDG environment configuration.
84+
///
85+
/// The iterator element type is [`PathBuf`].
86+
///
87+
/// This structure is created by [`xdg::data_dirs`] and [`xdg::config_dirs`].
88+
/// See the documentation of those functions for more.
89+
///
90+
/// [`xdg::data_dirs`]: data_dirs
91+
/// [`xdg::config_dirs`]: config_dirs
92+
//
93+
// This stores Option so we can track when we have a trailing empty component.
94+
// None is an exhausted iterator, Some("") is a trailing empty component.
95+
#[derive(Debug, Clone)]
96+
pub struct XdgDirsIter(Option<OsString>);
97+
98+
impl XdgDirsIter {
99+
fn new(env: &str, default: impl AsRef<OsStr>) -> Self {
100+
let dirs = var_os(env).filter(|s| !s.is_empty()).unwrap_or_else(|| default.as_ref().into());
101+
Self(Some(dirs))
102+
}
103+
}
104+
105+
impl Iterator for XdgDirsIter {
106+
type Item = PathBuf;
107+
108+
fn next(&mut self) -> Option<Self::Item> {
109+
let dirs = self.0.take()?;
110+
let next = split_paths(&dirs).next()?;
111+
let len = next.as_os_str().len();
112+
let mut bytes = dirs.into_encoded_bytes();
113+
if len < bytes.len() {
114+
// Remove the path about to be returned and the separator after it.
115+
bytes.drain(..len + 1);
116+
// SAFETY: UNIX guarantees that the path separator is b':'. As `bytes`
117+
// now holds the suffix after the separator, it's a valid OsStr.
118+
self.0 = Some(unsafe { OsString::from_encoded_bytes_unchecked(bytes) });
119+
}
120+
Some(next)
121+
}
122+
123+
fn size_hint(&self) -> (usize, Option<usize>) {
124+
split_paths(&self.0).size_hint()
125+
}
126+
}
127+
128+
/// A set of preference ordered directories relative to which data files should be searched.
129+
///
130+
/// If an application defines a data file to be at `$XDG_DATA_DIRS/appid/file.name`, this means that:
131+
///
132+
/// - The initial data file should be installed to `{system_data_dir}/appid/file.name`.
133+
/// - A user-specific version of the data file may be created at
134+
/// <code>{[data_home_dir][]()}/appid/file.name</code>.
135+
/// - Lookups for the data file should search for `./appid/file.name` relative to
136+
/// `data_home_dir` and each directory in `data_dirs`, giving preference to
137+
/// files found relative to an earlier directory in the search order.
138+
///
139+
/// An application may choose to handle a file being located under multiple base
140+
/// directories however it sees fit, so long as it respects the search order.
141+
/// For example, it could say that only the first file found is used, or that
142+
/// data within the files is merged in some way.
143+
pub fn data_dirs() -> XdgDirsIter {
144+
// NB: the spec uses trailing slashes only for this default, for some reason
145+
XdgDirsIter::new("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/")
146+
}
147+
148+
/// A set of preference ordered directories relative to which configuration files should be searched.
149+
///
150+
/// If an application defines a configuration file to be at `$XDG_CONFIG_DIRS/appid/file.name`, this means that:
151+
///
152+
/// - The initial configuration file should be installed to `{system_config_dir}/xdg/appid/file.name`.
153+
/// - A user-specific version of the configuration file may be created at
154+
/// <code>{[config_home_dir][]()}/appid/file.name</code>.
155+
/// - Lookups for the configuration file should search for `./appid/file.name`
156+
/// relative to `config_home_dir` and each directory in `config_dirs`, giving
157+
/// preference to files found relative to an earlier directory in the search order.
158+
///
159+
/// An application may choose to handle a file being located under multiple base
160+
/// directories however it sees fit, so long as it respects the search order.
161+
/// For example, it could say that only the first file found is used, or that
162+
/// data within the files is merged in some way.
163+
pub fn config_dirs() -> XdgDirsIter {
164+
XdgDirsIter::new("XDG_CONFIG_DIRS", "/etc/xdg")
165+
}

0 commit comments

Comments
 (0)