Scott Baker | 1fe7273 | 2019-10-21 10:58:51 -0700 | [diff] [blame^] | 1 | package version |
| 2 | |
| 3 | import ( |
| 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. |
| 13 | var ( |
| 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. |
| 20 | const ( |
| 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. |
| 34 | type Version struct { |
| 35 | metadata string |
| 36 | pre string |
| 37 | segments []int64 |
| 38 | si int |
| 39 | original string |
| 40 | } |
| 41 | |
| 42 | func 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. |
| 49 | func 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/ |
| 56 | func NewSemver(v string) (*Version, error) { |
| 57 | return newVersion(v, semverRegexp) |
| 58 | } |
| 59 | |
| 60 | func 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. |
| 102 | func 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. |
| 116 | func (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 | |
| 185 | func allZero(segs []int64) bool { |
| 186 | for _, s := range segs { |
| 187 | if s != 0 { |
| 188 | return false |
| 189 | } |
| 190 | } |
| 191 | return true |
| 192 | } |
| 193 | |
| 194 | func 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 | |
| 241 | func 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. |
| 282 | func (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. |
| 287 | func (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. |
| 292 | func (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. |
| 297 | func (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. |
| 302 | func (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". |
| 311 | func (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". |
| 321 | func (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. |
| 330 | func (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. |
| 343 | func (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. |
| 357 | func (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. |
| 378 | func (v *Version) Original() string { |
| 379 | return v.original |
| 380 | } |