github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/util/version/version.go (about)

     1  // Copyright 2018 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package version
    12  
    13  import (
    14  	"fmt"
    15  	"regexp"
    16  	"strconv"
    17  	"strings"
    18  
    19  	"github.com/cockroachdb/errors"
    20  )
    21  
    22  // Version represents a semantic version; see
    23  // https://semver.org/spec/v2.0.0.html.
    24  type Version struct {
    25  	major      int32
    26  	minor      int32
    27  	patch      int32
    28  	preRelease string
    29  	metadata   string
    30  }
    31  
    32  // Major returns the major (first) version number.
    33  func (v *Version) Major() int {
    34  	return int(v.major)
    35  }
    36  
    37  // Minor returns the minor (second) version number.
    38  func (v *Version) Minor() int {
    39  	return int(v.minor)
    40  }
    41  
    42  // Patch returns the patch (third) version number.
    43  func (v *Version) Patch() int {
    44  	return int(v.patch)
    45  }
    46  
    47  // PreRelease returns the pre-release version (if present).
    48  func (v *Version) PreRelease() string {
    49  	return v.preRelease
    50  }
    51  
    52  // Metadata returns the metadata (if present).
    53  func (v *Version) Metadata() string {
    54  	return v.metadata
    55  }
    56  
    57  // String returns the string representation, in the format:
    58  //
    59  //	"v1.2.3-beta+md"
    60  func (v Version) String() string {
    61  	var b strings.Builder
    62  	fmt.Fprintf(&b, "v%d.%d.%d", v.major, v.minor, v.patch)
    63  	if v.preRelease != "" {
    64  		fmt.Fprintf(&b, "-%s", v.preRelease)
    65  	}
    66  	if v.metadata != "" {
    67  		fmt.Fprintf(&b, "+%s", v.metadata)
    68  	}
    69  	return b.String()
    70  }
    71  
    72  // versionRE is the regexp that is used to verify that a version string is
    73  // of the form "vMAJOR.MINOR.PATCH[-PRERELEASE][+METADATA]". This
    74  // conforms to https://semver.org/spec/v2.0.0.html
    75  var versionRE = regexp.MustCompile(
    76  	`^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-.]+)?(\+[0-9A-Za-z-.]+|)?$`,
    77  	// ^major           ^minor           ^patch         ^preRelease       ^metadata
    78  )
    79  
    80  // numericRE is the regexp used to check if an identifier is numeric.
    81  var numericRE = regexp.MustCompile(`^(0|[1-9][0-9]*)$`)
    82  
    83  // Parse creates a version from a string. The string must be a valid semantic
    84  // version (as per https://semver.org/spec/v2.0.0.html) in the format:
    85  //
    86  //	"vMINOR.MAJOR.PATCH[-PRERELEASE][+METADATA]".
    87  //
    88  // MINOR, MAJOR, and PATCH are numeric values (without any leading 0s).
    89  // PRERELEASE and METADATA can contain ASCII characters and digits, hyphens and
    90  // dots.
    91  func Parse(str string) (*Version, error) {
    92  	if !versionRE.MatchString(str) {
    93  		return nil, errors.Errorf("invalid version string '%s'", str)
    94  	}
    95  
    96  	var v Version
    97  	r := strings.NewReader(str)
    98  	// Read the major.minor.patch part.
    99  	_, err := fmt.Fscanf(r, "v%d.%d.%d", &v.major, &v.minor, &v.patch)
   100  	if err != nil {
   101  		panic(fmt.Sprintf("invalid version '%s' passed the regex: %s", str, err))
   102  	}
   103  	remaining := str[len(str)-r.Len():]
   104  	// Read the pre-release, if present.
   105  	if len(remaining) > 0 && remaining[0] == '-' {
   106  		p := strings.IndexRune(remaining, '+')
   107  		if p == -1 {
   108  			p = len(remaining)
   109  		}
   110  		v.preRelease = remaining[1:p]
   111  		remaining = remaining[p:]
   112  	}
   113  	// Read the metadata, if present.
   114  	if len(remaining) > 0 {
   115  		if remaining[0] != '+' {
   116  			panic(fmt.Sprintf("invalid version '%s' passed the regex", str))
   117  		}
   118  		v.metadata = remaining[1:]
   119  	}
   120  	return &v, nil
   121  }
   122  
   123  // MustParse is like Parse but panics on any error. Recommended as an
   124  // initializer for global values.
   125  func MustParse(str string) *Version {
   126  	v, err := Parse(str)
   127  	if err != nil {
   128  		panic(err)
   129  	}
   130  	return v
   131  }
   132  
   133  func cmpVal(a, b int32) int {
   134  	if a > b {
   135  		return +1
   136  	}
   137  	if a < b {
   138  		return -1
   139  	}
   140  	return 0
   141  }
   142  
   143  // Compare returns -1, 0, or +1 indicating the relative ordering of versions.
   144  func (v *Version) Compare(w *Version) int {
   145  	if v := cmpVal(v.major, w.major); v != 0 {
   146  		return v
   147  	}
   148  	if v := cmpVal(v.minor, w.minor); v != 0 {
   149  		return v
   150  	}
   151  	if v := cmpVal(v.patch, w.patch); v != 0 {
   152  		return v
   153  	}
   154  	if v.preRelease != w.preRelease {
   155  		if v.preRelease == "" && w.preRelease != "" {
   156  			// 1.0.0 is greater than 1.0.0-alpha.
   157  			return 1
   158  		}
   159  		if v.preRelease != "" && w.preRelease == "" {
   160  			// 1.0.0-alpha is less than 1.0.0.
   161  			return -1
   162  		}
   163  
   164  		// Quoting from https://semver.org/spec/v2.0.0.html:
   165  		//   Precedence for two pre-release versions with the same major, minor, and
   166  		//   patch version MUST be determined by comparing each dot separated
   167  		//   identifier from left to right until a difference is found as follows:
   168  		//    (1) Identifiers consisting of only digits are compared numerically.
   169  		//    (2) identifiers with letters or hyphens are compared lexically in ASCII
   170  		//        sort order.
   171  		//    (3) Numeric identifiers always have lower precedence than non-numeric
   172  		//        identifiers.
   173  		//    (4) A larger set of pre-release fields has a higher precedence than a
   174  		//        smaller set, if all of the preceding identifiers are equal.
   175  		//
   176  		vs := strings.Split(v.preRelease, ".")
   177  		ws := strings.Split(w.preRelease, ".")
   178  		for ; len(vs) > 0 && len(ws) > 0; vs, ws = vs[1:], ws[1:] {
   179  			vStr, wStr := vs[0], ws[0]
   180  			if vStr == wStr {
   181  				continue
   182  			}
   183  			vNumeric := numericRE.MatchString(vStr)
   184  			wNumeric := numericRE.MatchString(wStr)
   185  			switch {
   186  			case vNumeric && wNumeric:
   187  				// Case 1.
   188  				vVal, err := strconv.Atoi(vStr)
   189  				if err != nil {
   190  					panic(err)
   191  				}
   192  				wVal, err := strconv.Atoi(wStr)
   193  				if err != nil {
   194  					panic(err)
   195  				}
   196  				if vVal == wVal {
   197  					panic("different strings yield the same numbers")
   198  				}
   199  				if vVal < wVal {
   200  					return -1
   201  				}
   202  				return 1
   203  
   204  			case vNumeric:
   205  				// Case 3.
   206  				return -1
   207  
   208  			case wNumeric:
   209  				// Case 3.
   210  				return 1
   211  
   212  			default:
   213  				// Case 2.
   214  				if vStr < wStr {
   215  					return -1
   216  				}
   217  				return 1
   218  			}
   219  		}
   220  
   221  		if len(vs) > 0 {
   222  			// Case 4.
   223  			return +1
   224  		}
   225  		if len(ws) > 0 {
   226  			// Case 4.
   227  			return -1
   228  		}
   229  	}
   230  
   231  	return 0
   232  }
   233  
   234  // AtLeast returns true if v >= w.
   235  func (v *Version) AtLeast(w *Version) bool {
   236  	return v.Compare(w) >= 0
   237  }