gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }