]> gitweb.factorcode.org Git - factor.git/commitdiff
semver: Add more semver code to parse ranges. Need to merge with semantic-versioning...
authorDoug Coleman <doug.coleman@gmail.com>
Mon, 21 Dec 2020 16:30:12 +0000 (10:30 -0600)
committerDoug Coleman <doug.coleman@gmail.com>
Mon, 21 Dec 2020 16:30:12 +0000 (10:30 -0600)
extra/semver/semver-tests.factor [new file with mode: 0644]
extra/semver/semver.factor [new file with mode: 0644]

diff --git a/extra/semver/semver-tests.factor b/extra/semver/semver-tests.factor
new file mode 100644 (file)
index 0000000..3515f94
--- /dev/null
@@ -0,0 +1,142 @@
+! 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
diff --git a/extra/semver/semver.factor b/extra/semver/semver.factor
new file mode 100644 (file)
index 0000000..9cbad48
--- /dev/null
@@ -0,0 +1,229 @@
+! 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 ]]
+]=]
+