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 opqaue 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	var buffer bytes.Buffer
199
200	for i, comp := range v.components {
201		if i > 0 {
202			buffer.WriteString(".")
203		}
204		buffer.WriteString(fmt.Sprintf("%d", comp))
205	}
206	if v.preRelease != "" {
207		buffer.WriteString("-")
208		buffer.WriteString(v.preRelease)
209	}
210	if v.buildMetadata != "" {
211		buffer.WriteString("+")
212		buffer.WriteString(v.buildMetadata)
213	}
214
215	return buffer.String()
216}
217
218// compareInternal returns -1 if v is less than other, 1 if it is greater than other, or 0
219// if they are equal
220func (v *Version) compareInternal(other *Version) int {
221
222	vLen := len(v.components)
223	oLen := len(other.components)
224	for i := 0; i < vLen && i < oLen; i++ {
225		switch {
226		case other.components[i] < v.components[i]:
227			return 1
228		case other.components[i] > v.components[i]:
229			return -1
230		}
231	}
232
233	// If components are common but one has more items and they are not zeros, it is bigger
234	switch {
235	case oLen < vLen && !onlyZeros(v.components[oLen:]):
236		return 1
237	case oLen > vLen && !onlyZeros(other.components[vLen:]):
238		return -1
239	}
240
241	if !v.semver || !other.semver {
242		return 0
243	}
244
245	switch {
246	case v.preRelease == "" && other.preRelease != "":
247		return 1
248	case v.preRelease != "" && other.preRelease == "":
249		return -1
250	case v.preRelease == other.preRelease: // includes case where both are ""
251		return 0
252	}
253
254	vPR := strings.Split(v.preRelease, ".")
255	oPR := strings.Split(other.preRelease, ".")
256	for i := 0; i < len(vPR) && i < len(oPR); i++ {
257		vNum, err := strconv.ParseUint(vPR[i], 10, 0)
258		if err == nil {
259			oNum, err := strconv.ParseUint(oPR[i], 10, 0)
260			if err == nil {
261				switch {
262				case oNum < vNum:
263					return 1
264				case oNum > vNum:
265					return -1
266				default:
267					continue
268				}
269			}
270		}
271		if oPR[i] < vPR[i] {
272			return 1
273		} else if oPR[i] > vPR[i] {
274			return -1
275		}
276	}
277
278	switch {
279	case len(oPR) < len(vPR):
280		return 1
281	case len(oPR) > len(vPR):
282		return -1
283	}
284
285	return 0
286}
287
288// returns false if array contain any non-zero element
289func onlyZeros(array []uint) bool {
290	for _, num := range array {
291		if num != 0 {
292			return false
293		}
294	}
295	return true
296}
297
298// AtLeast tests if a version is at least equal to a given minimum version. If both
299// Versions are Semantic Versions, this will use the Semantic Version comparison
300// algorithm. Otherwise, it will compare only the numeric components, with non-present
301// components being considered "0" (ie, "1.4" is equal to "1.4.0").
302func (v *Version) AtLeast(min *Version) bool {
303	return v.compareInternal(min) != -1
304}
305
306// LessThan tests if a version is less than a given version. (It is exactly the opposite
307// of AtLeast, for situations where asking "is v too old?" makes more sense than asking
308// "is v new enough?".)
309func (v *Version) LessThan(other *Version) bool {
310	return v.compareInternal(other) == -1
311}
312
313// Compare compares v against a version string (which will be parsed as either Semantic
314// or non-Semantic depending on v). On success it returns -1 if v is less than other, 1 if
315// it is greater than other, or 0 if they are equal.
316func (v *Version) Compare(other string) (int, error) {
317	ov, err := parse(other, v.semver)
318	if err != nil {
319		return 0, err
320	}
321	return v.compareInternal(ov), nil
322}
323