diff --git a/CHANGELOG.md b/CHANGELOG.md
index 292ae9d..d2032d1 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
### v0.6.7
* New --get-exit-code flag for exec command #127
+* New semver_parse, semver_is_equal and semver_is_newer commands #129
### v0.6.6 (2020-08-14)
diff --git a/docs/sdk.md b/docs/sdk.md
index 5354376..27dc7b7 100644
--- a/docs/sdk.md
+++ b/docs/sdk.md
@@ -132,6 +132,9 @@
* [std::scope::Clear (clear_scope)](#std__scope__Clear)
* [std::scope::PopStack (scope_pop_stack)](#std__scope__PopStack)
* [std::scope::PushStack (scope_push_stack)](#std__scope__PushStack)
+* [std::semver::IsEqual (semver_is_equal)](#std__semver__IsEqual)
+* [std::semver::IsNewer (semver_is_newer)](#std__semver__IsNewer)
+* [std::semver::Parse (semver_parse)](#std__semver__Parse)
* [std::string::Base64 (base64)](#std__string__Base64)
* [std::string::Base64Decode (base64_decode)](#std__string__Base64Decode)
* [std::string::Base64Encode (base64_encode)](#std__string__Base64Encode)
@@ -4929,6 +4932,105 @@ echo ${defined}
#### Aliases:
scope_push_stack
+
+## std::semver::IsEqual
+```sh
+output = semver_is_equal value1 value2
+```
+
+Returns true if both semver values are valid and equal.
+
+#### Parameters
+
+Two semver values to compare.
+
+#### Return Value
+
+True if both semver values are valid and equal, else false.
+
+#### Examples
+
+```sh
+equal = semver_is_equal 1.2.3 1.2.3
+assert ${equal}
+
+equal = semver_is_equal 1.2.3 2.2.3
+assert_false ${equal}
+```
+
+
+#### Aliases:
+semver_is_equal
+
+
+## std::semver::IsNewer
+```sh
+output = semver_is_newer newer older
+```
+
+Returns true if both semver values are valid and first value is newer.
+
+#### Parameters
+
+* The expected newer value
+* The expected older value
+
+#### Return Value
+
+True if both semver values are valid and first value is newer, else false.
+
+#### Examples
+
+```sh
+newer = semver_is_newer 3.2.3 2.2.3
+assert ${newer}
+
+newer = semver_is_newer 1.2.3 2.2.3
+assert_false ${newer}
+
+newer = semver_is_newer 1.2.3 1.2.3
+assert_false ${newer}
+```
+
+
+#### Aliases:
+semver_is_newer
+
+
+## std::semver::Parse
+```sh
+base = semver_parse value
+```
+
+Parses the provided value and sets the major, minor and patch variables.
+The variable names are based on the output variable name, for example if the output variable name is out:
+
+* out.major - Holds the output major version
+* out.minor - Holds the output minor version
+* out.patch - Holds the output patch version
+
+#### Parameters
+
+The semver value.
+
+#### Return Value
+
+The major, minor and patch values.
+
+#### Examples
+
+```sh
+version = semver_parse 1.2.3
+
+echo ${version.major}
+echo ${version.minor}
+echo ${version.patch}
+```
+
+
+#### Aliases:
+semver_parse
+
## std::string::Base64
diff --git a/duckscript_sdk/Cargo.toml b/duckscript_sdk/Cargo.toml
index 7240474..3685ee0 100644
--- a/duckscript_sdk/Cargo.toml
+++ b/duckscript_sdk/Cargo.toml
@@ -36,6 +36,7 @@ java-properties = "^1"
meval = "^0.2"
num_cpus = "^1"
rand = "^0.7"
+semver = "^0.10"
serde_json = "1"
walkdir = "^2"
which = { version = "^4", default-features = false }
diff --git a/duckscript_sdk/src/sdk/std/mod.rs b/duckscript_sdk/src/sdk/std/mod.rs
index a834040..a41e166 100755
--- a/duckscript_sdk/src/sdk/std/mod.rs
+++ b/duckscript_sdk/src/sdk/std/mod.rs
@@ -18,6 +18,7 @@ mod process;
mod read;
pub(crate) mod release;
pub(crate) mod scope;
+mod semver;
pub(crate) mod string;
mod test;
mod thread;
@@ -51,6 +52,7 @@ pub(crate) fn load(commands: &mut Commands) -> Result<(), ScriptError> {
on_error::load(commands, PACKAGE)?;
process::load(commands, PACKAGE)?;
scope::load(commands, PACKAGE)?;
+ semver::load(commands, PACKAGE)?;
string::load(commands, PACKAGE)?;
test::load(commands, PACKAGE)?;
thread::load(commands, PACKAGE)?;
diff --git a/duckscript_sdk/src/sdk/std/semver/is_equal/help.md b/duckscript_sdk/src/sdk/std/semver/is_equal/help.md
new file mode 100644
index 0000000..0bd832d
--- /dev/null
+++ b/duckscript_sdk/src/sdk/std/semver/is_equal/help.md
@@ -0,0 +1,23 @@
+```sh
+output = semver_is_equal value1 value2
+```
+
+Returns true if both semver values are valid and equal.
+
+#### Parameters
+
+Two semver values to compare.
+
+#### Return Value
+
+True if both semver values are valid and equal, else false.
+
+#### Examples
+
+```sh
+equal = semver_is_equal 1.2.3 1.2.3
+assert ${equal}
+
+equal = semver_is_equal 1.2.3 2.2.3
+assert_false ${equal}
+```
diff --git a/duckscript_sdk/src/sdk/std/semver/is_equal/mod.rs b/duckscript_sdk/src/sdk/std/semver/is_equal/mod.rs
new file mode 100755
index 0000000..fd20919
--- /dev/null
+++ b/duckscript_sdk/src/sdk/std/semver/is_equal/mod.rs
@@ -0,0 +1,54 @@
+use crate::utils::pckg;
+use duckscript::types::command::{Command, CommandResult};
+use semver::Version;
+
+#[cfg(test)]
+#[path = "./mod_test.rs"]
+mod mod_test;
+
+#[derive(Clone)]
+pub(crate) struct CommandImpl {
+ package: String,
+}
+
+impl Command for CommandImpl {
+ fn name(&self) -> String {
+ pckg::concat(&self.package, "IsEqual")
+ }
+
+ fn aliases(&self) -> Vec {
+ vec!["semver_is_equal".to_string()]
+ }
+
+ fn help(&self) -> String {
+ include_str!("help.md").to_string()
+ }
+
+ fn clone_and_box(&self) -> Box {
+ Box::new((*self).clone())
+ }
+
+ fn run(&self, arguments: Vec) -> CommandResult {
+ if arguments.len() < 2 {
+ CommandResult::Error("Missing semver values to compare.".to_string())
+ } else {
+ match Version::parse(&arguments[0]) {
+ Ok(version1) => match Version::parse(&arguments[1]) {
+ Ok(version2) => {
+ let result = if version1 == version2 { true } else { false };
+
+ CommandResult::Continue(Some(result.to_string()))
+ }
+ Err(error) => CommandResult::Error(error.to_string()),
+ },
+ Err(error) => CommandResult::Error(error.to_string()),
+ }
+ }
+ }
+}
+
+pub(crate) fn create(package: &str) -> Box {
+ Box::new(CommandImpl {
+ package: package.to_string(),
+ })
+}
diff --git a/duckscript_sdk/src/sdk/std/semver/is_equal/mod_test.rs b/duckscript_sdk/src/sdk/std/semver/is_equal/mod_test.rs
new file mode 100644
index 0000000..7f66d93
--- /dev/null
+++ b/duckscript_sdk/src/sdk/std/semver/is_equal/mod_test.rs
@@ -0,0 +1,26 @@
+use super::*;
+use crate::test;
+
+#[test]
+fn common_functions() {
+ test::test_common_command_functions(create(""));
+}
+
+#[test]
+fn run_no_args() {
+ test::run_script_and_error(vec![create("")], "out = semver_is_equal", "out");
+}
+
+#[test]
+fn run_single_arg() {
+ test::run_script_and_error(vec![create("")], "out = semver_is_equal 1.2.3", "out");
+}
+
+#[test]
+fn run_invalid_args() {
+ test::run_script_and_error(
+ vec![create("")],
+ "out = semver_is_equal abc_123 123_test",
+ "out",
+ );
+}
diff --git a/duckscript_sdk/src/sdk/std/semver/is_newer/help.md b/duckscript_sdk/src/sdk/std/semver/is_newer/help.md
new file mode 100644
index 0000000..e141934
--- /dev/null
+++ b/duckscript_sdk/src/sdk/std/semver/is_newer/help.md
@@ -0,0 +1,27 @@
+```sh
+output = semver_is_newer newer older
+```
+
+Returns true if both semver values are valid and first value is newer.
+
+#### Parameters
+
+* The expected newer value
+* The expected older value
+
+#### Return Value
+
+True if both semver values are valid and first value is newer, else false.
+
+#### Examples
+
+```sh
+newer = semver_is_newer 3.2.3 2.2.3
+assert ${newer}
+
+newer = semver_is_newer 1.2.3 2.2.3
+assert_false ${newer}
+
+newer = semver_is_newer 1.2.3 1.2.3
+assert_false ${newer}
+```
diff --git a/duckscript_sdk/src/sdk/std/semver/is_newer/mod.rs b/duckscript_sdk/src/sdk/std/semver/is_newer/mod.rs
new file mode 100755
index 0000000..20d4a2c
--- /dev/null
+++ b/duckscript_sdk/src/sdk/std/semver/is_newer/mod.rs
@@ -0,0 +1,58 @@
+use crate::utils::pckg;
+use duckscript::types::command::{Command, CommandResult};
+use semver::Version;
+
+#[cfg(test)]
+#[path = "./mod_test.rs"]
+mod mod_test;
+
+#[derive(Clone)]
+pub(crate) struct CommandImpl {
+ package: String,
+}
+
+impl Command for CommandImpl {
+ fn name(&self) -> String {
+ pckg::concat(&self.package, "IsNewer")
+ }
+
+ fn aliases(&self) -> Vec {
+ vec!["semver_is_newer".to_string()]
+ }
+
+ fn help(&self) -> String {
+ include_str!("help.md").to_string()
+ }
+
+ fn clone_and_box(&self) -> Box {
+ Box::new((*self).clone())
+ }
+
+ fn run(&self, arguments: Vec) -> CommandResult {
+ if arguments.len() < 2 {
+ CommandResult::Error("Missing semver values to compare.".to_string())
+ } else {
+ match Version::parse(&arguments[0]) {
+ Ok(newer_version) => match Version::parse(&arguments[1]) {
+ Ok(older_version) => {
+ let result = if newer_version > older_version {
+ true
+ } else {
+ false
+ };
+
+ CommandResult::Continue(Some(result.to_string()))
+ }
+ Err(error) => CommandResult::Error(error.to_string()),
+ },
+ Err(error) => CommandResult::Error(error.to_string()),
+ }
+ }
+ }
+}
+
+pub(crate) fn create(package: &str) -> Box {
+ Box::new(CommandImpl {
+ package: package.to_string(),
+ })
+}
diff --git a/duckscript_sdk/src/sdk/std/semver/is_newer/mod_test.rs b/duckscript_sdk/src/sdk/std/semver/is_newer/mod_test.rs
new file mode 100644
index 0000000..2f16842
--- /dev/null
+++ b/duckscript_sdk/src/sdk/std/semver/is_newer/mod_test.rs
@@ -0,0 +1,26 @@
+use super::*;
+use crate::test;
+
+#[test]
+fn common_functions() {
+ test::test_common_command_functions(create(""));
+}
+
+#[test]
+fn run_no_args() {
+ test::run_script_and_error(vec![create("")], "out = semver_is_newer", "out");
+}
+
+#[test]
+fn run_single_arg() {
+ test::run_script_and_error(vec![create("")], "out = semver_is_newer 1.2.3", "out");
+}
+
+#[test]
+fn run_invalid_args() {
+ test::run_script_and_error(
+ vec![create("")],
+ "out = semver_is_newer abc_123 123_test",
+ "out",
+ );
+}
diff --git a/duckscript_sdk/src/sdk/std/semver/mod.rs b/duckscript_sdk/src/sdk/std/semver/mod.rs
new file mode 100755
index 0000000..80548fe
--- /dev/null
+++ b/duckscript_sdk/src/sdk/std/semver/mod.rs
@@ -0,0 +1,19 @@
+mod is_equal;
+mod is_newer;
+mod parse;
+
+use crate::utils::pckg;
+use duckscript::types::command::Commands;
+use duckscript::types::error::ScriptError;
+
+static PACKAGE: &str = "semver";
+
+pub(crate) fn load(commands: &mut Commands, parent: &str) -> Result<(), ScriptError> {
+ let package = pckg::concat(parent, PACKAGE);
+
+ commands.set(is_equal::create(&package))?;
+ commands.set(is_newer::create(&package))?;
+ commands.set(parse::create(&package))?;
+
+ Ok(())
+}
diff --git a/duckscript_sdk/src/sdk/std/semver/parse/help.md b/duckscript_sdk/src/sdk/std/semver/parse/help.md
new file mode 100644
index 0000000..d06a9f4
--- /dev/null
+++ b/duckscript_sdk/src/sdk/std/semver/parse/help.md
@@ -0,0 +1,28 @@
+```sh
+base = semver_parse value
+```
+
+Parses the provided value and sets the major, minor and patch variables.
+The variable names are based on the output variable name, for example if the output variable name is out:
+
+* out.major - Holds the output major version
+* out.minor - Holds the output minor version
+* out.patch - Holds the output patch version
+
+#### Parameters
+
+The semver value.
+
+#### Return Value
+
+The major, minor and patch values.
+
+#### Examples
+
+```sh
+version = semver_parse 1.2.3
+
+echo ${version.major}
+echo ${version.minor}
+echo ${version.patch}
+```
diff --git a/duckscript_sdk/src/sdk/std/semver/parse/mod.rs b/duckscript_sdk/src/sdk/std/semver/parse/mod.rs
new file mode 100755
index 0000000..e0482ae
--- /dev/null
+++ b/duckscript_sdk/src/sdk/std/semver/parse/mod.rs
@@ -0,0 +1,81 @@
+use crate::utils::pckg;
+use duckscript::types::command::{Command, CommandResult, Commands};
+use duckscript::types::instruction::Instruction;
+use duckscript::types::runtime::StateValue;
+use semver::Version;
+use std::collections::HashMap;
+
+#[cfg(test)]
+#[path = "./mod_test.rs"]
+mod mod_test;
+
+#[derive(Clone)]
+pub(crate) struct CommandImpl {
+ package: String,
+}
+
+impl Command for CommandImpl {
+ fn name(&self) -> String {
+ pckg::concat(&self.package, "Parse")
+ }
+
+ fn aliases(&self) -> Vec {
+ vec!["semver_parse".to_string()]
+ }
+
+ fn help(&self) -> String {
+ include_str!("help.md").to_string()
+ }
+
+ fn clone_and_box(&self) -> Box {
+ Box::new((*self).clone())
+ }
+
+ fn requires_context(&self) -> bool {
+ true
+ }
+
+ fn run_with_context(
+ &self,
+ arguments: Vec,
+ _state: &mut HashMap,
+ variables: &mut HashMap,
+ output_variable: Option,
+ _instructions: &Vec,
+ _commands: &mut Commands,
+ _line: usize,
+ ) -> CommandResult {
+ if arguments.is_empty() {
+ CommandResult::Error("No semver value provided.".to_string())
+ } else {
+ match Version::parse(&arguments[0]) {
+ Ok(version) => match output_variable {
+ Some(name) => {
+ variables.insert(
+ format!("{}.major", &name).to_string(),
+ version.major.to_string(),
+ );
+ variables.insert(
+ format!("{}.minor", &name).to_string(),
+ version.minor.to_string(),
+ );
+ variables.insert(
+ format!("{}.patch", &name).to_string(),
+ version.patch.to_string(),
+ );
+
+ CommandResult::Continue(None)
+ }
+ None => CommandResult::Continue(None),
+ },
+ Err(error) => CommandResult::Error(error.to_string()),
+ }
+ }
+ }
+}
+
+pub(crate) fn create(package: &str) -> Box {
+ Box::new(CommandImpl {
+ package: package.to_string(),
+ })
+}
diff --git a/duckscript_sdk/src/sdk/std/semver/parse/mod_test.rs b/duckscript_sdk/src/sdk/std/semver/parse/mod_test.rs
new file mode 100644
index 0000000..1132a31
--- /dev/null
+++ b/duckscript_sdk/src/sdk/std/semver/parse/mod_test.rs
@@ -0,0 +1,45 @@
+use super::*;
+use crate::test;
+use crate::test::CommandValidation;
+
+#[test]
+fn common_functions() {
+ test::test_common_command_functions(create(""));
+}
+
+#[test]
+fn run_no_args() {
+ test::run_script_and_error(vec![create("")], "out = semver_parse", "out");
+}
+
+#[test]
+fn run_invalid() {
+ test::run_script_and_error(vec![create("")], "out = semver_parse aaa_bbb", "out");
+}
+
+#[test]
+fn run_major() {
+ test::run_script_and_validate(
+ vec![create("")],
+ "out = semver_parse 1.2.3",
+ CommandValidation::Match("out.major".to_string(), "1".to_string()),
+ );
+}
+
+#[test]
+fn run_minor() {
+ test::run_script_and_validate(
+ vec![create("")],
+ "out = semver_parse 1.2.3",
+ CommandValidation::Match("out.minor".to_string(), "2".to_string()),
+ );
+}
+
+#[test]
+fn run_patch() {
+ test::run_script_and_validate(
+ vec![create("")],
+ "out = semver_parse 1.2.3",
+ CommandValidation::Match("out.patch".to_string(), "3".to_string()),
+ );
+}
diff --git a/test/std/semver/semver_compare_test.ds b/test/std/semver/semver_compare_test.ds
new file mode 100644
index 0000000..e65f97c
--- /dev/null
+++ b/test/std/semver/semver_compare_test.ds
@@ -0,0 +1,42 @@
+
+fn test_equal
+ equal = semver_is_equal 1.2.3 1.2.3
+ assert ${equal}
+
+ newer = semver_is_newer 1.2.3 1.2.3
+ assert_false ${newer}
+end
+
+fn test_not_equal_major
+ equal = semver_is_equal 1.2.3 2.2.3
+ assert_false ${equal}
+
+ newer = semver_is_newer 1.2.3 2.2.3
+ assert_false ${newer}
+
+ newer = semver_is_newer 3.2.3 2.2.3
+ assert ${newer}
+end
+
+fn test_not_equal_minor
+ equal = semver_is_equal 1.2.3 1.3.3
+ assert_false ${equal}
+
+ newer = semver_is_newer 1.2.3 1.3.3
+ assert_false ${newer}
+
+ newer = semver_is_newer 1.3.3 1.2.3
+ assert ${newer}
+end
+
+fn test_not_equal_patch
+ equal = semver_is_equal 1.2.3 1.2.4
+ assert_false ${equal}
+
+ newer = semver_is_newer 1.2.3 1.2.4
+ assert_false ${newer}
+
+ newer = semver_is_newer 1.2.4 1.2.3
+ assert ${newer}
+end
+
diff --git a/test/std/semver/semver_parse_test.ds b/test/std/semver/semver_parse_test.ds
new file mode 100644
index 0000000..c3b1a1d
--- /dev/null
+++ b/test/std/semver/semver_parse_test.ds
@@ -0,0 +1,9 @@
+
+fn test_valid
+ version = semver_parse 1.2.3
+
+ assert_eq ${version.major} 1
+ assert_eq ${version.minor} 2
+ assert_eq ${version.patch} 3
+end
+