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