]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package version |
2 | ||
3 | import ( | |
4 | "bytes" | |
5 | "fmt" | |
6 | "reflect" | |
7 | "regexp" | |
8 | "strconv" | |
9 | "strings" | |
10 | ) | |
11 | ||
12 | // The compiled regular expression used to test the validity of a version. | |
107c1cdb ND |
13 | var ( |
14 | versionRegexp *regexp.Regexp | |
15 | semverRegexp *regexp.Regexp | |
16 | ) | |
bae9f6d2 JC |
17 | |
18 | // The raw regular expression string used for testing the validity | |
19 | // of a version. | |
107c1cdb ND |
20 | const ( |
21 | VersionRegexpRaw string = `v?([0-9]+(\.[0-9]+)*?)` + | |
22 | `(-([0-9]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)|(-?([A-Za-z\-~]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)))?` + | |
23 | `(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` + | |
24 | `?` | |
25 | ||
26 | // SemverRegexpRaw requires a separator between version and prerelease | |
27 | SemverRegexpRaw string = `v?([0-9]+(\.[0-9]+)*?)` + | |
28 | `(-([0-9]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)|(-([A-Za-z\-~]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)))?` + | |
29 | `(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` + | |
30 | `?` | |
31 | ) | |
bae9f6d2 JC |
32 | |
33 | // Version represents a single version. | |
34 | type Version struct { | |
35 | metadata string | |
36 | pre string | |
37 | segments []int64 | |
38 | si int | |
15c0b25d | 39 | original string |
bae9f6d2 JC |
40 | } |
41 | ||
42 | func init() { | |
43 | versionRegexp = regexp.MustCompile("^" + VersionRegexpRaw + "$") | |
107c1cdb | 44 | semverRegexp = regexp.MustCompile("^" + SemverRegexpRaw + "$") |
bae9f6d2 JC |
45 | } |
46 | ||
47 | // NewVersion parses the given version and returns a new | |
48 | // Version. | |
49 | func NewVersion(v string) (*Version, error) { | |
107c1cdb ND |
50 | return newVersion(v, versionRegexp) |
51 | } | |
52 | ||
53 | // NewSemver parses the given version and returns a new | |
54 | // Version that adheres strictly to SemVer specs | |
55 | // https://semver.org/ | |
56 | func NewSemver(v string) (*Version, error) { | |
57 | return newVersion(v, semverRegexp) | |
58 | } | |
59 | ||
60 | func newVersion(v string, pattern *regexp.Regexp) (*Version, error) { | |
61 | matches := pattern.FindStringSubmatch(v) | |
bae9f6d2 JC |
62 | if matches == nil { |
63 | return nil, fmt.Errorf("Malformed version: %s", v) | |
64 | } | |
65 | segmentsStr := strings.Split(matches[1], ".") | |
66 | segments := make([]int64, len(segmentsStr)) | |
67 | si := 0 | |
68 | for i, str := range segmentsStr { | |
69 | val, err := strconv.ParseInt(str, 10, 64) | |
70 | if err != nil { | |
71 | return nil, fmt.Errorf( | |
72 | "Error parsing version: %s", err) | |
73 | } | |
74 | ||
75 | segments[i] = int64(val) | |
76 | si++ | |
77 | } | |
78 | ||
79 | // Even though we could support more than three segments, if we | |
80 | // got less than three, pad it with 0s. This is to cover the basic | |
81 | // default usecase of semver, which is MAJOR.MINOR.PATCH at the minimum | |
82 | for i := len(segments); i < 3; i++ { | |
83 | segments = append(segments, 0) | |
84 | } | |
85 | ||
15c0b25d AP |
86 | pre := matches[7] |
87 | if pre == "" { | |
88 | pre = matches[4] | |
89 | } | |
90 | ||
bae9f6d2 | 91 | return &Version{ |
15c0b25d AP |
92 | metadata: matches[10], |
93 | pre: pre, | |
bae9f6d2 JC |
94 | segments: segments, |
95 | si: si, | |
15c0b25d | 96 | original: v, |
bae9f6d2 JC |
97 | }, nil |
98 | } | |
99 | ||
100 | // Must is a helper that wraps a call to a function returning (*Version, error) | |
101 | // and panics if error is non-nil. | |
102 | func Must(v *Version, err error) *Version { | |
103 | if err != nil { | |
104 | panic(err) | |
105 | } | |
106 | ||
107 | return v | |
108 | } | |
109 | ||
110 | // Compare compares this version to another version. This | |
111 | // returns -1, 0, or 1 if this version is smaller, equal, | |
112 | // or larger than the other version, respectively. | |
113 | // | |
114 | // If you want boolean results, use the LessThan, Equal, | |
115 | // or GreaterThan methods. | |
116 | func (v *Version) Compare(other *Version) int { | |
117 | // A quick, efficient equality check | |
118 | if v.String() == other.String() { | |
119 | return 0 | |
120 | } | |
121 | ||
122 | segmentsSelf := v.Segments64() | |
123 | segmentsOther := other.Segments64() | |
124 | ||
125 | // If the segments are the same, we must compare on prerelease info | |
126 | if reflect.DeepEqual(segmentsSelf, segmentsOther) { | |
127 | preSelf := v.Prerelease() | |
128 | preOther := other.Prerelease() | |
129 | if preSelf == "" && preOther == "" { | |
130 | return 0 | |
131 | } | |
132 | if preSelf == "" { | |
133 | return 1 | |
134 | } | |
135 | if preOther == "" { | |
136 | return -1 | |
137 | } | |
138 | ||
139 | return comparePrereleases(preSelf, preOther) | |
140 | } | |
141 | ||
142 | // Get the highest specificity (hS), or if they're equal, just use segmentSelf length | |
143 | lenSelf := len(segmentsSelf) | |
144 | lenOther := len(segmentsOther) | |
145 | hS := lenSelf | |
146 | if lenSelf < lenOther { | |
147 | hS = lenOther | |
148 | } | |
149 | // Compare the segments | |
150 | // Because a constraint could have more/less specificity than the version it's | |
151 | // checking, we need to account for a lopsided or jagged comparison | |
152 | for i := 0; i < hS; i++ { | |
153 | if i > lenSelf-1 { | |
154 | // This means Self had the lower specificity | |
155 | // Check to see if the remaining segments in Other are all zeros | |
156 | if !allZero(segmentsOther[i:]) { | |
157 | // if not, it means that Other has to be greater than Self | |
158 | return -1 | |
159 | } | |
160 | break | |
161 | } else if i > lenOther-1 { | |
162 | // this means Other had the lower specificity | |
163 | // Check to see if the remaining segments in Self are all zeros - | |
164 | if !allZero(segmentsSelf[i:]) { | |
165 | //if not, it means that Self has to be greater than Other | |
166 | return 1 | |
167 | } | |
168 | break | |
169 | } | |
170 | lhs := segmentsSelf[i] | |
171 | rhs := segmentsOther[i] | |
172 | if lhs == rhs { | |
173 | continue | |
174 | } else if lhs < rhs { | |
175 | return -1 | |
176 | } | |
177 | // Otherwis, rhs was > lhs, they're not equal | |
178 | return 1 | |
179 | } | |
180 | ||
181 | // if we got this far, they're equal | |
182 | return 0 | |
183 | } | |
184 | ||
185 | func allZero(segs []int64) bool { | |
186 | for _, s := range segs { | |
187 | if s != 0 { | |
188 | return false | |
189 | } | |
190 | } | |
191 | return true | |
192 | } | |
193 | ||
194 | func comparePart(preSelf string, preOther string) int { | |
195 | if preSelf == preOther { | |
196 | return 0 | |
197 | } | |
198 | ||
15c0b25d AP |
199 | var selfInt int64 |
200 | selfNumeric := true | |
201 | selfInt, err := strconv.ParseInt(preSelf, 10, 64) | |
202 | if err != nil { | |
203 | selfNumeric = false | |
204 | } | |
205 | ||
206 | var otherInt int64 | |
207 | otherNumeric := true | |
208 | otherInt, err = strconv.ParseInt(preOther, 10, 64) | |
209 | if err != nil { | |
210 | otherNumeric = false | |
211 | } | |
212 | ||
bae9f6d2 JC |
213 | // if a part is empty, we use the other to decide |
214 | if preSelf == "" { | |
15c0b25d | 215 | if otherNumeric { |
bae9f6d2 JC |
216 | return -1 |
217 | } | |
218 | return 1 | |
219 | } | |
220 | ||
221 | if preOther == "" { | |
15c0b25d | 222 | if selfNumeric { |
bae9f6d2 JC |
223 | return 1 |
224 | } | |
225 | return -1 | |
226 | } | |
227 | ||
15c0b25d AP |
228 | if selfNumeric && !otherNumeric { |
229 | return -1 | |
230 | } else if !selfNumeric && otherNumeric { | |
231 | return 1 | |
232 | } else if !selfNumeric && !otherNumeric && preSelf > preOther { | |
233 | return 1 | |
234 | } else if selfInt > otherInt { | |
bae9f6d2 JC |
235 | return 1 |
236 | } | |
237 | ||
238 | return -1 | |
239 | } | |
240 | ||
241 | func comparePrereleases(v string, other string) int { | |
242 | // the same pre release! | |
243 | if v == other { | |
244 | return 0 | |
245 | } | |
246 | ||
247 | // split both pre releases for analyse their parts | |
248 | selfPreReleaseMeta := strings.Split(v, ".") | |
249 | otherPreReleaseMeta := strings.Split(other, ".") | |
250 | ||
251 | selfPreReleaseLen := len(selfPreReleaseMeta) | |
252 | otherPreReleaseLen := len(otherPreReleaseMeta) | |
253 | ||
254 | biggestLen := otherPreReleaseLen | |
255 | if selfPreReleaseLen > otherPreReleaseLen { | |
256 | biggestLen = selfPreReleaseLen | |
257 | } | |
258 | ||
259 | // loop for parts to find the first difference | |
260 | for i := 0; i < biggestLen; i = i + 1 { | |
261 | partSelfPre := "" | |
262 | if i < selfPreReleaseLen { | |
263 | partSelfPre = selfPreReleaseMeta[i] | |
264 | } | |
265 | ||
266 | partOtherPre := "" | |
267 | if i < otherPreReleaseLen { | |
268 | partOtherPre = otherPreReleaseMeta[i] | |
269 | } | |
270 | ||
271 | compare := comparePart(partSelfPre, partOtherPre) | |
272 | // if parts are equals, continue the loop | |
273 | if compare != 0 { | |
274 | return compare | |
275 | } | |
276 | } | |
277 | ||
278 | return 0 | |
279 | } | |
280 | ||
281 | // Equal tests if two versions are equal. | |
282 | func (v *Version) Equal(o *Version) bool { | |
283 | return v.Compare(o) == 0 | |
284 | } | |
285 | ||
286 | // GreaterThan tests if this version is greater than another version. | |
287 | func (v *Version) GreaterThan(o *Version) bool { | |
288 | return v.Compare(o) > 0 | |
289 | } | |
290 | ||
291 | // LessThan tests if this version is less than another version. | |
292 | func (v *Version) LessThan(o *Version) bool { | |
293 | return v.Compare(o) < 0 | |
294 | } | |
295 | ||
296 | // Metadata returns any metadata that was part of the version | |
297 | // string. | |
298 | // | |
299 | // Metadata is anything that comes after the "+" in the version. | |
300 | // For example, with "1.2.3+beta", the metadata is "beta". | |
301 | func (v *Version) Metadata() string { | |
302 | return v.metadata | |
303 | } | |
304 | ||
305 | // Prerelease returns any prerelease data that is part of the version, | |
306 | // or blank if there is no prerelease data. | |
307 | // | |
308 | // Prerelease information is anything that comes after the "-" in the | |
309 | // version (but before any metadata). For example, with "1.2.3-beta", | |
310 | // the prerelease information is "beta". | |
311 | func (v *Version) Prerelease() string { | |
312 | return v.pre | |
313 | } | |
314 | ||
315 | // Segments returns the numeric segments of the version as a slice of ints. | |
316 | // | |
317 | // This excludes any metadata or pre-release information. For example, | |
318 | // for a version "1.2.3-beta", segments will return a slice of | |
319 | // 1, 2, 3. | |
320 | func (v *Version) Segments() []int { | |
321 | segmentSlice := make([]int, len(v.segments)) | |
322 | for i, v := range v.segments { | |
323 | segmentSlice[i] = int(v) | |
324 | } | |
325 | return segmentSlice | |
326 | } | |
327 | ||
328 | // Segments64 returns the numeric segments of the version as a slice of int64s. | |
329 | // | |
330 | // This excludes any metadata or pre-release information. For example, | |
331 | // for a version "1.2.3-beta", segments will return a slice of | |
332 | // 1, 2, 3. | |
333 | func (v *Version) Segments64() []int64 { | |
15c0b25d AP |
334 | result := make([]int64, len(v.segments)) |
335 | copy(result, v.segments) | |
336 | return result | |
bae9f6d2 JC |
337 | } |
338 | ||
339 | // String returns the full version string included pre-release | |
340 | // and metadata information. | |
15c0b25d AP |
341 | // |
342 | // This value is rebuilt according to the parsed segments and other | |
343 | // information. Therefore, ambiguities in the version string such as | |
344 | // prefixed zeroes (1.04.0 => 1.4.0), `v` prefix (v1.0.0 => 1.0.0), and | |
345 | // missing parts (1.0 => 1.0.0) will be made into a canonicalized form | |
346 | // as shown in the parenthesized examples. | |
bae9f6d2 JC |
347 | func (v *Version) String() string { |
348 | var buf bytes.Buffer | |
349 | fmtParts := make([]string, len(v.segments)) | |
350 | for i, s := range v.segments { | |
351 | // We can ignore err here since we've pre-parsed the values in segments | |
352 | str := strconv.FormatInt(s, 10) | |
353 | fmtParts[i] = str | |
354 | } | |
355 | fmt.Fprintf(&buf, strings.Join(fmtParts, ".")) | |
356 | if v.pre != "" { | |
357 | fmt.Fprintf(&buf, "-%s", v.pre) | |
358 | } | |
359 | if v.metadata != "" { | |
360 | fmt.Fprintf(&buf, "+%s", v.metadata) | |
361 | } | |
362 | ||
363 | return buf.String() | |
364 | } | |
15c0b25d AP |
365 | |
366 | // Original returns the original parsed version as-is, including any | |
367 | // potential whitespace, `v` prefix, etc. | |
368 | func (v *Version) Original() string { | |
369 | return v.original | |
370 | } |