123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- 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 = `[vV]?` + // Optional [vV] prefix
- `([0-9]+(\.[0-9]+)*?)` + // ( MajorNum ( '.' MinorNums ) *? )
- `(-` + // Followed by (optionally): ( '-'
- `([0-9]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)` + // Either ( PreNum String ( '.' OtherString ) * )
- `|` +
- `([-\.]?([A-Za-z\-~]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)))?` + // Or ( ['-' '.' ] ? ( AlphaHyphenTilde String * ( '.' String ) * ))) ?
- `(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` + // and more Optionally: ( '+' String ( '.' String ) * )
- `([\+\.\-~]g[0-9A-Fa-f]{10}$)?` + // Optionally a: ( Punct 'g' Sha )
- `?`
-
- // 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 {
- if v == nil || o == nil {
- return v == o
- }
-
- 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
- }
-
- // GreaterThanOrEqual 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
- }
-
- // LessThanOrEqual 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
- }
|