mirror of
https://gitea.com/gitea/tea.git
synced 2024-12-04 14:46:40 -05:00
381 lines
9.5 KiB
Go
381 lines
9.5 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,
|
||
|
// GreaterThan, GreaterThanOrEqual or LessThanOrEqual 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
|
||
|
}
|
||
|
|
||
|
// GreaterThanOrEqualTo tests if this version is greater than or equal to another version.
|
||
|
func (v *Version) GreaterThanOrEqual(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
|
||
|
}
|
||
|
|
||
|
// LessThanOrEqualTo tests if this version is less than or equal to another version.
|
||
|
func (v *Version) LessThanOrEqual(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
|
||
|
}
|