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// FinalizeVersion discards prerelease and build number and only returns
65// major, minor and patch number.
66func (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// Equals checks if v is equal to o.
77func (v Version) Equals(o Version) bool {
78	return (v.Compare(o) == 0)
79}
80
81// EQ checks if v is equal to o.
82func (v Version) EQ(o Version) bool {
83	return (v.Compare(o) == 0)
84}
85
86// NE checks if v is not equal to o.
87func (v Version) NE(o Version) bool {
88	return (v.Compare(o) != 0)
89}
90
91// GT checks if v is greater than o.
92func (v Version) GT(o Version) bool {
93	return (v.Compare(o) == 1)
94}
95
96// GTE checks if v is greater than or equal to o.
97func (v Version) GTE(o Version) bool {
98	return (v.Compare(o) >= 0)
99}
100
101// GE checks if v is greater than or equal to o.
102func (v Version) GE(o Version) bool {
103	return (v.Compare(o) >= 0)
104}
105
106// LT checks if v is less than o.
107func (v Version) LT(o Version) bool {
108	return (v.Compare(o) == -1)
109}
110
111// LTE checks if v is less than or equal to o.
112func (v Version) LTE(o Version) bool {
113	return (v.Compare(o) <= 0)
114}
115
116// LE checks if v is less than or equal to o.
117func (v Version) LE(o Version) bool {
118	return (v.Compare(o) <= 0)
119}
120
121// Compare compares Versions v to o:
122// -1 == v is less than o
123// 0 == v is equal to o
124// 1 == v is greater than o
125func (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	// Quick comparison if a version has no prerelease versions
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	// If all pr versions are the equal but one has further prversion, this one greater
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// IncrementPatch increments the patch version
177func (v *Version) IncrementPatch() error {
178	v.Patch++
179	return nil
180}
181
182// IncrementMinor increments the minor version
183func (v *Version) IncrementMinor() error {
184	v.Minor++
185	v.Patch = 0
186	return nil
187}
188
189// IncrementMajor increments the major version
190func (v *Version) IncrementMajor() error {
191	v.Major++
192	v.Minor = 0
193	v.Patch = 0
194	return nil
195}
196
197// Validate validates v and returns error in case
198func (v Version) Validate() error {
199	// Major, Minor, Patch already validated using uint64
200
201	for _, pre := range v.Pre {
202		if !pre.IsNum { //Numeric prerelease versions already uint64
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// New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error
225func New(s string) (*Version, error) {
226	v, err := Parse(s)
227	vp := &v
228	return vp, err
229}
230
231// Make is an alias for Parse, parses version string and returns a validated Version or error
232func Make(s string) (Version, error) {
233	return Parse(s)
234}
235
236// ParseTolerant allows for certain version specifications that do not strictly adhere to semver
237// specs to be parsed by this library. It does so by normalizing versions before passing them to
238// Parse(). It currently trims spaces, removes a "v" prefix, adds a 0 patch number to versions
239// with only major and minor components specified, and removes leading 0s.
240func ParseTolerant(s string) (Version, error) {
241	s = strings.TrimSpace(s)
242	s = strings.TrimPrefix(s, "v")
243
244	// Split into major.minor.(patch+pr+meta)
245	parts := strings.SplitN(s, ".", 3)
246	// Remove leading zeros.
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	// Fill up shortened versions.
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// Parse parses version string and returns a validated Version or error
271func Parse(s string) (Version, error) {
272	if len(s) == 0 {
273		return Version{}, errors.New("Version string empty")
274	}
275
276	// Split into major.minor.(patch+pr+meta)
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	// Major
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	// Minor
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	// Prerelease
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	// Build meta data
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// MustParse is like Parse but panics if the version cannot be parsed.
360func 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// PRVersion represents a PreRelease Version
369type PRVersion struct {
370	VersionStr string
371	VersionNum uint64
372	IsNum      bool
373}
374
375// NewPRVersion creates a new valid prerelease version
376func 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		// Might never be hit, but just in case
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// IsNumeric checks if prerelease-version is numeric
403func (v PRVersion) IsNumeric() bool {
404	return v.IsNum
405}
406
407// Compare compares two PreRelease Versions v and o:
408// -1 == v is less than o
409// 0 == v is equal to o
410// 1 == v is greater than o
411func (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 { // both are Alphas
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// PreRelease version to string
436func (v PRVersion) String() string {
437	if v.IsNum {
438		return strconv.FormatUint(v.VersionNum, 10)
439	}
440	return v.VersionStr
441}
442
443func 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
449func hasLeadingZeroes(s string) bool {
450	return len(s) > 1 && s[0] == '0'
451}
452
453// NewBuildVersion creates a new valid build version
454func 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// FinalizeVersion returns the major, minor and patch number only and discards
465// prerelease and build number.
466func 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