diff --git a/Cargo.lock b/Cargo.lock index 4aa3072..49c5fa2 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 20ba6d9..34217b7 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 2d625f0..36bbd24 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), e.g., `"0.1.0"` matches a `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 84c21c0..086ede1 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 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 5511329..150f293 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -412,6 +412,21 @@ test! { stderr: "echo Bar\n", } +#[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') }} + ", + ) + .stdout("true\nfalse\n") + .stderr("echo true\necho false\n") + .run(); +} + fn assert_eval_eq(expression: &str, result: &str) { Test::new() .justfile(format!("x := {expression}")) diff --git a/tests/test.rs b/tests/test.rs index e8dc786..4aad92b 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,