cuelang.org/go@v0.10.1/internal/mod/semver/semver.go (about)

     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  	"cmp"
    27  	"slices"
    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  // Sort sorts a list of semantic version strings.
   158  func Sort(list []string) {
   159  	slices.SortFunc(list, func(a, b string) int {
   160  		if c := Compare(a, b); c != 0 {
   161  			return c
   162  		}
   163  		return cmp.Compare(a, b)
   164  	})
   165  }
   166  
   167  func parse(v string) (p parsed, ok bool) {
   168  	if v == "" || v[0] != 'v' {
   169  		return
   170  	}
   171  	p.major, v, ok = parseInt(v[1:])
   172  	if !ok {
   173  		return
   174  	}
   175  	if v == "" {
   176  		p.minor = "0"
   177  		p.patch = "0"
   178  		p.short = ".0.0"
   179  		return
   180  	}
   181  	if v[0] != '.' {
   182  		ok = false
   183  		return
   184  	}
   185  	p.minor, v, ok = parseInt(v[1:])
   186  	if !ok {
   187  		return
   188  	}
   189  	if v == "" {
   190  		p.patch = "0"
   191  		p.short = ".0"
   192  		return
   193  	}
   194  	if v[0] != '.' {
   195  		ok = false
   196  		return
   197  	}
   198  	p.patch, v, ok = parseInt(v[1:])
   199  	if !ok {
   200  		return
   201  	}
   202  	if len(v) > 0 && v[0] == '-' {
   203  		p.prerelease, v, ok = parsePrerelease(v)
   204  		if !ok {
   205  			return
   206  		}
   207  	}
   208  	if len(v) > 0 && v[0] == '+' {
   209  		p.build, v, ok = parseBuild(v)
   210  		if !ok {
   211  			return
   212  		}
   213  	}
   214  	if v != "" {
   215  		ok = false
   216  		return
   217  	}
   218  	ok = true
   219  	return
   220  }
   221  
   222  func parseInt(v string) (t, rest string, ok bool) {
   223  	if v == "" {
   224  		return
   225  	}
   226  	if v[0] < '0' || '9' < v[0] {
   227  		return
   228  	}
   229  	i := 1
   230  	for i < len(v) && '0' <= v[i] && v[i] <= '9' {
   231  		i++
   232  	}
   233  	if v[0] == '0' && i != 1 {
   234  		return
   235  	}
   236  	return v[:i], v[i:], true
   237  }
   238  
   239  func parsePrerelease(v string) (t, rest string, ok bool) {
   240  	// "A pre-release version MAY be denoted by appending a hyphen and
   241  	// a series of dot separated identifiers immediately following the patch version.
   242  	// Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-].
   243  	// Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes."
   244  	if v == "" || v[0] != '-' {
   245  		return
   246  	}
   247  	i := 1
   248  	start := 1
   249  	for i < len(v) && v[i] != '+' {
   250  		if !isIdentChar(v[i]) && v[i] != '.' {
   251  			return
   252  		}
   253  		if v[i] == '.' {
   254  			if start == i || isBadNum(v[start:i]) {
   255  				return
   256  			}
   257  			start = i + 1
   258  		}
   259  		i++
   260  	}
   261  	if start == i || isBadNum(v[start:i]) {
   262  		return
   263  	}
   264  	return v[:i], v[i:], true
   265  }
   266  
   267  func parseBuild(v string) (t, rest string, ok bool) {
   268  	if v == "" || v[0] != '+' {
   269  		return
   270  	}
   271  	i := 1
   272  	start := 1
   273  	for i < len(v) {
   274  		if !isIdentChar(v[i]) && v[i] != '.' {
   275  			return
   276  		}
   277  		if v[i] == '.' {
   278  			if start == i {
   279  				return
   280  			}
   281  			start = i + 1
   282  		}
   283  		i++
   284  	}
   285  	if start == i {
   286  		return
   287  	}
   288  	return v[:i], v[i:], true
   289  }
   290  
   291  func isIdentChar(c byte) bool {
   292  	return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-'
   293  }
   294  
   295  func isBadNum(v string) bool {
   296  	i := 0
   297  	for i < len(v) && '0' <= v[i] && v[i] <= '9' {
   298  		i++
   299  	}
   300  	return i == len(v) && i > 1 && v[0] == '0'
   301  }
   302  
   303  func isNum(v string) bool {
   304  	i := 0
   305  	for i < len(v) && '0' <= v[i] && v[i] <= '9' {
   306  		i++
   307  	}
   308  	return i == len(v)
   309  }
   310  
   311  func compareInt(x, y string) int {
   312  	if x == y {
   313  		return 0
   314  	}
   315  	if len(x) < len(y) {
   316  		return -1
   317  	}
   318  	if len(x) > len(y) {
   319  		return +1
   320  	}
   321  	if x < y {
   322  		return -1
   323  	} else {
   324  		return +1
   325  	}
   326  }
   327  
   328  func comparePrerelease(x, y string) int {
   329  	// "When major, minor, and patch are equal, a pre-release version has
   330  	// lower precedence than a normal version.
   331  	// Example: 1.0.0-alpha < 1.0.0.
   332  	// Precedence for two pre-release versions with the same major, minor,
   333  	// and patch version MUST be determined by comparing each dot separated
   334  	// identifier from left to right until a difference is found as follows:
   335  	// identifiers consisting of only digits are compared numerically and
   336  	// identifiers with letters or hyphens are compared lexically in ASCII
   337  	// sort order. Numeric identifiers always have lower precedence than
   338  	// non-numeric identifiers. A larger set of pre-release fields has a
   339  	// higher precedence than a smaller set, if all of the preceding
   340  	// identifiers are equal.
   341  	// Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta <
   342  	// 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0."
   343  	if x == y {
   344  		return 0
   345  	}
   346  	if x == "" {
   347  		return +1
   348  	}
   349  	if y == "" {
   350  		return -1
   351  	}
   352  	for x != "" && y != "" {
   353  		x = x[1:] // skip - or .
   354  		y = y[1:] // skip - or .
   355  		var dx, dy string
   356  		dx, x = nextIdent(x)
   357  		dy, y = nextIdent(y)
   358  		if dx != dy {
   359  			ix := isNum(dx)
   360  			iy := isNum(dy)
   361  			if ix != iy {
   362  				if ix {
   363  					return -1
   364  				} else {
   365  					return +1
   366  				}
   367  			}
   368  			if ix {
   369  				if len(dx) < len(dy) {
   370  					return -1
   371  				}
   372  				if len(dx) > len(dy) {
   373  					return +1
   374  				}
   375  			}
   376  			if dx < dy {
   377  				return -1
   378  			} else {
   379  				return +1
   380  			}
   381  		}
   382  	}
   383  	if x == "" {
   384  		return -1
   385  	} else {
   386  		return +1
   387  	}
   388  }
   389  
   390  func nextIdent(x string) (dx, rest string) {
   391  	i := 0
   392  	for i < len(x) && x[i] != '.' {
   393  		i++
   394  	}
   395  	return x[:i], x[i:]
   396  }