github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/util/version/version.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package version 18 19 import ( 20 "bytes" 21 "fmt" 22 "regexp" 23 "strconv" 24 "strings" 25 ) 26 27 // Version is an opaque representation of a version number 28 type Version struct { 29 components []uint 30 semver bool 31 preRelease string 32 buildMetadata string 33 } 34 35 var ( 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 42 func 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. 93 func ParseGeneric(str string) (*Version, error) { 94 return parse(str, false) 95 } 96 97 // MustParseGeneric is like ParseGeneric except that it panics on error 98 func 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. 111 func ParseSemantic(str string) (*Version, error) { 112 return parse(str, true) 113 } 114 115 // MustParseSemantic is like ParseSemantic except that it panics on error 116 func 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 125 func (v *Version) Major() uint { 126 return v.components[0] 127 } 128 129 // Minor returns the minor release number 130 func (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 135 func (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 "" 143 func (v *Version) BuildMetadata() string { 144 return v.buildMetadata 145 } 146 147 // PreRelease returns the prerelease metadata, if v is a Semantic Version, or "" 148 func (v *Version) PreRelease() string { 149 return v.preRelease 150 } 151 152 // Components returns the version number components 153 func (v *Version) Components() []uint { 154 return v.components 155 } 156 157 // WithMajor returns copy of the version object with requested major number 158 func (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 165 func (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 172 func (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 179 func (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 187 func (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. 197 func (v *Version) String() string { 198 if v == nil { 199 return "<nil>" 200 } 201 var buffer bytes.Buffer 202 203 for i, comp := range v.components { 204 if i > 0 { 205 buffer.WriteString(".") 206 } 207 buffer.WriteString(fmt.Sprintf("%d", comp)) 208 } 209 if v.preRelease != "" { 210 buffer.WriteString("-") 211 buffer.WriteString(v.preRelease) 212 } 213 if v.buildMetadata != "" { 214 buffer.WriteString("+") 215 buffer.WriteString(v.buildMetadata) 216 } 217 218 return buffer.String() 219 } 220 221 // compareInternal returns -1 if v is less than other, 1 if it is greater than other, or 0 222 // if they are equal 223 func (v *Version) compareInternal(other *Version) int { 224 225 vLen := len(v.components) 226 oLen := len(other.components) 227 for i := 0; i < vLen && i < oLen; i++ { 228 switch { 229 case other.components[i] < v.components[i]: 230 return 1 231 case other.components[i] > v.components[i]: 232 return -1 233 } 234 } 235 236 // If components are common but one has more items and they are not zeros, it is bigger 237 switch { 238 case oLen < vLen && !onlyZeros(v.components[oLen:]): 239 return 1 240 case oLen > vLen && !onlyZeros(other.components[vLen:]): 241 return -1 242 } 243 244 if !v.semver || !other.semver { 245 return 0 246 } 247 248 switch { 249 case v.preRelease == "" && other.preRelease != "": 250 return 1 251 case v.preRelease != "" && other.preRelease == "": 252 return -1 253 case v.preRelease == other.preRelease: // includes case where both are "" 254 return 0 255 } 256 257 vPR := strings.Split(v.preRelease, ".") 258 oPR := strings.Split(other.preRelease, ".") 259 for i := 0; i < len(vPR) && i < len(oPR); i++ { 260 vNum, err := strconv.ParseUint(vPR[i], 10, 0) 261 if err == nil { 262 oNum, err := strconv.ParseUint(oPR[i], 10, 0) 263 if err == nil { 264 switch { 265 case oNum < vNum: 266 return 1 267 case oNum > vNum: 268 return -1 269 default: 270 continue 271 } 272 } 273 } 274 if oPR[i] < vPR[i] { 275 return 1 276 } else if oPR[i] > vPR[i] { 277 return -1 278 } 279 } 280 281 switch { 282 case len(oPR) < len(vPR): 283 return 1 284 case len(oPR) > len(vPR): 285 return -1 286 } 287 288 return 0 289 } 290 291 // returns false if array contain any non-zero element 292 func onlyZeros(array []uint) bool { 293 for _, num := range array { 294 if num != 0 { 295 return false 296 } 297 } 298 return true 299 } 300 301 // AtLeast tests if a version is at least equal to a given minimum version. If both 302 // Versions are Semantic Versions, this will use the Semantic Version comparison 303 // algorithm. Otherwise, it will compare only the numeric components, with non-present 304 // components being considered "0" (ie, "1.4" is equal to "1.4.0"). 305 func (v *Version) AtLeast(min *Version) bool { 306 return v.compareInternal(min) != -1 307 } 308 309 // LessThan tests if a version is less than a given version. (It is exactly the opposite 310 // of AtLeast, for situations where asking "is v too old?" makes more sense than asking 311 // "is v new enough?".) 312 func (v *Version) LessThan(other *Version) bool { 313 return v.compareInternal(other) == -1 314 } 315 316 // Compare compares v against a version string (which will be parsed as either Semantic 317 // or non-Semantic depending on v). On success it returns -1 if v is less than other, 1 if 318 // it is greater than other, or 0 if they are equal. 319 func (v *Version) Compare(other string) (int, error) { 320 ov, err := parse(other, v.semver) 321 if err != nil { 322 return 0, err 323 } 324 return v.compareInternal(ov), nil 325 }