1package semver
2
3import (
4	"bytes"
5	"encoding/json"
6	"errors"
7	"fmt"
8	"regexp"
9	"strconv"
10	"strings"
11)
12
13// The compiled version of the regex created at init() is cached here so it
14// only needs to be created once.
15var versionRegex *regexp.Regexp
16var validPrereleaseRegex *regexp.Regexp
17
18var (
19	// ErrInvalidSemVer is returned a version is found to be invalid when
20	// being parsed.
21	ErrInvalidSemVer = errors.New("Invalid Semantic Version")
22
23	// ErrInvalidMetadata is returned when the metadata is an invalid format
24	ErrInvalidMetadata = errors.New("Invalid Metadata string")
25
26	// ErrInvalidPrerelease is returned when the pre-release is an invalid format
27	ErrInvalidPrerelease = errors.New("Invalid Prerelease string")
28)
29
30// SemVerRegex is the regular expression used to parse a semantic version.
31const SemVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` +
32	`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
33	`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`
34
35// ValidPrerelease is the regular expression which validates
36// both prerelease and metadata values.
37const ValidPrerelease string = `^([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*)`
38
39// Version represents a single semantic version.
40type Version struct {
41	major, minor, patch int64
42	pre                 string
43	metadata            string
44	original            string
45}
46
47func init() {
48	versionRegex = regexp.MustCompile("^" + SemVerRegex + "$")
49	validPrereleaseRegex = regexp.MustCompile(ValidPrerelease)
50}
51
52// NewVersion parses a given version and returns an instance of Version or
53// an error if unable to parse the version.
54func NewVersion(v string) (*Version, error) {
55	m := versionRegex.FindStringSubmatch(v)
56	if m == nil {
57		return nil, ErrInvalidSemVer
58	}
59
60	sv := &Version{
61		metadata: m[8],
62		pre:      m[5],
63		original: v,
64	}
65
66	var temp int64
67	temp, err := strconv.ParseInt(m[1], 10, 64)
68	if err != nil {
69		return nil, fmt.Errorf("Error parsing version segment: %s", err)
70	}
71	sv.major = temp
72
73	if m[2] != "" {
74		temp, err = strconv.ParseInt(strings.TrimPrefix(m[2], "."), 10, 64)
75		if err != nil {
76			return nil, fmt.Errorf("Error parsing version segment: %s", err)
77		}
78		sv.minor = temp
79	} else {
80		sv.minor = 0
81	}
82
83	if m[3] != "" {
84		temp, err = strconv.ParseInt(strings.TrimPrefix(m[3], "."), 10, 64)
85		if err != nil {
86			return nil, fmt.Errorf("Error parsing version segment: %s", err)
87		}
88		sv.patch = temp
89	} else {
90		sv.patch = 0
91	}
92
93	return sv, nil
94}
95
96// MustParse parses a given version and panics on error.
97func MustParse(v string) *Version {
98	sv, err := NewVersion(v)
99	if err != nil {
100		panic(err)
101	}
102	return sv
103}
104
105// String converts a Version object to a string.
106// Note, if the original version contained a leading v this version will not.
107// See the Original() method to retrieve the original value. Semantic Versions
108// don't contain a leading v per the spec. Instead it's optional on
109// impelementation.
110func (v *Version) String() string {
111	var buf bytes.Buffer
112
113	fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch)
114	if v.pre != "" {
115		fmt.Fprintf(&buf, "-%s", v.pre)
116	}
117	if v.metadata != "" {
118		fmt.Fprintf(&buf, "+%s", v.metadata)
119	}
120
121	return buf.String()
122}
123
124// Original returns the original value passed in to be parsed.
125func (v *Version) Original() string {
126	return v.original
127}
128
129// Major returns the major version.
130func (v *Version) Major() int64 {
131	return v.major
132}
133
134// Minor returns the minor version.
135func (v *Version) Minor() int64 {
136	return v.minor
137}
138
139// Patch returns the patch version.
140func (v *Version) Patch() int64 {
141	return v.patch
142}
143
144// Prerelease returns the pre-release version.
145func (v *Version) Prerelease() string {
146	return v.pre
147}
148
149// Metadata returns the metadata on the version.
150func (v *Version) Metadata() string {
151	return v.metadata
152}
153
154// originalVPrefix returns the original 'v' prefix if any.
155func (v *Version) originalVPrefix() string {
156
157	// Note, only lowercase v is supported as a prefix by the parser.
158	if v.original != "" && v.original[:1] == "v" {
159		return v.original[:1]
160	}
161	return ""
162}
163
164// IncPatch produces the next patch version.
165// If the current version does not have prerelease/metadata information,
166// it unsets metadata and prerelease values, increments patch number.
167// If the current version has any of prerelease or metadata information,
168// it unsets both values and keeps curent patch value
169func (v Version) IncPatch() Version {
170	vNext := v
171	// according to http://semver.org/#spec-item-9
172	// Pre-release versions have a lower precedence than the associated normal version.
173	// according to http://semver.org/#spec-item-10
174	// Build metadata SHOULD be ignored when determining version precedence.
175	if v.pre != "" {
176		vNext.metadata = ""
177		vNext.pre = ""
178	} else {
179		vNext.metadata = ""
180		vNext.pre = ""
181		vNext.patch = v.patch + 1
182	}
183	vNext.original = v.originalVPrefix() + "" + vNext.String()
184	return vNext
185}
186
187// IncMinor produces the next minor version.
188// Sets patch to 0.
189// Increments minor number.
190// Unsets metadata.
191// Unsets prerelease status.
192func (v Version) IncMinor() Version {
193	vNext := v
194	vNext.metadata = ""
195	vNext.pre = ""
196	vNext.patch = 0
197	vNext.minor = v.minor + 1
198	vNext.original = v.originalVPrefix() + "" + vNext.String()
199	return vNext
200}
201
202// IncMajor produces the next major version.
203// Sets patch to 0.
204// Sets minor to 0.
205// Increments major number.
206// Unsets metadata.
207// Unsets prerelease status.
208func (v Version) IncMajor() Version {
209	vNext := v
210	vNext.metadata = ""
211	vNext.pre = ""
212	vNext.patch = 0
213	vNext.minor = 0
214	vNext.major = v.major + 1
215	vNext.original = v.originalVPrefix() + "" + vNext.String()
216	return vNext
217}
218
219// SetPrerelease defines the prerelease value.
220// Value must not include the required 'hypen' prefix.
221func (v Version) SetPrerelease(prerelease string) (Version, error) {
222	vNext := v
223	if len(prerelease) > 0 && !validPrereleaseRegex.MatchString(prerelease) {
224		return vNext, ErrInvalidPrerelease
225	}
226	vNext.pre = prerelease
227	vNext.original = v.originalVPrefix() + "" + vNext.String()
228	return vNext, nil
229}
230
231// SetMetadata defines metadata value.
232// Value must not include the required 'plus' prefix.
233func (v Version) SetMetadata(metadata string) (Version, error) {
234	vNext := v
235	if len(metadata) > 0 && !validPrereleaseRegex.MatchString(metadata) {
236		return vNext, ErrInvalidMetadata
237	}
238	vNext.metadata = metadata
239	vNext.original = v.originalVPrefix() + "" + vNext.String()
240	return vNext, nil
241}
242
243// LessThan tests if one version is less than another one.
244func (v *Version) LessThan(o *Version) bool {
245	return v.Compare(o) < 0
246}
247
248// GreaterThan tests if one version is greater than another one.
249func (v *Version) GreaterThan(o *Version) bool {
250	return v.Compare(o) > 0
251}
252
253// Equal tests if two versions are equal to each other.
254// Note, versions can be equal with different metadata since metadata
255// is not considered part of the comparable version.
256func (v *Version) Equal(o *Version) bool {
257	return v.Compare(o) == 0
258}
259
260// Compare compares this version to another one. It returns -1, 0, or 1 if
261// the version smaller, equal, or larger than the other version.
262//
263// Versions are compared by X.Y.Z. Build metadata is ignored. Prerelease is
264// lower than the version without a prerelease.
265func (v *Version) Compare(o *Version) int {
266	// Compare the major, minor, and patch version for differences. If a
267	// difference is found return the comparison.
268	if d := compareSegment(v.Major(), o.Major()); d != 0 {
269		return d
270	}
271	if d := compareSegment(v.Minor(), o.Minor()); d != 0 {
272		return d
273	}
274	if d := compareSegment(v.Patch(), o.Patch()); d != 0 {
275		return d
276	}
277
278	// At this point the major, minor, and patch versions are the same.
279	ps := v.pre
280	po := o.Prerelease()
281
282	if ps == "" && po == "" {
283		return 0
284	}
285	if ps == "" {
286		return 1
287	}
288	if po == "" {
289		return -1
290	}
291
292	return comparePrerelease(ps, po)
293}
294
295// UnmarshalJSON implements JSON.Unmarshaler interface.
296func (v *Version) UnmarshalJSON(b []byte) error {
297	var s string
298	if err := json.Unmarshal(b, &s); err != nil {
299		return err
300	}
301	temp, err := NewVersion(s)
302	if err != nil {
303		return err
304	}
305	v.major = temp.major
306	v.minor = temp.minor
307	v.patch = temp.patch
308	v.pre = temp.pre
309	v.metadata = temp.metadata
310	v.original = temp.original
311	temp = nil
312	return nil
313}
314
315// MarshalJSON implements JSON.Marshaler interface.
316func (v *Version) MarshalJSON() ([]byte, error) {
317	return json.Marshal(v.String())
318}
319
320func compareSegment(v, o int64) int {
321	if v < o {
322		return -1
323	}
324	if v > o {
325		return 1
326	}
327
328	return 0
329}
330
331func comparePrerelease(v, o string) int {
332
333	// split the prelease versions by their part. The separator, per the spec,
334	// is a .
335	sparts := strings.Split(v, ".")
336	oparts := strings.Split(o, ".")
337
338	// Find the longer length of the parts to know how many loop iterations to
339	// go through.
340	slen := len(sparts)
341	olen := len(oparts)
342
343	l := slen
344	if olen > slen {
345		l = olen
346	}
347
348	// Iterate over each part of the prereleases to compare the differences.
349	for i := 0; i < l; i++ {
350		// Since the lentgh of the parts can be different we need to create
351		// a placeholder. This is to avoid out of bounds issues.
352		stemp := ""
353		if i < slen {
354			stemp = sparts[i]
355		}
356
357		otemp := ""
358		if i < olen {
359			otemp = oparts[i]
360		}
361
362		d := comparePrePart(stemp, otemp)
363		if d != 0 {
364			return d
365		}
366	}
367
368	// Reaching here means two versions are of equal value but have different
369	// metadata (the part following a +). They are not identical in string form
370	// but the version comparison finds them to be equal.
371	return 0
372}
373
374func comparePrePart(s, o string) int {
375	// Fastpath if they are equal
376	if s == o {
377		return 0
378	}
379
380	// When s or o are empty we can use the other in an attempt to determine
381	// the response.
382	if s == "" {
383		if o != "" {
384			return -1
385		}
386		return 1
387	}
388
389	if o == "" {
390		if s != "" {
391			return 1
392		}
393		return -1
394	}
395
396	// When comparing strings "99" is greater than "103". To handle
397	// cases like this we need to detect numbers and compare them.
398
399	oi, n1 := strconv.ParseInt(o, 10, 64)
400	si, n2 := strconv.ParseInt(s, 10, 64)
401
402	// The case where both are strings compare the strings
403	if n1 != nil && n2 != nil {
404		if s > o {
405			return 1
406		}
407		return -1
408	} else if n1 != nil {
409		// o is a string and s is a number
410		return -1
411	} else if n2 != nil {
412		// s is a string and o is a number
413		return 1
414	}
415	// Both are numbers
416	if si > oi {
417		return 1
418	}
419	return -1
420
421}
422