Skip to content

Commit 78b9ff6

Browse files
committed
semver: basic semver parsing package
Copied from cmd/go/internal/semver. For golang/go#31761. Change-Id: I59e8a264f49a5d20ba1caa85b59c3177209a6ff5 Reviewed-on: https://go-review.googlesource.com/c/mod/+/176460 Run-TryBot: Russ Cox <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Hyang-Ah Hana Kim <[email protected]>
1 parent f80ec78 commit 78b9ff6

File tree

3 files changed

+572
-0
lines changed

3 files changed

+572
-0
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module golang.org/x/mod

semver/semver.go

Lines changed: 388 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
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+
// parsed returns the parsed form of a semantic version string.
26+
type 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.
37+
func 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.
47+
func 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.
64+
func 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.
75+
func 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.
90+
func 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.
101+
func 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.
115+
func 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.
141+
func 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+
150+
func 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+
214+
func 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+
231+
func 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+
259+
func 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]) && 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+
283+
func isIdentChar(c byte) bool {
284+
return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-'
285+
}
286+
287+
func 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+
295+
func 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+
303+
func 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+
320+
func 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+
382+
func 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+
}

0 commit comments

Comments
 (0)