1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package semver
24
25 import "sort"
26
27
28 type parsed struct {
29 major string
30 minor string
31 patch string
32 short string
33 prerelease string
34 build string
35 }
36
37
38 func IsValid(v string) bool {
39 _, ok := parse(v)
40 return ok
41 }
42
43
44
45
46
47
48 func Canonical(v string) string {
49 p, ok := parse(v)
50 if !ok {
51 return ""
52 }
53 if p.build != "" {
54 return v[:len(v)-len(p.build)]
55 }
56 if p.short != "" {
57 return v + p.short
58 }
59 return v
60 }
61
62
63
64
65 func Major(v string) string {
66 pv, ok := parse(v)
67 if !ok {
68 return ""
69 }
70 return v[:1+len(pv.major)]
71 }
72
73
74
75
76 func MajorMinor(v string) string {
77 pv, ok := parse(v)
78 if !ok {
79 return ""
80 }
81 i := 1 + len(pv.major)
82 if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor {
83 return v[:j]
84 }
85 return v[:i] + "." + pv.minor
86 }
87
88
89
90
91 func Prerelease(v string) string {
92 pv, ok := parse(v)
93 if !ok {
94 return ""
95 }
96 return pv.prerelease
97 }
98
99
100
101
102 func Build(v string) string {
103 pv, ok := parse(v)
104 if !ok {
105 return ""
106 }
107 return pv.build
108 }
109
110
111
112
113
114
115
116 func Compare(v, w string) int {
117 pv, ok1 := parse(v)
118 pw, ok2 := parse(w)
119 if !ok1 && !ok2 {
120 return 0
121 }
122 if !ok1 {
123 return -1
124 }
125 if !ok2 {
126 return +1
127 }
128 if c := compareInt(pv.major, pw.major); c != 0 {
129 return c
130 }
131 if c := compareInt(pv.minor, pw.minor); c != 0 {
132 return c
133 }
134 if c := compareInt(pv.patch, pw.patch); c != 0 {
135 return c
136 }
137 return comparePrerelease(pv.prerelease, pw.prerelease)
138 }
139
140
141
142
143
144
145 func Max(v, w string) string {
146 v = Canonical(v)
147 w = Canonical(w)
148 if Compare(v, w) > 0 {
149 return v
150 }
151 return w
152 }
153
154
155 type ByVersion []string
156
157 func (vs ByVersion) Len() int { return len(vs) }
158 func (vs ByVersion) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
159 func (vs ByVersion) Less(i, j int) bool {
160 cmp := Compare(vs[i], vs[j])
161 if cmp != 0 {
162 return cmp < 0
163 }
164 return vs[i] < vs[j]
165 }
166
167
168 func Sort(list []string) {
169 sort.Sort(ByVersion(list))
170 }
171
172 func parse(v string) (p parsed, ok bool) {
173 if v == "" || v[0] != 'v' {
174 return
175 }
176 p.major, v, ok = parseInt(v[1:])
177 if !ok {
178 return
179 }
180 if v == "" {
181 p.minor = "0"
182 p.patch = "0"
183 p.short = ".0.0"
184 return
185 }
186 if v[0] != '.' {
187 ok = false
188 return
189 }
190 p.minor, v, ok = parseInt(v[1:])
191 if !ok {
192 return
193 }
194 if v == "" {
195 p.patch = "0"
196 p.short = ".0"
197 return
198 }
199 if v[0] != '.' {
200 ok = false
201 return
202 }
203 p.patch, v, ok = parseInt(v[1:])
204 if !ok {
205 return
206 }
207 if len(v) > 0 && v[0] == '-' {
208 p.prerelease, v, ok = parsePrerelease(v)
209 if !ok {
210 return
211 }
212 }
213 if len(v) > 0 && v[0] == '+' {
214 p.build, v, ok = parseBuild(v)
215 if !ok {
216 return
217 }
218 }
219 if v != "" {
220 ok = false
221 return
222 }
223 ok = true
224 return
225 }
226
227 func parseInt(v string) (t, rest string, ok bool) {
228 if v == "" {
229 return
230 }
231 if v[0] < '0' || '9' < v[0] {
232 return
233 }
234 i := 1
235 for i < len(v) && '0' <= v[i] && v[i] <= '9' {
236 i++
237 }
238 if v[0] == '0' && i != 1 {
239 return
240 }
241 return v[:i], v[i:], true
242 }
243
244 func parsePrerelease(v string) (t, rest string, ok bool) {
245
246
247
248
249 if v == "" || v[0] != '-' {
250 return
251 }
252 i := 1
253 start := 1
254 for i < len(v) && v[i] != '+' {
255 if !isIdentChar(v[i]) && v[i] != '.' {
256 return
257 }
258 if v[i] == '.' {
259 if start == i || isBadNum(v[start:i]) {
260 return
261 }
262 start = i + 1
263 }
264 i++
265 }
266 if start == i || isBadNum(v[start:i]) {
267 return
268 }
269 return v[:i], v[i:], true
270 }
271
272 func parseBuild(v string) (t, rest string, ok bool) {
273 if v == "" || v[0] != '+' {
274 return
275 }
276 i := 1
277 start := 1
278 for i < len(v) {
279 if !isIdentChar(v[i]) && v[i] != '.' {
280 return
281 }
282 if v[i] == '.' {
283 if start == i {
284 return
285 }
286 start = i + 1
287 }
288 i++
289 }
290 if start == i {
291 return
292 }
293 return v[:i], v[i:], true
294 }
295
296 func isIdentChar(c byte) bool {
297 return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-'
298 }
299
300 func isBadNum(v string) bool {
301 i := 0
302 for i < len(v) && '0' <= v[i] && v[i] <= '9' {
303 i++
304 }
305 return i == len(v) && i > 1 && v[0] == '0'
306 }
307
308 func isNum(v string) bool {
309 i := 0
310 for i < len(v) && '0' <= v[i] && v[i] <= '9' {
311 i++
312 }
313 return i == len(v)
314 }
315
316 func compareInt(x, y string) int {
317 if x == y {
318 return 0
319 }
320 if len(x) < len(y) {
321 return -1
322 }
323 if len(x) > len(y) {
324 return +1
325 }
326 if x < y {
327 return -1
328 } else {
329 return +1
330 }
331 }
332
333 func comparePrerelease(x, y string) int {
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348 if x == y {
349 return 0
350 }
351 if x == "" {
352 return +1
353 }
354 if y == "" {
355 return -1
356 }
357 for x != "" && y != "" {
358 x = x[1:]
359 y = y[1:]
360 var dx, dy string
361 dx, x = nextIdent(x)
362 dy, y = nextIdent(y)
363 if dx != dy {
364 ix := isNum(dx)
365 iy := isNum(dy)
366 if ix != iy {
367 if ix {
368 return -1
369 } else {
370 return +1
371 }
372 }
373 if ix {
374 if len(dx) < len(dy) {
375 return -1
376 }
377 if len(dx) > len(dy) {
378 return +1
379 }
380 }
381 if dx < dy {
382 return -1
383 } else {
384 return +1
385 }
386 }
387 }
388 if x == "" {
389 return -1
390 } else {
391 return +1
392 }
393 }
394
395 func nextIdent(x string) (dx, rest string) {
396 i := 0
397 for i < len(x) && x[i] != '.' {
398 i++
399 }
400 return x[:i], x[i:]
401 }
402
View as plain text