github.com/blend/go-sdk@v1.20220411.3/semver/version.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package semver
     9  
    10  import (
    11  	"bytes"
    12  	"fmt"
    13  	"reflect"
    14  	"regexp"
    15  	"strconv"
    16  	"strings"
    17  )
    18  
    19  // The compiled regular expression used to test the validity of a version.
    20  var versionRegexp *regexp.Regexp
    21  
    22  // VersionRegexpRaw is the raw regular expression string used for
    23  // testing the validity of a version.
    24  const VersionRegexpRaw string = `v?([0-9]+(\.[0-9]+)*?)` +
    25  	`(-?([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` +
    26  	`(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` +
    27  	`?`
    28  
    29  // Version represents a single version.
    30  type Version struct {
    31  	metadata string
    32  	pre      string
    33  	segments []int64
    34  	si       int
    35  }
    36  
    37  func init() {
    38  	versionRegexp = regexp.MustCompile("^" + VersionRegexpRaw + "$")
    39  }
    40  
    41  // NewVersion parses the given version and returns a new Version.
    42  func NewVersion(v string) (*Version, error) {
    43  	matches := versionRegexp.FindStringSubmatch(v)
    44  	if matches == nil {
    45  		return nil, fmt.Errorf("malformed version: %s", v)
    46  	}
    47  	segmentsStr := strings.Split(matches[1], ".")
    48  	segments := make([]int64, len(segmentsStr))
    49  	si := 0
    50  	for i, str := range segmentsStr {
    51  		val, err := strconv.ParseInt(str, 10, 64)
    52  		if err != nil {
    53  			return nil, fmt.Errorf(
    54  				"error parsing version: %s", err)
    55  		}
    56  
    57  		segments[i] = int64(val)
    58  		si++
    59  	}
    60  
    61  	for i := len(segments); i < 3; i++ {
    62  		segments = append(segments, 0)
    63  	}
    64  
    65  	return &Version{
    66  		metadata: matches[7],
    67  		pre:      matches[4],
    68  		segments: segments,
    69  		si:       si,
    70  	}, nil
    71  }
    72  
    73  // Must is a helper that wraps a call to a function returning (*Version, error)
    74  // and panics if error is non-nil.
    75  func Must(v *Version, err error) *Version {
    76  	if err != nil {
    77  		panic(err)
    78  	}
    79  
    80  	return v
    81  }
    82  
    83  // Compare compares this version to another version. This
    84  // returns -1, 0, or 1 if this version is smaller, equal,
    85  // or larger than the other version, respectively.
    86  //
    87  // If you want boolean results, use the LessThan, Equal,
    88  // or GreaterThan methods.
    89  func (v *Version) Compare(other *Version) int {
    90  	// A quick, efficient equality check
    91  	if v.String() == other.String() {
    92  		return 0
    93  	}
    94  
    95  	segmentsSelf := v.Segments64()
    96  	segmentsOther := other.Segments64()
    97  
    98  	// If the segments are the same, we must compare on prerelease info
    99  	if reflect.DeepEqual(segmentsSelf, segmentsOther) {
   100  		preSelf := v.Prerelease()
   101  		preOther := other.Prerelease()
   102  		if preSelf == "" && preOther == "" {
   103  			return 0
   104  		}
   105  		if preSelf == "" {
   106  			return 1
   107  		}
   108  		if preOther == "" {
   109  			return -1
   110  		}
   111  
   112  		return comparePrereleases(preSelf, preOther)
   113  	}
   114  
   115  	// Get the highest specificity (hS), or if they're equal, just use segmentSelf length
   116  	lenSelf := len(segmentsSelf)
   117  	lenOther := len(segmentsOther)
   118  	hS := lenSelf
   119  	if lenSelf < lenOther {
   120  		hS = lenOther
   121  	}
   122  	// Compare the segments
   123  	// Because a constraint could have more/less specificity than the version it's
   124  	// checking, we need to account for a lopsided or jagged comparison
   125  	for i := 0; i < hS; i++ {
   126  		if i > lenSelf-1 {
   127  			// This means Self had the lower specificity
   128  			// Check to see if the remaining segments in Other are all zeros
   129  			if !allZero(segmentsOther[i:]) {
   130  				// if not, it means that Other has to be greater than Self
   131  				return -1
   132  			}
   133  			break
   134  		} else if i > lenOther-1 {
   135  			// this means Other had the lower specificity
   136  			// Check to see if the remaining segments in Self are all zeros -
   137  			if !allZero(segmentsSelf[i:]) {
   138  				//if not, it means that Self has to be greater than Other
   139  				return 1
   140  			}
   141  			break
   142  		}
   143  		lhs := segmentsSelf[i]
   144  		rhs := segmentsOther[i]
   145  		if lhs == rhs {
   146  			continue
   147  		} else if lhs < rhs {
   148  			return -1
   149  		}
   150  		// Otherwis, rhs was > lhs, they're not equal
   151  		return 1
   152  	}
   153  
   154  	// if we got this far, they're equal
   155  	return 0
   156  }
   157  
   158  func allZero(segs []int64) bool {
   159  	for _, s := range segs {
   160  		if s != 0 {
   161  			return false
   162  		}
   163  	}
   164  	return true
   165  }
   166  
   167  func comparePart(preSelf string, preOther string) int {
   168  	if preSelf == preOther {
   169  		return 0
   170  	}
   171  
   172  	var selfInt int64
   173  	selfNumeric := true
   174  	selfInt, err := strconv.ParseInt(preSelf, 10, 64)
   175  	if err != nil {
   176  		selfNumeric = false
   177  	}
   178  
   179  	var otherInt int64
   180  	otherNumeric := true
   181  	otherInt, err = strconv.ParseInt(preOther, 10, 64)
   182  	if err != nil {
   183  		otherNumeric = false
   184  	}
   185  
   186  	// if a part is empty, we use the other to decide
   187  	if preSelf == "" {
   188  		if otherNumeric {
   189  			return -1
   190  		}
   191  		return 1
   192  	}
   193  
   194  	if preOther == "" {
   195  		if selfNumeric {
   196  			return 1
   197  		}
   198  		return -1
   199  	}
   200  
   201  	if selfNumeric && !otherNumeric {
   202  		return -1
   203  	} else if !selfNumeric && otherNumeric {
   204  		return 1
   205  	} else if !selfNumeric && !otherNumeric && preSelf > preOther {
   206  		return 1
   207  	} else if selfInt > otherInt {
   208  		return 1
   209  	}
   210  
   211  	return -1
   212  }
   213  
   214  func comparePrereleases(v string, other string) int {
   215  	// the same pre release!
   216  	if v == other {
   217  		return 0
   218  	}
   219  
   220  	// split both pre releases for analyze their parts
   221  	selfPreReleaseMeta := strings.Split(v, ".")
   222  	otherPreReleaseMeta := strings.Split(other, ".")
   223  
   224  	selfPreReleaseLen := len(selfPreReleaseMeta)
   225  	otherPreReleaseLen := len(otherPreReleaseMeta)
   226  
   227  	biggestLen := otherPreReleaseLen
   228  	if selfPreReleaseLen > otherPreReleaseLen {
   229  		biggestLen = selfPreReleaseLen
   230  	}
   231  
   232  	// loop for parts to find the first difference
   233  	for i := 0; i < biggestLen; i = i + 1 {
   234  		partSelfPre := ""
   235  		if i < selfPreReleaseLen {
   236  			partSelfPre = selfPreReleaseMeta[i]
   237  		}
   238  
   239  		partOtherPre := ""
   240  		if i < otherPreReleaseLen {
   241  			partOtherPre = otherPreReleaseMeta[i]
   242  		}
   243  
   244  		compare := comparePart(partSelfPre, partOtherPre)
   245  		// if parts are equals, continue the loop
   246  		if compare != 0 {
   247  			return compare
   248  		}
   249  	}
   250  
   251  	return 0
   252  }
   253  
   254  // Equal tests if two versions are equal.
   255  func (v *Version) Equal(o *Version) bool {
   256  	return v.Compare(o) == 0
   257  }
   258  
   259  // GreaterThan tests if this version is greater than another version.
   260  func (v *Version) GreaterThan(o *Version) bool {
   261  	return v.Compare(o) > 0
   262  }
   263  
   264  // LessThan tests if this version is less than another version.
   265  func (v *Version) LessThan(o *Version) bool {
   266  	return v.Compare(o) < 0
   267  }
   268  
   269  // Metadata returns any metadata that was part of the version
   270  // string.
   271  //
   272  // Metadata is anything that comes after the "+" in the version.
   273  // For example, with "1.2.3+beta", the metadata is "beta".
   274  func (v *Version) Metadata() string {
   275  	return v.metadata
   276  }
   277  
   278  // Prerelease returns any prerelease data that is part of the version,
   279  // or blank if there is no prerelease data.
   280  //
   281  // Prerelease information is anything that comes after the "-" in the
   282  // version (but before any metadata). For example, with "1.2.3-beta",
   283  // the prerelease information is "beta".
   284  func (v *Version) Prerelease() string {
   285  	return v.pre
   286  }
   287  
   288  // Segments returns the numeric segments of the version as a slice of ints.
   289  //
   290  // This excludes any metadata or pre-release information. For example,
   291  // for a version "1.2.3-beta", segments will return a slice of
   292  // 1, 2, 3.
   293  func (v *Version) Segments() []int {
   294  	segmentSlice := make([]int, len(v.segments))
   295  	for i, v := range v.segments {
   296  		segmentSlice[i] = int(v)
   297  	}
   298  	return segmentSlice
   299  }
   300  
   301  // Segments64 returns the numeric segments of the version as a slice of int64s.
   302  //
   303  // This excludes any metadata or pre-release information. For example,
   304  // for a version "1.2.3-beta", segments will return a slice of
   305  // 1, 2, 3.
   306  func (v *Version) Segments64() []int64 {
   307  	return v.segments
   308  }
   309  
   310  // String returns the full version string included pre-release
   311  // and metadata information.
   312  func (v *Version) String() string {
   313  	var buf bytes.Buffer
   314  	fmtParts := make([]string, len(v.segments))
   315  	for i, s := range v.segments {
   316  		// We can ignore err here since we've pre-parsed the values in segments
   317  		str := strconv.FormatInt(s, 10)
   318  		fmtParts[i] = str
   319  	}
   320  	fmt.Fprint(&buf, strings.Join(fmtParts, "."))
   321  	if v.pre != "" {
   322  		fmt.Fprintf(&buf, "-%s", v.pre)
   323  	}
   324  	if v.metadata != "" {
   325  		fmt.Fprintf(&buf, "+%s", v.metadata)
   326  	}
   327  
   328  	return buf.String()
   329  }
   330  
   331  // Major returns the Major segment, or the highest order segment.
   332  func (v *Version) Major() (major int64) {
   333  	if len(v.segments) < 1 {
   334  		return
   335  	}
   336  	major = v.segments[0]
   337  	return
   338  }
   339  
   340  // Minor returns the Minor segment, or the second highest order segment.
   341  func (v *Version) Minor() (minor int64) {
   342  	if len(v.segments) < 2 {
   343  		return
   344  	}
   345  	minor = v.segments[1]
   346  	return
   347  }
   348  
   349  // Patch returns the Patch segment, or the third highest order segment.
   350  func (v *Version) Patch() (patch int64) {
   351  	if len(v.segments) < 3 {
   352  		return
   353  	}
   354  	patch = v.segments[2]
   355  	return
   356  }
   357  
   358  // BumpMajor increments the Major field by 1 and resets all other fields to their default values
   359  func (v *Version) BumpMajor() {
   360  	v.segments = []int64{v.Major() + 1, 0, 0}
   361  	v.pre = ""
   362  	v.metadata = ""
   363  }
   364  
   365  // BumpMinor increments the Minor field by 1 and resets all other fields to their default values
   366  func (v *Version) BumpMinor() {
   367  	v.segments = []int64{v.Major(), v.Minor() + 1, 0}
   368  	v.pre = ""
   369  	v.metadata = ""
   370  }
   371  
   372  // BumpPatch increments the Patch field by 1 and resets all other fields to their default values
   373  func (v *Version) BumpPatch() {
   374  	v.segments = []int64{v.Major(), v.Minor(), v.Patch() + 1}
   375  	v.pre = ""
   376  	v.metadata = ""
   377  }
   378  
   379  // Collection is a type that implements the sort.Interface interface
   380  // so that versions can be sorted.
   381  type Collection []*Version
   382  
   383  func (v Collection) Len() int {
   384  	return len(v)
   385  }
   386  
   387  func (v Collection) Less(i, j int) bool {
   388  	return v[i].LessThan(v[j])
   389  }
   390  
   391  func (v Collection) Swap(i, j int) {
   392  	v[i], v[j] = v[j], v[i]
   393  }