forked from nsqio/nsq
/
semver.go
173 lines (159 loc) · 4.09 KB
/
semver.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// COPIED FROM http://code.google.com/p/go-semver/
//
// The version package implements semantic versions as described
// at http://semver.org/
package semver
import (
"fmt"
"regexp"
"strconv"
"strings"
)
// Version represents a parsed version. See http://semver.org/ for
// detailed description of the various components.
type Version struct {
Major int // The major version number.
Minor int // The minor version number.
Patch int // The patch version number.
Prerelease []string // The pre-release version (dot-separated elements)
Build []string // The build version (dot-separated elements)
}
var charClasses = strings.NewReplacer("d", `[0-9]`, "c", `[\-0-9a-z]`)
const pattern = `^(d{1,9})\.(d{1,9})\.(d{1,9})(-c+(\.c+)*)?(\+c+(\.c+)*)?$`
var versionPat = regexp.MustCompile(charClasses.Replace(pattern))
// Parse parses the version, which is of one of the following forms:
// 1.2.3
// 1.2.3-prerelease
// 1.2.3+build
// 1.2.3-prerelease+build
func Parse(s string) (*Version, error) {
m := versionPat.FindStringSubmatch(s)
if m == nil {
return nil, fmt.Errorf("invalid version %q", s)
}
v := new(Version)
v.Major = atoi(m[1])
v.Minor = atoi(m[2])
v.Patch = atoi(m[3])
if m[4] != "" {
v.Prerelease = strings.Split(m[4][1:], ".")
}
if m[6] != "" {
v.Build = strings.Split(m[6][1:], ".")
}
return v, nil
}
// atoi is the same as strconv.Atoi but assumes that
// the string has been verified to be a valid integer.
func atoi(s string) int {
n, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return n
}
func (v *Version) String() string {
var pre, build string
if v.Prerelease != nil {
pre = "-" + strings.Join(v.Prerelease, ".")
}
if v.Build != nil {
build = "+" + strings.Join(v.Build, ".")
}
return fmt.Sprintf("%d.%d.%d%s%s", v.Major, v.Minor, v.Patch, pre, build)
}
func allDigits(s string) bool {
for _, c := range s {
if c < '0' || c > '9' {
return false
}
}
return true
}
// lessIds returns whether the slice of identifiers a is less than b,
// as specified in semver.org,
func lessIds(a, b []string) (v bool) {
i := 0
for ; i < len(a) && i < len(b); i++ {
if c := cmp(a[i], b[i]); c != 0 {
return c < 0
}
}
return i < len(b)
}
// eqIds returns whether the slice of identifiers a is equal to b,
// as specified in semver.org,
func eqIds(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i, s := range a {
if cmp(s, b[i]) != 0 {
return false
}
}
return true
}
// cmp implements comparison of identifiers as specified at semver.org.
// It returns 1, -1 or 0 if a is greater-than, less-than or equal to b,
// respectively.
//
// Identifiers consisting of only digits are compared numerically and
// identifiers with letters or dashes are compared lexically in ASCII
// sort order. Numeric identifiers always have lower precedence than
// non-numeric identifiers.
func cmp(a, b string) int {
numa, numb := allDigits(a), allDigits(b)
switch {
case numa && numb:
return numCmp(a, b)
case numa:
return -1
case numb:
return 1
case a < b:
return -1
case a > b:
return 1
}
return 0
}
// numCmp 1, -1 or 0 depending on whether the known-to-be-all-digits
// strings a and b are numerically greater than, less than or equal to
// each other. Avoiding the conversion means we can work correctly with
// very long version numbers.
func numCmp(a, b string) int {
a = strings.TrimLeft(a, "0")
b = strings.TrimLeft(b, "0")
switch {
case len(a) < len(b):
return -1
case len(a) > len(b):
return 1
case a < b:
return -1
case a > b:
return 1
}
return 0
}
// Less returns whether v is semantically earlier in the
// version sequence than w.
func (v *Version) Less(w *Version) bool {
switch {
case v.Major != w.Major:
return v.Major < w.Major
case v.Minor != w.Minor:
return v.Minor < w.Minor
case v.Patch != w.Patch:
return v.Patch < w.Patch
case !eqIds(v.Prerelease, w.Prerelease):
if v.Prerelease == nil || w.Prerelease == nil {
return v.Prerelease != nil
}
return lessIds(v.Prerelease, w.Prerelease)
case !eqIds(v.Build, w.Build):
return lessIds(v.Build, w.Build)
}
return false
}