blob: 110fc23e1520bfab7824bab65cb58e6f44780fc2 [file] [log] [blame]
khenaidooab1f7bd2019-11-14 14:00:27 -05001// 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}