--- /dev/null
+! Copyright (C) 2020 Doug Coleman.
+! See http://factorcode.org/license.txt for BSD license.
+USING: assocs kernel math.order semver sequences
+sequences.extras tools.test ;
+IN: semver.tests
+
+CONSTANT: semver-ranges {
+ { "1.0.0 - 2.0.0" ">=1.0.0 <=2.0.0" }
+ { "1.0.0 - 2.0.0" ">=1.0.0-0 <2.0.1-0" }
+ { "1 - 2" ">=1.0.0 <3.0.0-0" }
+ { "1 - 2" ">=1.0.0-0 <3.0.0-0" }
+ { "1.0 - 2.0" ">=1.0.0 <2.1.0-0" }
+ { "1.0 - 2.0" ">=1.0.0-0 <2.1.0-0" }
+ { "1.0.0" "1.0.0" }
+ { ">=*" "*" }
+ ! { "" "*" }
+ { "*" "*" }
+ { "*" "*" }
+ { ">=1.0.0" ">=1.0.0" }
+ { ">1.0.0" ">1.0.0" }
+ { "<=2.0.0" "<=2.0.0" }
+ { "1" ">=1.0.0 <2.0.0-0" }
+ { "<=2.0.0" "<=2.0.0" }
+ { "<=2.0.0" "<=2.0.0" }
+ { "<2.0.0" "<2.0.0" }
+ { "<2.0.0" "<2.0.0" }
+ { ">= 1.0.0" ">=1.0.0" }
+ { ">= 1.0.0" ">=1.0.0" }
+ { ">= 1.0.0" ">=1.0.0" }
+ { "> 1.0.0" ">1.0.0" }
+ { "> 1.0.0" ">1.0.0" }
+ { "<= 2.0.0" "<=2.0.0" }
+ { "<= 2.0.0" "<=2.0.0" }
+ { "<= 2.0.0" "<=2.0.0" }
+ { "< 2.0.0" "<2.0.0" }
+ { "<\t2.0.0" "<2.0.0" }
+ { ">=0.1.97" ">=0.1.97" }
+ { ">=0.1.97" ">=0.1.97" }
+ { "0.1.20 || 1.2.4" "0.1.20||1.2.4" }
+ { ">=0.2.3 || <0.0.1" ">=0.2.3||<0.0.1" }
+ { ">=0.2.3 || <0.0.1" ">=0.2.3||<0.0.1" }
+ { ">=0.2.3 || <0.0.1" ">=0.2.3||<0.0.1" }
+ { "||" "*" }
+ { "2.x.x" ">=2.0.0 <3.0.0-0" }
+ { "1.2.x" ">=1.2.0 <1.3.0-0" }
+ { "1.2.x || 2.x" ">=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0" }
+ { "1.2.x || 2.x" ">=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0" }
+ { "x" "*" }
+ { "2.*.*" ">=2.0.0 <3.0.0-0" }
+ { "1.2.*" ">=1.2.0 <1.3.0-0" }
+ { "1.2.* || 2.*" ">=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0" }
+ { "*" "*" }
+ { "2" ">=2.0.0 <3.0.0-0" }
+ { "2.3" ">=2.3.0 <2.4.0-0" }
+ { "~2.4" ">=2.4.0 <2.5.0-0" }
+ { "~2.4" ">=2.4.0 <2.5.0-0" }
+ { "~>3.2.1" ">=3.2.1 <3.3.0-0" }
+ { "~1" ">=1.0.0 <2.0.0-0" }
+ { "~>1" ">=1.0.0 <2.0.0-0" }
+ { "~> 1" ">=1.0.0 <2.0.0-0" }
+ { "~1.0" ">=1.0.0 <1.1.0-0" }
+ { "~ 1.0" ">=1.0.0 <1.1.0-0" }
+ { "^0" "<1.0.0-0" }
+ { "^ 1" ">=1.0.0 <2.0.0-0" }
+ { "^0.1" ">=0.1.0 <0.2.0-0" }
+ { "^1.0" ">=1.0.0 <2.0.0-0" }
+ { "^1.2" ">=1.2.0 <2.0.0-0" }
+ { "^0.0.1" ">=0.0.1 <0.0.2-0" }
+ { "^0.0.1-beta" ">=0.0.1-beta <0.0.2-0" }
+ { "^0.1.2" ">=0.1.2 <0.2.0-0" }
+ { "^1.2.3" ">=1.2.3 <2.0.0-0" }
+ { "^1.2.3-beta.4" ">=1.2.3-beta.4 <2.0.0-0" }
+ { "<1" "<1.0.0-0" }
+ { "< 1" "<1.0.0-0" }
+ { ">=1" ">=1.0.0" }
+ { ">= 1" ">=1.0.0" }
+ { "<1.2" "<1.2.0-0" }
+ { "< 1.2" "<1.2.0-0" }
+ { "1" ">=1.0.0 <2.0.0-0" }
+ { ">01.02.03" ">1.2.3" }
+ ! { ">01.02.03" null" }
+ ! { "~1.2.3beta" ">=1.2.3-beta <1.3.0-0" }
+ ! { "~1.2.3beta" null" }
+ { "^ 1.2 ^ 1" ">=1.2.0 <2.0.0-0 >=1.0.0" }
+ { "1.2 - 3.4.5" ">=1.2.0 <=3.4.5" }
+ { "1.2.3 - 3.4" ">=1.2.3 <3.5.0-0" }
+ { "1.2 - 3.4" ">=1.2.0 <3.5.0-0" }
+ { ">1" ">=2.0.0" }
+ { ">1.2" ">=1.3.0" }
+ { ">X" "<0.0.0-0" }
+ { "<X" "<0.0.0-0" }
+ { "<x <* || >* 2.x" "<0.0.0-0" }
+ { ">x 2.x || * || <x" "*" }
+}
+
+
+! first > second
+CONSTANT: semver-gt-comparisons {
+ { "0.0.0" "0.0.0-foo" }
+ { "0.0.1" "0.0.0" }
+ { "1.0.0" "0.9.9" }
+ { "0.10.0" "0.9.0" }
+ { "0.99.0" "0.10.0" }
+ { "2.0.0" "1.2.3" }
+ ! { "v0.0.0" "0.0.0-oo" }
+ ! { "v0.0.1" "0.0.0" }
+ ! { "v1.0.0" "0.9.9" }
+ ! { "v0.10.0" "0.9.0" }
+ ! { "v0.99.0" "0.10.0" }
+ ! { "v2.0.0" "1.2.3" }
+ ! { "0.0.0" "v0.0.0-fo" }
+ ! { "0.0.1" "v0.0.0" }
+ ! { "1.0.0" "v0.9.9" }
+ ! { "0.10.0" "v0.9.0" }
+ ! { "0.99.0" "v0.10.0" }
+ ! { "2.0.0" "v1.2.3" }
+ { "1.2.3" "1.2.3-asf" }
+ { "1.2.3" "1.2.3-4" }
+ { "1.2.3" "1.2.3-4-fo" }
+ { "1.2.3-5-foo" "1.2.3-5" }
+ { "1.2.3-5" "1.2.3-4" }
+ { "1.2.3-5-foo" "1.2.3-5-Foo" }
+ { "3.0.0" "2.7.2+asdf" }
+ { "1.2.3-a.10" "1.2.3-a.5" }
+ { "1.2.3-a.b" "1.2.3-a.5" }
+ { "1.2.3-a.b" "1.2.3-a" }
+ ! { "1.2.3-a.b.c.10.d.5" ".2.3-a.b.c.5.d.100" }
+ { "1.2.3-r2" "1.2.3-r100" }
+ { "1.2.3-r100" "1.2.3-R2" }
+}
+
+{ t } [
+ semver-gt-comparisons
+ [ first2 [ parse-semver ] bi@ <=> ] map-zip
+ values [ +gt+ = ] all?
+] unit-test
+
+{ t } [
+ semver-gt-comparisons
+ [ first2 swap [ parse-semver ] bi@ <=> ] map-zip
+ values [ +lt+ = ] all?
+] unit-test
\ No newline at end of file
--- /dev/null
+! Copyright (C) 2020 Doug Coleman.
+! See http://factorcode.org/license.txt for BSD license.
+USING: accessors arrays assocs combinators
+combinators.short-circuit combinators.smart kernel math
+math.order math.parser multiline peg.ebnf sequences
+sequences.deep sequences.extras sequences.private sorting.human
+splitting strings ;
+IN: semver
+
+! caret - up to next major versions, aka only major version needs to match as long as minor/patch are >=
+! tilde - last number can increment, e.g. ~1.2 is <2.0, ~1.2.3 is <1.3
+
+: ?string>number ( str -- number/str )
+ dup string>number dup not -rot ? ;
+
+: split-numbers ( str -- seq )
+ [ { } ] [ "." split [ ?string>number ] map ] if-empty ;
+
+: ?inc-string ( str -- str' )
+ string>number 1 + number>string ;
+
+SINGLETONS: major minor patch prerelease build prepatch preminor premajor ;
+
+TUPLE: semver
+ { major integer initial: 0 }
+ { minor integer initial: 0 }
+ { patch integer initial: 0 }
+ { prerelease initial: "" }
+ { build initial: "" } ;
+
+: parse-semver ( str -- semver )
+ "+" split1
+ [ "-" split1 ] dip
+ [ "." split [ string>number ] map first3 ] 2dip
+ semver boa ;
+
+: first-semver-slot ( semver -- class )
+ {
+ { [ dup major>> 0 > ] [ drop major ] }
+ { [ dup minor>> 0 > ] [ drop minor ] }
+ { [ dup patch>> 0 > ] [ drop patch ] }
+ { [ dup prerelease>> length 0 > ] [ drop prerelease ] }
+ { [ dup build>> length 0 > ] [ drop build ] }
+ [ drop major ]
+ } cond ;
+
+: last-semver-slot ( semver -- class )
+ {
+ { [ dup build>> length 0 > ] [ drop build ] }
+ { [ dup prerelease>> length 0 > ] [ drop prerelease ] }
+ { [ dup patch>> 0 > ] [ drop patch ] }
+ { [ dup minor>> 0 > ] [ drop minor ] }
+ { [ dup major>> 0 > ] [ drop major ] }
+ [ drop major ]
+ } cond ;
+
+: semver>string ( semver -- string )
+ [
+ {
+ [ major>> number>string "." ]
+ [ minor>> number>string "." ]
+ [ patch>> number>string ]
+ [ prerelease>> [ "" "" ] [ "-" swap ] if-empty ]
+ [ build>> [ "" "" ] [ "+" swap ] if-empty ]
+ } cleave
+ ] "" append-outputs-as ;
+
+: semver-inc-major ( semver -- semver )
+ dup prerelease>> [
+ [ 1 + ] change-major
+ 0 >>minor
+ 0 >>patch
+ "" >>prerelease
+ "" >>build
+ ] [
+ drop
+ "" >>prerelease
+ "" >>build
+ ] if-empty ;
+
+: semver-inc-minor ( semver -- semver )
+ dup prerelease>> [
+ [ 1 + ] change-minor
+ 0 >>patch
+ "" >>prerelease
+ "" >>build
+ ] [
+ drop
+ "" >>prerelease
+ "" >>build
+ ] if-empty ;
+
+: semver-inc-patch ( semver -- semver )
+ dup prerelease>> [
+ [ 1 + ] change-patch
+ 0 >>patch
+ "" >>prerelease
+ "" >>build
+ ] [
+ drop
+ "" >>prerelease
+ "" >>build
+ ] if-empty ;
+
+: semver-inc-prerelease ( semver -- semver )
+ dup prerelease>> [
+ "0"
+ ] [
+ "." split
+ dup [ string>number ] find-last [
+ over [ ?inc-string ] change-nth
+ "." join
+ ] [
+ 2drop "dev.0"
+ ] if
+ ] if-empty >>prerelease
+ "" >>build ;
+
+: semver-inc-prerelease-id ( semver id -- semver )
+ over prerelease>> [
+ "0" "." glue
+ ] [
+ 2dup swap head? [
+ "." split
+ dup [ string>number ] find-last [
+ over [ ?inc-string ] change-nth
+ "." join nip
+ ] [
+ 2drop "0" "." glue
+ ] if
+ ] [
+ drop "0" "." glue
+ ] if
+ ] if-empty >>prerelease
+ "" >>build ;
+
+: semver-inc-prepatch ( semver -- semver )
+ [ 1 + ] change-patch
+ "dev.0" >>prerelease
+ "" >>build ;
+
+: semver-inc-preminor ( semver -- semver )
+ [ 1 + ] change-minor
+ 0 >>patch
+ "dev.0" >>prerelease
+ "" >>build ;
+
+: semver-inc-premajor ( semver -- semver )
+ [ 1 + ] change-major
+ 0 >>minor
+ 0 >>patch
+ "dev.0" >>prerelease
+ "" >>build ;
+
+GENERIC: lower-range ( obj -- str )
+
+M: string lower-range ( obj -- semver )
+ parse-semver semver>string ">=" prepend ;
+
+M: array lower-range ( obj -- semver )
+ parse-semver semver>string ">=" prepend ;
+
+GENERIC: upper-range ( obj -- str )
+
+M: string upper-range ( obj -- semver )
+ parse-semver semver>string "<=" prepend ;
+
+M: array upper-range ( obj -- semver )
+ parse-semver semver>string "<=" prepend ;
+
+: major-minor-patch-compare ( s1 s2 -- <=> part )
+ 2dup [ major>> ] compare
+ dup +eq+ eq? [
+ drop 2dup [ minor>> ] compare
+ dup +eq+ eq? [
+ drop [ patch>> ] compare patch
+ ] [
+ 2nip minor
+ ] if
+ ] [
+ 2nip major
+ ] if ; inline
+
+: compare-prelreases ( semver1 semver2 -- <=> )
+ [ prerelease>> ] bi@ over empty? [
+ nip empty? +eq+ +gt+ ?
+ ] [
+ dup empty? [ 2drop +lt+ ] [
+ [ split-numbers ] bi@
+ f pad-longest zip [
+ first2
+ {
+ { [ 2dup [ integer? ] both? ] [ <=> ] }
+ { [ 2dup [ string? ] both? ] [ <=> ] }
+ { [ over integer? ] [ 2drop +lt+ ] }
+ { [ dup integer? ] [ 2drop +gt+ ] }
+ { [ over f = ] [ 2drop +lt+ ] }
+ { [ dup f = ] [ 2drop +gt+ ] }
+ [ 2drop +eq+ ]
+ } cond
+ ] [
+ +eq+ = not
+ ] find-pred 2drop
+ ] if
+ ] if ;
+
+M: semver <=>
+ 2dup major-minor-patch-compare drop
+ dup +eq+ = [ drop compare-prelreases ] [ 2nip ] if ;
+
+EBNF: parse-range [=[
+ logical-or = [\s\t]*~ '||' [\s\t]*~
+ range = hyphen | simple ( [\s\t]*~ simple )* => [[ first2 swap prefix ]]
+ hyphen = partial:p1 [\s\t]*~ '-':t [\s\t]*~ partial:p2 => [[ p1 t p2 3array ]]
+ simple = primitive | partial | tilde | caret
+ primitive = ( '~>' | '>=' | '<=' | '>' | '<' | '=' ) [\s\t]*~ partial
+ partial = xr ( '.' xr ( '.' xr qualifier? )? )? => [[ flatten concat ]]
+ xr = 'x' | 'X' | "*" | nr
+ nr = [0-9]+ => [[ string>number number>string ]]
+ tilde = '~' [\s\t]*~ partial
+ caret = '^' [\s\t]*~ partial
+ qualifier = ( '-' pre )? ( '+' build )?
+ pre = parts
+ build = parts
+ parts = part ( '.' part )*
+ part = nr | [-0-9A-Za-z]+ => [[ >string ]]
+ range-set = range? ( logical-or range? )* => [[ first2 swap prefix ]]
+]=]
+