diff --git a/README.md b/README.md index 6c5bd75..167ab1b 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 943c534..cc7116e 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 { + 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 { Ok(target::arch().to_owned()) } diff --git a/tests/functions.rs b/tests/functions.rs index ffbf2f6..5eb865e 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()