1 ! Copyright (C) 2020 Doug Coleman.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors arrays assocs combinators
4 combinators.short-circuit combinators.smart kernel math
5 math.order math.parser multiline peg.ebnf sequences
6 sequences.deep sequences.extras sequences.private sorting.human
10 ! caret - up to next major versions, aka only major version needs to match as long as minor/patch are >=
11 ! tilde - last number can increment, e.g. ~1.2 is <2.0, ~1.2.3 is <1.3
13 : ?string>number ( str -- number/str )
14 dup string>number dup not -rot ? ;
16 : split-numbers ( str -- seq )
17 [ { } ] [ "." split [ ?string>number ] map ] if-empty ;
19 : ?inc-string ( str -- str' )
20 string>number 1 + number>string ;
22 SINGLETONS: major minor patch prerelease build prepatch preminor premajor ;
25 { major integer initial: 0 }
26 { minor integer initial: 0 }
27 { patch integer initial: 0 }
28 { prerelease initial: "" }
29 { build initial: "" } ;
31 : parse-semver ( str -- semver )
34 [ "." split [ string>number ] map first3 ] 2dip
37 : first-semver-slot ( semver -- class )
39 { [ dup major>> 0 > ] [ drop major ] }
40 { [ dup minor>> 0 > ] [ drop minor ] }
41 { [ dup patch>> 0 > ] [ drop patch ] }
42 { [ dup prerelease>> length 0 > ] [ drop prerelease ] }
43 { [ dup build>> length 0 > ] [ drop build ] }
47 : last-semver-slot ( semver -- class )
49 { [ dup build>> length 0 > ] [ drop build ] }
50 { [ dup prerelease>> length 0 > ] [ drop prerelease ] }
51 { [ dup patch>> 0 > ] [ drop patch ] }
52 { [ dup minor>> 0 > ] [ drop minor ] }
53 { [ dup major>> 0 > ] [ drop major ] }
57 : semver>string ( semver -- string )
60 [ major>> number>string "." ]
61 [ minor>> number>string "." ]
62 [ patch>> number>string ]
63 [ prerelease>> [ "" "" ] [ "-" swap ] if-empty ]
64 [ build>> [ "" "" ] [ "+" swap ] if-empty ]
66 ] "" append-outputs-as ;
68 : semver-inc-major ( semver -- semver )
81 : semver-inc-minor ( semver -- semver )
93 : semver-inc-patch ( semver -- semver )
105 : semver-inc-prerelease ( semver -- semver )
110 dup [ string>number ] find-last [
111 over [ ?inc-string ] change-nth
116 ] if-empty >>prerelease
119 : semver-inc-prerelease-id ( semver id -- semver )
125 dup [ string>number ] find-last [
126 over [ ?inc-string ] change-nth
134 ] if-empty >>prerelease
137 : semver-inc-prepatch ( semver -- semver )
142 : semver-inc-preminor ( semver -- semver )
148 : semver-inc-premajor ( semver -- semver )
155 GENERIC: lower-range ( obj -- str )
157 M: string lower-range ( obj -- semver )
158 parse-semver semver>string ">=" prepend ;
160 M: array lower-range ( obj -- semver )
161 parse-semver semver>string ">=" prepend ;
163 GENERIC: upper-range ( obj -- str )
165 M: string upper-range ( obj -- semver )
166 parse-semver semver>string "<=" prepend ;
168 M: array upper-range ( obj -- semver )
169 parse-semver semver>string "<=" prepend ;
171 : major-minor-patch-compare ( s1 s2 -- <=> part )
172 2dup [ major>> ] compare
174 drop 2dup [ minor>> ] compare
176 drop [ patch>> ] compare patch
184 : compare-prelreases ( semver1 semver2 -- <=> )
185 [ prerelease>> ] bi@ over empty? [
186 nip empty? +eq+ +gt+ ?
188 dup empty? [ 2drop +lt+ ] [
189 [ split-numbers ] bi@
193 { [ 2dup [ integer? ] both? ] [ <=> ] }
194 { [ 2dup [ string? ] both? ] [ <=> ] }
195 { [ over integer? ] [ 2drop +lt+ ] }
196 { [ dup integer? ] [ 2drop +gt+ ] }
197 { [ over f = ] [ 2drop +lt+ ] }
198 { [ dup f = ] [ 2drop +gt+ ] }
208 2dup major-minor-patch-compare drop
209 dup +eq+ = [ drop compare-prelreases ] [ 2nip ] if ;
211 EBNF: parse-range [=[
212 logical-or = [\s\t]*~ '||' [\s\t]*~
213 range = hyphen | simple ( [\s\t]*~ simple )* => [[ first2 swap prefix ]]
214 hyphen = partial:p1 [\s\t]*~ '-':t [\s\t]*~ partial:p2 => [[ p1 t p2 3array ]]
215 simple = primitive | partial | tilde | caret
216 primitive = ( '~>' | '>=' | '<=' | '>' | '<' | '=' ) [\s\t]*~ partial
217 partial = xr ( '.' xr ( '.' xr qualifier? )? )? => [[ flatten concat ]]
218 xr = 'x' | 'X' | "*" | nr
219 nr = [0-9]+ => [[ string>number number>string ]]
220 tilde = '~' [\s\t]*~ partial
221 caret = '^' [\s\t]*~ partial
222 qualifier = ( '-' pre )? ( '+' build )?
225 parts = part ( '.' part )*
226 part = nr | [-0-9A-Za-z]+ => [[ >string ]]
227 range-set = range? ( logical-or range? )* => [[ first2 swap prefix ]]