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