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