1package semver
2
3import (
4 "errors"
5 "fmt"
6 "strconv"
7 "strings"
8)
9
10const (
11 numbers string = "0123456789"
12 alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
13 alphanum = alphas + numbers
14)
15
16// SpecVersion is the latest fully supported spec version of semver
17var SpecVersion = Version{
18 Major: 2,
19 Minor: 0,
20 Patch: 0,
21}
22
23// Version represents a semver compatible version
24type Version struct {
25 Major uint64
26 Minor uint64
27 Patch uint64
28 Pre []PRVersion
29 Build []string //No Precedence
30}
31
32// Version to string
33func (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// Equals checks if v is equal to o.
65func (v Version) Equals(o Version) bool {
66 return (v.Compare(o) == 0)
67}
68
69// EQ checks if v is equal to o.
70func (v Version) EQ(o Version) bool {
71 return (v.Compare(o) == 0)
72}
73
74// NE checks if v is not equal to o.
75func (v Version) NE(o Version) bool {
76 return (v.Compare(o) != 0)
77}
78
79// GT checks if v is greater than o.
80func (v Version) GT(o Version) bool {
81 return (v.Compare(o) == 1)
82}
83
84// GTE checks if v is greater than or equal to o.
85func (v Version) GTE(o Version) bool {
86 return (v.Compare(o) >= 0)
87}
88
89// GE checks if v is greater than or equal to o.
90func (v Version) GE(o Version) bool {
91 return (v.Compare(o) >= 0)
92}
93
94// LT checks if v is less than o.
95func (v Version) LT(o Version) bool {
96 return (v.Compare(o) == -1)
97}
98
99// LTE checks if v is less than or equal to o.
100func (v Version) LTE(o Version) bool {
101 return (v.Compare(o) <= 0)
102}
103
104// LE checks if v is less than or equal to o.
105func (v Version) LE(o Version) bool {
106 return (v.Compare(o) <= 0)
107}
108
109// Compare compares Versions v to o:
110// -1 == v is less than o
111// 0 == v is equal to o
112// 1 == v is greater than o
113func (v Version) Compare(o Version) int {
114 if v.Major != o.Major {
115 if v.Major > o.Major {
116 return 1
117 }
118 return -1
119 }
120 if v.Minor != o.Minor {
121 if v.Minor > o.Minor {
122 return 1
123 }
124 return -1
125 }
126 if v.Patch != o.Patch {
127 if v.Patch > o.Patch {
128 return 1
129 }
130 return -1
131 }
132
133 // Quick comparison if a version has no prerelease versions
134 if len(v.Pre) == 0 && len(o.Pre) == 0 {
135 return 0
136 } else if len(v.Pre) == 0 && len(o.Pre) > 0 {
137 return 1
138 } else if len(v.Pre) > 0 && len(o.Pre) == 0 {
139 return -1
140 }
141
142 i := 0
143 for ; i < len(v.Pre) && i < len(o.Pre); i++ {
144 if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 {
145 continue
146 } else if comp == 1 {
147 return 1
148 } else {
149 return -1
150 }
151 }
152
153 // If all pr versions are the equal but one has further prversion, this one greater
154 if i == len(v.Pre) && i == len(o.Pre) {
155 return 0
156 } else if i == len(v.Pre) && i < len(o.Pre) {
157 return -1
158 } else {
159 return 1
160 }
161
162}
163
164// IncrementPatch increments the patch version
165func (v *Version) IncrementPatch() error {
166 if v.Major == 0 {
167 return fmt.Errorf("Patch version can not be incremented for %q", v.String())
168 }
169 v.Patch += 1
170 return nil
171}
172
173// IncrementMinor increments the minor version
174func (v *Version) IncrementMinor() error {
175 if v.Major == 0 {
176 return fmt.Errorf("Minor version can not be incremented for %q", v.String())
177 }
178 v.Minor += 1
179 v.Patch = 0
180 return nil
181}
182
183// IncrementMajor increments the major version
184func (v *Version) IncrementMajor() error {
185 if v.Major == 0 {
186 return fmt.Errorf("Major version can not be incremented for %q", v.String())
187 }
188 v.Major += 1
189 v.Minor = 0
190 v.Patch = 0
191 return nil
192}
193
194// Validate validates v and returns error in case
195func (v Version) Validate() error {
196 // Major, Minor, Patch already validated using uint64
197
198 for _, pre := range v.Pre {
199 if !pre.IsNum { //Numeric prerelease versions already uint64
200 if len(pre.VersionStr) == 0 {
201 return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr)
202 }
203 if !containsOnly(pre.VersionStr, alphanum) {
204 return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr)
205 }
206 }
207 }
208
209 for _, build := range v.Build {
210 if len(build) == 0 {
211 return fmt.Errorf("Build meta data can not be empty %q", build)
212 }
213 if !containsOnly(build, alphanum) {
214 return fmt.Errorf("Invalid character(s) found in build meta data %q", build)
215 }
216 }
217
218 return nil
219}
220
221// New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error
222func New(s string) (vp *Version, err error) {
223 v, err := Parse(s)
224 vp = &v
225 return
226}
227
228// Make is an alias for Parse, parses version string and returns a validated Version or error
229func Make(s string) (Version, error) {
230 return Parse(s)
231}
232
233// ParseTolerant allows for certain version specifications that do not strictly adhere to semver
234// specs to be parsed by this library. It does so by normalizing versions before passing them to
235// Parse(). It currently trims spaces, removes a "v" prefix, adds a 0 patch number to versions
236// with only major and minor components specified, and removes leading 0s.
237func ParseTolerant(s string) (Version, error) {
238 s = strings.TrimSpace(s)
239 s = strings.TrimPrefix(s, "v")
240
241 // Split into major.minor.(patch+pr+meta)
242 parts := strings.SplitN(s, ".", 3)
243 // Remove leading zeros.
244 for i, p := range parts {
245 if len(p) > 1 {
246 parts[i] = strings.TrimPrefix(p, "0")
247 }
248 }
249 // Fill up shortened versions.
250 if len(parts) < 3 {
251 if strings.ContainsAny(parts[len(parts)-1], "+-") {
252 return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data")
253 }
254 for len(parts) < 3 {
255 parts = append(parts, "0")
256 }
257 }
258 s = strings.Join(parts, ".")
259
260 return Parse(s)
261}
262
263// Parse parses version string and returns a validated Version or error
264func Parse(s string) (Version, error) {
265 if len(s) == 0 {
266 return Version{}, errors.New("Version string empty")
267 }
268
269 // Split into major.minor.(patch+pr+meta)
270 parts := strings.SplitN(s, ".", 3)
271 if len(parts) != 3 {
272 return Version{}, errors.New("No Major.Minor.Patch elements found")
273 }
274
275 // Major
276 if !containsOnly(parts[0], numbers) {
277 return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0])
278 }
279 if hasLeadingZeroes(parts[0]) {
280 return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0])
281 }
282 major, err := strconv.ParseUint(parts[0], 10, 64)
283 if err != nil {
284 return Version{}, err
285 }
286
287 // Minor
288 if !containsOnly(parts[1], numbers) {
289 return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1])
290 }
291 if hasLeadingZeroes(parts[1]) {
292 return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1])
293 }
294 minor, err := strconv.ParseUint(parts[1], 10, 64)
295 if err != nil {
296 return Version{}, err
297 }
298
299 v := Version{}
300 v.Major = major
301 v.Minor = minor
302
303 var build, prerelease []string
304 patchStr := parts[2]
305
306 if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 {
307 build = strings.Split(patchStr[buildIndex+1:], ".")
308 patchStr = patchStr[:buildIndex]
309 }
310
311 if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 {
312 prerelease = strings.Split(patchStr[preIndex+1:], ".")
313 patchStr = patchStr[:preIndex]
314 }
315
316 if !containsOnly(patchStr, numbers) {
317 return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr)
318 }
319 if hasLeadingZeroes(patchStr) {
320 return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr)
321 }
322 patch, err := strconv.ParseUint(patchStr, 10, 64)
323 if err != nil {
324 return Version{}, err
325 }
326
327 v.Patch = patch
328
329 // Prerelease
330 for _, prstr := range prerelease {
331 parsedPR, err := NewPRVersion(prstr)
332 if err != nil {
333 return Version{}, err
334 }
335 v.Pre = append(v.Pre, parsedPR)
336 }
337
338 // Build meta data
339 for _, str := range build {
340 if len(str) == 0 {
341 return Version{}, errors.New("Build meta data is empty")
342 }
343 if !containsOnly(str, alphanum) {
344 return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str)
345 }
346 v.Build = append(v.Build, str)
347 }
348
349 return v, nil
350}
351
352// MustParse is like Parse but panics if the version cannot be parsed.
353func MustParse(s string) Version {
354 v, err := Parse(s)
355 if err != nil {
356 panic(`semver: Parse(` + s + `): ` + err.Error())
357 }
358 return v
359}
360
361// PRVersion represents a PreRelease Version
362type PRVersion struct {
363 VersionStr string
364 VersionNum uint64
365 IsNum bool
366}
367
368// NewPRVersion creates a new valid prerelease version
369func NewPRVersion(s string) (PRVersion, error) {
370 if len(s) == 0 {
371 return PRVersion{}, errors.New("Prerelease is empty")
372 }
373 v := PRVersion{}
374 if containsOnly(s, numbers) {
375 if hasLeadingZeroes(s) {
376 return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s)
377 }
378 num, err := strconv.ParseUint(s, 10, 64)
379
380 // Might never be hit, but just in case
381 if err != nil {
382 return PRVersion{}, err
383 }
384 v.VersionNum = num
385 v.IsNum = true
386 } else if containsOnly(s, alphanum) {
387 v.VersionStr = s
388 v.IsNum = false
389 } else {
390 return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s)
391 }
392 return v, nil
393}
394
395// IsNumeric checks if prerelease-version is numeric
396func (v PRVersion) IsNumeric() bool {
397 return v.IsNum
398}
399
400// Compare compares two PreRelease Versions v and o:
401// -1 == v is less than o
402// 0 == v is equal to o
403// 1 == v is greater than o
404func (v PRVersion) Compare(o PRVersion) int {
405 if v.IsNum && !o.IsNum {
406 return -1
407 } else if !v.IsNum && o.IsNum {
408 return 1
409 } else if v.IsNum && o.IsNum {
410 if v.VersionNum == o.VersionNum {
411 return 0
412 } else if v.VersionNum > o.VersionNum {
413 return 1
414 } else {
415 return -1
416 }
417 } else { // both are Alphas
418 if v.VersionStr == o.VersionStr {
419 return 0
420 } else if v.VersionStr > o.VersionStr {
421 return 1
422 } else {
423 return -1
424 }
425 }
426}
427
428// PreRelease version to string
429func (v PRVersion) String() string {
430 if v.IsNum {
431 return strconv.FormatUint(v.VersionNum, 10)
432 }
433 return v.VersionStr
434}
435
436func containsOnly(s string, set string) bool {
437 return strings.IndexFunc(s, func(r rune) bool {
438 return !strings.ContainsRune(set, r)
439 }) == -1
440}
441
442func hasLeadingZeroes(s string) bool {
443 return len(s) > 1 && s[0] == '0'
444}
445
446// NewBuildVersion creates a new valid build version
447func NewBuildVersion(s string) (string, error) {
448 if len(s) == 0 {
449 return "", errors.New("Buildversion is empty")
450 }
451 if !containsOnly(s, alphanum) {
452 return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s)
453 }
454 return s, nil
455}