1 ! Copyright (C) 2020 Doug Coleman.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors arrays assocs combinators combinators.smart
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 : parse-semver ( str -- semver )
32 [ "." split [ string>number ] map first3 ] 2dip
35 : first-semver-slot ( semver -- class )
37 { [ dup major>> 0 > ] [ drop major ] }
38 { [ dup minor>> 0 > ] [ drop minor ] }
39 { [ dup patch>> 0 > ] [ drop patch ] }
40 { [ dup prerelease>> length 0 > ] [ drop prerelease ] }
41 { [ dup build>> length 0 > ] [ drop build ] }
45 : last-semver-slot ( semver -- class )
47 { [ dup build>> length 0 > ] [ drop build ] }
48 { [ dup prerelease>> length 0 > ] [ drop prerelease ] }
49 { [ dup patch>> 0 > ] [ drop patch ] }
50 { [ dup minor>> 0 > ] [ drop minor ] }
51 { [ dup major>> 0 > ] [ drop major ] }
55 : semver>string ( semver -- string )
58 [ major>> number>string "." ]
59 [ minor>> number>string "." ]
60 [ patch>> number>string ]
61 [ prerelease>> [ "" "" ] [ "-" swap ] if-empty ]
62 [ build>> [ "" "" ] [ "+" swap ] if-empty ]
64 ] "" append-outputs-as ;
66 : semver-inc-major ( semver -- semver )
79 : semver-inc-minor ( semver -- semver )
91 : semver-inc-patch ( semver -- semver )
103 : semver-inc-prerelease ( semver -- semver )
108 dup [ string>number ] find-last [
109 over [ ?inc-string ] change-nth
114 ] if-empty >>prerelease
117 : semver-inc-prerelease-id ( semver id -- semver )
123 dup [ string>number ] find-last [
124 over [ ?inc-string ] change-nth
132 ] if-empty >>prerelease
135 : semver-inc-prepatch ( semver -- semver )
140 : semver-inc-preminor ( semver -- semver )
146 : semver-inc-premajor ( semver -- semver )
153 GENERIC: lower-range ( obj -- str )
155 M: string lower-range ( obj -- semver )
156 parse-semver semver>string ">=" prepend ;
158 M: array lower-range ( obj -- semver )
159 parse-semver semver>string ">=" prepend ;
161 GENERIC: upper-range ( obj -- str )
163 M: string upper-range ( obj -- semver )
164 parse-semver semver>string "<=" prepend ;
166 M: array upper-range ( obj -- semver )
167 parse-semver semver>string "<=" prepend ;
169 : major-minor-patch-compare ( s1 s2 -- <=> part )
170 2dup [ major>> ] compare
172 drop 2dup [ minor>> ] compare
174 drop [ patch>> ] compare patch
182 : compare-prelreases ( semver1 semver2 -- <=> )
183 [ prerelease>> ] bi@ over empty? [
184 nip empty? +eq+ +gt+ ?
186 dup empty? [ 2drop +lt+ ] [
187 [ split-numbers ] bi@
191 { [ 2dup [ integer? ] both? ] [ <=> ] }
192 { [ 2dup [ string? ] both? ] [ <=> ] }
193 { [ over integer? ] [ 2drop +lt+ ] }
194 { [ dup integer? ] [ 2drop +gt+ ] }
195 { [ over f = ] [ 2drop +lt+ ] }
196 { [ dup f = ] [ 2drop +gt+ ] }
206 2dup major-minor-patch-compare drop
207 dup +eq+ = [ drop compare-prelreases ] [ 2nip ] if ;
209 EBNF: parse-range [=[
210 logical-or = [\s\t]*~ '||' [\s\t]*~
211 range = hyphen | simple ( [\s\t]*~ simple )* => [[ first2 swap prefix ]]
212 hyphen = partial:p1 [\s\t]*~ '-':t [\s\t]*~ partial:p2 => [[ p1 t p2 3array ]]
213 simple = primitive | partial | tilde | caret
214 primitive = ( '~>' | '>=' | '<=' | '>' | '<' | '=' ) [\s\t]*~ partial
215 partial = xr ( '.' xr ( '.' xr qualifier? )? )? => [[ flatten concat ]]
216 xr = 'x' | 'X' | "*" | nr
217 nr = [0-9]+ => [[ string>number number>string ]]
218 tilde = '~' [\s\t]*~ partial
219 caret = '^' [\s\t]*~ partial
220 qualifier = ( '-' pre )? ( '+' build )?
223 parts = part ( '.' part )*
224 part = nr | [-0-9A-Za-z]+ => [[ >string ]]
225 range-set = range? ( logical-or range? )* => [[ first2 swap prefix ]]