Skip to content

Commit

Permalink
Add clean function for simplifying paths (#883)
Browse files Browse the repository at this point in the history
  • Loading branch information
casey authored Jun 25, 2021
1 parent 87e3952 commit 024f827
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 255 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ dotenv = "0.15.0"
edit-distance = "2.0.0"
env_logger = "0.8.0"
lazy_static = "1.0.0"
lexiclean = "0.0.1"
libc = "0.2.0"
log = "0.4.4"
snafu = "0.6.0"
Expand Down
3 changes: 1 addition & 2 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -807,8 +807,7 @@ These functions can fail, for example if a path does not have an extension, whic
===== Infallible

- `join(a, b)` - Join path `a` with path `b`. `join("foo/bar", "baz")` is `foo/bar/baz`.

These functions always succeed.
- `clean(path)` - Simplify `path` by removing extra path separators, intermediate `.` components, and `..` where possible. `clean("foo//bar")` is `foo/bar`, `clean("foo/..")` is `.`, `clean("foo/./bar")` is `foo/bar`.

=== Command Evaluation Using Backticks

Expand Down
1 change: 1 addition & 0 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub(crate) use std::{
pub(crate) use camino::Utf8Path;
pub(crate) use derivative::Derivative;
pub(crate) use edit_distance::edit_distance;
pub(crate) use lexiclean::Lexiclean;
pub(crate) use libc::EXIT_FAILURE;
pub(crate) use log::{info, warn};
pub(crate) use snafu::{ResultExt, Snafu};
Expand Down
5 changes: 5 additions & 0 deletions src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub(crate) enum Function {
lazy_static! {
pub(crate) static ref TABLE: BTreeMap<&'static str, Function> = vec![
("arch", Nullary(arch)),
("clean", Unary(clean)),
("env_var", Unary(env_var)),
("env_var_or_default", Binary(env_var_or_default)),
("extension", Unary(extension)),
Expand Down Expand Up @@ -43,6 +44,10 @@ fn arch(_context: &FunctionContext) -> Result<String, String> {
Ok(target::arch().to_owned())
}

fn clean(_context: &FunctionContext, path: &str) -> Result<String, String> {
Ok(Path::new(path).lexiclean().to_str().unwrap().to_owned())
}

fn env_var(context: &FunctionContext, key: &str) -> Result<String, String> {
use std::env::VarError::*;

Expand Down
264 changes: 264 additions & 0 deletions tests/functions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
use crate::common::*;

test! {
name: test_os_arch_functions_in_interpolation,
justfile: r#"
foo:
echo {{arch()}} {{os()}} {{os_family()}}
"#,
stdout: format!("{} {} {}\n", target::arch(), target::os(), target::os_family()).as_str(),
stderr: format!("echo {} {} {}\n", target::arch(), target::os(), target::os_family()).as_str(),
}

test! {
name: test_os_arch_functions_in_expression,
justfile: r#"
a := arch()
o := os()
f := os_family()
foo:
echo {{a}} {{o}} {{f}}
"#,
stdout: format!("{} {} {}\n", target::arch(), target::os(), target::os_family()).as_str(),
stderr: format!("echo {} {} {}\n", target::arch(), target::os(), target::os_family()).as_str(),
}

#[cfg(not(windows))]
test! {
name: env_var_functions,
justfile: r#"
p := env_var('USER')
b := env_var_or_default('ZADDY', 'HTAP')
x := env_var_or_default('XYZ', 'ABC')
foo:
/bin/echo '{{p}}' '{{b}}' '{{x}}'
"#,
stdout: format!("{} HTAP ABC\n", env::var("USER").unwrap()).as_str(),
stderr: format!("/bin/echo '{}' 'HTAP' 'ABC'\n", env::var("USER").unwrap()).as_str(),
}

#[cfg(not(windows))]
test! {
name: path_functions,
justfile: r#"
we := without_extension('/foo/bar/baz.hello')
fs := file_stem('/foo/bar/baz.hello')
fn := file_name('/foo/bar/baz.hello')
dir := parent_directory('/foo/bar/baz.hello')
ext := extension('/foo/bar/baz.hello')
jn := join('a', 'b')
foo:
/bin/echo '{{we}}' '{{fs}}' '{{fn}}' '{{dir}}' '{{ext}}' '{{jn}}'
"#,
stdout: "/foo/bar/baz baz baz.hello /foo/bar hello a/b\n",
stderr: "/bin/echo '/foo/bar/baz' 'baz' 'baz.hello' '/foo/bar' 'hello' 'a/b'\n",
}

#[cfg(not(windows))]
test! {
name: path_functions2,
justfile: r#"
we := without_extension('/foo/bar/baz')
fs := file_stem('/foo/bar/baz.hello.ciao')
fn := file_name('/bar/baz.hello.ciao')
dir := parent_directory('/foo/')
ext := extension('/foo/bar/baz.hello.ciao')
foo:
/bin/echo '{{we}}' '{{fs}}' '{{fn}}' '{{dir}}' '{{ext}}'
"#,
stdout: "/foo/bar/baz baz.hello baz.hello.ciao / ciao\n",
stderr: "/bin/echo '/foo/bar/baz' 'baz.hello' 'baz.hello.ciao' '/' 'ciao'\n",
}

#[cfg(not(windows))]
test! {
name: broken_without_extension_function,
justfile: r#"
we := without_extension('')
foo:
/bin/echo '{{we}}'
"#,
stdout: "",
stderr: format!("{} {}\n{}\n{}\n{}\n",
"error: Call to function `without_extension` failed:",
"Could not extract parent from ``",
" |",
"1 | we := without_extension(\'\')",
" | ^^^^^^^^^^^^^^^^^").as_str(),
status: EXIT_FAILURE,
}

#[cfg(not(windows))]
test! {
name: broken_extension_function,
justfile: r#"
we := extension('')
foo:
/bin/echo '{{we}}'
"#,
stdout: "",
stderr: format!("{}\n{}\n{}\n{}\n",
"error: Call to function `extension` failed: Could not extract extension from ``",
" |",
"1 | we := extension(\'\')",
" | ^^^^^^^^^").as_str(),
status: EXIT_FAILURE,
}

#[cfg(not(windows))]
test! {
name: broken_extension_function2,
justfile: r#"
we := extension('foo')
foo:
/bin/echo '{{we}}'
"#,
stdout: "",
stderr: format!("{}\n{}\n{}\n{}\n",
"error: Call to function `extension` failed: Could not extract extension from `foo`",
" |",
"1 | we := extension(\'foo\')",
" | ^^^^^^^^^").as_str(),
status: EXIT_FAILURE,
}

#[cfg(not(windows))]
test! {
name: broken_file_stem_function,
justfile: r#"
we := file_stem('')
foo:
/bin/echo '{{we}}'
"#,
stdout: "",
stderr: format!("{}\n{}\n{}\n{}\n",
"error: Call to function `file_stem` failed: Could not extract file stem from ``",
" |",
"1 | we := file_stem(\'\')",
" | ^^^^^^^^^").as_str(),
status: EXIT_FAILURE,
}

#[cfg(not(windows))]
test! {
name: broken_file_name_function,
justfile: r#"
we := file_name('')
foo:
/bin/echo '{{we}}'
"#,
stdout: "",
stderr: format!("{}\n{}\n{}\n{}\n",
"error: Call to function `file_name` failed: Could not extract file name from ``",
" |",
"1 | we := file_name(\'\')",
" | ^^^^^^^^^").as_str(),
status: EXIT_FAILURE,
}

#[cfg(not(windows))]
test! {
name: broken_directory_function,
justfile: r#"
we := parent_directory('')
foo:
/bin/echo '{{we}}'
"#,
stdout: "",
stderr: format!("{} {}\n{}\n{}\n{}\n",
"error: Call to function `parent_directory` failed:",
"Could not extract parent directory from ``",
" |",
"1 | we := parent_directory(\'\')",
" | ^^^^^^^^^^^^^^^^").as_str(),
status: EXIT_FAILURE,
}

#[cfg(not(windows))]
test! {
name: broken_directory_function2,
justfile: r#"
we := parent_directory('/')
foo:
/bin/echo '{{we}}'
"#,
stdout: "",
stderr: format!("{} {}\n{}\n{}\n{}\n",
"error: Call to function `parent_directory` failed:",
"Could not extract parent directory from `/`",
" |",
"1 | we := parent_directory(\'/\')",
" | ^^^^^^^^^^^^^^^^").as_str(),
status: EXIT_FAILURE,
}

#[cfg(windows)]
test! {
name: env_var_functions,
justfile: r#"
p := env_var('USERNAME')
b := env_var_or_default('ZADDY', 'HTAP')
x := env_var_or_default('XYZ', 'ABC')
foo:
/bin/echo '{{p}}' '{{b}}' '{{x}}'
"#,
stdout: format!("{} HTAP ABC\n", env::var("USERNAME").unwrap()).as_str(),
stderr: format!("/bin/echo '{}' 'HTAP' 'ABC'\n", env::var("USERNAME").unwrap()).as_str(),
}

test! {
name: env_var_failure,
justfile: "a:\n echo {{env_var('ZADDY')}}",
args: ("a"),
stdout: "",
stderr: "error: Call to function `env_var` failed: environment variable `ZADDY` not present
|
2 | echo {{env_var('ZADDY')}}
| ^^^^^^^
",
status: EXIT_FAILURE,
}

test! {
name: test_just_executable_function,
justfile: "
a:
@printf 'Executable path is: %s\\n' '{{ just_executable() }}'
",
args: ("a"),
stdout: format!("Executable path is: {}\n", executable_path("just").to_str().unwrap()).as_str(),
stderr: "",
status: EXIT_SUCCESS,
}

test! {
name: test_os_arch_functions_in_default,
justfile: r#"
foo a=arch() o=os() f=os_family():
echo {{a}} {{o}} {{f}}
"#,
stdout: format!("{} {} {}\n", target::arch(), target::os(), target::os_family()).as_str(),
stderr: format!("echo {} {} {}\n", target::arch(), target::os(), target::os_family()).as_str(),
}

test! {
name: clean,
justfile: "
foo:
echo {{ clean('a/../b') }}
",
stdout: "b\n",
stderr: "echo b\n",
}
1 change: 1 addition & 0 deletions tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod evaluate;
mod examples;
mod export;
mod fmt;
mod functions;
mod init;
mod interrupts;
mod invocation_directory;
Expand Down
Loading

0 comments on commit 024f827

Please sign in to comment.