semver.go (9058B)
1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package semver implements comparison of semantic version strings. 6 // In this package, semantic version strings must begin with a leading "v", 7 // as in "v1.0.0". 8 // 9 // The general form of a semantic version string accepted by this package is 10 // 11 // vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]] 12 // 13 // where square brackets indicate optional parts of the syntax; 14 // MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros; 15 // PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers 16 // using only alphanumeric characters and hyphens; and 17 // all-numeric PRERELEASE identifiers must not have leading zeros. 18 // 19 // This package follows Semantic Versioning 2.0.0 (see semver.org) 20 // with two exceptions. First, it requires the "v" prefix. Second, it recognizes 21 // vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes) 22 // as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0. 23 package semver 24 25 import "sort" 26 27 // parsed returns the parsed form of a semantic version string. 28 type parsed struct { 29 major string 30 minor string 31 patch string 32 short string 33 prerelease string 34 build string 35 } 36 37 // IsValid reports whether v is a valid semantic version string. 38 func IsValid(v string) bool { 39 _, ok := parse(v) 40 return ok 41 } 42 43 // Canonical returns the canonical formatting of the semantic version v. 44 // It fills in any missing .MINOR or .PATCH and discards build metadata. 45 // Two semantic versions compare equal only if their canonical formattings 46 // are identical strings. 47 // The canonical invalid semantic version is the empty string. 48 func Canonical(v string) string { 49 p, ok := parse(v) 50 if !ok { 51 return "" 52 } 53 if p.build != "" { 54 return v[:len(v)-len(p.build)] 55 } 56 if p.short != "" { 57 return v + p.short 58 } 59 return v 60 } 61 62 // Major returns the major version prefix of the semantic version v. 63 // For example, Major("v2.1.0") == "v2". 64 // If v is an invalid semantic version string, Major returns the empty string. 65 func Major(v string) string { 66 pv, ok := parse(v) 67 if !ok { 68 return "" 69 } 70 return v[:1+len(pv.major)] 71 } 72 73 // MajorMinor returns the major.minor version prefix of the semantic version v. 74 // For example, MajorMinor("v2.1.0") == "v2.1". 75 // If v is an invalid semantic version string, MajorMinor returns the empty string. 76 func MajorMinor(v string) string { 77 pv, ok := parse(v) 78 if !ok { 79 return "" 80 } 81 i := 1 + len(pv.major) 82 if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor { 83 return v[:j] 84 } 85 return v[:i] + "." + pv.minor 86 } 87 88 // Prerelease returns the prerelease suffix of the semantic version v. 89 // For example, Prerelease("v2.1.0-pre+meta") == "-pre". 90 // If v is an invalid semantic version string, Prerelease returns the empty string. 91 func Prerelease(v string) string { 92 pv, ok := parse(v) 93 if !ok { 94 return "" 95 } 96 return pv.prerelease 97 } 98 99 // Build returns the build suffix of the semantic version v. 100 // For example, Build("v2.1.0+meta") == "+meta". 101 // If v is an invalid semantic version string, Build returns the empty string. 102 func Build(v string) string { 103 pv, ok := parse(v) 104 if !ok { 105 return "" 106 } 107 return pv.build 108 } 109 110 // Compare returns an integer comparing two versions according to 111 // semantic version precedence. 112 // The result will be 0 if v == w, -1 if v < w, or +1 if v > w. 113 // 114 // An invalid semantic version string is considered less than a valid one. 115 // All invalid semantic version strings compare equal to each other. 116 func Compare(v, w string) int { 117 pv, ok1 := parse(v) 118 pw, ok2 := parse(w) 119 if !ok1 && !ok2 { 120 return 0 121 } 122 if !ok1 { 123 return -1 124 } 125 if !ok2 { 126 return +1 127 } 128 if c := compareInt(pv.major, pw.major); c != 0 { 129 return c 130 } 131 if c := compareInt(pv.minor, pw.minor); c != 0 { 132 return c 133 } 134 if c := compareInt(pv.patch, pw.patch); c != 0 { 135 return c 136 } 137 return comparePrerelease(pv.prerelease, pw.prerelease) 138 } 139 140 // Max canonicalizes its arguments and then returns the version string 141 // that compares greater. 142 // 143 // Deprecated: use Compare instead. In most cases, returning a canonicalized 144 // version is not expected or desired. 145 func Max(v, w string) string { 146 v = Canonical(v) 147 w = Canonical(w) 148 if Compare(v, w) > 0 { 149 return v 150 } 151 return w 152 } 153 154 // ByVersion implements sort.Interface for sorting semantic version strings. 155 type ByVersion []string 156 157 func (vs ByVersion) Len() int { return len(vs) } 158 func (vs ByVersion) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] } 159 func (vs ByVersion) Less(i, j int) bool { 160 cmp := Compare(vs[i], vs[j]) 161 if cmp != 0 { 162 return cmp < 0 163 } 164 return vs[i] < vs[j] 165 } 166 167 // Sort sorts a list of semantic version strings using ByVersion. 168 func Sort(list []string) { 169 sort.Sort(ByVersion(list)) 170 } 171 172 func parse(v string) (p parsed, ok bool) { 173 if v == "" || v[0] != 'v' { 174 return 175 } 176 p.major, v, ok = parseInt(v[1:]) 177 if !ok { 178 return 179 } 180 if v == "" { 181 p.minor = "0" 182 p.patch = "0" 183 p.short = ".0.0" 184 return 185 } 186 if v[0] != '.' { 187 ok = false 188 return 189 } 190 p.minor, v, ok = parseInt(v[1:]) 191 if !ok { 192 return 193 } 194 if v == "" { 195 p.patch = "0" 196 p.short = ".0" 197 return 198 } 199 if v[0] != '.' { 200 ok = false 201 return 202 } 203 p.patch, v, ok = parseInt(v[1:]) 204 if !ok { 205 return 206 } 207 if len(v) > 0 && v[0] == '-' { 208 p.prerelease, v, ok = parsePrerelease(v) 209 if !ok { 210 return 211 } 212 } 213 if len(v) > 0 && v[0] == '+' { 214 p.build, v, ok = parseBuild(v) 215 if !ok { 216 return 217 } 218 } 219 if v != "" { 220 ok = false 221 return 222 } 223 ok = true 224 return 225 } 226 227 func parseInt(v string) (t, rest string, ok bool) { 228 if v == "" { 229 return 230 } 231 if v[0] < '0' || '9' < v[0] { 232 return 233 } 234 i := 1 235 for i < len(v) && '0' <= v[i] && v[i] <= '9' { 236 i++ 237 } 238 if v[0] == '0' && i != 1 { 239 return 240 } 241 return v[:i], v[i:], true 242 } 243 244 func parsePrerelease(v string) (t, rest string, ok bool) { 245 // "A pre-release version MAY be denoted by appending a hyphen and 246 // a series of dot separated identifiers immediately following the patch version. 247 // Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. 248 // Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes." 249 if v == "" || v[0] != '-' { 250 return 251 } 252 i := 1 253 start := 1 254 for i < len(v) && v[i] != '+' { 255 if !isIdentChar(v[i]) && v[i] != '.' { 256 return 257 } 258 if v[i] == '.' { 259 if start == i || isBadNum(v[start:i]) { 260 return 261 } 262 start = i + 1 263 } 264 i++ 265 } 266 if start == i || isBadNum(v[start:i]) { 267 return 268 } 269 return v[:i], v[i:], true 270 } 271 272 func parseBuild(v string) (t, rest string, ok bool) { 273 if v == "" || v[0] != '+' { 274 return 275 } 276 i := 1 277 start := 1 278 for i < len(v) { 279 if !isIdentChar(v[i]) && v[i] != '.' { 280 return 281 } 282 if v[i] == '.' { 283 if start == i { 284 return 285 } 286 start = i + 1 287 } 288 i++ 289 } 290 if start == i { 291 return 292 } 293 return v[:i], v[i:], true 294 } 295 296 func isIdentChar(c byte) bool { 297 return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-' 298 } 299 300 func isBadNum(v string) bool { 301 i := 0 302 for i < len(v) && '0' <= v[i] && v[i] <= '9' { 303 i++ 304 } 305 return i == len(v) && i > 1 && v[0] == '0' 306 } 307 308 func isNum(v string) bool { 309 i := 0 310 for i < len(v) && '0' <= v[i] && v[i] <= '9' { 311 i++ 312 } 313 return i == len(v) 314 } 315 316 func compareInt(x, y string) int { 317 if x == y { 318 return 0 319 } 320 if len(x) < len(y) { 321 return -1 322 } 323 if len(x) > len(y) { 324 return +1 325 } 326 if x < y { 327 return -1 328 } else { 329 return +1 330 } 331 } 332 333 func comparePrerelease(x, y string) int { 334 // "When major, minor, and patch are equal, a pre-release version has 335 // lower precedence than a normal version. 336 // Example: 1.0.0-alpha < 1.0.0. 337 // Precedence for two pre-release versions with the same major, minor, 338 // and patch version MUST be determined by comparing each dot separated 339 // identifier from left to right until a difference is found as follows: 340 // identifiers consisting of only digits are compared numerically and 341 // identifiers with letters or hyphens are compared lexically in ASCII 342 // sort order. Numeric identifiers always have lower precedence than 343 // non-numeric identifiers. A larger set of pre-release fields has a 344 // higher precedence than a smaller set, if all of the preceding 345 // identifiers are equal. 346 // Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 347 // 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0." 348 if x == y { 349 return 0 350 } 351 if x == "" { 352 return +1 353 } 354 if y == "" { 355 return -1 356 } 357 for x != "" && y != "" { 358 x = x[1:] // skip - or . 359 y = y[1:] // skip - or . 360 var dx, dy string 361 dx, x = nextIdent(x) 362 dy, y = nextIdent(y) 363 if dx != dy { 364 ix := isNum(dx) 365 iy := isNum(dy) 366 if ix != iy { 367 if ix { 368 return -1 369 } else { 370 return +1 371 } 372 } 373 if ix { 374 if len(dx) < len(dy) { 375 return -1 376 } 377 if len(dx) > len(dy) { 378 return +1 379 } 380 } 381 if dx < dy { 382 return -1 383 } else { 384 return +1 385 } 386 } 387 } 388 if x == "" { 389 return -1 390 } else { 391 return +1 392 } 393 } 394 395 func nextIdent(x string) (dx, rest string) { 396 i := 0 397 for i < len(x) && x[i] != '.' { 398 i++ 399 } 400 return x[:i], x[i:] 401 }