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