summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTrevor Gross <tmgross@umich.edu>2024-01-11 18:50:04 -0500
committerGitHub <noreply@github.com>2024-01-11 23:50:04 +0000
commitc2af5a1dd6f471349b71a63703ed51e722fba7a2 (patch)
tree13f29524a6b7c11f8486cd2d7fa2948c384ab70d
parent6d320f77af5d1c9f8c9ead657055efc86e95a4a2 (diff)
Add functions to return XDG base directories (#1822)
-rw-r--r--README.md17
-rw-r--r--src/function.rs43
-rw-r--r--tests/directories.rs85
-rw-r--r--tests/lib.rs1
4 files changed, 146 insertions, 0 deletions
diff --git a/README.md b/README.md
index aae963c7..87eb44c4 100644
--- a/README.md
+++ b/README.md
@@ -1415,6 +1415,23 @@ which will halt execution.
`requirement`, e.g., `">=0.1.0"`, returning `"true"` if so and `"false"`
otherwise.
+##### XDG Directories
+
+These functions return paths to user-specific directories for things like
+configuration, data, caches, executables, and the user's home directory. These
+functions follow the
+[XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html),
+and are implemented with the
+[`dirs`](https://docs.rs/dirs/latest/dirs/index.html) crate.
+
+- `cache_directory()` - The user-specific cache directory.
+- `config_directory()` - The user-specific configuration directory.
+- `config_local_directory()` - The local user-specific configuration directory.
+- `data_directory()` - The user-specific data directory.
+- `data_local_directory()` - The local user-specific data directory.
+- `executable_directory()` - The user-specific executable directory.
+- `home_directory()` - The user's home directory.
+
### Recipe Attributes
Recipes may be annotated with attributes that change their behavior.
diff --git a/src/function.rs b/src/function.rs
index 086ede1b..c4923e59 100644
--- a/src/function.rs
+++ b/src/function.rs
@@ -21,15 +21,22 @@ pub(crate) fn get(name: &str) -> Option<Function> {
let function = match name {
"absolute_path" => Unary(absolute_path),
"arch" => Nullary(arch),
+ "cache_directory" => Nullary(|_| dir("cache", dirs::cache_dir)),
"capitalize" => Unary(capitalize),
"clean" => Unary(clean),
+ "config_directory" => Nullary(|_| dir("config", dirs::config_dir)),
+ "config_local_directory" => Nullary(|_| dir("local config", dirs::config_local_dir)),
+ "data_directory" => Nullary(|_| dir("data", dirs::data_dir)),
+ "data_local_directory" => Nullary(|_| dir("local data", dirs::data_local_dir)),
"env" => UnaryOpt(env),
"env_var" => Unary(env_var),
"env_var_or_default" => Binary(env_var_or_default),
"error" => Unary(error),
+ "executable_directory" => Nullary(|_| dir("executable", dirs::executable_dir)),
"extension" => Unary(extension),
"file_name" => Unary(file_name),
"file_stem" => Unary(file_stem),
+ "home_directory" => Nullary(|_| dir("home", dirs::home_dir)),
"invocation_directory" => Nullary(invocation_directory),
"invocation_directory_native" => Nullary(invocation_directory_native),
"join" => BinaryPlus(join),
@@ -114,6 +121,22 @@ fn clean(_context: &FunctionContext, path: &str) -> Result<String, String> {
Ok(Path::new(path).lexiclean().to_str().unwrap().to_owned())
}
+fn dir(name: &'static str, f: fn() -> Option<PathBuf>) -> Result<String, String> {
+ match f() {
+ Some(path) => path
+ .as_os_str()
+ .to_str()
+ .map(str::to_string)
+ .ok_or_else(|| {
+ format!(
+ "unable to convert {name} directory path to string: {}",
+ path.display(),
+ )
+ }),
+ None => Err(format!("{name} directory not found")),
+ }
+}
+
fn env_var(context: &FunctionContext, key: &str) -> Result<String, String> {
use std::env::VarError::*;
@@ -433,3 +456,23 @@ fn semver_matches(
.to_string(),
)
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn dir_not_found() {
+ assert_eq!(dir("foo", || None).unwrap_err(), "foo directory not found");
+ }
+
+ #[cfg(unix)]
+ #[test]
+ fn dir_not_unicode() {
+ use std::os::unix::ffi::OsStrExt;
+ assert_eq!(
+ dir("foo", || Some(OsStr::from_bytes(b"\xe0\x80\x80").into())).unwrap_err(),
+ "unable to convert foo directory path to string: ���",
+ );
+ }
+}
diff --git a/tests/directories.rs b/tests/directories.rs
new file mode 100644
index 00000000..41a05cd6
--- /dev/null
+++ b/tests/directories.rs
@@ -0,0 +1,85 @@
+use super::*;
+
+#[test]
+fn cache_directory() {
+ Test::new()
+ .justfile("x := cache_directory()")
+ .args(["--evaluate", "x"])
+ .stdout(dirs::cache_dir().unwrap_or_default().to_string_lossy())
+ .run();
+}
+
+#[test]
+fn config_directory() {
+ Test::new()
+ .justfile("x := config_directory()")
+ .args(["--evaluate", "x"])
+ .stdout(dirs::config_dir().unwrap_or_default().to_string_lossy())
+ .run();
+}
+
+#[test]
+fn config_local_directory() {
+ Test::new()
+ .justfile("x := config_local_directory()")
+ .args(["--evaluate", "x"])
+ .stdout(
+ dirs::config_local_dir()
+ .unwrap_or_default()
+ .to_string_lossy(),
+ )
+ .run();
+}
+
+#[test]
+fn data_directory() {
+ Test::new()
+ .justfile("x := data_directory()")
+ .args(["--evaluate", "x"])
+ .stdout(dirs::data_dir().unwrap_or_default().to_string_lossy())
+ .run();
+}
+
+#[test]
+fn data_local_directory() {
+ Test::new()
+ .justfile("x := data_local_directory()")
+ .args(["--evaluate", "x"])
+ .stdout(dirs::data_local_dir().unwrap_or_default().to_string_lossy())
+ .run();
+}
+
+#[test]
+fn executable_directory() {
+ if let Some(executable_dir) = dirs::executable_dir() {
+ Test::new()
+ .justfile("x := executable_directory()")
+ .args(["--evaluate", "x"])
+ .stdout(executable_dir.to_string_lossy())
+ .run();
+ } else {
+ Test::new()
+ .justfile("x := executable_directory()")
+ .args(["--evaluate", "x"])
+ .stderr(
+ "
+ error: Call to function `executable_directory` failed: executable directory not found
+ ——▶ justfile:1:6
+ │
+ 1 │ x := executable_directory()
+ │ ^^^^^^^^^^^^^^^^^^^^
+ ",
+ )
+ .status(EXIT_FAILURE)
+ .run();
+ }
+}
+
+#[test]
+fn home_directory() {
+ Test::new()
+ .justfile("x := home_directory()")
+ .args(["--evaluate", "x"])
+ .stdout(dirs::home_dir().unwrap_or_default().to_string_lossy())
+ .run();
+}
diff --git a/tests/lib.rs b/tests/lib.rs
index 24d3ffa2..a9f3c344 100644
--- a/tests/lib.rs
+++ b/tests/lib.rs
@@ -44,6 +44,7 @@ mod completions;
mod conditional;
mod confirm;
mod delimiters;
+mod directories;
mod dotenv;
mod edit;
mod equals;