1// Copyright 2013-2015 CoreOS, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Semantic Versions http://semver.org
16package semver
17
18import (
19	"bytes"
20	"errors"
21	"fmt"
22	"strconv"
23	"strings"
24)
25
26type Version struct {
27	Major      int64
28	Minor      int64
29	Patch      int64
30	PreRelease PreRelease
31	Metadata   string
32}
33
34type PreRelease string
35
36func splitOff(input *string, delim string) (val string) {
37	parts := strings.SplitN(*input, delim, 2)
38
39	if len(parts) == 2 {
40		*input = parts[0]
41		val = parts[1]
42	}
43
44	return val
45}
46
47func New(version string) *Version {
48	return Must(NewVersion(version))
49}
50
51func NewVersion(version string) (*Version, error) {
52	v := Version{}
53
54	if err := v.Set(version); err != nil {
55		return nil, err
56	}
57
58	return &v, nil
59}
60
61// Must is a helper for wrapping NewVersion and will panic if err is not nil.
62func Must(v *Version, err error) *Version {
63	if err != nil {
64		panic(err)
65	}
66	return v
67}
68
69// Set parses and updates v from the given version string. Implements flag.Value
70func (v *Version) Set(version string) error {
71	metadata := splitOff(&version, "+")
72	preRelease := PreRelease(splitOff(&version, "-"))
73	dotParts := strings.SplitN(version, ".", 3)
74
75	if len(dotParts) != 3 {
76		return fmt.Errorf("%s is not in dotted-tri format", version)
77	}
78
79	parsed := make([]int64, 3, 3)
80
81	for i, v := range dotParts[:3] {
82		val, err := strconv.ParseInt(v, 10, 64)
83		parsed[i] = val
84		if err != nil {
85			return err
86		}
87	}
88
89	v.Metadata = metadata
90	v.PreRelease = preRelease
91	v.Major = parsed[0]
92	v.Minor = parsed[1]
93	v.Patch = parsed[2]
94	return nil
95}
96
97func (v Version) String() string {
98	var buffer bytes.Buffer
99
100	fmt.Fprintf(&buffer, "%d.%d.%d", v.Major, v.Minor, v.Patch)
101
102	if v.PreRelease != "" {
103		fmt.Fprintf(&buffer, "-%s", v.PreRelease)
104	}
105
106	if v.Metadata != "" {
107		fmt.Fprintf(&buffer, "+%s", v.Metadata)
108	}
109
110	return buffer.String()
111}
112
113func (v *Version) UnmarshalYAML(unmarshal func(interface{}) error) error {
114	var data string
115	if err := unmarshal(&data); err != nil {
116		return err
117	}
118	return v.Set(data)
119}
120
121func (v Version) MarshalJSON() ([]byte, error) {
122	return []byte(`"` + v.String() + `"`), nil
123}
124
125func (v *Version) UnmarshalJSON(data []byte) error {
126	l := len(data)
127	if l == 0 || string(data) == `""` {
128		return nil
129	}
130	if l < 2 || data[0] != '"' || data[l-1] != '"' {
131		return errors.New("invalid semver string")
132	}
133	return v.Set(string(data[1 : l-1]))
134}
135
136// Compare tests if v is less than, equal to, or greater than versionB,
137// returning -1, 0, or +1 respectively.
138func (v Version) Compare(versionB Version) int {
139	if cmp := recursiveCompare(v.Slice(), versionB.Slice()); cmp != 0 {
140		return cmp
141	}
142	return preReleaseCompare(v, versionB)
143}
144
145// Equal tests if v is equal to versionB.
146func (v Version) Equal(versionB Version) bool {
147	return v.Compare(versionB) == 0
148}
149
150// LessThan tests if v is less than versionB.
151func (v Version) LessThan(versionB Version) bool {
152	return v.Compare(versionB) < 0
153}
154
155// Slice converts the comparable parts of the semver into a slice of integers.
156func (v Version) Slice() []int64 {
157	return []int64{v.Major, v.Minor, v.Patch}
158}
159
160func (p PreRelease) Slice() []string {
161	preRelease := string(p)
162	return strings.Split(preRelease, ".")
163}
164
165func preReleaseCompare(versionA Version, versionB Version) int {
166	a := versionA.PreRelease
167	b := versionB.PreRelease
168
169	/* Handle the case where if two versions are otherwise equal it is the
170	 * one without a PreRelease that is greater */
171	if len(a) == 0 && (len(b) > 0) {
172		return 1
173	} else if len(b) == 0 && (len(a) > 0) {
174		return -1
175	}
176
177	// If there is a prerelease, check and compare each part.
178	return recursivePreReleaseCompare(a.Slice(), b.Slice())
179}
180
181func recursiveCompare(versionA []int64, versionB []int64) int {
182	if len(versionA) == 0 {
183		return 0
184	}
185
186	a := versionA[0]
187	b := versionB[0]
188
189	if a > b {
190		return 1
191	} else if a < b {
192		return -1
193	}
194
195	return recursiveCompare(versionA[1:], versionB[1:])
196}
197
198func recursivePreReleaseCompare(versionA []string, versionB []string) int {
199	// A larger set of pre-release fields has a higher precedence than a smaller set,
200	// if all of the preceding identifiers are equal.
201	if len(versionA) == 0 {
202		if len(versionB) > 0 {
203			return -1
204		}
205		return 0
206	} else if len(versionB) == 0 {
207		// We're longer than versionB so return 1.
208		return 1
209	}
210
211	a := versionA[0]
212	b := versionB[0]
213
214	aInt := false
215	bInt := false
216
217	aI, err := strconv.Atoi(versionA[0])
218	if err == nil {
219		aInt = true
220	}
221
222	bI, err := strconv.Atoi(versionB[0])
223	if err == nil {
224		bInt = true
225	}
226
227	// Handle Integer Comparison
228	if aInt && bInt {
229		if aI > bI {
230			return 1
231		} else if aI < bI {
232			return -1
233		}
234	}
235
236	// Handle String Comparison
237	if a > b {
238		return 1
239	} else if a < b {
240		return -1
241	}
242
243	return recursivePreReleaseCompare(versionA[1:], versionB[1:])
244}
245
246// BumpMajor increments the Major field by 1 and resets all other fields to their default values
247func (v *Version) BumpMajor() {
248	v.Major += 1
249	v.Minor = 0
250	v.Patch = 0
251	v.PreRelease = PreRelease("")
252	v.Metadata = ""
253}
254
255// BumpMinor increments the Minor field by 1 and resets all other fields to their default values
256func (v *Version) BumpMinor() {
257	v.Minor += 1
258	v.Patch = 0
259	v.PreRelease = PreRelease("")
260	v.Metadata = ""
261}
262
263// BumpPatch increments the Patch field by 1 and resets all other fields to their default values
264func (v *Version) BumpPatch() {
265	v.Patch += 1
266	v.PreRelease = PreRelease("")
267	v.Metadata = ""
268}
269