github.com/blend/go-sdk@v1.20220411.3/semver/version.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package semver 9 10 import ( 11 "bytes" 12 "fmt" 13 "reflect" 14 "regexp" 15 "strconv" 16 "strings" 17 ) 18 19 // The compiled regular expression used to test the validity of a version. 20 var versionRegexp *regexp.Regexp 21 22 // VersionRegexpRaw is the raw regular expression string used for 23 // testing the validity of a version. 24 const VersionRegexpRaw string = `v?([0-9]+(\.[0-9]+)*?)` + 25 `(-?([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` + 26 `(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` + 27 `?` 28 29 // Version represents a single version. 30 type Version struct { 31 metadata string 32 pre string 33 segments []int64 34 si int 35 } 36 37 func init() { 38 versionRegexp = regexp.MustCompile("^" + VersionRegexpRaw + "$") 39 } 40 41 // NewVersion parses the given version and returns a new Version. 42 func NewVersion(v string) (*Version, error) { 43 matches := versionRegexp.FindStringSubmatch(v) 44 if matches == nil { 45 return nil, fmt.Errorf("malformed version: %s", v) 46 } 47 segmentsStr := strings.Split(matches[1], ".") 48 segments := make([]int64, len(segmentsStr)) 49 si := 0 50 for i, str := range segmentsStr { 51 val, err := strconv.ParseInt(str, 10, 64) 52 if err != nil { 53 return nil, fmt.Errorf( 54 "error parsing version: %s", err) 55 } 56 57 segments[i] = int64(val) 58 si++ 59 } 60 61 for i := len(segments); i < 3; i++ { 62 segments = append(segments, 0) 63 } 64 65 return &Version{ 66 metadata: matches[7], 67 pre: matches[4], 68 segments: segments, 69 si: si, 70 }, nil 71 } 72 73 // Must is a helper that wraps a call to a function returning (*Version, error) 74 // and panics if error is non-nil. 75 func Must(v *Version, err error) *Version { 76 if err != nil { 77 panic(err) 78 } 79 80 return v 81 } 82 83 // Compare compares this version to another version. This 84 // returns -1, 0, or 1 if this version is smaller, equal, 85 // or larger than the other version, respectively. 86 // 87 // If you want boolean results, use the LessThan, Equal, 88 // or GreaterThan methods. 89 func (v *Version) Compare(other *Version) int { 90 // A quick, efficient equality check 91 if v.String() == other.String() { 92 return 0 93 } 94 95 segmentsSelf := v.Segments64() 96 segmentsOther := other.Segments64() 97 98 // If the segments are the same, we must compare on prerelease info 99 if reflect.DeepEqual(segmentsSelf, segmentsOther) { 100 preSelf := v.Prerelease() 101 preOther := other.Prerelease() 102 if preSelf == "" && preOther == "" { 103 return 0 104 } 105 if preSelf == "" { 106 return 1 107 } 108 if preOther == "" { 109 return -1 110 } 111 112 return comparePrereleases(preSelf, preOther) 113 } 114 115 // Get the highest specificity (hS), or if they're equal, just use segmentSelf length 116 lenSelf := len(segmentsSelf) 117 lenOther := len(segmentsOther) 118 hS := lenSelf 119 if lenSelf < lenOther { 120 hS = lenOther 121 } 122 // Compare the segments 123 // Because a constraint could have more/less specificity than the version it's 124 // checking, we need to account for a lopsided or jagged comparison 125 for i := 0; i < hS; i++ { 126 if i > lenSelf-1 { 127 // This means Self had the lower specificity 128 // Check to see if the remaining segments in Other are all zeros 129 if !allZero(segmentsOther[i:]) { 130 // if not, it means that Other has to be greater than Self 131 return -1 132 } 133 break 134 } else if i > lenOther-1 { 135 // this means Other had the lower specificity 136 // Check to see if the remaining segments in Self are all zeros - 137 if !allZero(segmentsSelf[i:]) { 138 //if not, it means that Self has to be greater than Other 139 return 1 140 } 141 break 142 } 143 lhs := segmentsSelf[i] 144 rhs := segmentsOther[i] 145 if lhs == rhs { 146 continue 147 } else if lhs < rhs { 148 return -1 149 } 150 // Otherwis, rhs was > lhs, they're not equal 151 return 1 152 } 153 154 // if we got this far, they're equal 155 return 0 156 } 157 158 func allZero(segs []int64) bool { 159 for _, s := range segs { 160 if s != 0 { 161 return false 162 } 163 } 164 return true 165 } 166 167 func comparePart(preSelf string, preOther string) int { 168 if preSelf == preOther { 169 return 0 170 } 171 172 var selfInt int64 173 selfNumeric := true 174 selfInt, err := strconv.ParseInt(preSelf, 10, 64) 175 if err != nil { 176 selfNumeric = false 177 } 178 179 var otherInt int64 180 otherNumeric := true 181 otherInt, err = strconv.ParseInt(preOther, 10, 64) 182 if err != nil { 183 otherNumeric = false 184 } 185 186 // if a part is empty, we use the other to decide 187 if preSelf == "" { 188 if otherNumeric { 189 return -1 190 } 191 return 1 192 } 193 194 if preOther == "" { 195 if selfNumeric { 196 return 1 197 } 198 return -1 199 } 200 201 if selfNumeric && !otherNumeric { 202 return -1 203 } else if !selfNumeric && otherNumeric { 204 return 1 205 } else if !selfNumeric && !otherNumeric && preSelf > preOther { 206 return 1 207 } else if selfInt > otherInt { 208 return 1 209 } 210 211 return -1 212 } 213 214 func comparePrereleases(v string, other string) int { 215 // the same pre release! 216 if v == other { 217 return 0 218 } 219 220 // split both pre releases for analyze their parts 221 selfPreReleaseMeta := strings.Split(v, ".") 222 otherPreReleaseMeta := strings.Split(other, ".") 223 224 selfPreReleaseLen := len(selfPreReleaseMeta) 225 otherPreReleaseLen := len(otherPreReleaseMeta) 226 227 biggestLen := otherPreReleaseLen 228 if selfPreReleaseLen > otherPreReleaseLen { 229 biggestLen = selfPreReleaseLen 230 } 231 232 // loop for parts to find the first difference 233 for i := 0; i < biggestLen; i = i + 1 { 234 partSelfPre := "" 235 if i < selfPreReleaseLen { 236 partSelfPre = selfPreReleaseMeta[i] 237 } 238 239 partOtherPre := "" 240 if i < otherPreReleaseLen { 241 partOtherPre = otherPreReleaseMeta[i] 242 } 243 244 compare := comparePart(partSelfPre, partOtherPre) 245 // if parts are equals, continue the loop 246 if compare != 0 { 247 return compare 248 } 249 } 250 251 return 0 252 } 253 254 // Equal tests if two versions are equal. 255 func (v *Version) Equal(o *Version) bool { 256 return v.Compare(o) == 0 257 } 258 259 // GreaterThan tests if this version is greater than another version. 260 func (v *Version) GreaterThan(o *Version) bool { 261 return v.Compare(o) > 0 262 } 263 264 // LessThan tests if this version is less than another version. 265 func (v *Version) LessThan(o *Version) bool { 266 return v.Compare(o) < 0 267 } 268 269 // Metadata returns any metadata that was part of the version 270 // string. 271 // 272 // Metadata is anything that comes after the "+" in the version. 273 // For example, with "1.2.3+beta", the metadata is "beta". 274 func (v *Version) Metadata() string { 275 return v.metadata 276 } 277 278 // Prerelease returns any prerelease data that is part of the version, 279 // or blank if there is no prerelease data. 280 // 281 // Prerelease information is anything that comes after the "-" in the 282 // version (but before any metadata). For example, with "1.2.3-beta", 283 // the prerelease information is "beta". 284 func (v *Version) Prerelease() string { 285 return v.pre 286 } 287 288 // Segments returns the numeric segments of the version as a slice of ints. 289 // 290 // This excludes any metadata or pre-release information. For example, 291 // for a version "1.2.3-beta", segments will return a slice of 292 // 1, 2, 3. 293 func (v *Version) Segments() []int { 294 segmentSlice := make([]int, len(v.segments)) 295 for i, v := range v.segments { 296 segmentSlice[i] = int(v) 297 } 298 return segmentSlice 299 } 300 301 // Segments64 returns the numeric segments of the version as a slice of int64s. 302 // 303 // This excludes any metadata or pre-release information. For example, 304 // for a version "1.2.3-beta", segments will return a slice of 305 // 1, 2, 3. 306 func (v *Version) Segments64() []int64 { 307 return v.segments 308 } 309 310 // String returns the full version string included pre-release 311 // and metadata information. 312 func (v *Version) String() string { 313 var buf bytes.Buffer 314 fmtParts := make([]string, len(v.segments)) 315 for i, s := range v.segments { 316 // We can ignore err here since we've pre-parsed the values in segments 317 str := strconv.FormatInt(s, 10) 318 fmtParts[i] = str 319 } 320 fmt.Fprint(&buf, strings.Join(fmtParts, ".")) 321 if v.pre != "" { 322 fmt.Fprintf(&buf, "-%s", v.pre) 323 } 324 if v.metadata != "" { 325 fmt.Fprintf(&buf, "+%s", v.metadata) 326 } 327 328 return buf.String() 329 } 330 331 // Major returns the Major segment, or the highest order segment. 332 func (v *Version) Major() (major int64) { 333 if len(v.segments) < 1 { 334 return 335 } 336 major = v.segments[0] 337 return 338 } 339 340 // Minor returns the Minor segment, or the second highest order segment. 341 func (v *Version) Minor() (minor int64) { 342 if len(v.segments) < 2 { 343 return 344 } 345 minor = v.segments[1] 346 return 347 } 348 349 // Patch returns the Patch segment, or the third highest order segment. 350 func (v *Version) Patch() (patch int64) { 351 if len(v.segments) < 3 { 352 return 353 } 354 patch = v.segments[2] 355 return 356 } 357 358 // BumpMajor increments the Major field by 1 and resets all other fields to their default values 359 func (v *Version) BumpMajor() { 360 v.segments = []int64{v.Major() + 1, 0, 0} 361 v.pre = "" 362 v.metadata = "" 363 } 364 365 // BumpMinor increments the Minor field by 1 and resets all other fields to their default values 366 func (v *Version) BumpMinor() { 367 v.segments = []int64{v.Major(), v.Minor() + 1, 0} 368 v.pre = "" 369 v.metadata = "" 370 } 371 372 // BumpPatch increments the Patch field by 1 and resets all other fields to their default values 373 func (v *Version) BumpPatch() { 374 v.segments = []int64{v.Major(), v.Minor(), v.Patch() + 1} 375 v.pre = "" 376 v.metadata = "" 377 } 378 379 // Collection is a type that implements the sort.Interface interface 380 // so that versions can be sorted. 381 type Collection []*Version 382 383 func (v Collection) Len() int { 384 return len(v) 385 } 386 387 func (v Collection) Less(i, j int) bool { 388 return v[i].LessThan(v[j]) 389 } 390 391 func (v Collection) Swap(i, j int) { 392 v[i], v[j] = v[j], v[i] 393 }