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