1 ! Copyright (C) 2020 Doug Coleman.
2 ! See https://factorcode.org/license.txt for BSD license.
4 USING: accessors arrays assocs.extras combinators
5 combinators.short-circuit combinators.smart io kernel math
6 math.order math.parser multiline peg.ebnf sequences
7 sequences.deep splitting strings ;
18 ERROR: malformed-semver obj ;
20 GENERIC: >semver ( obj -- semver )
23 dup "+" split1 [ "-" split1 ] dip [
24 "." split [ string>number ] map
25 dup { [ length 3 = ] [ [ integer? ] all? ] } 1&&
26 [ nip first3 ] [ drop malformed-semver ] if
29 : semver>string ( semver -- string )
32 [ major>> number>string "." ]
33 [ minor>> number>string "." ]
34 [ patch>> number>string ]
35 [ prerelease>> [ f f ] [ "-" swap ] if-empty ]
36 [ build>> [ f f ] [ "+" swap ] if-empty ]
38 ] "" append-outputs-as ;
40 : semver. ( semver -- )
43 : bump-major ( semver -- semver )
45 [ prerelease>> empty? ]
52 ] when f >>prerelease ;
54 : bump-minor ( semver -- semver )
56 [ prerelease>> empty? ]
61 ] when f >>prerelease ;
63 : bump-patch ( semver -- semver )
64 f >>build dup prerelease>> empty? [
66 ] when f >>prerelease ;
68 : bump-prerelease ( semver id -- semver )
70 [ bump-patch ] dip [ "0" ] [ ".0" append ] if-empty
74 dup [ string>number ] find-last [
75 over [ string>number 1 + number>string ] change-nth
78 2drop [ "0" ] [ ".0" append ] if-empty
81 drop [ "0" ] [ ".0" append ] if-empty
83 ] if-empty >>prerelease f >>build ;
85 : bump-dev ( semver -- semver ) f bump-prerelease ;
87 : bump-alpha ( semver -- semver ) "alpha" bump-prerelease ;
89 : bump-beta ( semver -- semver ) "beta" bump-prerelease ;
91 : bump-rc ( semver -- semver ) "rc" bump-prerelease ;
93 : bump-premajor ( semver -- semver )
94 [ 1 + ] change-major 0 >>minor 0 >>patch "0" >>prerelease f >>build ;
96 : bump-preminor ( semver -- semver )
97 [ 1 + ] change-minor 0 >>patch "0" >>prerelease f >>build ;
99 : bump-prepatch ( semver -- semver )
100 [ 1 + ] change-patch "0" >>prerelease f >>build ;
102 : lower-range ( semver -- str )
103 semver>string ">=" prepend ;
105 : upper-range ( semver -- str )
106 semver>string "<=" prepend ;
110 : major<=> ( semvar1 semvar2 -- <=> ) [ major>> ] compare ; inline
112 : minor<=> ( semvar1 semvar2 -- <=> ) [ minor>> ] compare ; inline
114 : patch<=> ( semvar1 semvar2 -- <=> ) [ patch>> ] compare ; inline
116 : prerelease<=> ( semver1 semver2 -- <=> )
117 [ prerelease>> ] bi@ {
118 { [ over empty? ] [ nip empty? +eq+ +gt+ ? ] }
119 { [ dup empty? ] [ 2drop +lt+ ] }
121 [ "." split [ [ string>number ] keep or ] map ] bi@
124 { [ over not ] [ 2drop +lt+ ] }
125 { [ dup not ] [ 2drop +gt+ ] }
126 { [ 2dup [ integer? ] both? ] [ <=> ] }
127 { [ 2dup [ string? ] both? ] [ <=> ] }
128 { [ over integer? ] [ 2drop +lt+ ] }
129 { [ dup integer? ] [ 2drop +gt+ ] }
131 } cond dup +eq+ eq? [ drop f ] when
132 ] map-find drop +eq+ or
139 2dup major<=> dup +eq+ eq? [
140 drop 2dup minor<=> dup +eq+ eq? [
141 drop 2dup patch<=> dup +eq+ eq? [
147 : semver<=> ( obj1 obj2 -- <=> ) [ >semver ] compare ; inline
149 ! caret - up to next major versions, aka only major version needs to match as long as minor/patch are >=
150 ! tilde - last number can increment, e.g. ~1.2 is <2.0, ~1.2.3 is <1.3
152 SINGLETONS: major minor patch prerelease build prepatch preminor premajor ;
154 : first-semver-slot ( semver -- class )
156 { [ dup major>> 0 > ] [ drop major ] }
157 { [ dup minor>> 0 > ] [ drop minor ] }
158 { [ dup patch>> 0 > ] [ drop patch ] }
159 { [ dup prerelease>> length 0 > ] [ drop prerelease ] }
160 { [ dup build>> length 0 > ] [ drop build ] }
164 : last-semver-slot ( semver -- class )
166 { [ dup build>> length 0 > ] [ drop build ] }
167 { [ dup prerelease>> length 0 > ] [ drop prerelease ] }
168 { [ dup patch>> 0 > ] [ drop patch ] }
169 { [ dup minor>> 0 > ] [ drop minor ] }
170 { [ dup major>> 0 > ] [ drop major ] }
174 EBNF: parse-semver-range [=[
175 logical-or = [\s\t]*~ '||' [\s\t]*~
176 range = hyphen | simple ( [\s\t]*~ simple )* => [[ first2 swap prefix ]]
177 hyphen = partial:p1 [\s\t]*~ '-':t [\s\t]*~ partial:p2 => [[ p1 t p2 3array ]]
178 simple = primitive | partial | tilde | caret
179 primitive = ( '~>' | '>=' | '<=' | '>' | '<' | '=' ) [\s\t]*~ partial
180 partial = xr ( '.' xr ( '.' xr qualifier? )? )? => [[ flatten concat ]]
181 xr = 'x' | 'X' | "*" | nr
182 nr = [0-9]+ => [[ string>number number>string ]]
183 tilde = '~' [\s\t]*~ partial
184 caret = '^' [\s\t]*~ partial
185 qualifier = ( '-' pre )? ( '+' build )?
188 parts = part ( '.' part )*
189 part = nr | [-0-9A-Za-z]+ => [[ >string ]]
190 range-set = range? ( logical-or range? )* => [[ first2 swap prefix ]]