Repository: casey/just
Pull request #1121 (2022-02-27 17:02:25)

Add `absolute_path` function
I asked on the discord and was told there was no function to get the absolute path from a relative path, so here one is!

I haven't written tests for this, please let me know what form the should take.

casey: For some reason the CI job didn't start, so I pushed an empty commit. This should merge soon. If this is functionality that you need, I'm happy to cut a release, just let me know.
Laura7089: > 

I'm in no desperate need :)

This has been my first even contribution to a FOSS project, so thanks for giving me the opportunity and helping me through it!

Patch:
diff --git a/README.md b/README.md
index 6c5bd757..167ab1bc 100644
--- a/README.md
+++ b/README.md
@@ -988,6 +988,8 @@ The executable is at: /bin/just
 
 ##### Fallible
 
+- `absolute_path(path)` - Absolute path to relative `path` in the invocation directory. `absolute_path("./bar.txt")` in directory `/foo` is `/foo/bar.txt`.
+
 - `extension(path)` - Extension of `path`. `extension("/foo/bar.txt")` is `txt`.
 
 - `file_name(path)` - File name of `path` with any leading directory components removed. `file_name("/foo/bar.txt")` is `bar.txt`.
diff --git a/src/function.rs b/src/function.rs
index 943c5347..cc7116e0 100644
--- a/src/function.rs
+++ b/src/function.rs
@@ -14,6 +14,7 @@ pub(crate) enum Function {
 
 lazy_static! {
   pub(crate) static ref TABLE: BTreeMap<&'static str, Function> = vec![
+    ("absolute_path", Unary(absolute_path)),
     ("arch", Nullary(arch)),
     ("clean", Unary(clean)),
     ("env_var", Unary(env_var)),
@@ -59,6 +60,17 @@ impl Function {
   }
 }
 
+fn absolute_path(context: &FunctionContext, path: &str) -> Result<String, String> {
+  let abs_path_unchecked = context.search.working_directory.join(path).lexiclean();
+  match abs_path_unchecked.to_str() {
+    Some(absolute_path) => Ok(absolute_path.to_owned()),
+    None => Err(format!(
+      "Working directory is not valid unicode: {}",
+      context.search.working_directory.display()
+    )),
+  }
+}
+
 fn arch(_context: &FunctionContext) -> Result<String, String> {
   Ok(target::arch().to_owned())
 }


Test patch:
diff --git a/tests/functions.rs b/tests/functions.rs
index ffbf2f60..5eb865e0 100644
--- a/tests/functions.rs
+++ b/tests/functions.rs
@@ -404,6 +404,54 @@ fn test_path_exists_filepath_doesnt_exist() {
     .run();
 }
 
+#[test]
+fn test_absolute_path_resolves() {
+  let test_object = Test::new()
+    .justfile("path := absolute_path('./test_file')")
+    .args(&["--evaluate", "path"]);
+
+  let mut tempdir = test_object.tempdir.path().to_owned();
+
+  // Just retrieves the current directory via env::current_dir(), which
+  // does the moral equivalent of canonicalize, which will remove symlinks.
+  // So, we have to canonicalize here, so that we can match it.
+  if cfg!(unix) {
+    tempdir = tempdir.canonicalize().unwrap();
+  }
+
+  test_object
+    .stdout(tempdir.join("test_file").to_str().unwrap().to_owned())
+    .run();
+}
+
+#[test]
+fn test_absolute_path_resolves_parent() {
+  let test_object = Test::new()
+    .justfile("path := absolute_path('../test_file')")
+    .args(&["--evaluate", "path"]);
+
+  let mut tempdir = test_object.tempdir.path().to_owned();
+
+  // Just retrieves the current directory via env::current_dir(), which
+  // does the moral equivalent of canonicalize, which will remove symlinks.
+  // So, we have to canonicalize here, so that we can match it.
+  if cfg!(unix) {
+    tempdir = tempdir.canonicalize().unwrap();
+  }
+
+  test_object
+    .stdout(
+      tempdir
+        .parent()
+        .unwrap()
+        .join("test_file")
+        .to_str()
+        .unwrap()
+        .to_owned(),
+    )
+    .run();
+}
+
 #[test]
 fn path_exists_subdir() {
   Test::new()