1/*
2Copyright 2016 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package version
18
19import (
20	"bytes"
21	"fmt"
22	"regexp"
23	"strconv"
24	"strings"
25)
26
27// Version is an opaque representation of a version number
28type Version struct {
29	components    []uint
30	semver        bool
31	preRelease    string
32	buildMetadata string
33}
34
35var (
36	// versionMatchRE splits a version string into numeric and "extra" parts
37	versionMatchRE = regexp.MustCompile(`^\s*v?([0-9]+(?:\.[0-9]+)*)(.*)*$`)
38	// extraMatchRE splits the "extra" part of versionMatchRE into semver pre-release and build metadata; it does not validate the "no leading zeroes" constraint for pre-release
39	extraMatchRE = regexp.MustCompile(`^(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?\s*$`)
40)
41
42func parse(str string, semver bool) (*Version, error) {
43	parts := versionMatchRE.FindStringSubmatch(str)
44	if parts == nil {
45		return nil, fmt.Errorf("could not parse %q as version", str)
46	}
47	numbers, extra := parts[1], parts[2]
48
49	components := strings.Split(numbers, ".")
50	if (semver && len(components) != 3) || (!semver && len(components) < 2) {
51		return nil, fmt.Errorf("illegal version string %q", str)
52	}
53
54	v := &Version{
55		components: make([]uint, len(components)),
56		semver:     semver,
57	}
58	for i, comp := range components {
59		if (i == 0 || semver) && strings.HasPrefix(comp, "0") && comp != "0" {
60			return nil, fmt.Errorf("illegal zero-prefixed version component %q in %q", comp, str)
61		}
62		num, err := strconv.ParseUint(comp, 10, 0)
63		if err != nil {
64			return nil, fmt.Errorf("illegal non-numeric version component %q in %q: %v", comp, str, err)
65		}
66		v.components[i] = uint(num)
67	}
68
69	if semver && extra != "" {
70		extraParts := extraMatchRE.FindStringSubmatch(extra)
71		if extraParts == nil {
72			return nil, fmt.Errorf("could not parse pre-release/metadata (%s) in version %q", extra, str)
73		}
74		v.preRelease, v.buildMetadata = extraParts[1], extraParts[2]
75
76		for _, comp := range strings.Split(v.preRelease, ".") {
77			if _, err := strconv.ParseUint(comp, 10, 0); err == nil {
78				if strings.HasPrefix(comp, "0") && comp != "0" {
79					return nil, fmt.Errorf("illegal zero-prefixed version component %q in %q", comp, str)
80				}
81			}
82		}
83	}
84
85	return v, nil
86}
87
88// ParseGeneric parses a "generic" version string. The version string must consist of two
89// or more dot-separated numeric fields (the first of which can't have leading zeroes),
90// followed by arbitrary uninterpreted data (which need not be separated from the final
91// numeric field by punctuation). For convenience, leading and trailing whitespace is
92// ignored, and the version can be preceded by the letter "v". See also ParseSemantic.
93func ParseGeneric(str string) (*Version, error) {
94	return parse(str, false)
95}
96
97// MustParseGeneric is like ParseGeneric except that it panics on error
98func MustParseGeneric(str string) *Version {
99	v, err := ParseGeneric(str)
100	if err != nil {
101		panic(err)
102	}
103	return v
104}
105
106// ParseSemantic parses a version string that exactly obeys the syntax and semantics of
107// the "Semantic Versioning" specification (http://semver.org/) (although it ignores
108// leading and trailing whitespace, and allows the version to be preceded by "v"). For
109// version strings that are not guaranteed to obey the Semantic Versioning syntax, use
110// ParseGeneric.
111func ParseSemantic(str string) (*Version, error) {
112	return parse(str, true)
113}
114
115// MustParseSemantic is like ParseSemantic except that it panics on error
116func MustParseSemantic(str string) *Version {
117	v, err := ParseSemantic(str)
118	if err != nil {
119		panic(err)
120	}
121	return v
122}
123
124// Major returns the major release number
125func (v *Version) Major() uint {
126	return v.components[0]
127}
128
129// Minor returns the minor release number
130func (v *Version) Minor() uint {
131	return v.components[1]
132}
133
134// Patch returns the patch release number if v is a Semantic Version, or 0
135func (v *Version) Patch() uint {
136	if len(v.components) < 3 {
137		return 0
138	}
139	return v.components[2]
140}
141
142// BuildMetadata returns the build metadata, if v is a Semantic Version, or ""
143func (v *Version) BuildMetadata() string {
144	return v.buildMetadata
145}
146
147// PreRelease returns the prerelease metadata, if v is a Semantic Version, or ""
148func (v *Version) PreRelease() string {
149	return v.preRelease
150}
151
152// Components returns the version number components
153func (v *Version) Components() []uint {
154	return v.components
155}
156
157// WithMajor returns copy of the version object with requested major number
158func (v *Version) WithMajor(major uint) *Version {
159	result := *v
160	result.components = []uint{major, v.Minor(), v.Patch()}
161	return &result
162}
163
164// WithMinor returns copy of the version object with requested minor number
165func (v *Version) WithMinor(minor uint) *Version {
166	result := *v
167	result.components = []uint{v.Major(), minor, v.Patch()}
168	return &result
169}
170
171// WithPatch returns copy of the version object with requested patch number
172func (v *Version) WithPatch(patch uint) *Version {
173	result := *v
174	result.components = []uint{v.Major(), v.Minor(), patch}
175	return &result
176}
177
178// WithPreRelease returns copy of the version object with requested prerelease
179func (v *Version) WithPreRelease(preRelease string) *Version {
180	result := *v
181	result.components = []uint{v.Major(), v.Minor(), v.Patch()}
182	result.preRelease = preRelease
183	return &result
184}
185
186// WithBuildMetadata returns copy of the version object with requested buildMetadata
187func (v *Version) WithBuildMetadata(buildMetadata string) *Version {
188	result := *v
189	result.components = []uint{v.Major(), v.Minor(), v.Patch()}
190	result.buildMetadata = buildMetadata
191	return &result
192}
193
194// String converts a Version back to a string; note that for versions parsed with
195// ParseGeneric, this will not include the trailing uninterpreted portion of the version
196// number.
197func (v *Version) String() string {
198	if v == nil {
199		return "<nil>"
200	}
201	var buffer bytes.Buffer
202
203	for i, comp := range v.components {
204		if i > 0 {
205			buffer.WriteString(".")
206		}
207		buffer.WriteString(fmt.Sprintf("%d", comp))
208	}
209	if v.preRelease != "" {
210		buffer.WriteString("-")
211		buffer.WriteString(v.preRelease)
212	}
213	if v.buildMetadata != "" {
214		buffer.WriteString("+")
215		buffer.WriteString(v.buildMetadata)
216	}
217
218	return buffer.String()
219}
220
221// compareInternal returns -1 if v is less than other, 1 if it is greater than other, or 0
222// if they are equal
223func (v *Version) compareInternal(other *Version) int {
224
225	vLen := len(v.components)
226	oLen := len(other.components)
227	for i := 0; i < vLen && i < oLen; i++ {
228		switch {
229		case other.components[i] < v.components[i]:
230			return 1
231		case other.components[i] > v.components[i]:
232			return -1
233		}
234	}
235
236	// If components are common but one has more items and they are not zeros, it is bigger
237	switch {
238	case oLen < vLen && !onlyZeros(v.components[oLen:]):
239		return 1
240	case oLen > vLen && !onlyZeros(other.components[vLen:]):
241		return -1
242	}
243
244	if !v.semver || !other.semver {
245		return 0
246	}
247
248	switch {
249	case v.preRelease == "" && other.preRelease != "":
250		return 1
251	case v.preRelease != "" && other.preRelease == "":
252		return -1
253	case v.preRelease == other.preRelease: // includes case where both are ""
254		return 0
255	}
256
257	vPR := strings.Split(v.preRelease, ".")
258	oPR := strings.Split(other.preRelease, ".")
259	for i := 0; i < len(vPR) && i < len(oPR); i++ {
260		vNum, err := strconv.ParseUint(vPR[i], 10, 0)
261		if err == nil {
262			oNum, err := strconv.ParseUint(oPR[i], 10, 0)
263			if err == nil {
264				switch {
265				case oNum < vNum:
266					return 1
267				case oNum > vNum:
268					return -1
269				default:
270					continue
271				}
272			}
273		}
274		if oPR[i] < vPR[i] {
275			return 1
276		} else if oPR[i] > vPR[i] {
277			return -1
278		}
279	}
280
281	switch {
282	case len(oPR) < len(vPR):
283		return 1
284	case len(oPR) > len(vPR):
285		return -1
286	}
287
288	return 0
289}
290
291// returns false if array contain any non-zero element
292func onlyZeros(array []uint) bool {
293	for _, num := range array {
294		if num != 0 {
295			return false
296		}
297	}
298	return true
299}
300
301// AtLeast tests if a version is at least equal to a given minimum version. If both
302// Versions are Semantic Versions, this will use the Semantic Version comparison
303// algorithm. Otherwise, it will compare only the numeric components, with non-present
304// components being considered "0" (ie, "1.4" is equal to "1.4.0").
305func (v *Version) AtLeast(min *Version) bool {
306	return v.compareInternal(min) != -1
307}
308
309// LessThan tests if a version is less than a given version. (It is exactly the opposite
310// of AtLeast, for situations where asking "is v too old?" makes more sense than asking
311// "is v new enough?".)
312func (v *Version) LessThan(other *Version) bool {
313	return v.compareInternal(other) == -1
314}
315
316// Compare compares v against a version string (which will be parsed as either Semantic
317// or non-Semantic depending on v). On success it returns -1 if v is less than other, 1 if
318// it is greater than other, or 0 if they are equal.
319func (v *Version) Compare(other string) (int, error) {
320	ov, err := parse(other, v.semver)
321	if err != nil {
322		return 0, err
323	}
324	return v.compareInternal(ov), nil
325}
326