mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 18:13:42 +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
 | 
						|
}
 |