...

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

Documentation: golang.org/x/mod/semver

     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  }
   402  

View as plain text