summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCasey Rodarmor <casey@rodarmor.com>2024-01-19 12:04:28 -0800
committerGitHub <noreply@github.com>2024-01-19 20:04:28 +0000
commit43d88f50e02057e5d91602ef4ffdd0ddfc094099 (patch)
treec4837c56b8a33d9da3865983e0f6ec9dfdbecd2b
parent22d462bd5596eae8d0d5e9a6b9dd330e25255987 (diff)
Add function to canonicalize paths (#1859)
-rw-r--r--README.md5
-rw-r--r--src/function.rs13
-rw-r--r--tests/functions.rs11
-rw-r--r--tests/test.rs11
4 files changed, 39 insertions, 1 deletions
diff --git a/README.md b/README.md
index b65fccfb..7e669400 100644
--- a/README.md
+++ b/README.md
@@ -1381,7 +1381,10 @@ The process ID is: 420
##### Fallible
- `absolute_path(path)` - Absolute path to relative `path` in the working
- directory. `absolute_path("./bar.txt")` in directory `/foo` is `/foo/bar.txt`.
+ directory. `absolute_path("./bar.txt")` in directory `/foo` is
+ `/foo/bar.txt`.
+- `canonicalize(path)` - Canonicalize `path` by resolving symlinks and removing
+ `.`, `..`, and extra `/`s where possible.
- `extension(path)` - Extension of `path`. `extension("/foo/bar.txt")` is
`txt`.
- `file_name(path)` - File name of `path` with any leading directory components
diff --git a/src/function.rs b/src/function.rs
index 464e64a1..1262076b 100644
--- a/src/function.rs
+++ b/src/function.rs
@@ -21,6 +21,7 @@ pub(crate) fn get(name: &str) -> Option<Function> {
let function = match name {
"absolute_path" => Unary(absolute_path),
"arch" => Nullary(arch),
+ "canonicalize" => Unary(canonicalize),
"cache_directory" => Nullary(|_| dir("cache", dirs::cache_dir)),
"capitalize" => Unary(capitalize),
"clean" => Unary(clean),
@@ -106,6 +107,18 @@ fn arch(_context: &FunctionContext) -> Result<String, String> {
Ok(target::arch().to_owned())
}
+fn canonicalize(_context: &FunctionContext, path: &str) -> Result<String, String> {
+ let canonical =
+ std::fs::canonicalize(path).map_err(|err| format!("I/O error canonicalizing path: {err}"))?;
+
+ canonical.to_str().map(str::to_string).ok_or_else(|| {
+ format!(
+ "Canonical path is not valid unicode: {}",
+ canonical.display(),
+ )
+ })
+}
+
fn capitalize(_context: &FunctionContext, s: &str) -> Result<String, String> {
let mut capitalized = String::new();
for (i, c) in s.chars().enumerate() {
diff --git a/tests/functions.rs b/tests/functions.rs
index e1da40ad..c3565242 100644
--- a/tests/functions.rs
+++ b/tests/functions.rs
@@ -662,3 +662,14 @@ fn just_pid() {
assert_eq!(stdout.parse::<u32>().unwrap(), pid);
}
+
+#[cfg(unix)]
+#[test]
+fn canonicalize() {
+ Test::new()
+ .args(["--evaluate", "x"])
+ .justfile("x := canonicalize('foo')")
+ .symlink("justfile", "foo")
+ .stdout_regex(".*/justfile")
+ .run();
+}
diff --git a/tests/test.rs b/tests/test.rs
index a615fb65..2a3a6ac3 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -113,6 +113,17 @@ impl Test {
self.tempdir.path().join("justfile")
}
+ #[cfg(unix)]
+ #[track_caller]
+ pub(crate) fn symlink(self, original: &str, link: &str) -> Self {
+ std::os::unix::fs::symlink(
+ self.tempdir.path().join(original),
+ self.tempdir.path().join(link),
+ )
+ .unwrap();
+ self
+ }
+
pub(crate) fn no_justfile(mut self) -> Self {
self.justfile = None;
self