semver.go

  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.
 23package semver
 24
 25// parsed returns the parsed form of a semantic version string.
 26type parsed struct {
 27	major      string
 28	minor      string
 29	patch      string
 30	short      string
 31	prerelease string
 32	build      string
 33	err        string
 34}
 35
 36// IsValid reports whether v is a valid semantic version string.
 37func IsValid(v string) bool {
 38	_, ok := parse(v)
 39	return ok
 40}
 41
 42// Canonical returns the canonical formatting of the semantic version v.
 43// It fills in any missing .MINOR or .PATCH and discards build metadata.
 44// Two semantic versions compare equal only if their canonical formattings
 45// are identical strings.
 46// The canonical invalid semantic version is the empty string.
 47func Canonical(v string) string {
 48	p, ok := parse(v)
 49	if !ok {
 50		return ""
 51	}
 52	if p.build != "" {
 53		return v[:len(v)-len(p.build)]
 54	}
 55	if p.short != "" {
 56		return v + p.short
 57	}
 58	return v
 59}
 60
 61// Major returns the major version prefix of the semantic version v.
 62// For example, Major("v2.1.0") == "v2".
 63// If v is an invalid semantic version string, Major returns the empty string.
 64func Major(v string) string {
 65	pv, ok := parse(v)
 66	if !ok {
 67		return ""
 68	}
 69	return v[:1+len(pv.major)]
 70}
 71
 72// MajorMinor returns the major.minor version prefix of the semantic version v.
 73// For example, MajorMinor("v2.1.0") == "v2.1".
 74// If v is an invalid semantic version string, MajorMinor returns the empty string.
 75func MajorMinor(v string) string {
 76	pv, ok := parse(v)
 77	if !ok {
 78		return ""
 79	}
 80	i := 1 + len(pv.major)
 81	if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor {
 82		return v[:j]
 83	}
 84	return v[:i] + "." + pv.minor
 85}
 86
 87// Prerelease returns the prerelease suffix of the semantic version v.
 88// For example, Prerelease("v2.1.0-pre+meta") == "-pre".
 89// If v is an invalid semantic version string, Prerelease returns the empty string.
 90func Prerelease(v string) string {
 91	pv, ok := parse(v)
 92	if !ok {
 93		return ""
 94	}
 95	return pv.prerelease
 96}
 97
 98// Build returns the build suffix of the semantic version v.
 99// For example, Build("v2.1.0+meta") == "+meta".
100// If v is an invalid semantic version string, Build returns the empty string.
101func Build(v string) string {
102	pv, ok := parse(v)
103	if !ok {
104		return ""
105	}
106	return pv.build
107}
108
109// Compare returns an integer comparing two versions according to
110// according to semantic version precedence.
111// The result will be 0 if v == w, -1 if v < w, or +1 if v > w.
112//
113// An invalid semantic version string is considered less than a valid one.
114// All invalid semantic version strings compare equal to each other.
115func Compare(v, w string) int {
116	pv, ok1 := parse(v)
117	pw, ok2 := parse(w)
118	if !ok1 && !ok2 {
119		return 0
120	}
121	if !ok1 {
122		return -1
123	}
124	if !ok2 {
125		return +1
126	}
127	if c := compareInt(pv.major, pw.major); c != 0 {
128		return c
129	}
130	if c := compareInt(pv.minor, pw.minor); c != 0 {
131		return c
132	}
133	if c := compareInt(pv.patch, pw.patch); c != 0 {
134		return c
135	}
136	return comparePrerelease(pv.prerelease, pw.prerelease)
137}
138
139// Max canonicalizes its arguments and then returns the version string
140// that compares greater.
141func Max(v, w string) string {
142	v = Canonical(v)
143	w = Canonical(w)
144	if Compare(v, w) > 0 {
145		return v
146	}
147	return w
148}
149
150func parse(v string) (p parsed, ok bool) {
151	if v == "" || v[0] != 'v' {
152		p.err = "missing v prefix"
153		return
154	}
155	p.major, v, ok = parseInt(v[1:])
156	if !ok {
157		p.err = "bad major version"
158		return
159	}
160	if v == "" {
161		p.minor = "0"
162		p.patch = "0"
163		p.short = ".0.0"
164		return
165	}
166	if v[0] != '.' {
167		p.err = "bad minor prefix"
168		ok = false
169		return
170	}
171	p.minor, v, ok = parseInt(v[1:])
172	if !ok {
173		p.err = "bad minor version"
174		return
175	}
176	if v == "" {
177		p.patch = "0"
178		p.short = ".0"
179		return
180	}
181	if v[0] != '.' {
182		p.err = "bad patch prefix"
183		ok = false
184		return
185	}
186	p.patch, v, ok = parseInt(v[1:])
187	if !ok {
188		p.err = "bad patch version"
189		return
190	}
191	if len(v) > 0 && v[0] == '-' {
192		p.prerelease, v, ok = parsePrerelease(v)
193		if !ok {
194			p.err = "bad prerelease"
195			return
196		}
197	}
198	if len(v) > 0 && v[0] == '+' {
199		p.build, v, ok = parseBuild(v)
200		if !ok {
201			p.err = "bad build"
202			return
203		}
204	}
205	if v != "" {
206		p.err = "junk on end"
207		ok = false
208		return
209	}
210	ok = true
211	return
212}
213
214func parseInt(v string) (t, rest string, ok bool) {
215	if v == "" {
216		return
217	}
218	if v[0] < '0' || '9' < v[0] {
219		return
220	}
221	i := 1
222	for i < len(v) && '0' <= v[i] && v[i] <= '9' {
223		i++
224	}
225	if v[0] == '0' && i != 1 {
226		return
227	}
228	return v[:i], v[i:], true
229}
230
231func parsePrerelease(v string) (t, rest string, ok bool) {
232	// "A pre-release version MAY be denoted by appending a hyphen and
233	// a series of dot separated identifiers immediately following the patch version.
234	// Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-].
235	// Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes."
236	if v == "" || v[0] != '-' {
237		return
238	}
239	i := 1
240	start := 1
241	for i < len(v) && v[i] != '+' {
242		if !isIdentChar(v[i]) && v[i] != '.' {
243			return
244		}
245		if v[i] == '.' {
246			if start == i || isBadNum(v[start:i]) {
247				return
248			}
249			start = i + 1
250		}
251		i++
252	}
253	if start == i || isBadNum(v[start:i]) {
254		return
255	}
256	return v[:i], v[i:], true
257}
258
259func parseBuild(v string) (t, rest string, ok bool) {
260	if v == "" || v[0] != '+' {
261		return
262	}
263	i := 1
264	start := 1
265	for i < len(v) {
266		if !isIdentChar(v[i]) {
267			return
268		}
269		if v[i] == '.' {
270			if start == i {
271				return
272			}
273			start = i + 1
274		}
275		i++
276	}
277	if start == i {
278		return
279	}
280	return v[:i], v[i:], true
281}
282
283func isIdentChar(c byte) bool {
284	return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-'
285}
286
287func isBadNum(v string) bool {
288	i := 0
289	for i < len(v) && '0' <= v[i] && v[i] <= '9' {
290		i++
291	}
292	return i == len(v) && i > 1 && v[0] == '0'
293}
294
295func isNum(v string) bool {
296	i := 0
297	for i < len(v) && '0' <= v[i] && v[i] <= '9' {
298		i++
299	}
300	return i == len(v)
301}
302
303func compareInt(x, y string) int {
304	if x == y {
305		return 0
306	}
307	if len(x) < len(y) {
308		return -1
309	}
310	if len(x) > len(y) {
311		return +1
312	}
313	if x < y {
314		return -1
315	} else {
316		return +1
317	}
318}
319
320func comparePrerelease(x, y string) int {
321	// "When major, minor, and patch are equal, a pre-release version has
322	// lower precedence than a normal version.
323	// Example: 1.0.0-alpha < 1.0.0.
324	// Precedence for two pre-release versions with the same major, minor,
325	// and patch version MUST be determined by comparing each dot separated
326	// identifier from left to right until a difference is found as follows:
327	// identifiers consisting of only digits are compared numerically and
328	// identifiers with letters or hyphens are compared lexically in ASCII
329	// sort order. Numeric identifiers always have lower precedence than
330	// non-numeric identifiers. A larger set of pre-release fields has a
331	// higher precedence than a smaller set, if all of the preceding
332	// identifiers are equal.
333	// Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta <
334	// 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0."
335	if x == y {
336		return 0
337	}
338	if x == "" {
339		return +1
340	}
341	if y == "" {
342		return -1
343	}
344	for x != "" && y != "" {
345		x = x[1:] // skip - or .
346		y = y[1:] // skip - or .
347		var dx, dy string
348		dx, x = nextIdent(x)
349		dy, y = nextIdent(y)
350		if dx != dy {
351			ix := isNum(dx)
352			iy := isNum(dy)
353			if ix != iy {
354				if ix {
355					return -1
356				} else {
357					return +1
358				}
359			}
360			if ix {
361				if len(dx) < len(dy) {
362					return -1
363				}
364				if len(dx) > len(dy) {
365					return +1
366				}
367			}
368			if dx < dy {
369				return -1
370			} else {
371				return +1
372			}
373		}
374	}
375	if x == "" {
376		return -1
377	} else {
378		return +1
379	}
380}
381
382func nextIdent(x string) (dx, rest string) {
383	i := 0
384	for i < len(x) && x[i] != '.' {
385		i++
386	}
387	return x[:i], x[i:]
388}