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