1 ! Copyright (C) 2020 Doug Coleman.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors arrays assocs combinators combinators.smart io
4 kernel math math.order math.parser multiline peg.ebnf sequences
5 sequences.deep sequences.extras splitting strings ;
8 ! caret - up to next major versions, aka only major version needs to match as long as minor/patch are >=
9 ! tilde - last number can increment, e.g. ~1.2 is <2.0, ~1.2.3 is <1.3
11 : ?string>number ( str -- number/str )
12 dup string>number dup not -rot ? ;
14 : split-numbers ( str -- seq )
15 [ { } ] [ "." split [ ?string>number ] map ] if-empty ;
17 : ?inc-string ( str -- str' )
18 string>number 1 + number>string ;
20 SINGLETONS: major minor patch prerelease build prepatch preminor premajor ;
23 { major integer initial: 0 }
24 { minor integer initial: 0 }
25 { patch integer initial: 0 }
26 { prerelease initial: "" }
27 { build initial: "" } ;
29 ERROR: malformed-semver parts ;
30 : check-semver-parts ( seq -- seq )
31 dup length 3 <= [ malformed-semver ] unless ;
33 : parse-semver ( str -- semver )
36 [ "." split [ string>number ] map check-semver-parts first3 ] 2dip
39 : <semver> ( str -- semver ) parse-semver ; inline
41 : first-semver-slot ( semver -- class )
43 { [ dup major>> 0 > ] [ drop major ] }
44 { [ dup minor>> 0 > ] [ drop minor ] }
45 { [ dup patch>> 0 > ] [ drop patch ] }
46 { [ dup prerelease>> length 0 > ] [ drop prerelease ] }
47 { [ dup build>> length 0 > ] [ drop build ] }
51 : last-semver-slot ( semver -- class )
53 { [ dup build>> length 0 > ] [ drop build ] }
54 { [ dup prerelease>> length 0 > ] [ drop prerelease ] }
55 { [ dup patch>> 0 > ] [ drop patch ] }
56 { [ dup minor>> 0 > ] [ drop minor ] }
57 { [ dup major>> 0 > ] [ drop major ] }
61 : semver>string ( semver -- string )
64 [ major>> number>string "." ]
65 [ minor>> number>string "." ]
66 [ patch>> number>string ]
67 [ prerelease>> [ "" "" ] [ "-" swap ] if-empty ]
68 [ build>> [ "" "" ] [ "+" swap ] if-empty ]
70 ] "" append-outputs-as ;
72 : semver. ( semver -- )
75 : semver-inc-major ( semver -- semver )
88 : semver-inc-minor ( semver -- semver )
100 : semver-inc-patch ( semver -- semver )
111 : semver-inc-prerelease ( semver -- semver )
116 dup [ string>number ] find-last [
117 over [ ?inc-string ] change-nth
122 ] if-empty >>prerelease
125 : semver-inc-prerelease-id ( semver id -- semver )
131 dup [ string>number ] find-last [
132 over [ ?inc-string ] change-nth
140 ] if-empty >>prerelease
143 : semver-inc-prepatch ( semver -- semver )
148 : semver-inc-preminor ( semver -- semver )
154 : semver-inc-premajor ( semver -- semver )
161 GENERIC: lower-range ( obj -- str )
163 M: string lower-range ( obj -- semver )
164 parse-semver semver>string ">=" prepend ;
166 M: array lower-range ( obj -- semver )
167 parse-semver semver>string ">=" prepend ;
169 GENERIC: upper-range ( obj -- str )
171 M: string upper-range ( obj -- semver )
172 parse-semver semver>string "<=" prepend ;
174 M: array upper-range ( obj -- semver )
175 parse-semver semver>string "<=" prepend ;
177 : major-minor-patch-compare ( s1 s2 -- <=> part )
178 2dup [ major>> ] compare
180 drop 2dup [ minor>> ] compare
182 drop [ patch>> ] compare patch
190 : compare-prelreases ( semver1 semver2 -- <=> )
191 [ prerelease>> ] bi@ over empty? [
192 nip empty? +eq+ +gt+ ?
194 dup empty? [ 2drop +lt+ ] [
195 [ split-numbers ] bi@
199 { [ 2dup [ integer? ] both? ] [ <=> ] }
200 { [ 2dup [ string? ] both? ] [ <=> ] }
201 { [ over integer? ] [ 2drop +lt+ ] }
202 { [ dup integer? ] [ 2drop +gt+ ] }
203 { [ over f = ] [ 2drop +lt+ ] }
204 { [ dup f = ] [ 2drop +gt+ ] }
214 2dup major-minor-patch-compare drop
215 dup +eq+ = [ drop compare-prelreases ] [ 2nip ] if ;
217 EBNF: parse-range [=[
218 logical-or = [\s\t]*~ '||' [\s\t]*~
219 range = hyphen | simple ( [\s\t]*~ simple )* => [[ first2 swap prefix ]]
220 hyphen = partial:p1 [\s\t]*~ '-':t [\s\t]*~ partial:p2 => [[ p1 t p2 3array ]]
221 simple = primitive | partial | tilde | caret
222 primitive = ( '~>' | '>=' | '<=' | '>' | '<' | '=' ) [\s\t]*~ partial
223 partial = xr ( '.' xr ( '.' xr qualifier? )? )? => [[ flatten concat ]]
224 xr = 'x' | 'X' | "*" | nr
225 nr = [0-9]+ => [[ string>number number>string ]]
226 tilde = '~' [\s\t]*~ partial
227 caret = '^' [\s\t]*~ partial
228 qualifier = ( '-' pre )? ( '+' build )?
231 parts = part ( '.' part )*
232 part = nr | [-0-9A-Za-z]+ => [[ >string ]]
233 range-set = range? ( logical-or range? )* => [[ first2 swap prefix ]]