1 // Copyright 2018 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4 5 // Package semver implements comparison of semantic version strings.
6 // In this package, semantic version strings must begin with a leading "v",
7 // as in "v1.0.0".
8 //
9 // The general form of a semantic version string accepted by this package is
10 //
11 // vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]]
12 //
13 // where square brackets indicate optional parts of the syntax;
14 // MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros;
15 // PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers
16 // using only alphanumeric characters and hyphens; and
17 // all-numeric PRERELEASE identifiers must not have leading zeros.
18 //
19 // This package follows Semantic Versioning 2.0.0 (see semver.org)
20 // with two exceptions. First, it requires the "v" prefix. Second, it recognizes
21 // vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes)
22 // as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0.
23 package semver
24 25 import (
26 "slices"
27 "strings"
28 )
29 30 // parsed returns the parsed form of a semantic version string.
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 // IsValid reports whether v is a valid semantic version string.
41 func IsValid(v string) bool {
42 _, ok := parse(v)
43 return ok
44 }
45 46 // Canonical returns the canonical formatting of the semantic version v.
47 // It fills in any missing .MINOR or .PATCH and discards build metadata.
48 // Two semantic versions compare equal only if their canonical formatting
49 // is an identical string.
50 // The canonical invalid semantic version is the empty string.
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 // Major returns the major version prefix of the semantic version v.
66 // For example, Major("v2.1.0") == "v2".
67 // If v is an invalid semantic version string, Major returns the empty string.
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 // MajorMinor returns the major.minor version prefix of the semantic version v.
77 // For example, MajorMinor("v2.1.0") == "v2.1".
78 // If v is an invalid semantic version string, MajorMinor returns the empty string.
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 // Prerelease returns the prerelease suffix of the semantic version v.
92 // For example, Prerelease("v2.1.0-pre+meta") == "-pre".
93 // If v is an invalid semantic version string, Prerelease returns the empty string.
94 func Prerelease(v string) string {
95 pv, ok := parse(v)
96 if !ok {
97 return ""
98 }
99 return pv.prerelease
100 }
101 102 // Build returns the build suffix of the semantic version v.
103 // For example, Build("v2.1.0+meta") == "+meta".
104 // If v is an invalid semantic version string, Build returns the empty string.
105 func Build(v string) string {
106 pv, ok := parse(v)
107 if !ok {
108 return ""
109 }
110 return pv.build
111 }
112 113 // Compare returns an integer comparing two versions according to
114 // semantic version precedence.
115 // The result will be 0 if v == w, -1 if v < w, or +1 if v > w.
116 //
117 // An invalid semantic version string is considered less than a valid one.
118 // All invalid semantic version strings compare equal to each other.
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 // Max canonicalizes its arguments and then returns the version string
144 // that compares greater.
145 //
146 // Deprecated: use [Compare] instead. In most cases, returning a canonicalized
147 // version is not expected or desired.
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 // ByVersion implements [sort.Interface] for sorting semantic version strings.
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 // Sort sorts a list of semantic version strings using [Compare] and falls back
165 // to use [strings.Compare] if both versions are considered equal.
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 // "A pre-release version MAY be denoted by appending a hyphen and
252 // a series of dot separated identifiers immediately following the patch version.
253 // Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-].
254 // Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes."
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 // "When major, minor, and patch are equal, a pre-release version has
341 // lower precedence than a normal version.
342 // Example: 1.0.0-alpha < 1.0.0.
343 // Precedence for two pre-release versions with the same major, minor,
344 // and patch version MUST be determined by comparing each dot separated
345 // identifier from left to right until a difference is found as follows:
346 // identifiers consisting of only digits are compared numerically and
347 // identifiers with letters or hyphens are compared lexically in ASCII
348 // sort order. Numeric identifiers always have lower precedence than
349 // non-numeric identifiers. A larger set of pre-release fields has a
350 // higher precedence than a smaller set, if all of the preceding
351 // identifiers are equal.
352 // Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta <
353 // 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0."
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:] // skip - or .
365 y = y[1:] // skip - or .
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