Skip to content

Commit 876e334

Browse files
committed
Auto merge of #157518 - CAD97:xdg_basedir, r=aapoalas
Add xdg_basedir API Implements an API providing easy access to the XDG Base Directories. I chose to defer `runtime_dir` (`XDG_RUNTIME_DIR`) since the lack of a specified default (but specified expectation for applications to determine an alternative option somehow) makes the correct usage (and thus ideal API) less obvious. - Accepted ACP: rust-lang/libs-team#805 - Tracking issue: #157515
2 parents 8d6b380 + 378da80 commit 876e334

3 files changed

Lines changed: 169 additions & 0 deletions

File tree

library/std/src/env.rs

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

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: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
//! XDG (X Desktop Group) related functionality for Unix platforms.
2+
//!
3+
//! The [XDG Base Directory Specification][basedir] defines a set of base
4+
//! directories, relative to which user-specific files should be looked for. 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::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::{home_dir, split_paths, var_os};
25+
use crate::ffi::{OsStr, OsString};
26+
use crate::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: &str) -> 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 (cached) data 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+
#[derive(Debug, Clone)]
93+
pub struct XdgDirsIter {
94+
list: OsString,
95+
off: usize,
96+
}
97+
98+
impl XdgDirsIter {
99+
fn new(env: &str, default: &str) -> Self {
100+
let dirs = var_os(env).filter(|s| !s.is_empty()).unwrap_or_else(|| default.into());
101+
Self { list: dirs, off: 0 }
102+
}
103+
104+
fn remaining(&self) -> Option<&OsStr> {
105+
self.list.as_encoded_bytes().get(self.off..).map(|bytes| {
106+
// SAFETY: `self.off` is the index after a path separator (or the
107+
// start of the string), so is a valid OsStr boundary.
108+
unsafe { OsStr::from_encoded_bytes_unchecked(bytes) }
109+
})
110+
}
111+
}
112+
113+
impl Iterator for XdgDirsIter {
114+
type Item = PathBuf;
115+
116+
fn next(&mut self) -> Option<Self::Item> {
117+
let rest = self.remaining()?;
118+
let next = split_paths(rest).next()?;
119+
let len = next.as_os_str().len();
120+
self.off += len + 1; // Offset after this path and the separator after it.
121+
Some(next)
122+
}
123+
124+
fn size_hint(&self) -> (usize, Option<usize>) {
125+
let Some(dirs) = self.remaining() else { return (0, Some(0)) };
126+
split_paths(dirs).size_hint()
127+
}
128+
}
129+
130+
/// A set of preference ordered directories relative to which data files should be searched.
131+
///
132+
/// If an application defines a data file to be at `$XDG_DATA_DIRS/appid/file.name`, this means that:
133+
///
134+
/// - The initial data file should be installed to `{system_data_dir}/appid/file.name`.
135+
/// - A user-specific version of the data file may be created at
136+
/// <code>{[data_home_dir][]()}/appid/file.name</code>.
137+
/// - Lookups for the data file should search for `./appid/file.name` relative to
138+
/// `data_home_dir` and each directory in `data_dirs`, giving preference to
139+
/// files found relative to an earlier directory in the search order.
140+
///
141+
/// An application may choose to handle a file being located under multiple base
142+
/// directories however it sees fit, so long as it respects the search order.
143+
/// For example, it could say that only the first file found is used, or that
144+
/// data within the files is merged in some way.
145+
pub fn data_dirs() -> XdgDirsIter {
146+
// NB: the spec uses trailing slashes only for this default, for some reason
147+
XdgDirsIter::new("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/")
148+
}
149+
150+
/// A set of preference ordered directories relative to which configuration files should be searched.
151+
///
152+
/// If an application defines a configuration file to be at `$XDG_CONFIG_DIRS/appid/file.name`, this means that:
153+
///
154+
/// - The initial configuration file should be installed to `{system_config_dir}/xdg/appid/file.name`.
155+
/// - A user-specific version of the configuration file may be created at
156+
/// <code>{[config_home_dir][]()}/appid/file.name</code>.
157+
/// - Lookups for the configuration file should search for `./appid/file.name`
158+
/// relative to `config_home_dir` and each directory in `config_dirs`, giving
159+
/// preference to files found relative to an earlier directory in the search order.
160+
///
161+
/// An application may choose to handle a file being located under multiple base
162+
/// directories however it sees fit, so long as it respects the search order.
163+
/// For example, it could say that only the first file found is used, or that
164+
/// data within the files is merged in some way.
165+
pub fn config_dirs() -> XdgDirsIter {
166+
XdgDirsIter::new("XDG_CONFIG_DIRS", "/etc/xdg")
167+
}

0 commit comments

Comments
 (0)