From 27264e2dc1d882cbc35e0065e84dc5ae613f7aa5 Mon Sep 17 00:00:00 2001 From: vados Date: Fri, 27 Oct 2023 08:40:45 +0900 Subject: [PATCH 1/4] feat: add `semver_matches` utility function While coordinating different programs using `just`, it's often necessary to compare the *versions* of different binaries or utilitiies. Assuming that the utility in question providers a `--version` switch and can reasonably print out a version, being able to match semver of the utility to an expectation is very useful. This commit adds a `semver_matches` utility which utilizes the `semver` crate (https://crates.io/crates/semver) to provide matching functionality to `Justfile`s. Signed-off-by: vados Co-authored-by: Casey Rodarmor --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + README.md | 4 ++++ src/function.rs | 22 ++++++++++++++++++++++ tests/functions.rs | 11 +++++++++++ 5 files changed, 45 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 4aa30724fe..49c5fa281f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -337,6 +337,7 @@ dependencies = [ "num_cpus", "pretty_assertions", "regex", + "semver", "serde", "serde_json", "sha2", @@ -573,6 +574,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + [[package]] name = "serde" version = "1.0.188" diff --git a/Cargo.toml b/Cargo.toml index 20ba6d92dc..34217b76b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ libc = "0.2.0" log = "0.4.4" num_cpus = "1.15.0" regex = "1.5.4" +semver = "1.0.20" serde = { version = "1.0.130", features = ["derive", "rc"] } serde_json = "1.0.68" sha2 = "0.10" diff --git a/README.md b/README.md index 2d625f097a..c6c6968fdd 100644 --- a/README.md +++ b/README.md @@ -1241,6 +1241,10 @@ These functions can fail, for example if a path does not have an extension, whic - `sha256_file(path)` - Return the SHA-256 hash of the file at `path` as a hexadecimal string. - `uuid()` - Return a randomly generated UUID. +#### Semantic Versions + +- `semver_matches(version, requirement)`master - Check whether a [semantic version](https://semver.org) `version`, e.g., `"0.1.0"` matches requirement `requirement`, e.g., `">=0.1.0"`, returning `"true"` if so and `"false"` otherwise. + ### Recipe Attributes Recipes may be annotated with attributes that change their behavior. diff --git a/src/function.rs b/src/function.rs index 84c21c0352..ed75ae851a 100644 --- a/src/function.rs +++ b/src/function.rs @@ -4,6 +4,7 @@ use { ToKebabCase, ToLowerCamelCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnakeCase, ToTitleCase, ToUpperCamelCase, }, + semver::{Version, VersionReq}, Function::*, }; @@ -46,6 +47,7 @@ pub(crate) fn get(name: &str) -> Option { "quote" => Unary(quote), "replace" => Ternary(replace), "replace_regex" => Ternary(replace_regex), + "semver_matches" => Binary(semver_matches), "sha256" => Unary(sha256), "sha256_file" => Unary(sha256_file), "shoutykebabcase" => Unary(shoutykebabcase), @@ -411,3 +413,23 @@ fn without_extension(_context: &FunctionContext, path: &str) -> Result=0.1.0") +fn semver_matches( + _context: &FunctionContext, + version: &str, + requirement: &str, +) -> Result { + Ok( + requirement + .parse::() + .map_err(|err| format!("invalid semver version requirement: {err}"))? + .matches( + &version + .parse::() + .map_err(|err| format!("invalid semver version: {err}"))?, + ) + .to_string(), + ) +} diff --git a/tests/functions.rs b/tests/functions.rs index 5511329adc..1077167c2b 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -412,6 +412,17 @@ test! { stderr: "echo Bar\n", } +test! { + name: semver_matches_gte, + justfile: " + foo: + echo {{ semver_matches('0.1.0', '>=0.1.0') }} + echo {{ semver_matches('0.1.0', '>=0.0.1') }} + ", + stdout: "true\ntrue\n", + stderr: "echo true\necho true\n", +} + fn assert_eval_eq(expression: &str, result: &str) { Test::new() .justfile(format!("x := {expression}")) From ff9f5031be35e171aeb27a140bddc4b19089def5 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 27 Oct 2023 12:59:47 -0700 Subject: [PATCH 2/4] Tweak readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c6c6968fdd..36bbd242fe 100644 --- a/README.md +++ b/README.md @@ -1243,7 +1243,7 @@ These functions can fail, for example if a path does not have an extension, whic #### Semantic Versions -- `semver_matches(version, requirement)`master - Check whether a [semantic version](https://semver.org) `version`, e.g., `"0.1.0"` matches requirement `requirement`, e.g., `">=0.1.0"`, returning `"true"` if so and `"false"` otherwise. +- `semver_matches(version, requirement)`master - Check whether a [semantic `version`](https://semver.org), e.g., `"0.1.0"` matches a `requirement`, e.g., `">=0.1.0"`, returning `"true"` if so and `"false"` otherwise. ### Recipe Attributes From 77328fe481be5613deccf94e9fe0f93927fc27eb Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 27 Oct 2023 13:00:45 -0700 Subject: [PATCH 3/4] tweak --- src/function.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/function.rs b/src/function.rs index ed75ae851a..086ede1b81 100644 --- a/src/function.rs +++ b/src/function.rs @@ -424,7 +424,7 @@ fn semver_matches( Ok( requirement .parse::() - .map_err(|err| format!("invalid semver version requirement: {err}"))? + .map_err(|err| format!("invalid semver requirement: {err}"))? .matches( &version .parse::() From 534b774a52a9f72371f339cbf80f41fd44b834ba Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 27 Oct 2023 13:03:55 -0700 Subject: [PATCH 4/4] tweak --- tests/functions.rs | 16 ++++++++++------ tests/test.rs | 1 + 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/functions.rs b/tests/functions.rs index 1077167c2b..150f293d48 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -412,15 +412,19 @@ test! { stderr: "echo Bar\n", } -test! { - name: semver_matches_gte, - justfile: " +#[test] +fn semver_matches() { + Test::new() + .justfile( + " foo: echo {{ semver_matches('0.1.0', '>=0.1.0') }} - echo {{ semver_matches('0.1.0', '>=0.0.1') }} + echo {{ semver_matches('0.1.0', '=0.0.1') }} ", - stdout: "true\ntrue\n", - stderr: "echo true\necho true\n", + ) + .stdout("true\nfalse\n") + .stderr("echo true\necho false\n") + .run(); } fn assert_eval_eq(expression: &str, result: &str) { diff --git a/tests/test.rs b/tests/test.rs index e8dc786bb7..4aad92b730 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -39,6 +39,7 @@ pub(crate) struct Output { pub(crate) tempdir: TempDir, } +#[must_use] pub(crate) struct Test { pub(crate) args: Vec, pub(crate) current_dir: PathBuf,