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	if v == nil || o == nil {
284		return v == o
285	}
286
287	return v.Compare(o) == 0
288}
289
290// GreaterThan tests if this version is greater than another version.
291func (v *Version) GreaterThan(o *Version) bool {
292	return v.Compare(o) > 0
293}
294
295// GreaterThanOrEqual tests if this version is greater than or equal to another version.
296func (v *Version) GreaterThanOrEqual(o *Version) bool {
297	return v.Compare(o) >= 0
298}
299
300// LessThan tests if this version is less than another version.
301func (v *Version) LessThan(o *Version) bool {
302	return v.Compare(o) < 0
303}
304
305// LessThanOrEqual tests if this version is less than or equal to another version.
306func (v *Version) LessThanOrEqual(o *Version) bool {
307	return v.Compare(o) <= 0
308}
309
310// Metadata returns any metadata that was part of the version
311// string.
312//
313// Metadata is anything that comes after the "+" in the version.
314// For example, with "1.2.3+beta", the metadata is "beta".
315func (v *Version) Metadata() string {
316	return v.metadata
317}
318
319// Prerelease returns any prerelease data that is part of the version,
320// or blank if there is no prerelease data.
321//
322// Prerelease information is anything that comes after the "-" in the
323// version (but before any metadata). For example, with "1.2.3-beta",
324// the prerelease information is "beta".
325func (v *Version) Prerelease() string {
326	return v.pre
327}
328
329// Segments returns the numeric segments of the version as a slice of ints.
330//
331// This excludes any metadata or pre-release information. For example,
332// for a version "1.2.3-beta", segments will return a slice of
333// 1, 2, 3.
334func (v *Version) Segments() []int {
335	segmentSlice := make([]int, len(v.segments))
336	for i, v := range v.segments {
337		segmentSlice[i] = int(v)
338	}
339	return segmentSlice
340}
341
342// Segments64 returns the numeric segments of the version as a slice of int64s.
343//
344// This excludes any metadata or pre-release information. For example,
345// for a version "1.2.3-beta", segments will return a slice of
346// 1, 2, 3.
347func (v *Version) Segments64() []int64 {
348	result := make([]int64, len(v.segments))
349	copy(result, v.segments)
350	return result
351}
352
353// String returns the full version string included pre-release
354// and metadata information.
355//
356// This value is rebuilt according to the parsed segments and other
357// information. Therefore, ambiguities in the version string such as
358// prefixed zeroes (1.04.0 => 1.4.0), `v` prefix (v1.0.0 => 1.0.0), and
359// missing parts (1.0 => 1.0.0) will be made into a canonicalized form
360// as shown in the parenthesized examples.
361func (v *Version) String() string {
362	var buf bytes.Buffer
363	fmtParts := make([]string, len(v.segments))
364	for i, s := range v.segments {
365		// We can ignore err here since we've pre-parsed the values in segments
366		str := strconv.FormatInt(s, 10)
367		fmtParts[i] = str
368	}
369	fmt.Fprintf(&buf, strings.Join(fmtParts, "."))
370	if v.pre != "" {
371		fmt.Fprintf(&buf, "-%s", v.pre)
372	}
373	if v.metadata != "" {
374		fmt.Fprintf(&buf, "+%s", v.metadata)
375	}
376
377	return buf.String()
378}
379
380// Original returns the original parsed version as-is, including any
381// potential whitespace, `v` prefix, etc.
382func (v *Version) Original() string {
383	return v.original
384}
385