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