mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-10-31 08:03:43 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			371 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			371 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package version
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"reflect"
 | |
| 	"regexp"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| // The compiled regular expression used to test the validity of a version.
 | |
| var (
 | |
| 	versionRegexp *regexp.Regexp
 | |
| 	semverRegexp  *regexp.Regexp
 | |
| )
 | |
| 
 | |
| // The raw regular expression string used for testing the validity
 | |
| // of a version.
 | |
| const (
 | |
| 	VersionRegexpRaw string = `v?([0-9]+(\.[0-9]+)*?)` +
 | |
| 		`(-([0-9]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)|(-?([A-Za-z\-~]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)))?` +
 | |
| 		`(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` +
 | |
| 		`?`
 | |
| 
 | |
| 	// SemverRegexpRaw requires a separator between version and prerelease
 | |
| 	SemverRegexpRaw string = `v?([0-9]+(\.[0-9]+)*?)` +
 | |
| 		`(-([0-9]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)|(-([A-Za-z\-~]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)))?` +
 | |
| 		`(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` +
 | |
| 		`?`
 | |
| )
 | |
| 
 | |
| // Version represents a single version.
 | |
| type Version struct {
 | |
| 	metadata string
 | |
| 	pre      string
 | |
| 	segments []int64
 | |
| 	si       int
 | |
| 	original string
 | |
| }
 | |
| 
 | |
| func init() {
 | |
| 	versionRegexp = regexp.MustCompile("^" + VersionRegexpRaw + "$")
 | |
| 	semverRegexp = regexp.MustCompile("^" + SemverRegexpRaw + "$")
 | |
| }
 | |
| 
 | |
| // NewVersion parses the given version and returns a new
 | |
| // Version.
 | |
| func NewVersion(v string) (*Version, error) {
 | |
| 	return newVersion(v, versionRegexp)
 | |
| }
 | |
| 
 | |
| // NewSemver parses the given version and returns a new
 | |
| // Version that adheres strictly to SemVer specs
 | |
| // https://semver.org/
 | |
| func NewSemver(v string) (*Version, error) {
 | |
| 	return newVersion(v, semverRegexp)
 | |
| }
 | |
| 
 | |
| func newVersion(v string, pattern *regexp.Regexp) (*Version, error) {
 | |
| 	matches := pattern.FindStringSubmatch(v)
 | |
| 	if matches == nil {
 | |
| 		return nil, fmt.Errorf("Malformed version: %s", v)
 | |
| 	}
 | |
| 	segmentsStr := strings.Split(matches[1], ".")
 | |
| 	segments := make([]int64, len(segmentsStr))
 | |
| 	si := 0
 | |
| 	for i, str := range segmentsStr {
 | |
| 		val, err := strconv.ParseInt(str, 10, 64)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf(
 | |
| 				"Error parsing version: %s", err)
 | |
| 		}
 | |
| 
 | |
| 		segments[i] = int64(val)
 | |
| 		si++
 | |
| 	}
 | |
| 
 | |
| 	// Even though we could support more than three segments, if we
 | |
| 	// got less than three, pad it with 0s. This is to cover the basic
 | |
| 	// default usecase of semver, which is MAJOR.MINOR.PATCH at the minimum
 | |
| 	for i := len(segments); i < 3; i++ {
 | |
| 		segments = append(segments, 0)
 | |
| 	}
 | |
| 
 | |
| 	pre := matches[7]
 | |
| 	if pre == "" {
 | |
| 		pre = matches[4]
 | |
| 	}
 | |
| 
 | |
| 	return &Version{
 | |
| 		metadata: matches[10],
 | |
| 		pre:      pre,
 | |
| 		segments: segments,
 | |
| 		si:       si,
 | |
| 		original: v,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // Must is a helper that wraps a call to a function returning (*Version, error)
 | |
| // and panics if error is non-nil.
 | |
| func Must(v *Version, err error) *Version {
 | |
| 	if err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 
 | |
| 	return v
 | |
| }
 | |
| 
 | |
| // Compare compares this version to another version. This
 | |
| // returns -1, 0, or 1 if this version is smaller, equal,
 | |
| // or larger than the other version, respectively.
 | |
| //
 | |
| // If you want boolean results, use the LessThan, Equal,
 | |
| // or GreaterThan methods.
 | |
| func (v *Version) Compare(other *Version) int {
 | |
| 	// A quick, efficient equality check
 | |
| 	if v.String() == other.String() {
 | |
| 		return 0
 | |
| 	}
 | |
| 
 | |
| 	segmentsSelf := v.Segments64()
 | |
| 	segmentsOther := other.Segments64()
 | |
| 
 | |
| 	// If the segments are the same, we must compare on prerelease info
 | |
| 	if reflect.DeepEqual(segmentsSelf, segmentsOther) {
 | |
| 		preSelf := v.Prerelease()
 | |
| 		preOther := other.Prerelease()
 | |
| 		if preSelf == "" && preOther == "" {
 | |
| 			return 0
 | |
| 		}
 | |
| 		if preSelf == "" {
 | |
| 			return 1
 | |
| 		}
 | |
| 		if preOther == "" {
 | |
| 			return -1
 | |
| 		}
 | |
| 
 | |
| 		return comparePrereleases(preSelf, preOther)
 | |
| 	}
 | |
| 
 | |
| 	// Get the highest specificity (hS), or if they're equal, just use segmentSelf length
 | |
| 	lenSelf := len(segmentsSelf)
 | |
| 	lenOther := len(segmentsOther)
 | |
| 	hS := lenSelf
 | |
| 	if lenSelf < lenOther {
 | |
| 		hS = lenOther
 | |
| 	}
 | |
| 	// Compare the segments
 | |
| 	// Because a constraint could have more/less specificity than the version it's
 | |
| 	// checking, we need to account for a lopsided or jagged comparison
 | |
| 	for i := 0; i < hS; i++ {
 | |
| 		if i > lenSelf-1 {
 | |
| 			// This means Self had the lower specificity
 | |
| 			// Check to see if the remaining segments in Other are all zeros
 | |
| 			if !allZero(segmentsOther[i:]) {
 | |
| 				// if not, it means that Other has to be greater than Self
 | |
| 				return -1
 | |
| 			}
 | |
| 			break
 | |
| 		} else if i > lenOther-1 {
 | |
| 			// this means Other had the lower specificity
 | |
| 			// Check to see if the remaining segments in Self are all zeros -
 | |
| 			if !allZero(segmentsSelf[i:]) {
 | |
| 				//if not, it means that Self has to be greater than Other
 | |
| 				return 1
 | |
| 			}
 | |
| 			break
 | |
| 		}
 | |
| 		lhs := segmentsSelf[i]
 | |
| 		rhs := segmentsOther[i]
 | |
| 		if lhs == rhs {
 | |
| 			continue
 | |
| 		} else if lhs < rhs {
 | |
| 			return -1
 | |
| 		}
 | |
| 		// Otherwis, rhs was > lhs, they're not equal
 | |
| 		return 1
 | |
| 	}
 | |
| 
 | |
| 	// if we got this far, they're equal
 | |
| 	return 0
 | |
| }
 | |
| 
 | |
| func allZero(segs []int64) bool {
 | |
| 	for _, s := range segs {
 | |
| 		if s != 0 {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func comparePart(preSelf string, preOther string) int {
 | |
| 	if preSelf == preOther {
 | |
| 		return 0
 | |
| 	}
 | |
| 
 | |
| 	var selfInt int64
 | |
| 	selfNumeric := true
 | |
| 	selfInt, err := strconv.ParseInt(preSelf, 10, 64)
 | |
| 	if err != nil {
 | |
| 		selfNumeric = false
 | |
| 	}
 | |
| 
 | |
| 	var otherInt int64
 | |
| 	otherNumeric := true
 | |
| 	otherInt, err = strconv.ParseInt(preOther, 10, 64)
 | |
| 	if err != nil {
 | |
| 		otherNumeric = false
 | |
| 	}
 | |
| 
 | |
| 	// if a part is empty, we use the other to decide
 | |
| 	if preSelf == "" {
 | |
| 		if otherNumeric {
 | |
| 			return -1
 | |
| 		}
 | |
| 		return 1
 | |
| 	}
 | |
| 
 | |
| 	if preOther == "" {
 | |
| 		if selfNumeric {
 | |
| 			return 1
 | |
| 		}
 | |
| 		return -1
 | |
| 	}
 | |
| 
 | |
| 	if selfNumeric && !otherNumeric {
 | |
| 		return -1
 | |
| 	} else if !selfNumeric && otherNumeric {
 | |
| 		return 1
 | |
| 	} else if !selfNumeric && !otherNumeric && preSelf > preOther {
 | |
| 		return 1
 | |
| 	} else if selfInt > otherInt {
 | |
| 		return 1
 | |
| 	}
 | |
| 
 | |
| 	return -1
 | |
| }
 | |
| 
 | |
| func comparePrereleases(v string, other string) int {
 | |
| 	// the same pre release!
 | |
| 	if v == other {
 | |
| 		return 0
 | |
| 	}
 | |
| 
 | |
| 	// split both pre releases for analyse their parts
 | |
| 	selfPreReleaseMeta := strings.Split(v, ".")
 | |
| 	otherPreReleaseMeta := strings.Split(other, ".")
 | |
| 
 | |
| 	selfPreReleaseLen := len(selfPreReleaseMeta)
 | |
| 	otherPreReleaseLen := len(otherPreReleaseMeta)
 | |
| 
 | |
| 	biggestLen := otherPreReleaseLen
 | |
| 	if selfPreReleaseLen > otherPreReleaseLen {
 | |
| 		biggestLen = selfPreReleaseLen
 | |
| 	}
 | |
| 
 | |
| 	// loop for parts to find the first difference
 | |
| 	for i := 0; i < biggestLen; i = i + 1 {
 | |
| 		partSelfPre := ""
 | |
| 		if i < selfPreReleaseLen {
 | |
| 			partSelfPre = selfPreReleaseMeta[i]
 | |
| 		}
 | |
| 
 | |
| 		partOtherPre := ""
 | |
| 		if i < otherPreReleaseLen {
 | |
| 			partOtherPre = otherPreReleaseMeta[i]
 | |
| 		}
 | |
| 
 | |
| 		compare := comparePart(partSelfPre, partOtherPre)
 | |
| 		// if parts are equals, continue the loop
 | |
| 		if compare != 0 {
 | |
| 			return compare
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return 0
 | |
| }
 | |
| 
 | |
| // Equal tests if two versions are equal.
 | |
| func (v *Version) Equal(o *Version) bool {
 | |
| 	return v.Compare(o) == 0
 | |
| }
 | |
| 
 | |
| // GreaterThan tests if this version is greater than another version.
 | |
| func (v *Version) GreaterThan(o *Version) bool {
 | |
| 	return v.Compare(o) > 0
 | |
| }
 | |
| 
 | |
| // LessThan tests if this version is less than another version.
 | |
| func (v *Version) LessThan(o *Version) bool {
 | |
| 	return v.Compare(o) < 0
 | |
| }
 | |
| 
 | |
| // Metadata returns any metadata that was part of the version
 | |
| // string.
 | |
| //
 | |
| // Metadata is anything that comes after the "+" in the version.
 | |
| // For example, with "1.2.3+beta", the metadata is "beta".
 | |
| func (v *Version) Metadata() string {
 | |
| 	return v.metadata
 | |
| }
 | |
| 
 | |
| // Prerelease returns any prerelease data that is part of the version,
 | |
| // or blank if there is no prerelease data.
 | |
| //
 | |
| // Prerelease information is anything that comes after the "-" in the
 | |
| // version (but before any metadata). For example, with "1.2.3-beta",
 | |
| // the prerelease information is "beta".
 | |
| func (v *Version) Prerelease() string {
 | |
| 	return v.pre
 | |
| }
 | |
| 
 | |
| // Segments returns the numeric segments of the version as a slice of ints.
 | |
| //
 | |
| // This excludes any metadata or pre-release information. For example,
 | |
| // for a version "1.2.3-beta", segments will return a slice of
 | |
| // 1, 2, 3.
 | |
| func (v *Version) Segments() []int {
 | |
| 	segmentSlice := make([]int, len(v.segments))
 | |
| 	for i, v := range v.segments {
 | |
| 		segmentSlice[i] = int(v)
 | |
| 	}
 | |
| 	return segmentSlice
 | |
| }
 | |
| 
 | |
| // Segments64 returns the numeric segments of the version as a slice of int64s.
 | |
| //
 | |
| // This excludes any metadata or pre-release information. For example,
 | |
| // for a version "1.2.3-beta", segments will return a slice of
 | |
| // 1, 2, 3.
 | |
| func (v *Version) Segments64() []int64 {
 | |
| 	result := make([]int64, len(v.segments))
 | |
| 	copy(result, v.segments)
 | |
| 	return result
 | |
| }
 | |
| 
 | |
| // String returns the full version string included pre-release
 | |
| // and metadata information.
 | |
| //
 | |
| // This value is rebuilt according to the parsed segments and other
 | |
| // information. Therefore, ambiguities in the version string such as
 | |
| // prefixed zeroes (1.04.0 => 1.4.0), `v` prefix (v1.0.0 => 1.0.0), and
 | |
| // missing parts (1.0 => 1.0.0) will be made into a canonicalized form
 | |
| // as shown in the parenthesized examples.
 | |
| func (v *Version) String() string {
 | |
| 	var buf bytes.Buffer
 | |
| 	fmtParts := make([]string, len(v.segments))
 | |
| 	for i, s := range v.segments {
 | |
| 		// We can ignore err here since we've pre-parsed the values in segments
 | |
| 		str := strconv.FormatInt(s, 10)
 | |
| 		fmtParts[i] = str
 | |
| 	}
 | |
| 	fmt.Fprintf(&buf, strings.Join(fmtParts, "."))
 | |
| 	if v.pre != "" {
 | |
| 		fmt.Fprintf(&buf, "-%s", v.pre)
 | |
| 	}
 | |
| 	if v.metadata != "" {
 | |
| 		fmt.Fprintf(&buf, "+%s", v.metadata)
 | |
| 	}
 | |
| 
 | |
| 	return buf.String()
 | |
| }
 | |
| 
 | |
| // Original returns the original parsed version as-is, including any
 | |
| // potential whitespace, `v` prefix, etc.
 | |
| func (v *Version) Original() string {
 | |
| 	return v.original
 | |
| }
 | 
