Source file src/cmd/vendor/golang.org/x/mod/semver/semver.go

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

View as plain text