Add functions to return XDG base directories (#1822)

This commit is contained in:
Trevor Gross 2024-01-11 18:50:04 -05:00 committed by GitHub
parent 6d320f77af
commit c2af5a1dd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 146 additions and 0 deletions

View File

@ -1415,6 +1415,23 @@ which will halt execution.
`requirement`, e.g., `">=0.1.0"`, returning `"true"` if so and `"false"` `requirement`, e.g., `">=0.1.0"`, returning `"true"` if so and `"false"`
otherwise. 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 ### Recipe Attributes
Recipes may be annotated with attributes that change their behavior. Recipes may be annotated with attributes that change their behavior.

View File

@ -21,15 +21,22 @@ pub(crate) fn get(name: &str) -> Option<Function> {
let function = match name { let function = match name {
"absolute_path" => Unary(absolute_path), "absolute_path" => Unary(absolute_path),
"arch" => Nullary(arch), "arch" => Nullary(arch),
"cache_directory" => Nullary(|_| dir("cache", dirs::cache_dir)),
"capitalize" => Unary(capitalize), "capitalize" => Unary(capitalize),
"clean" => Unary(clean), "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" => UnaryOpt(env),
"env_var" => Unary(env_var), "env_var" => Unary(env_var),
"env_var_or_default" => Binary(env_var_or_default), "env_var_or_default" => Binary(env_var_or_default),
"error" => Unary(error), "error" => Unary(error),
"executable_directory" => Nullary(|_| dir("executable", dirs::executable_dir)),
"extension" => Unary(extension), "extension" => Unary(extension),
"file_name" => Unary(file_name), "file_name" => Unary(file_name),
"file_stem" => Unary(file_stem), "file_stem" => Unary(file_stem),
"home_directory" => Nullary(|_| dir("home", dirs::home_dir)),
"invocation_directory" => Nullary(invocation_directory), "invocation_directory" => Nullary(invocation_directory),
"invocation_directory_native" => Nullary(invocation_directory_native), "invocation_directory_native" => Nullary(invocation_directory_native),
"join" => BinaryPlus(join), "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()) 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> { fn env_var(context: &FunctionContext, key: &str) -> Result<String, String> {
use std::env::VarError::*; use std::env::VarError::*;
@ -433,3 +456,23 @@ fn semver_matches(
.to_string(), .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: <20><><EFBFBD>",
);
}
}

85
tests/directories.rs Normal file
View File

@ -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();
}

View File

@ -44,6 +44,7 @@ mod completions;
mod conditional; mod conditional;
mod confirm; mod confirm;
mod delimiters; mod delimiters;
mod directories;
mod dotenv; mod dotenv;
mod edit; mod edit;
mod equals; mod equals;