Last active
September 4, 2019 12:56
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 || "'") | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"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'" | |
} | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"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 | |
} | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
"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" | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]") | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"5.0.0-RC.5+201901300532" |
@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.
@jwdonahue I've updated my gist using your regex and test version strings, which really helped. Thanks!
Wow! :-)
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
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.