1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
//! The directory in which a platform keeps the local data of a user
use typed_fields::path;
path!(
/// The directory in which the platform keeps the local data of a user
///
/// The common platforms have one such directory, and it holds the files
/// of all applications of the user. The data in it stays on one machine,
/// and it does not roam to the other machines of the user on Windows.
///
/// Where the directory is differs by platform, and it is below the home
/// directory of the user. An instance of this type says only where the
/// platform keeps the data. It does not say that the directory exists.
LocalDataDirectory
);
impl LocalDataDirectory {
/// Returns the local data directory of the platform for the current user
///
/// On Linux, and on the other systems that follow the [XDG Base
/// Directory Specification][xdg], the directory is the one that the
/// environment variable `XDG_DATA_HOME` names, and `.local/share` in the
/// home directory when the variable holds no absolute path. On macOS, it
/// is `Library/Application Support` in the home directory, and the
/// variable `XDG_DATA_HOME` has no effect. On Windows, it is the
/// directory for the local application data of the user.
///
/// The function determines the directory again on every call, so a
/// change to `XDG_DATA_HOME` or to `HOME` takes effect at the next call.
///
/// Returns [`None`] when the platform has no local data directory, or
/// when the function cannot determine the home directory of the user.
///
/// [xdg]: https://specifications.freedesktop.org/basedir/latest/
// The rules of a platform belong to that platform, and a library keeps
// up with them for us. This type wraps the answer, so that the library
// stays out of the public API and the crate can replace it without a
// breaking change.
// project[impl data.base.macos]
// project[impl data.base.windows]
// project[impl data.base.xdg]
// project[impl data.base.xdg.default]
pub(super) fn of_platform() -> Option<Self> {
dirs::data_local_dir().map(Self::new)
}
}
#[cfg(test)]
mod tests {
// An assertion in a test panics by design. A `# Panics` section on every
// test would repeat that and give the reader no information.
#![allow(clippy::missing_panics_doc)]
use std::path::PathBuf;
use super::*;
// Each of these tests states the rule of one platform, and it runs only on
// that platform. A test cannot set an environment variable to reach the
// rule of another one, because a process shares its environment with every
// thread in it and tests run in parallel. The rule of a platform therefore
// stays unverified until a machine of that platform runs the tests.
//
// The environment that the test got decides which branch of a rule it
// reaches. On a system that follows the XDG specification, a run with
// `XDG_DATA_HOME` exercises the variable, and a run without it exercises
// the default.
// project[verify data.base.macos]
#[cfg(target_os = "macos")]
#[test]
fn of_platform_on_macos_returns_the_directory_of_apple() {
let home = std::env::var_os("HOME").expect("the test needs a home directory");
let expected = PathBuf::from(home)
.join("Library")
.join("Application Support");
let directory = LocalDataDirectory::of_platform();
assert_eq!(directory, Some(LocalDataDirectory::new(expected)));
}
// project[verify data.base.windows]
#[cfg(windows)]
#[test]
fn of_platform_on_windows_returns_the_local_application_data() {
let expected = std::env::var_os("LOCALAPPDATA")
.map(PathBuf::from)
.expect("the test needs a directory for local application data");
let directory = LocalDataDirectory::of_platform();
assert_eq!(directory, Some(LocalDataDirectory::new(expected)));
}
// project[verify data.base.xdg]
// project[verify data.base.xdg.default]
#[cfg(not(any(target_os = "macos", windows)))]
#[test]
fn of_platform_on_xdg_systems_returns_the_data_home() {
let home = std::env::var_os("HOME").expect("the test needs a home directory");
let expected = std::env::var_os("XDG_DATA_HOME")
.map(PathBuf::from)
.filter(|path| path.is_absolute())
.unwrap_or_else(|| PathBuf::from(home).join(".local").join("share"));
let directory = LocalDataDirectory::of_platform();
assert_eq!(directory, Some(LocalDataDirectory::new(expected)));
}
}