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