Add clean function for simplifying paths (#883)

This commit is contained in:
Casey Rodarmor 2021-06-24 23:41:20 -07:00 committed by GitHub
parent 87e395254b
commit 024f8279bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 280 additions and 255 deletions

7
Cargo.lock generated
View File

@ -207,6 +207,7 @@ dependencies = [
"env_logger", "env_logger",
"executable-path", "executable-path",
"lazy_static", "lazy_static",
"lexiclean",
"libc", "libc",
"log", "log",
"pretty_assertions", "pretty_assertions",
@ -227,6 +228,12 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lexiclean"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "441225017b106b9f902e97947a6d31e44ebcf274b91bdbfb51e5c477fcd468e5"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.97" version = "0.2.97"

View File

@ -26,6 +26,7 @@ dotenv = "0.15.0"
edit-distance = "2.0.0" edit-distance = "2.0.0"
env_logger = "0.8.0" env_logger = "0.8.0"
lazy_static = "1.0.0" lazy_static = "1.0.0"
lexiclean = "0.0.1"
libc = "0.2.0" libc = "0.2.0"
log = "0.4.4" log = "0.4.4"
snafu = "0.6.0" snafu = "0.6.0"

View File

@ -807,8 +807,7 @@ These functions can fail, for example if a path does not have an extension, whic
===== Infallible ===== Infallible
- `join(a, b)` - Join path `a` with path `b`. `join("foo/bar", "baz")` is `foo/bar/baz`. - `join(a, b)` - Join path `a` with path `b`. `join("foo/bar", "baz")` is `foo/bar/baz`.
- `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`.
These functions always succeed.
=== Command Evaluation Using Backticks === Command Evaluation Using Backticks

View File

@ -22,6 +22,7 @@ pub(crate) use std::{
pub(crate) use camino::Utf8Path; pub(crate) use camino::Utf8Path;
pub(crate) use derivative::Derivative; pub(crate) use derivative::Derivative;
pub(crate) use edit_distance::edit_distance; pub(crate) use edit_distance::edit_distance;
pub(crate) use lexiclean::Lexiclean;
pub(crate) use libc::EXIT_FAILURE; pub(crate) use libc::EXIT_FAILURE;
pub(crate) use log::{info, warn}; pub(crate) use log::{info, warn};
pub(crate) use snafu::{ResultExt, Snafu}; pub(crate) use snafu::{ResultExt, Snafu};

View File

@ -10,6 +10,7 @@ pub(crate) enum Function {
lazy_static! { lazy_static! {
pub(crate) static ref TABLE: BTreeMap<&'static str, Function> = vec![ pub(crate) static ref TABLE: BTreeMap<&'static str, Function> = vec![
("arch", Nullary(arch)), ("arch", Nullary(arch)),
("clean", Unary(clean)),
("env_var", Unary(env_var)), ("env_var", Unary(env_var)),
("env_var_or_default", Binary(env_var_or_default)), ("env_var_or_default", Binary(env_var_or_default)),
("extension", Unary(extension)), ("extension", Unary(extension)),
@ -43,6 +44,10 @@ fn arch(_context: &FunctionContext) -> Result<String, String> {
Ok(target::arch().to_owned()) 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> { fn env_var(context: &FunctionContext, key: &str) -> Result<String, String> {
use std::env::VarError::*; use std::env::VarError::*;

264
tests/functions.rs Normal file
View File

@ -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",
}

View File

@ -15,6 +15,7 @@ mod evaluate;
mod examples; mod examples;
mod export; mod export;
mod fmt; mod fmt;
mod functions;
mod init; mod init;
mod interrupts; mod interrupts;
mod invocation_directory; mod invocation_directory;

View File

@ -1131,249 +1131,6 @@ foo:
stderr: "echo abc\n", stderr: "echo abc\n",
} }
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! { test! {
name: infallable_command, name: infallable_command,
justfile: r#" justfile: r#"
@ -2026,16 +1783,6 @@ foo x=y:
stderr: "echo foo\n", stderr: "echo foo\n",
} }
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! { test! {
name: unterminated_interpolation_eol, name: unterminated_interpolation_eol,
justfile: " justfile: "