Skip to content

Instantly share code, notes, and snippets.

@joewiz
Last active September 4, 2019 12:56
Show Gist options
  • Save joewiz/b349e2853a17bf817e5d0013d01fa9f9 to your computer and use it in GitHub Desktop.
Save joewiz/b349e2853a17bf817e5d0013d01fa9f9 to your computer and use it in GitHub Desktop.
Validate, compare, sort, parse, and serialize Semantic Versioning (semver) versions, using XQuery - development has moved to https://github.com/eXist-db/semver.xq
xquery version "3.1";
(:~ Validate, compare, sort, parse, and serialize Semantic Versioning (SemVer)
: 2.0.0 version strings, using XQuery.
:
: SemVer rules are applied strictly, raising errors when version strings do
: not conform to the spec.
:
: @author Joe Wicentowski
: @version 2.1.0
: @see https://semver.org/spec/v2.0.0.html
: @see https://gist.github.com/joewiz/b349e2853a17bf817e5d0013d01fa9f9
:)
module namespace semver = "http://joewiz.org/ns/xquery/semver";
(:~ A regular expression for checking a SemVer version string
: @author David Fichtmueller
: @see https://github.com/semver/semver/pull/460
:)
declare variable $semver:regex := "^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$";
(:~ Validate whether a SemVer string conforms to the spec
: @param A version string
: @return True if the version is valid, false if not
:)
declare function semver:validate($version as xs:string) as xs:boolean {
try {
let $parsed := semver:parse($version)
return
true()
} catch * {
false()
}
};
(:~ Parse a SemVer version string
: @param A version string
: @return A map containing analysis of the parsed version, containing entries for each identifier ("major", "minor", "patch", "pre-release", and "build-metadata"), and an "identifiers" entry with all identifiers in an array.
: @error regex-error
: @error identifier-error
:)
declare function semver:parse($version as xs:string) as map(*) {
(: run the version against the standard SemVer regex :)
let $analysis := analyze-string($version, $semver:regex)
let $groups := $analysis/fn:match/fn:group
return
if (exists($groups)) then
try {
(: an inline function for casting identifiers to the appropriate types :)
let $cast-identifier := function($identifier as xs:string) {
if ($identifier castable as xs:integer) then
$identifier cast as xs:integer
else
$identifier
}
let $release-identifiers := subsequence($groups, 1, 3) ! $cast-identifier(.)
let $pre-release-identifiers := array { $groups[4] ! tokenize(., "\.") ! $cast-identifier(.) }
let $build-metadata-identifiers := array { $groups[5] ! tokenize(., "\.") ! $cast-identifier(.) }
return
map {
"major": $release-identifiers[1],
"minor": $release-identifiers[2],
"patch": $release-identifiers[3],
"pre-release": $pre-release-identifiers,
"build-metadata": $build-metadata-identifiers,
"identifiers": array { $release-identifiers, $pre-release-identifiers, $build-metadata-identifiers }
}
} catch * {
semver:error("identifier-error", $version)
}
else
semver:error("regex-error", $version)
};
(:~ Serialize a SemVer string
: @param The major version
: @param The minor version
: @param The patch version
: @param Pre-release identifiers
: @param Build identifiers
: @return A SemVer string
:)
declare function semver:serialize($major as xs:integer, $minor as xs:integer, $patch as xs:integer, $pre-release as xs:anyAtomicType*, $build-metadata as xs:anyAtomicType*) {
let $release := string-join(($major, $minor, $patch), ".")
let $pre-release := string-join($pre-release ! string(.), ".")
let $build-metadata := string-join($build-metadata ! string(.), ".")
let $candidate :=
$release ||
(if ($pre-release) then "-" || $pre-release else ()) ||
(if ($build-metadata) then "+" || $build-metadata else ())
(: raise an error if the candidate is invalid :)
let $check := semver:parse($candidate)
return
$candidate
};
(:~ Serialize a parsed SemVer string
: @param A map containing the components of the SemVer
: @return A SemVer string
:)
declare function semver:serialize($version as map(*)) {
semver:serialize($version?major, $version?minor, $version?patch, $version?pre-release, $version?build-metadata)
};
(:~ Compare two versions
: @param A version string
: @param A second version string
: @return -1 if v1 < v2, 0 if v1 = v2, or 1 if v1 > v2.
:)
declare function semver:compare($v1 as xs:string, $v2 as xs:string) as xs:integer {
let $parsed-v1 := semver:parse($v1)
let $parsed-v2 := semver:parse($v2)
return
semver:compare-parsed($parsed-v1, $parsed-v2)
};
(:~ Test if v1 is a lower version than v2
: @param A version string
: @param A second version string
: @return true if v1 is less than v2
:)
declare function semver:lt($v1 as xs:string, $v2 as xs:string) as xs:boolean {
semver:compare($v1, $v2) eq -1
};
(:~ Test if v1 is a lower version or the same version as v2
: @param A version string
: @param A second version string
: @return true if v1 is less than or equal to v2
:)
declare function semver:le($v1 as xs:string, $v2 as xs:string) as xs:boolean {
semver:compare($v1, $v2) = (-1, 0)
};
(:~ Test if v1 is a higher version than v2
: @param A version string
: @param A second version string
: @return true if v1 is greater than v2
:)
declare function semver:gt($v1 as xs:string, $v2 as xs:string) as xs:boolean {
semver:compare($v1, $v2) eq 1
};
(:~ Test if v1 is the same or higher version than v2
: @param A version string
: @param A second version string
: @return true if v1 is greater than or equal to v2
:)
declare function semver:ge($v1 as xs:string, $v2 as xs:string) as xs:boolean {
semver:compare($v1, $v2) = (1, 0)
};
(:~ Test if v1 is equal to v2
: @param A version string
: @param A second version string
: @return true if v1 is equal to v2
:)
declare function semver:eq($v1 as xs:string, $v2 as xs:string) as xs:boolean {
semver:compare($v1, $v2) eq 0
};
(:~ Test if v1 is not equal to v2
: @param A version string
: @param A second version string
: @return true if v1 is not equal to v2
:)
declare function semver:ne($v1 as xs:string, $v2 as xs:string) as xs:boolean {
semver:compare($v1, $v2) ne 0
};
(:~ Sort SemVer strings
: @param A sequence of version strings
: @return A sequence of sorted version strings
:)
declare function semver:sort($versions as xs:string+) as xs:string+ {
let $parsed := $versions ! semver:parse(.)
(: First, sort versions by major, minor, and patch (using fast standard sort) :)
let $release-sorted := sort($parsed, (), function($p) { $p?major, $p?minor, $p?patch } )
(: Second, sort any versions with pre-release fields :)
let $pre-release-sorted :=
(: group by major, minor, and patch to limit sorting to like versions :)
for $p1 in $release-sorted
group by $major := $p1?major
order by $major
return
for $p2 in $p1
group by $minor := $p2?minor
order by $minor
return
for $p3 in $p2
group by $patch := $p3?patch
let $releases := $p3[?pre-release => array:size() eq 0]
let $pre-releases := $p3[?pre-release => array:size() gt 0]
order by $patch
return
(
semver:sort-pre-release($pre-releases, ()),
(: versions without pre-release metadata take precedence :)
$releases
)
for $version in $pre-release-sorted
return
semver:serialize($version)
};
(:~ Sort pre-release fields
: @param The versions to sort
: @param An accumulator for sorted versions
: @return Sorted versions
:)
declare %private function semver:sort-pre-release($parsed-versions as map(*)*, $sorted-versions as map(*)*) as map(*)* {
if (exists($parsed-versions)) then
let $head := head($parsed-versions)
let $rest := tail($parsed-versions)
let $is-largest-pre-release := every $item in $rest?pre-release satisfies semver:compare-pre-release($head?pre-release, $item) = (1, 0)
return
if ($is-largest-pre-release) then
semver:sort-pre-release(tail($parsed-versions), ($head, $sorted-versions))
else
semver:sort-pre-release(($rest, $head), $sorted-versions)
else
$sorted-versions
};
(:~ Compare two parsed SemVer versions
: @param A map containing analysis of a version string
: @param A map containing analysis of a second version string
: @return -1 if v1 < v2, 0 if v1 = v2, or 1 if v1 > v2.
:)
declare %private function semver:compare-parsed($v1 as map(*), $v2 as map(*)) as xs:integer {
(: Compare major, minor, and patch identifiers :)
let $release-comparison :=
semver:compare-release(
array:subarray($v1?identifiers, 1, 3),
array:subarray($v2?identifiers, 1, 3)
)
return
switch ($release-comparison)
case 0 return
(: When major, minor, and patch are equal, a pre-release version has lower precedence than a normal version. :)
if (array:size($v1?pre-release) eq 0 and array:size($v2?pre-release) gt 0) then
1
else if (array:size($v1?pre-release) gt 0 and array:size($v2?pre-release) eq 0) then
-1
else
(: When major, minor, and patch are equal, compare pre-release :)
(: Build metadata SHOULD be ignored when determining version precedence. :)
semver:compare-pre-release(
$v1?pre-release,
$v2?pre-release
)
default return
$release-comparison
};
declare %private function semver:compare-release($v1 as array(*), $v2 as array(*)) {
(: No (more) pairs to compare, so the release portions of the two versions are of equal precedence :)
if (array:size($v1) eq 0 and array:size($v2) eq 0) then
0
(: Compare members using numeric operators :)
else if (array:head($v1) lt array:head($v2)) then
-1
else if (array:head($v1) gt array:head($v2)) then
1
else
semver:compare-release(array:tail($v1), array:tail($v2))
};
declare %private function semver:compare-pre-release($v1 as array(*), $v2 as array(*)) {
(: No (more) pairs to compare, so the two versions are of equal precedence :)
if (array:size($v1) eq 0 and array:size($v2) eq 0) then
0
(: A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal. :)
else if (array:size($v1) eq 0) then
-1
else if (array:size($v2) eq 0) then
1
(: Numeric identifiers always have lower precedence than non-numeric identifiers. :)
else if (array:head($v1) instance of xs:string and array:head($v2) instance of xs:integer) then
1
else if (array:head($v1) instance of xs:integer and array:head($v2) instance of xs:string) then
-1
(: Compare values using comparison operators :)
else if (array:head($v1) lt array:head($v2)) then
-1
else if (array:head($v1) gt array:head($v2)) then
1
(: These identifiers are equal, so recurse to the next pair of identifiers :)
else
semver:compare-pre-release(array:tail($v1), array:tail($v2))
};
(:~ Raise a descriptive error
: @param An error code
: @param The version or identifier that triggered the error
: @return The error.
:)
declare %private function semver:error($code as xs:string, $version as xs:string) {
let $errors :=
map {
"regex-error":
map {
"description": "Version did not match regex for valid semver",
"qname": QName("http://joewiz.org/ns/xquery/semver", "regex-error")
},
"identifier-error":
map {
"description": "Version identifiers did not conform to semver spec",
"qname": QName("http://joewiz.org/ns/xquery/semver", "identifier-error")
}
}
let $error := $errors?($code)
return
error($error?qname, $error?description || ": '" || $version || "'")
};
xquery version "3.1";
import module namespace semver = "http://joewiz.org/ns/xquery/semver" at "semver.xqm";
(: Test string taken from https://github.com/semver/semver.org/issues/59#issuecomment-390854010 :)
let $versions :=
"0.0.4
1.2.3
10.20.30
1.1.2-prerelease+meta
1.1.2+meta
1.1.2+meta-valid
1.0.0-alpha
1.0.0-beta
1.0.0-alpha.beta
1.0.0-alpha.beta.1
1.0.0-alpha.1
1.0.0-alpha0.valid
1.0.0-alpha.0valid
1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay
1.0.0-rc.1+build.1
2.0.0-rc.1+build.123
1.2.3-beta
10.2.3-DEV-SNAPSHOT
1.2.3-SNAPSHOT-123
1.0.0
2.0.0
1.1.7
2.0.0+build.1848
2.0.1-alpha.1227
1.0.0-alpha+beta
1.2.3----RC-SNAPSHOT.12.9.1--.12+788
1.2.3----R-S.12.9.1--.12+meta
1.2.3----RC-SNAPSHOT.12.9.1--.12
1.0.0+0.build.1-rc.10000aaa-kk-0.1
99999999999999999999999.999999999999999999.99999999999999999
1.0.0-0A.is.legal
Begin Invalid
1
1.2
1.2.3-0123
1.2.3-0123.0123
1.1.2+.123
+invalid
-invalid
-invalid+invalid
-invalid.01
alpha
alpha.beta
alpha.beta.1
alpha.1
alpha+beta
alpha_beta
alpha.
alpha..
beta
1.0.0-alpha_beta
-alpha.
1.0.0-alpha..
1.0.0-alpha..1
1.0.0-alpha...1
1.0.0-alpha....1
1.0.0-alpha.....1
1.0.0-alpha......1
1.0.0-alpha.......1
01.1.1
1.01.1
1.1.01
1.2
1.2.3.DEV
1.2-SNAPSHOT
1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788
1.2-RC-SNAPSHOT
-1.0.3-gamma+b7718
+justmeta
9.8.7+meta+meta
9.8.7-whatever+meta+meta
99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12"
return
array {
tokenize($versions, "\n+") !
map {
"source": .,
"result": try { semver:parse(.) } catch * { $err:code || ": " || $err:description }
}
}
[
{
"source": "0.0.4",
"result": {
"patch": 4,
"identifiers": [
0,
0,
4,
[],
[]
],
"build-metadata": [],
"minor": 0,
"major": 0,
"pre-release": []
}
},
{
"source": "1.2.3",
"result": {
"patch": 3,
"identifiers": [
1,
2,
3,
[],
[]
],
"build-metadata": [],
"minor": 2,
"major": 1,
"pre-release": []
}
},
{
"source": "10.20.30",
"result": {
"patch": 30,
"identifiers": [
10,
20,
30,
[],
[]
],
"build-metadata": [],
"minor": 20,
"major": 10,
"pre-release": []
}
},
{
"source": "1.1.2-prerelease+meta",
"result": {
"patch": 2,
"identifiers": [
1,
1,
2,
["prerelease"],
["meta"]
],
"build-metadata": ["meta"],
"minor": 1,
"major": 1,
"pre-release": ["prerelease"]
}
},
{
"source": "1.1.2+meta",
"result": {
"patch": 2,
"identifiers": [
1,
1,
2,
["meta"],
[]
],
"build-metadata": [],
"minor": 1,
"major": 1,
"pre-release": ["meta"]
}
},
{
"source": "1.1.2+meta-valid",
"result": {
"patch": 2,
"identifiers": [
1,
1,
2,
["meta-valid"],
[]
],
"build-metadata": [],
"minor": 1,
"major": 1,
"pre-release": ["meta-valid"]
}
},
{
"source": "1.0.0-alpha",
"result": {
"patch": 0,
"identifiers": [
1,
0,
0,
["alpha"],
[]
],
"build-metadata": [],
"minor": 0,
"major": 1,
"pre-release": ["alpha"]
}
},
{
"source": "1.0.0-beta",
"result": {
"patch": 0,
"identifiers": [
1,
0,
0,
["beta"],
[]
],
"build-metadata": [],
"minor": 0,
"major": 1,
"pre-release": ["beta"]
}
},
{
"source": "1.0.0-alpha.beta",
"result": {
"patch": 0,
"identifiers": [
1,
0,
0,
[
"alpha",
"beta"
],
[]
],
"build-metadata": [],
"minor": 0,
"major": 1,
"pre-release": [
"alpha",
"beta"
]
}
},
{
"source": "1.0.0-alpha.beta.1",
"result": {
"patch": 0,
"identifiers": [
1,
0,
0,
[
"alpha",
"beta",
1
],
[]
],
"build-metadata": [],
"minor": 0,
"major": 1,
"pre-release": [
"alpha",
"beta",
1
]
}
},
{
"source": "1.0.0-alpha.1",
"result": {
"patch": 0,
"identifiers": [
1,
0,
0,
[
"alpha",
1
],
[]
],
"build-metadata": [],
"minor": 0,
"major": 1,
"pre-release": [
"alpha",
1
]
}
},
{
"source": "1.0.0-alpha0.valid",
"result": {
"patch": 0,
"identifiers": [
1,
0,
0,
[
"alpha0",
"valid"
],
[]
],
"build-metadata": [],
"minor": 0,
"major": 1,
"pre-release": [
"alpha0",
"valid"
]
}
},
{
"source": "1.0.0-alpha.0valid",
"result": {
"patch": 0,
"identifiers": [
1,
0,
0,
[
"alpha",
"0valid"
],
[]
],
"build-metadata": [],
"minor": 0,
"major": 1,
"pre-release": [
"alpha",
"0valid"
]
}
},
{
"source": "1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay",
"result": {
"patch": 0,
"identifiers": [
1,
0,
0,
[
"alpha-a",
"b-c-somethinglong"
],
[
"build",
"1-aef",
"1-its-okay"
]
],
"build-metadata": [
"build",
"1-aef",
"1-its-okay"
],
"minor": 0,
"major": 1,
"pre-release": [
"alpha-a",
"b-c-somethinglong"
]
}
},
{
"source": "1.0.0-rc.1+build.1",
"result": {
"patch": 0,
"identifiers": [
1,
0,
0,
[
"rc",
1
],
[
"build",
1
]
],
"build-metadata": [
"build",
1
],
"minor": 0,
"major": 1,
"pre-release": [
"rc",
1
]
}
},
{
"source": "2.0.0-rc.1+build.123",
"result": {
"patch": 0,
"identifiers": [
2,
0,
0,
[
"rc",
1
],
[
"build",
123
]
],
"build-metadata": [
"build",
123
],
"minor": 0,
"major": 2,
"pre-release": [
"rc",
1
]
}
},
{
"source": "1.2.3-beta",
"result": {
"patch": 3,
"identifiers": [
1,
2,
3,
["beta"],
[]
],
"build-metadata": [],
"minor": 2,
"major": 1,
"pre-release": ["beta"]
}
},
{
"source": "10.2.3-DEV-SNAPSHOT",
"result": {
"patch": 3,
"identifiers": [
10,
2,
3,
["DEV-SNAPSHOT"],
[]
],
"build-metadata": [],
"minor": 2,
"major": 10,
"pre-release": ["DEV-SNAPSHOT"]
}
},
{
"source": "1.2.3-SNAPSHOT-123",
"result": {
"patch": 3,
"identifiers": [
1,
2,
3,
["SNAPSHOT-123"],
[]
],
"build-metadata": [],
"minor": 2,
"major": 1,
"pre-release": ["SNAPSHOT-123"]
}
},
{
"source": "1.0.0",
"result": {
"patch": 0,
"identifiers": [
1,
0,
0,
[],
[]
],
"build-metadata": [],
"minor": 0,
"major": 1,
"pre-release": []
}
},
{
"source": "2.0.0",
"result": {
"patch": 0,
"identifiers": [
2,
0,
0,
[],
[]
],
"build-metadata": [],
"minor": 0,
"major": 2,
"pre-release": []
}
},
{
"source": "1.1.7",
"result": {
"patch": 7,
"identifiers": [
1,
1,
7,
[],
[]
],
"build-metadata": [],
"minor": 1,
"major": 1,
"pre-release": []
}
},
{
"source": "2.0.0+build.1848",
"result": {
"patch": 0,
"identifiers": [
2,
0,
0,
[
"build",
1848
],
[]
],
"build-metadata": [],
"minor": 0,
"major": 2,
"pre-release": [
"build",
1848
]
}
},
{
"source": "2.0.1-alpha.1227",
"result": {
"patch": 1,
"identifiers": [
2,
0,
1,
[
"alpha",
1227
],
[]
],
"build-metadata": [],
"minor": 0,
"major": 2,
"pre-release": [
"alpha",
1227
]
}
},
{
"source": "1.0.0-alpha+beta",
"result": {
"patch": 0,
"identifiers": [
1,
0,
0,
["alpha"],
["beta"]
],
"build-metadata": ["beta"],
"minor": 0,
"major": 1,
"pre-release": ["alpha"]
}
},
{
"source": "1.2.3----RC-SNAPSHOT.12.9.1--.12+788",
"result": {
"patch": 3,
"identifiers": [
1,
2,
3,
[
"---RC-SNAPSHOT",
12,
9,
"1--",
12
],
[788]
],
"build-metadata": [788],
"minor": 2,
"major": 1,
"pre-release": [
"---RC-SNAPSHOT",
12,
9,
"1--",
12
]
}
},
{
"source": "1.2.3----R-S.12.9.1--.12+meta",
"result": {
"patch": 3,
"identifiers": [
1,
2,
3,
[
"---R-S",
12,
9,
"1--",
12
],
["meta"]
],
"build-metadata": ["meta"],
"minor": 2,
"major": 1,
"pre-release": [
"---R-S",
12,
9,
"1--",
12
]
}
},
{
"source": "1.2.3----RC-SNAPSHOT.12.9.1--.12",
"result": {
"patch": 3,
"identifiers": [
1,
2,
3,
[
"---RC-SNAPSHOT",
12,
9,
"1--",
12
],
[]
],
"build-metadata": [],
"minor": 2,
"major": 1,
"pre-release": [
"---RC-SNAPSHOT",
12,
9,
"1--",
12
]
}
},
{
"source": "1.0.0+0.build.1-rc.10000aaa-kk-0.1",
"result": {
"patch": 0,
"identifiers": [
1,
0,
0,
[
0,
"build",
"1-rc",
"10000aaa-kk-0",
1
],
[]
],
"build-metadata": [],
"minor": 0,
"major": 1,
"pre-release": [
0,
"build",
"1-rc",
"10000aaa-kk-0",
1
]
}
},
{
"source": "99999999999999999999999.999999999999999999.99999999999999999",
"result": {
"patch": 99999999999999999,
"identifiers": [
99999999999999999999999,
999999999999999999,
99999999999999999,
[],
[]
],
"build-metadata": [],
"minor": 999999999999999999,
"major": 99999999999999999999999,
"pre-release": []
}
},
{
"source": "1.0.0-0A.is.legal",
"result": {
"patch": 0,
"identifiers": [
1,
0,
0,
[
"0A",
"is",
"legal"
],
[]
],
"build-metadata": [],
"minor": 0,
"major": 1,
"pre-release": [
"0A",
"is",
"legal"
]
}
},
{
"source": "Begin Invalid",
"result": "semver:regex-error: Version did not match regex for valid semver: 'Begin Invalid'"
},
{
"source": "1",
"result": "semver:regex-error: Version did not match regex for valid semver: '1'"
},
{
"source": "1.2",
"result": "semver:regex-error: Version did not match regex for valid semver: '1.2'"
},
{
"source": "1.2.3-0123",
"result": "semver:regex-error: Version did not match regex for valid semver: '1.2.3-0123'"
},
{
"source": "1.2.3-0123.0123",
"result": "semver:regex-error: Version did not match regex for valid semver: '1.2.3-0123.0123'"
},
{
"source": "1.1.2+.123",
"result": "semver:regex-error: Version did not match regex for valid semver: '1.1.2+.123'"
},
{
"source": "+invalid",
"result": "semver:regex-error: Version did not match regex for valid semver: '+invalid'"
},
{
"source": "-invalid",
"result": "semver:regex-error: Version did not match regex for valid semver: '-invalid'"
},
{
"source": "-invalid+invalid",
"result": "semver:regex-error: Version did not match regex for valid semver: '-invalid+invalid'"
},
{
"source": "-invalid.01",
"result": "semver:regex-error: Version did not match regex for valid semver: '-invalid.01'"
},
{
"source": "alpha",
"result": "semver:regex-error: Version did not match regex for valid semver: 'alpha'"
},
{
"source": "alpha.beta",
"result": "semver:regex-error: Version did not match regex for valid semver: 'alpha.beta'"
},
{
"source": "alpha.beta.1",
"result": "semver:regex-error: Version did not match regex for valid semver: 'alpha.beta.1'"
},
{
"source": "alpha.1",
"result": "semver:regex-error: Version did not match regex for valid semver: 'alpha.1'"
},
{
"source": "alpha+beta",
"result": "semver:regex-error: Version did not match regex for valid semver: 'alpha+beta'"
},
{
"source": "alpha_beta",
"result": "semver:regex-error: Version did not match regex for valid semver: 'alpha_beta'"
},
{
"source": "alpha.",
"result": "semver:regex-error: Version did not match regex for valid semver: 'alpha.'"
},
{
"source": "alpha..",
"result": "semver:regex-error: Version did not match regex for valid semver: 'alpha..'"
},
{
"source": "beta",
"result": "semver:regex-error: Version did not match regex for valid semver: 'beta'"
},
{
"source": "1.0.0-alpha_beta",
"result": "semver:regex-error: Version did not match regex for valid semver: '1.0.0-alpha_beta'"
},
{
"source": "-alpha.",
"result": "semver:regex-error: Version did not match regex for valid semver: '-alpha.'"
},
{
"source": "1.0.0-alpha..",
"result": "semver:regex-error: Version did not match regex for valid semver: '1.0.0-alpha..'"
},
{
"source": "1.0.0-alpha..1",
"result": "semver:regex-error: Version did not match regex for valid semver: '1.0.0-alpha..1'"
},
{
"source": "1.0.0-alpha...1",
"result": "semver:regex-error: Version did not match regex for valid semver: '1.0.0-alpha...1'"
},
{
"source": "1.0.0-alpha....1",
"result": "semver:regex-error: Version did not match regex for valid semver: '1.0.0-alpha....1'"
},
{
"source": "1.0.0-alpha.....1",
"result": "semver:regex-error: Version did not match regex for valid semver: '1.0.0-alpha.....1'"
},
{
"source": "1.0.0-alpha......1",
"result": "semver:regex-error: Version did not match regex for valid semver: '1.0.0-alpha......1'"
},
{
"source": "1.0.0-alpha.......1",
"result": "semver:regex-error: Version did not match regex for valid semver: '1.0.0-alpha.......1'"
},
{
"source": "01.1.1",
"result": "semver:regex-error: Version did not match regex for valid semver: '01.1.1'"
},
{
"source": "1.01.1",
"result": "semver:regex-error: Version did not match regex for valid semver: '1.01.1'"
},
{
"source": "1.1.01",
"result": "semver:regex-error: Version did not match regex for valid semver: '1.1.01'"
},
{
"source": "1.2",
"result": "semver:regex-error: Version did not match regex for valid semver: '1.2'"
},
{
"source": "1.2.3.DEV",
"result": "semver:regex-error: Version did not match regex for valid semver: '1.2.3.DEV'"
},
{
"source": "1.2-SNAPSHOT",
"result": "semver:regex-error: Version did not match regex for valid semver: '1.2-SNAPSHOT'"
},
{
"source": "1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788",
"result": "semver:regex-error: Version did not match regex for valid semver: '1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788'"
},
{
"source": "1.2-RC-SNAPSHOT",
"result": "semver:regex-error: Version did not match regex for valid semver: '1.2-RC-SNAPSHOT'"
},
{
"source": "-1.0.3-gamma+b7718",
"result": "semver:regex-error: Version did not match regex for valid semver: '-1.0.3-gamma+b7718'"
},
{
"source": "+justmeta",
"result": "semver:regex-error: Version did not match regex for valid semver: '+justmeta'"
},
{
"source": "9.8.7+meta+meta",
"result": "semver:regex-error: Version did not match regex for valid semver: '9.8.7+meta+meta'"
},
{
"source": "9.8.7-whatever+meta+meta",
"result": "semver:regex-error: Version did not match regex for valid semver: '9.8.7-whatever+meta+meta'"
},
{
"source": "99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12",
"result": "semver:regex-error: Version did not match regex for valid semver: '99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12'"
}
]
xquery version "3.1";
import module namespace semver = "http://joewiz.org/ns/xquery/semver" at "semver.xqm";
(: Test sets taken from examples in https://semver.org/ :)
let $version-sets :=
(
["1.0.0", "2.0.0"],
["2.0.0", "2.1.0"],
["2.1.0", "2.1.1"],
["1.0.0-alpha", "1.0.0-alpha.1"],
["1.0.0-alpha.1", "1.0.0-alpha.beta"],
["1.0.0-alpha.beta", "1.0.0-beta"],
["1.0.0-beta", "1.0.0-beta.2"],
["1.0.0-beta.2", "1.0.0-beta.11"],
["1.0.0-beta.11", "1.0.0-rc.1"],
["1.0.0-rc.1", "1.0.0"]
)
let $results :=
for $set in $version-sets
return
map { "set": $set, "lt": semver:lt($set?1, $set?2) }
return
array { $results }
[
{
"set": [
"1.0.0",
"2.0.0"
],
"lt": true
},
{
"set": [
"2.0.0",
"2.1.0"
],
"lt": true
},
{
"set": [
"2.1.0",
"2.1.1"
],
"lt": true
},
{
"set": [
"1.0.0-alpha",
"1.0.0-alpha.1"
],
"lt": true
},
{
"set": [
"1.0.0-alpha.1",
"1.0.0-alpha.beta"
],
"lt": true
},
{
"set": [
"1.0.0-alpha.beta",
"1.0.0-beta"
],
"lt": true
},
{
"set": [
"1.0.0-beta",
"1.0.0-beta.2"
],
"lt": true
},
{
"set": [
"1.0.0-beta.2",
"1.0.0-beta.11"
],
"lt": true
},
{
"set": [
"1.0.0-beta.11",
"1.0.0-rc.1"
],
"lt": true
},
{
"set": [
"1.0.0-rc.1",
"1.0.0"
],
"lt": true
}
]
xquery version "3.1";
import module namespace semver = "http://joewiz.org/ns/xquery/semver" at "semver.xqm";
(: Test sets taken from examples in https://semver.org/ :)
let $versions :=
(
"1.0.0", "2.0.0", "2.1.0", "2.1.1", "1.0.0-alpha", "1.0.0-alpha.1",
"1.0.0-alpha.beta", "1.0.0-beta", "1.0.0-beta.11", "1.0.0-beta.2", "1.0.0-rc.1"
)
return
array { semver:sort($versions) }
[
"1.0.0-alpha",
"1.0.0-alpha.1",
"1.0.0-alpha.beta",
"1.0.0-beta",
"1.0.0-beta.2",
"1.0.0-beta.11",
"1.0.0-rc.1",
"1.0.0",
"2.0.0",
"2.1.0",
"2.1.1"
]
xquery version "3.1";
import module namespace semver = "http://joewiz.org/ns/xquery/semver" at "semver.xqm";
semver:serialize(
5,
0,
0,
("RC", 5),
current-dateTime() =>
adjust-dateTime-to-timezone(xs:dayTimeDuration("PT0H")) =>
format-dateTime("[Y0001][M01][D01][H01][m01]")
)
"5.0.0-RC.5+201901300532"
@jwdonahue
Copy link

jwdonahue commented May 22, 2018

We have some test data you might be interested in. It would be interesting to see how well your algorithm works against the entire set.

See semver/semver.org#59 (comment) and related discussion.

@joewiz
Copy link
Author

joewiz commented Dec 13, 2018

@jwdonahue Thanks, I just noticed your reply. (Darn gist's lack of comment notifications!) I will check out your test data and try this out against it.

@joewiz
Copy link
Author

joewiz commented Dec 16, 2018

@jwdonahue I've updated my gist using your regex and test version strings, which really helped. Thanks!

@adamretter
Copy link

Wow! :-)

@joewiz
Copy link
Author

joewiz commented Aug 8, 2019

Further development on this semantic versioning library in XQuery has moved to https://github.com/eXist-db/semver.xq - since the eXist-db community will be using it in applications that need semantic versioning. We've already fixed a bug.

@jwdonahue Thanks again for your help in improving this! @adamretter has adapted your test version strings into a proper test suite, in the repo linked above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment