aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/vlib/semver
diff options
context:
space:
mode:
authorIndrajith K L2022-12-03 17:00:20 +0530
committerIndrajith K L2022-12-03 17:00:20 +0530
commitf5c4671bfbad96bf346bd7e9a21fc4317b4959df (patch)
tree2764fc62da58f2ba8da7ed341643fc359873142f /v_windows/v/vlib/semver
downloadcli-tools-windows-master.tar.gz
cli-tools-windows-master.tar.bz2
cli-tools-windows-master.zip
Adds most of the toolsHEADmaster
Diffstat (limited to 'v_windows/v/vlib/semver')
-rw-r--r--v_windows/v/vlib/semver/LICENSE.md21
-rw-r--r--v_windows/v/vlib/semver/README.md37
-rw-r--r--v_windows/v/vlib/semver/compare.v59
-rw-r--r--v_windows/v/vlib/semver/parse.v85
-rw-r--r--v_windows/v/vlib/semver/range.v252
-rw-r--r--v_windows/v/vlib/semver/semver.v110
-rw-r--r--v_windows/v/vlib/semver/semver_test.v192
-rw-r--r--v_windows/v/vlib/semver/util.v55
-rw-r--r--v_windows/v/vlib/semver/v.mod5
9 files changed, 816 insertions, 0 deletions
diff --git a/v_windows/v/vlib/semver/LICENSE.md b/v_windows/v/vlib/semver/LICENSE.md
new file mode 100644
index 0000000..8d5ff71
--- /dev/null
+++ b/v_windows/v/vlib/semver/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 alexesprit
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/v_windows/v/vlib/semver/README.md b/v_windows/v/vlib/semver/README.md
new file mode 100644
index 0000000..e7c8d20
--- /dev/null
+++ b/v_windows/v/vlib/semver/README.md
@@ -0,0 +1,37 @@
+# semver
+
+A library for working with versions in [semver][semver] format.
+
+## Usage
+
+```v
+import semver
+
+fn main() {
+ ver1 := semver.from('1.2.4') or {
+ println('Invalid version')
+ return
+ }
+ ver2 := semver.from('2.3.4') or {
+ println('Invalid version')
+ return
+ }
+ println(ver1.gt(ver2))
+ println(ver2.gt(ver1))
+ println(ver1.satisfies('>=1.1.0 <2.0.0'))
+ println(ver2.satisfies('>=1.1.0 <2.0.0'))
+ println(ver2.satisfies('>=1.1.0 <2.0.0 || >2.2.0'))
+}
+```
+
+```
+false
+true
+true
+false
+true
+```
+
+For more details see `semver.v` file.
+
+[semver]: https://semver.org/
diff --git a/v_windows/v/vlib/semver/compare.v b/v_windows/v/vlib/semver/compare.v
new file mode 100644
index 0000000..0b51d29
--- /dev/null
+++ b/v_windows/v/vlib/semver/compare.v
@@ -0,0 +1,59 @@
+module semver
+
+// * Private functions.
+[inline]
+fn version_satisfies(ver Version, input string) bool {
+ range := parse_range(input) or { return false }
+ return range.satisfies(ver)
+}
+
+fn compare_eq(v1 Version, v2 Version) bool {
+ return v1.major == v2.major && v1.minor == v2.minor && v1.patch == v2.patch
+ && v1.prerelease == v2.prerelease
+}
+
+fn compare_gt(v1 Version, v2 Version) bool {
+ if v1.major < v2.major {
+ return false
+ }
+ if v1.major > v2.major {
+ return true
+ }
+ if v1.minor < v2.minor {
+ return false
+ }
+ if v1.minor > v2.minor {
+ return true
+ }
+ return v1.patch > v2.patch
+}
+
+fn compare_lt(v1 Version, v2 Version) bool {
+ if v1.major > v2.major {
+ return false
+ }
+ if v1.major < v2.major {
+ return true
+ }
+ if v1.minor > v2.minor {
+ return false
+ }
+ if v1.minor < v2.minor {
+ return true
+ }
+ return v1.patch < v2.patch
+}
+
+fn compare_ge(v1 Version, v2 Version) bool {
+ if compare_eq(v1, v2) {
+ return true
+ }
+ return compare_gt(v1, v2)
+}
+
+fn compare_le(v1 Version, v2 Version) bool {
+ if compare_eq(v1, v2) {
+ return true
+ }
+ return compare_lt(v1, v2)
+}
diff --git a/v_windows/v/vlib/semver/parse.v b/v_windows/v/vlib/semver/parse.v
new file mode 100644
index 0000000..3443f59
--- /dev/null
+++ b/v_windows/v/vlib/semver/parse.v
@@ -0,0 +1,85 @@
+module semver
+
+// * Private structs and functions.
+struct RawVersion {
+ prerelease string
+ metadata string
+mut:
+ raw_ints []string
+}
+
+const (
+ ver_major = 0
+ ver_minor = 1
+ ver_patch = 2
+ versions = [ver_major, ver_minor, ver_patch]
+)
+
+// TODO: Rewrite using regexps?
+// /(\d+)\.(\d+)\.(\d+)(?:\-([0-9A-Za-z-.]+))?(?:\+([0-9A-Za-z-]+))?/
+fn parse(input string) RawVersion {
+ mut raw_version := input
+ mut prerelease := ''
+ mut metadata := ''
+ plus_idx := raw_version.last_index('+') or { -1 }
+ if plus_idx > 0 {
+ metadata = raw_version[(plus_idx + 1)..]
+ raw_version = raw_version[0..plus_idx]
+ }
+ hyphen_idx := raw_version.index('-') or { -1 }
+ if hyphen_idx > 0 {
+ prerelease = raw_version[(hyphen_idx + 1)..]
+ raw_version = raw_version[0..hyphen_idx]
+ }
+ raw_ints := raw_version.split('.')
+ return RawVersion{
+ prerelease: prerelease
+ metadata: metadata
+ raw_ints: raw_ints
+ }
+}
+
+fn (ver RawVersion) is_valid() bool {
+ if ver.raw_ints.len != 3 {
+ return false
+ }
+ return is_valid_number(ver.raw_ints[semver.ver_major])
+ && is_valid_number(ver.raw_ints[semver.ver_minor])
+ && is_valid_number(ver.raw_ints[semver.ver_patch]) && is_valid_string(ver.prerelease)
+ && is_valid_string(ver.metadata)
+}
+
+fn (ver RawVersion) is_missing(typ int) bool {
+ return typ >= ver.raw_ints.len - 1
+}
+
+fn (raw_ver RawVersion) coerce() ?Version {
+ ver := raw_ver.complete()
+ if !is_valid_number(ver.raw_ints[semver.ver_major]) {
+ return error('Invalid major version: $ver.raw_ints[ver_major]')
+ }
+ return ver.to_version()
+}
+
+fn (raw_ver RawVersion) complete() RawVersion {
+ mut raw_ints := raw_ver.raw_ints
+ for raw_ints.len < 3 {
+ raw_ints << '0'
+ }
+ return RawVersion{
+ prerelease: raw_ver.prerelease
+ metadata: raw_ver.metadata
+ raw_ints: raw_ints
+ }
+}
+
+fn (raw_ver RawVersion) validate() ?Version {
+ if !raw_ver.is_valid() {
+ return none
+ }
+ return raw_ver.to_version()
+}
+
+fn (raw_ver RawVersion) to_version() Version {
+ return Version{raw_ver.raw_ints[semver.ver_major].int(), raw_ver.raw_ints[semver.ver_minor].int(), raw_ver.raw_ints[semver.ver_patch].int(), raw_ver.prerelease, raw_ver.metadata}
+}
diff --git a/v_windows/v/vlib/semver/range.v b/v_windows/v/vlib/semver/range.v
new file mode 100644
index 0000000..fd0a769
--- /dev/null
+++ b/v_windows/v/vlib/semver/range.v
@@ -0,0 +1,252 @@
+module semver
+
+// * Private functions.
+const (
+ comparator_sep = ' '
+ comparator_set_sep = ' || '
+ hyphen_range_sep = ' - '
+ x_range_symbols = 'Xx*'
+)
+
+enum Operator {
+ gt
+ lt
+ ge
+ le
+ eq
+}
+
+struct Comparator {
+ ver Version
+ op Operator
+}
+
+struct ComparatorSet {
+ comparators []Comparator
+}
+
+struct Range {
+ comparator_sets []ComparatorSet
+}
+
+struct InvalidComparatorCountError {
+ msg string
+ code int
+}
+
+struct InvalidComparatorFormatError {
+ msg string
+ code int
+}
+
+fn (r Range) satisfies(ver Version) bool {
+ mut final_result := false
+ for set in r.comparator_sets {
+ final_result = final_result || set.satisfies(ver)
+ }
+ return final_result
+}
+
+fn (set ComparatorSet) satisfies(ver Version) bool {
+ for comp in set.comparators {
+ if !comp.satisfies(ver) {
+ return false
+ }
+ }
+ return true
+}
+
+fn (c Comparator) satisfies(ver Version) bool {
+ if c.op == .gt {
+ return ver.gt(c.ver)
+ }
+ if c.op == .lt {
+ return ver.lt(c.ver)
+ }
+ if c.op == .ge {
+ return ver.ge(c.ver)
+ }
+ if c.op == .le {
+ return ver.le(c.ver)
+ }
+ if c.op == .eq {
+ return ver.eq(c.ver)
+ }
+ return false
+}
+
+fn parse_range(input string) ?Range {
+ raw_comparator_sets := input.split(semver.comparator_set_sep)
+ mut comparator_sets := []ComparatorSet{}
+ for raw_comp_set in raw_comparator_sets {
+ if can_expand(raw_comp_set) {
+ s := expand_comparator_set(raw_comp_set) or { return err }
+ comparator_sets << s
+ } else {
+ s := parse_comparator_set(raw_comp_set) or { return err }
+ comparator_sets << s
+ }
+ }
+ return Range{comparator_sets}
+}
+
+fn parse_comparator_set(input string) ?ComparatorSet {
+ raw_comparators := input.split(semver.comparator_sep)
+ if raw_comparators.len > 2 {
+ return IError(&InvalidComparatorFormatError{
+ msg: 'Invalid format of comparator set for input "$input"'
+ })
+ }
+ mut comparators := []Comparator{}
+ for raw_comp in raw_comparators {
+ c := parse_comparator(raw_comp) or {
+ return IError(&InvalidComparatorFormatError{
+ msg: 'Invalid comparator "$raw_comp" in input "$input"'
+ })
+ }
+ comparators << c
+ }
+ return ComparatorSet{comparators}
+}
+
+fn parse_comparator(input string) ?Comparator {
+ mut op := Operator.eq
+ mut raw_version := ''
+ if input.starts_with('>=') {
+ op = .ge
+ raw_version = input[2..]
+ } else if input.starts_with('<=') {
+ op = .le
+ raw_version = input[2..]
+ } else if input.starts_with('>') {
+ op = .gt
+ raw_version = input[1..]
+ } else if input.starts_with('<') {
+ op = .lt
+ raw_version = input[1..]
+ } else if input.starts_with('=') {
+ raw_version = input[1..]
+ } else {
+ raw_version = input
+ }
+ version := coerce_version(raw_version) or { return none }
+ return Comparator{version, op}
+}
+
+fn parse_xrange(input string) ?Version {
+ mut raw_ver := parse(input).complete()
+ for typ in versions {
+ if raw_ver.raw_ints[typ].index_any(semver.x_range_symbols) == -1 {
+ continue
+ }
+ match typ {
+ ver_major {
+ raw_ver.raw_ints[ver_major] = '0'
+ raw_ver.raw_ints[ver_minor] = '0'
+ raw_ver.raw_ints[ver_patch] = '0'
+ }
+ ver_minor {
+ raw_ver.raw_ints[ver_minor] = '0'
+ raw_ver.raw_ints[ver_patch] = '0'
+ }
+ ver_patch {
+ raw_ver.raw_ints[ver_patch] = '0'
+ }
+ else {}
+ }
+ }
+ if !raw_ver.is_valid() {
+ return none
+ }
+ return raw_ver.to_version()
+}
+
+fn can_expand(input string) bool {
+ return input[0] == `~` || input[0] == `^` || input.contains(semver.hyphen_range_sep)
+ || input.index_any(semver.x_range_symbols) > -1
+}
+
+fn expand_comparator_set(input string) ?ComparatorSet {
+ match input[0] {
+ `~` { return expand_tilda(input[1..]) }
+ `^` { return expand_caret(input[1..]) }
+ else {}
+ }
+ if input.contains(semver.hyphen_range_sep) {
+ return expand_hyphen(input)
+ }
+ return expand_xrange(input)
+}
+
+fn expand_tilda(raw_version string) ?ComparatorSet {
+ min_ver := coerce_version(raw_version) or { return none }
+ mut max_ver := min_ver
+ if min_ver.minor == 0 && min_ver.patch == 0 {
+ max_ver = min_ver.increment(.major)
+ } else {
+ max_ver = min_ver.increment(.minor)
+ }
+ return make_comparator_set_ge_lt(min_ver, max_ver)
+}
+
+fn expand_caret(raw_version string) ?ComparatorSet {
+ min_ver := coerce_version(raw_version) or { return none }
+ mut max_ver := min_ver
+ if min_ver.major == 0 {
+ max_ver = min_ver.increment(.minor)
+ } else {
+ max_ver = min_ver.increment(.major)
+ }
+ return make_comparator_set_ge_lt(min_ver, max_ver)
+}
+
+fn expand_hyphen(raw_range string) ?ComparatorSet {
+ raw_versions := raw_range.split(semver.hyphen_range_sep)
+ if raw_versions.len != 2 {
+ return none
+ }
+ min_ver := coerce_version(raw_versions[0]) or { return none }
+ raw_max_ver := parse(raw_versions[1])
+ if raw_max_ver.is_missing(ver_major) {
+ return none
+ }
+ mut max_ver := raw_max_ver.coerce() or { return none }
+ if raw_max_ver.is_missing(ver_minor) {
+ max_ver = max_ver.increment(.minor)
+ return make_comparator_set_ge_lt(min_ver, max_ver)
+ }
+ return make_comparator_set_ge_le(min_ver, max_ver)
+}
+
+fn expand_xrange(raw_range string) ?ComparatorSet {
+ min_ver := parse_xrange(raw_range) or { return none }
+ if min_ver.major == 0 {
+ comparators := [
+ Comparator{min_ver, Operator.ge},
+ ]
+ return ComparatorSet{comparators}
+ }
+ mut max_ver := min_ver
+ if min_ver.minor == 0 {
+ max_ver = min_ver.increment(.major)
+ } else {
+ max_ver = min_ver.increment(.minor)
+ }
+ return make_comparator_set_ge_lt(min_ver, max_ver)
+}
+
+fn make_comparator_set_ge_lt(min Version, max Version) ComparatorSet {
+ comparators := [
+ Comparator{min, Operator.ge},
+ Comparator{max, Operator.lt},
+ ]
+ return ComparatorSet{comparators}
+}
+
+fn make_comparator_set_ge_le(min Version, max Version) ComparatorSet {
+ comparators := [
+ Comparator{min, Operator.ge},
+ Comparator{max, Operator.le},
+ ]
+ return ComparatorSet{comparators}
+}
diff --git a/v_windows/v/vlib/semver/semver.v b/v_windows/v/vlib/semver/semver.v
new file mode 100644
index 0000000..c35bbfb
--- /dev/null
+++ b/v_windows/v/vlib/semver/semver.v
@@ -0,0 +1,110 @@
+// * Documentation: https://docs.npmjs.com/misc/semver
+module semver
+
+// * Structures.
+// `Version` represents a semantic version in semver format.
+pub struct Version {
+pub:
+ major int
+ minor int
+ patch int
+ prerelease string
+ metadata string
+}
+
+// Increment represents the different types of version increments.
+pub enum Increment {
+ major
+ minor
+ patch
+}
+
+struct EmptyInputError {
+ msg string = 'Empty input'
+ code int
+}
+
+struct InvalidVersionFormatError {
+ msg string
+ code int
+}
+
+// * Constructor.
+// from returns a `Version` structure parsed from `input` `string`.
+pub fn from(input string) ?Version {
+ if input.len == 0 {
+ return IError(&EmptyInputError{})
+ }
+ raw_version := parse(input)
+ version := raw_version.validate() or {
+ return IError(&InvalidVersionFormatError{
+ msg: 'Invalid version format for input "$input"'
+ })
+ }
+ return version
+}
+
+// build returns a `Version` structure with given `major`, `minor` and `patch` versions.
+pub fn build(major int, minor int, patch int) Version {
+ // TODO Check if versions are greater than zero.
+ return Version{major, minor, patch, '', ''}
+}
+
+// * Transformation.
+// increment returns a `Version` structure with incremented values.
+pub fn (ver Version) increment(typ Increment) Version {
+ return increment_version(ver, typ)
+}
+
+// * Comparison.
+// satisfies returns `true` if the `input` expression can be validated to `true`
+// when run against this `Version`.
+// Example: assert semver.build(1,0,0).satisfies('<=2.0.0') == true
+// Example: assert semver.build(1,0,0).satisfies('>=2.0.0') == false
+pub fn (ver Version) satisfies(input string) bool {
+ return version_satisfies(ver, input)
+}
+
+// eq returns `true` if `v1` is equal to `v2`.
+pub fn (v1 Version) eq(v2 Version) bool {
+ return compare_eq(v1, v2)
+}
+
+// gt returns `true` if `v1` is greater than `v2`.
+pub fn (v1 Version) gt(v2 Version) bool {
+ return compare_gt(v1, v2)
+}
+
+// lt returns `true` if `v1` is less than `v2`.
+pub fn (v1 Version) lt(v2 Version) bool {
+ return compare_lt(v1, v2)
+}
+
+// ge returns `true` if `v1` is greater than or equal to `v2`.
+pub fn (v1 Version) ge(v2 Version) bool {
+ return compare_ge(v1, v2)
+}
+
+// le returns `true` if `v1` is less than or equal to `v2`.
+pub fn (v1 Version) le(v2 Version) bool {
+ return compare_le(v1, v2)
+}
+
+// * Utilites.
+// coerce converts the `input` version to a `Version` struct.
+// coerce will strip any contents *after* the parsed version string:
+/*
+Example:
+import semver
+v := semver.coerce('1.3-RC1-b2') or { semver.Version{} }
+assert v.satisfies('>1.0 <2.0') == true // 1.3.0
+*/
+pub fn coerce(input string) ?Version {
+ return coerce_version(input)
+}
+
+// is_valid returns `true` if the `input` `string` can be converted to
+// a (semantic) `Version` struct.
+pub fn is_valid(input string) bool {
+ return is_version_valid(input)
+}
diff --git a/v_windows/v/vlib/semver/semver_test.v b/v_windows/v/vlib/semver/semver_test.v
new file mode 100644
index 0000000..77c8cd7
--- /dev/null
+++ b/v_windows/v/vlib/semver/semver_test.v
@@ -0,0 +1,192 @@
+import semver
+
+struct TestVersion {
+ raw string
+ major int
+ minor int
+ patch int
+ prerelease string
+ metadata string
+}
+
+struct TestRange {
+ raw_version string
+ range_satisfied string
+ range_unsatisfied string
+}
+
+struct TestCoerce {
+ invalid string
+ valid string
+}
+
+const (
+ versions_to_test = [
+ TestVersion{'1.2.4', 1, 2, 4, '', ''},
+ TestVersion{'1.2.4-prerelease-1', 1, 2, 4, 'prerelease-1', ''},
+ TestVersion{'1.2.4+20191231', 1, 2, 4, '', '20191231'},
+ TestVersion{'1.2.4-prerelease-1+20191231', 1, 2, 4, 'prerelease-1', '20191231'},
+ TestVersion{'1.2.4+20191231-prerelease-1', 1, 2, 4, '', '20191231-prerelease-1'},
+ ]
+ ranges_to_test = [
+ TestRange{'1.1.0', '1.1.0', '1.1.1'},
+ TestRange{'1.1.0', '=1.1.0', '=1.1.1'},
+ TestRange{'1.1.0', '>=1.0.0', '<1.1.0'},
+ TestRange{'1.1.0', '>=1.0.0 <=1.1.0', '>=1.0.0 <1.1.0'},
+ TestRange{'2.3.1', '>=1.0.0 <=1.1.0 || >2.0.0 <2.3.4', '>=1.0.0 <1.1.0'},
+ TestRange{'2.3.1', '>=1.0.0 <=1.1.0 || >2.0.0 <2.3.4', '>=1.0.0 <1.1.0 || >4.0.0 <5.0.0'},
+ TestRange{'2.3.1', '~2.3.0', '~2.4.0'},
+ TestRange{'3.0.0', '~3.0.0', '~4.0.0'},
+ TestRange{'2.3.1', '^2.0.0', '^2.4.0'},
+ TestRange{'0.3.1', '^0.3.0', '^2.4.0'},
+ TestRange{'0.0.4', '^0.0.1', '^0.1.0'},
+ TestRange{'2.3.4', '^0.0.1 || ^2.3.0', '^3.1.0 || ^4.2.0'},
+ TestRange{'2.3.4', '>2 || <3', '>3 || >4'},
+ TestRange{'2.3.4', '2.3.4 - 2.3.5', '2.5.1 - 2.8.3'},
+ TestRange{'2.3.4', '2.2 - 2.3', '2.4 - 2.8'},
+ TestRange{'2.3.4', '2.3.x', '2.4.x'},
+ TestRange{'2.3.4', '2.x', '3.x'},
+ TestRange{'2.3.4', '*', '3.x'},
+ ]
+ coerce_to_test = [
+ TestCoerce{'1.2.0.4', '1.2.0'},
+ TestCoerce{'1.2.0', '1.2.0'},
+ TestCoerce{'1.2', '1.2.0'},
+ TestCoerce{'1', '1.0.0'},
+ TestCoerce{'1-alpha', '1.0.0-alpha'},
+ TestCoerce{'1+meta', '1.0.0+meta'},
+ TestCoerce{'1-alpha+meta', '1.0.0-alpha+meta'},
+ ]
+ invalid_versions_to_test = [
+ 'a.b.c',
+ '1.2',
+ '1.2.x',
+ '1.2.3.4',
+ '1.2.3-alpha@',
+ '1.2.3+meta%',
+ ]
+ invalid_ranges_to_test = [
+ '^a',
+ '~b',
+ 'a - c',
+ '>a',
+ 'a',
+ 'a.x',
+ ]
+)
+
+fn test_from() {
+ for item in versions_to_test {
+ ver := semver.from(item.raw) or {
+ assert false
+ return
+ }
+ assert ver.major == item.major
+ assert ver.minor == item.minor
+ assert ver.patch == item.patch
+ assert ver.metadata == item.metadata
+ assert ver.prerelease == item.prerelease
+ }
+ for ver in invalid_versions_to_test {
+ semver.from(ver) or {
+ assert true
+ continue
+ }
+ assert false
+ }
+}
+
+fn test_increment() {
+ version1 := semver.build(1, 2, 3)
+ version1_inc := version1.increment(.major)
+ assert version1_inc.major == 2
+ assert version1_inc.minor == 0
+ assert version1_inc.patch == 0
+ version2_inc := version1.increment(.minor)
+ assert version2_inc.major == 1
+ assert version2_inc.minor == 3
+ assert version2_inc.patch == 0
+ version3_inc := version1.increment(.patch)
+ assert version3_inc.major == 1
+ assert version3_inc.minor == 2
+ assert version3_inc.patch == 4
+}
+
+fn test_compare() {
+ first := semver.build(1, 0, 0)
+ patch := semver.build(1, 0, 1)
+ minor := semver.build(1, 2, 3)
+ major := semver.build(2, 0, 0)
+ assert first.le(first)
+ assert first.ge(first)
+ assert !first.lt(first)
+ assert !first.gt(first)
+ assert patch.ge(first)
+ assert first.le(patch)
+ assert !first.ge(patch)
+ assert !patch.le(first)
+ assert patch.gt(first)
+ assert first.lt(patch)
+ assert !first.gt(patch)
+ assert !patch.lt(first)
+ assert minor.gt(patch)
+ assert patch.lt(minor)
+ assert !patch.gt(minor)
+ assert !minor.lt(patch)
+ assert major.gt(minor)
+ assert minor.lt(major)
+ assert !minor.gt(major)
+ assert !major.lt(minor)
+}
+
+fn test_satisfies() {
+ for item in ranges_to_test {
+ ver := semver.from(item.raw_version) or {
+ assert false
+ return
+ }
+ assert ver.satisfies(item.range_satisfied)
+ assert !ver.satisfies(item.range_unsatisfied)
+ }
+}
+
+fn test_satisfies_invalid() {
+ ver := semver.from('1.0.0') or {
+ assert false
+ return
+ }
+ for item in invalid_ranges_to_test {
+ assert ver.satisfies(item) == false
+ }
+}
+
+fn test_coerce() {
+ for item in coerce_to_test {
+ valid := semver.from(item.valid) or {
+ assert false
+ return
+ }
+ fixed := semver.coerce(item.invalid) or {
+ assert false
+ return
+ }
+ assert fixed.eq(valid)
+ }
+}
+
+fn test_coerce_invalid() {
+ semver.coerce('a') or {
+ assert true
+ return
+ }
+ assert false
+}
+
+fn test_is_valid() {
+ for item in versions_to_test {
+ assert semver.is_valid(item.raw)
+ }
+ for item in invalid_versions_to_test {
+ assert semver.is_valid(item) == false
+ }
+}
diff --git a/v_windows/v/vlib/semver/util.v b/v_windows/v/vlib/semver/util.v
new file mode 100644
index 0000000..142ce19
--- /dev/null
+++ b/v_windows/v/vlib/semver/util.v
@@ -0,0 +1,55 @@
+module semver
+
+// * Private functions.
+[inline]
+fn is_version_valid(input string) bool {
+ raw_ver := parse(input)
+ return raw_ver.is_valid()
+}
+
+[inline]
+fn coerce_version(input string) ?Version {
+ raw_ver := parse(input)
+ ver := raw_ver.coerce() or { return error('Invalid version for input "$input"') }
+ return ver
+}
+
+[inline]
+fn increment_version(ver Version, typ Increment) Version {
+ mut major := ver.major
+ mut minor := ver.minor
+ mut patch := ver.patch
+ match typ {
+ .major {
+ major++
+ minor = 0
+ patch = 0
+ }
+ .minor {
+ minor++
+ patch = 0
+ }
+ .patch {
+ patch++
+ }
+ }
+ return Version{major, minor, patch, ver.prerelease, ver.metadata}
+}
+
+fn is_valid_string(input string) bool {
+ for c in input {
+ if !(c.is_letter() || c.is_digit() || c == `.` || c == `-`) {
+ return false
+ }
+ }
+ return true
+}
+
+fn is_valid_number(input string) bool {
+ for c in input {
+ if !c.is_digit() {
+ return false
+ }
+ }
+ return true
+}
diff --git a/v_windows/v/vlib/semver/v.mod b/v_windows/v/vlib/semver/v.mod
new file mode 100644
index 0000000..dd7671c
--- /dev/null
+++ b/v_windows/v/vlib/semver/v.mod
@@ -0,0 +1,5 @@
+Module {
+ name: 'semver'
+ version: '0.3.0'
+ deps: []
+}