From 43d88f50e02057e5d91602ef4ffdd0ddfc094099 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 19 Jan 2024 12:04:28 -0800 Subject: [PATCH] Add function to canonicalize paths (#1859) --- README.md | 5 ++++- src/function.rs | 13 +++++++++++++ tests/functions.rs | 11 +++++++++++ tests/test.rs | 11 +++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b65fccf..7e66940 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 464e64a..1262076 100644 --- a/src/function.rs +++ b/src/function.rs @@ -21,6 +21,7 @@ pub(crate) fn get(name: &str) -> Option { 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 { Ok(target::arch().to_owned()) } +fn canonicalize(_context: &FunctionContext, path: &str) -> Result { + 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 { let mut capitalized = String::new(); for (i, c) in s.chars().enumerate() { diff --git a/tests/functions.rs b/tests/functions.rs index e1da40a..c356524 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -662,3 +662,14 @@ fn just_pid() { assert_eq!(stdout.parse::().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 a615fb6..2a3a6ac 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