1 package semver
2
3 import (
4 "errors"
5 "fmt"
6 "strconv"
7 "strings"
8 )
9
10 const (
11 numbers string = "0123456789"
12 alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
13 alphanum = alphas + numbers
14 )
15
16
17 var SpecVersion = Version{
18 Major: 2,
19 Minor: 0,
20 Patch: 0,
21 }
22
23
24 type Version struct {
25 Major uint64
26 Minor uint64
27 Patch uint64
28 Pre []PRVersion
29 Build []string
30 }
31
32
33 func (v Version) String() string {
34 b := make([]byte, 0, 5)
35 b = strconv.AppendUint(b, v.Major, 10)
36 b = append(b, '.')
37 b = strconv.AppendUint(b, v.Minor, 10)
38 b = append(b, '.')
39 b = strconv.AppendUint(b, v.Patch, 10)
40
41 if len(v.Pre) > 0 {
42 b = append(b, '-')
43 b = append(b, v.Pre[0].String()...)
44
45 for _, pre := range v.Pre[1:] {
46 b = append(b, '.')
47 b = append(b, pre.String()...)
48 }
49 }
50
51 if len(v.Build) > 0 {
52 b = append(b, '+')
53 b = append(b, v.Build[0]...)
54
55 for _, build := range v.Build[1:] {
56 b = append(b, '.')
57 b = append(b, build...)
58 }
59 }
60
61 return string(b)
62 }
63
64
65
66 func (v Version) FinalizeVersion() string {
67 b := make([]byte, 0, 5)
68 b = strconv.AppendUint(b, v.Major, 10)
69 b = append(b, '.')
70 b = strconv.AppendUint(b, v.Minor, 10)
71 b = append(b, '.')
72 b = strconv.AppendUint(b, v.Patch, 10)
73 return string(b)
74 }
75
76
77 func (v Version) Equals(o Version) bool {
78 return (v.Compare(o) == 0)
79 }
80
81
82 func (v Version) EQ(o Version) bool {
83 return (v.Compare(o) == 0)
84 }
85
86
87 func (v Version) NE(o Version) bool {
88 return (v.Compare(o) != 0)
89 }
90
91
92 func (v Version) GT(o Version) bool {
93 return (v.Compare(o) == 1)
94 }
95
96
97 func (v Version) GTE(o Version) bool {
98 return (v.Compare(o) >= 0)
99 }
100
101
102 func (v Version) GE(o Version) bool {
103 return (v.Compare(o) >= 0)
104 }
105
106
107 func (v Version) LT(o Version) bool {
108 return (v.Compare(o) == -1)
109 }
110
111
112 func (v Version) LTE(o Version) bool {
113 return (v.Compare(o) <= 0)
114 }
115
116
117 func (v Version) LE(o Version) bool {
118 return (v.Compare(o) <= 0)
119 }
120
121
122
123
124
125 func (v Version) Compare(o Version) int {
126 if v.Major != o.Major {
127 if v.Major > o.Major {
128 return 1
129 }
130 return -1
131 }
132 if v.Minor != o.Minor {
133 if v.Minor > o.Minor {
134 return 1
135 }
136 return -1
137 }
138 if v.Patch != o.Patch {
139 if v.Patch > o.Patch {
140 return 1
141 }
142 return -1
143 }
144
145
146 if len(v.Pre) == 0 && len(o.Pre) == 0 {
147 return 0
148 } else if len(v.Pre) == 0 && len(o.Pre) > 0 {
149 return 1
150 } else if len(v.Pre) > 0 && len(o.Pre) == 0 {
151 return -1
152 }
153
154 i := 0
155 for ; i < len(v.Pre) && i < len(o.Pre); i++ {
156 if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 {
157 continue
158 } else if comp == 1 {
159 return 1
160 } else {
161 return -1
162 }
163 }
164
165
166 if i == len(v.Pre) && i == len(o.Pre) {
167 return 0
168 } else if i == len(v.Pre) && i < len(o.Pre) {
169 return -1
170 } else {
171 return 1
172 }
173
174 }
175
176
177 func (v *Version) IncrementPatch() error {
178 v.Patch++
179 return nil
180 }
181
182
183 func (v *Version) IncrementMinor() error {
184 v.Minor++
185 v.Patch = 0
186 return nil
187 }
188
189
190 func (v *Version) IncrementMajor() error {
191 v.Major++
192 v.Minor = 0
193 v.Patch = 0
194 return nil
195 }
196
197
198 func (v Version) Validate() error {
199
200
201 for _, pre := range v.Pre {
202 if !pre.IsNum {
203 if len(pre.VersionStr) == 0 {
204 return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr)
205 }
206 if !containsOnly(pre.VersionStr, alphanum) {
207 return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr)
208 }
209 }
210 }
211
212 for _, build := range v.Build {
213 if len(build) == 0 {
214 return fmt.Errorf("Build meta data can not be empty %q", build)
215 }
216 if !containsOnly(build, alphanum) {
217 return fmt.Errorf("Invalid character(s) found in build meta data %q", build)
218 }
219 }
220
221 return nil
222 }
223
224
225 func New(s string) (*Version, error) {
226 v, err := Parse(s)
227 vp := &v
228 return vp, err
229 }
230
231
232 func Make(s string) (Version, error) {
233 return Parse(s)
234 }
235
236
237
238
239
240 func ParseTolerant(s string) (Version, error) {
241 s = strings.TrimSpace(s)
242 s = strings.TrimPrefix(s, "v")
243
244
245 parts := strings.SplitN(s, ".", 3)
246
247 for i, p := range parts {
248 if len(p) > 1 {
249 p = strings.TrimLeft(p, "0")
250 if len(p) == 0 || !strings.ContainsAny(p[0:1], "0123456789") {
251 p = "0" + p
252 }
253 parts[i] = p
254 }
255 }
256
257 if len(parts) < 3 {
258 if strings.ContainsAny(parts[len(parts)-1], "+-") {
259 return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data")
260 }
261 for len(parts) < 3 {
262 parts = append(parts, "0")
263 }
264 }
265 s = strings.Join(parts, ".")
266
267 return Parse(s)
268 }
269
270
271 func Parse(s string) (Version, error) {
272 if len(s) == 0 {
273 return Version{}, errors.New("Version string empty")
274 }
275
276
277 parts := strings.SplitN(s, ".", 3)
278 if len(parts) != 3 {
279 return Version{}, errors.New("No Major.Minor.Patch elements found")
280 }
281
282
283 if !containsOnly(parts[0], numbers) {
284 return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0])
285 }
286 if hasLeadingZeroes(parts[0]) {
287 return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0])
288 }
289 major, err := strconv.ParseUint(parts[0], 10, 64)
290 if err != nil {
291 return Version{}, err
292 }
293
294
295 if !containsOnly(parts[1], numbers) {
296 return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1])
297 }
298 if hasLeadingZeroes(parts[1]) {
299 return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1])
300 }
301 minor, err := strconv.ParseUint(parts[1], 10, 64)
302 if err != nil {
303 return Version{}, err
304 }
305
306 v := Version{}
307 v.Major = major
308 v.Minor = minor
309
310 var build, prerelease []string
311 patchStr := parts[2]
312
313 if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 {
314 build = strings.Split(patchStr[buildIndex+1:], ".")
315 patchStr = patchStr[:buildIndex]
316 }
317
318 if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 {
319 prerelease = strings.Split(patchStr[preIndex+1:], ".")
320 patchStr = patchStr[:preIndex]
321 }
322
323 if !containsOnly(patchStr, numbers) {
324 return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr)
325 }
326 if hasLeadingZeroes(patchStr) {
327 return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr)
328 }
329 patch, err := strconv.ParseUint(patchStr, 10, 64)
330 if err != nil {
331 return Version{}, err
332 }
333
334 v.Patch = patch
335
336
337 for _, prstr := range prerelease {
338 parsedPR, err := NewPRVersion(prstr)
339 if err != nil {
340 return Version{}, err
341 }
342 v.Pre = append(v.Pre, parsedPR)
343 }
344
345
346 for _, str := range build {
347 if len(str) == 0 {
348 return Version{}, errors.New("Build meta data is empty")
349 }
350 if !containsOnly(str, alphanum) {
351 return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str)
352 }
353 v.Build = append(v.Build, str)
354 }
355
356 return v, nil
357 }
358
359
360 func MustParse(s string) Version {
361 v, err := Parse(s)
362 if err != nil {
363 panic(`semver: Parse(` + s + `): ` + err.Error())
364 }
365 return v
366 }
367
368
369 type PRVersion struct {
370 VersionStr string
371 VersionNum uint64
372 IsNum bool
373 }
374
375
376 func NewPRVersion(s string) (PRVersion, error) {
377 if len(s) == 0 {
378 return PRVersion{}, errors.New("Prerelease is empty")
379 }
380 v := PRVersion{}
381 if containsOnly(s, numbers) {
382 if hasLeadingZeroes(s) {
383 return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s)
384 }
385 num, err := strconv.ParseUint(s, 10, 64)
386
387
388 if err != nil {
389 return PRVersion{}, err
390 }
391 v.VersionNum = num
392 v.IsNum = true
393 } else if containsOnly(s, alphanum) {
394 v.VersionStr = s
395 v.IsNum = false
396 } else {
397 return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s)
398 }
399 return v, nil
400 }
401
402
403 func (v PRVersion) IsNumeric() bool {
404 return v.IsNum
405 }
406
407
408
409
410
411 func (v PRVersion) Compare(o PRVersion) int {
412 if v.IsNum && !o.IsNum {
413 return -1
414 } else if !v.IsNum && o.IsNum {
415 return 1
416 } else if v.IsNum && o.IsNum {
417 if v.VersionNum == o.VersionNum {
418 return 0
419 } else if v.VersionNum > o.VersionNum {
420 return 1
421 } else {
422 return -1
423 }
424 } else {
425 if v.VersionStr == o.VersionStr {
426 return 0
427 } else if v.VersionStr > o.VersionStr {
428 return 1
429 } else {
430 return -1
431 }
432 }
433 }
434
435
436 func (v PRVersion) String() string {
437 if v.IsNum {
438 return strconv.FormatUint(v.VersionNum, 10)
439 }
440 return v.VersionStr
441 }
442
443 func containsOnly(s string, set string) bool {
444 return strings.IndexFunc(s, func(r rune) bool {
445 return !strings.ContainsRune(set, r)
446 }) == -1
447 }
448
449 func hasLeadingZeroes(s string) bool {
450 return len(s) > 1 && s[0] == '0'
451 }
452
453
454 func NewBuildVersion(s string) (string, error) {
455 if len(s) == 0 {
456 return "", errors.New("Buildversion is empty")
457 }
458 if !containsOnly(s, alphanum) {
459 return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s)
460 }
461 return s, nil
462 }
463
464
465
466 func FinalizeVersion(s string) (string, error) {
467 v, err := Parse(s)
468 if err != nil {
469 return "", err
470 }
471 v.Pre = nil
472 v.Build = nil
473
474 finalVer := v.String()
475 return finalVer, nil
476 }
477
View as plain text