251 lines
5.6 KiB
Go
251 lines
5.6 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
|
|
|
|
// The raw regular expression string used for testing the validity
|
|
// of a version.
|
|
const VersionRegexpRaw string = `([0-9]+(\.[0-9]+){0,2})` +
|
|
`(-([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 []int
|
|
si int
|
|
}
|
|
|
|
func init() {
|
|
versionRegexp = regexp.MustCompile("^" + VersionRegexpRaw + "$")
|
|
}
|
|
|
|
// NewVersion parses the given version and returns a new
|
|
// Version.
|
|
func NewVersion(v string) (*Version, error) {
|
|
matches := versionRegexp.FindStringSubmatch(v)
|
|
if matches == nil {
|
|
return nil, fmt.Errorf("Malformed version: %s", v)
|
|
}
|
|
|
|
segmentsStr := strings.Split(matches[1], ".")
|
|
segments := make([]int, len(segmentsStr), 3)
|
|
si := 0
|
|
for i, str := range segmentsStr {
|
|
val, err := strconv.ParseInt(str, 10, 32)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(
|
|
"Error parsing version: %s", err)
|
|
}
|
|
|
|
segments[i] = int(val)
|
|
si += 1
|
|
}
|
|
for i := len(segments); i < 3; i++ {
|
|
segments = append(segments, 0)
|
|
}
|
|
|
|
return &Version{
|
|
metadata: matches[7],
|
|
pre: matches[4],
|
|
segments: segments,
|
|
si: si,
|
|
}, 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.Segments()
|
|
segmentsOther := other.Segments()
|
|
|
|
// 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)
|
|
}
|
|
|
|
// Compare the segments
|
|
for i := 0; i < len(segmentsSelf); i++ {
|
|
lhs := segmentsSelf[i]
|
|
rhs := segmentsOther[i]
|
|
|
|
if lhs == rhs {
|
|
continue
|
|
} else if lhs < rhs {
|
|
return -1
|
|
} else {
|
|
return 1
|
|
}
|
|
}
|
|
|
|
panic("should not be reached")
|
|
}
|
|
|
|
func comparePart(preSelf string, preOther string) int {
|
|
if preSelf == preOther {
|
|
return 0
|
|
}
|
|
|
|
// if a part is empty, we use the other to decide
|
|
if preSelf == "" {
|
|
_, notIsNumeric := strconv.ParseInt(preOther, 10, 64)
|
|
if notIsNumeric == nil {
|
|
return -1
|
|
}
|
|
return 1
|
|
}
|
|
|
|
if preOther == "" {
|
|
_, notIsNumeric := strconv.ParseInt(preSelf, 10, 64)
|
|
if notIsNumeric == nil {
|
|
return 1
|
|
}
|
|
return -1
|
|
}
|
|
|
|
if preSelf > preOther {
|
|
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.
|
|
//
|
|
// 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 {
|
|
return v.segments
|
|
}
|
|
|
|
// String returns the full version string included pre-release
|
|
// and metadata information.
|
|
func (v *Version) String() string {
|
|
var buf bytes.Buffer
|
|
fmt.Fprintf(&buf, "%d.%d.%d", v.segments[0], v.segments[1], v.segments[2])
|
|
if v.pre != "" {
|
|
fmt.Fprintf(&buf, "-%s", v.pre)
|
|
}
|
|
if v.metadata != "" {
|
|
fmt.Fprintf(&buf, "+%s", v.metadata)
|
|
}
|
|
|
|
return buf.String()
|
|
}
|