248 lines
6.0 KiB
Go
248 lines
6.0 KiB
Go
// Copyright 2013-2015 CoreOS, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Semantic Versions http://semver.org
|
|
|
|
// This file was originally vendored from:
|
|
// https://github.com/coreos/go-semver/tree/e214231b295a8ea9479f11b70b35d5acf3556d9b/semver
|
|
// There isn't a single line left from the original source today, but being generous about
|
|
// attribution won't hurt.
|
|
package semver
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/open-policy-agent/opa/v1/util"
|
|
)
|
|
|
|
// reMetaIdentifier matches pre-release and metadata identifiers against the spec requirements
|
|
var reMetaIdentifier = regexp.MustCompile(`^[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*$`)
|
|
|
|
// Version represents a parsed SemVer
|
|
type Version struct {
|
|
Major int64
|
|
Minor int64
|
|
Patch int64
|
|
PreRelease string `json:"PreRelease,omitempty"`
|
|
Metadata string `json:"Metadata,omitempty"`
|
|
}
|
|
|
|
// Parse constructs new semver Version from version string.
|
|
func Parse(version string) (v Version, err error) {
|
|
version = strings.TrimPrefix(version, "v")
|
|
|
|
version, v.Metadata = cut(version, '+')
|
|
if v.Metadata != "" && !reMetaIdentifier.MatchString(v.Metadata) {
|
|
return v, fmt.Errorf("invalid metadata identifier: %s", v.Metadata)
|
|
}
|
|
|
|
version, v.PreRelease = cut(version, '-')
|
|
if v.PreRelease != "" && !reMetaIdentifier.MatchString(v.PreRelease) {
|
|
return v, fmt.Errorf("invalid pre-release identifier: %s", v.PreRelease)
|
|
}
|
|
|
|
if strings.Count(version, ".") != 2 {
|
|
return v, fmt.Errorf("%s should contain major, minor, and patch versions", version)
|
|
}
|
|
|
|
major, after := cut(version, '.')
|
|
if v.Major, err = strconv.ParseInt(major, 10, 64); err != nil {
|
|
return v, err
|
|
}
|
|
|
|
minor, after := cut(after, '.')
|
|
if v.Minor, err = strconv.ParseInt(minor, 10, 64); err != nil {
|
|
return v, err
|
|
}
|
|
|
|
if v.Patch, err = strconv.ParseInt(after, 10, 64); err != nil {
|
|
return v, err
|
|
}
|
|
|
|
return v, nil
|
|
}
|
|
|
|
// MustParse is like Parse but panics if the version string is invalid instead of returning an error.
|
|
func MustParse(version string) Version {
|
|
v, err := Parse(version)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
// Compare compares two semver strings.
|
|
func Compare(a, b string) int {
|
|
aV, err := Parse(a)
|
|
if err != nil {
|
|
return -1
|
|
}
|
|
|
|
bV, err := Parse(b)
|
|
if err != nil {
|
|
return 1
|
|
}
|
|
|
|
return aV.Compare(bV)
|
|
}
|
|
|
|
// AppendText appends the textual representation of the version to b and returns the extended buffer.
|
|
// This method conforms to the encoding.TextAppender interface, and is useful for serializing the Version
|
|
// without allocating, provided the caller has pre-allocated sufficient space in b.
|
|
func (v Version) AppendText(b []byte) ([]byte, error) {
|
|
if b == nil {
|
|
b = make([]byte, 0, length(v))
|
|
}
|
|
|
|
b = append(strconv.AppendInt(b, v.Major, 10), '.')
|
|
b = append(strconv.AppendInt(b, v.Minor, 10), '.')
|
|
b = strconv.AppendInt(b, v.Patch, 10)
|
|
|
|
if v.PreRelease != "" {
|
|
b = append(append(b, '-'), v.PreRelease...)
|
|
}
|
|
if v.Metadata != "" {
|
|
b = append(append(b, '+'), v.Metadata...)
|
|
}
|
|
|
|
return b, nil
|
|
}
|
|
|
|
// String returns the string representation of the version.
|
|
func (v Version) String() string {
|
|
bs := make([]byte, 0, length(v))
|
|
bs, _ = v.AppendText(bs)
|
|
|
|
return string(bs)
|
|
}
|
|
|
|
// Compare tests if v is less than, equal to, or greater than other, returning -1, 0, or +1 respectively.
|
|
// Comparison is based on the SemVer specification (https://semver.org/#spec-item-11).
|
|
func (v Version) Compare(other Version) int {
|
|
if v.Major > other.Major {
|
|
return 1
|
|
} else if v.Major < other.Major {
|
|
return -1
|
|
}
|
|
|
|
if v.Minor > other.Minor {
|
|
return 1
|
|
} else if v.Minor < other.Minor {
|
|
return -1
|
|
}
|
|
|
|
if v.Patch > other.Patch {
|
|
return 1
|
|
} else if v.Patch < other.Patch {
|
|
return -1
|
|
}
|
|
|
|
if v.PreRelease == other.PreRelease {
|
|
return 0
|
|
}
|
|
|
|
// if two versions are otherwise equal it is the one without a pre-release that is greater
|
|
if v.PreRelease == "" && other.PreRelease != "" {
|
|
return 1
|
|
}
|
|
if other.PreRelease == "" && v.PreRelease != "" {
|
|
return -1
|
|
}
|
|
|
|
a, afterA := cut(v.PreRelease, '.')
|
|
b, afterB := cut(other.PreRelease, '.')
|
|
|
|
for {
|
|
if a == "" && b != "" {
|
|
return -1
|
|
}
|
|
if a != "" && b == "" {
|
|
return 1
|
|
}
|
|
|
|
aIsInt := isAllDecimals(a)
|
|
bIsInt := isAllDecimals(b)
|
|
|
|
// numeric identifiers have lower precedence than non-numeric
|
|
if aIsInt && !bIsInt {
|
|
return -1
|
|
} else if !aIsInt && bIsInt {
|
|
return 1
|
|
}
|
|
|
|
if aIsInt && bIsInt {
|
|
aInt, _ := strconv.Atoi(a)
|
|
bInt, _ := strconv.Atoi(b)
|
|
|
|
if aInt > bInt {
|
|
return 1
|
|
} else if aInt < bInt {
|
|
return -1
|
|
}
|
|
} else {
|
|
// string comparison
|
|
if a > b {
|
|
return 1
|
|
} else if a < b {
|
|
return -1
|
|
}
|
|
}
|
|
|
|
// a larger set of pre-release fields has a higher precedence than a
|
|
// smaller set, if all of the preceding identifiers are equal.
|
|
if afterA != "" && afterB == "" {
|
|
return 1
|
|
} else if afterA == "" && afterB != "" {
|
|
return -1
|
|
}
|
|
|
|
a, afterA = cut(afterA, '.')
|
|
b, afterB = cut(afterB, '.')
|
|
}
|
|
}
|
|
|
|
func isAllDecimals(s string) bool {
|
|
for _, r := range s {
|
|
if r < '0' || r > '9' {
|
|
return false
|
|
}
|
|
}
|
|
return s != ""
|
|
}
|
|
|
|
// length allows calculating the length of the version for pre-allocation.
|
|
func length(v Version) int {
|
|
n := util.NumDigitsInt64(v.Major) + util.NumDigitsInt64(v.Minor) + util.NumDigitsInt64(v.Patch) + 2
|
|
if v.PreRelease != "" {
|
|
n += len(v.PreRelease) + 1
|
|
}
|
|
if v.Metadata != "" {
|
|
n += len(v.Metadata) + 1
|
|
}
|
|
return n
|
|
}
|
|
|
|
// cut is a *slightly* faster version of strings.Cut only accepting
|
|
// single byte separators, and skipping the boolean return value.
|
|
func cut(s string, sep byte) (before, after string) {
|
|
if i := strings.IndexByte(s, sep); i >= 0 {
|
|
return s[:i], s[i+1:]
|
|
}
|
|
return s, ""
|
|
}
|