From d9048ee79ac8fe03719b82a1b54d43cb87354061 Mon Sep 17 00:00:00 2001 From: Anton Kucherov Date: Mon, 7 Oct 2019 17:03:05 +0300 Subject: [PATCH] PMM-4781 Change version to 1.5.0 because of bug in the package. See: https://github.com/Masterminds/semver/issues/125. --- Gopkg.lock | 6 +- Gopkg.toml | 2 +- go.mod | 2 +- go.sum | 4 +- .../Masterminds/semver/constraints.go | 240 +++++------------- vendor/github.com/Masterminds/semver/doc.go | 109 ++------ vendor/github.com/Masterminds/semver/fuzz.go | 22 -- .../github.com/Masterminds/semver/version.go | 228 +++-------------- .../Masterminds/semver/version_fuzz.go | 10 + 9 files changed, 140 insertions(+), 483 deletions(-) delete mode 100644 vendor/github.com/Masterminds/semver/fuzz.go create mode 100644 vendor/github.com/Masterminds/semver/version_fuzz.go diff --git a/Gopkg.lock b/Gopkg.lock index 5a2c58f66..a978e5e1e 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,12 +2,12 @@ [[projects]] - digest = "1:0579669610e7b4268094959686738fce4833e8274793f09df8fe5ab2ff2f5efd" + digest = "1:fcdee7c9f5c90ad1e18bcf0a091b53c74c8938bc5fe7d5333d3cda9367fab733" name = "github.com/Masterminds/semver" packages = ["."] pruneopts = "NUT" - revision = "fe7c21038085e01e67044ec1efe3afb1eaa59f75" - version = "v3.0.1" + revision = "805c489aa98f412e79eb308a37996bf9d8b1c91e" + version = "v1.5.0" [[projects]] branch = "master" diff --git a/Gopkg.toml b/Gopkg.toml index 964b3bd2b..0044330b5 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -9,4 +9,4 @@ [[constraint]] name = "github.com/Masterminds/semver" - version = "3.0.1" + version = "1.5.0" diff --git a/go.mod b/go.mod index ea41d8650..b707c76a4 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/percona/mongodb_exporter go 1.12 require ( - github.com/Masterminds/semver/v3 v3.0.1 // indirect + github.com/Masterminds/semver v1.5.0 github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 github.com/golang/snappy v0.0.1 // indirect github.com/google/go-cmp v0.3.0 // indirect diff --git a/go.sum b/go.sum index b54c79718..56724caf3 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Masterminds/semver/v3 v3.0.1 h1:2kKm5lb7dKVrt5TYUiAavE6oFc1cFT0057UVGT+JqLk= -github.com/Masterminds/semver/v3 v3.0.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= diff --git a/vendor/github.com/Masterminds/semver/constraints.go b/vendor/github.com/Masterminds/semver/constraints.go index 548e84389..b94b93413 100644 --- a/vendor/github.com/Masterminds/semver/constraints.go +++ b/vendor/github.com/Masterminds/semver/constraints.go @@ -1,7 +1,6 @@ package semver import ( - "bytes" "errors" "fmt" "regexp" @@ -24,18 +23,7 @@ func NewConstraint(c string) (*Constraints, error) { ors := strings.Split(c, "||") or := make([][]*constraint, len(ors)) for k, v := range ors { - - // TODO: Find a way to validate and fetch all the constraints in a simpler form - - // Validate the segment - if !validConstraintRegex.MatchString(v) { - return nil, fmt.Errorf("improper constraint: %s", v) - } - - cs := findConstraintRegex.FindAllString(v, -1) - if cs == nil { - cs = append(cs, v) - } + cs := strings.Split(v, ",") result := make([]*constraint, len(cs)) for i, s := range cs { pc, err := parseConstraint(s) @@ -97,7 +85,7 @@ func (cs Constraints) Validate(v *Version) (bool, []error) { } else { if !c.check(v) { - em := fmt.Errorf(constraintMsg[c.origfunc], v, c.orig) + em := fmt.Errorf(c.msg, v, c.orig) e = append(e, em) joy = false } @@ -112,41 +100,9 @@ func (cs Constraints) Validate(v *Version) (bool, []error) { return false, e } -func (cs Constraints) String() string { - buf := make([]string, len(cs.constraints)) - var tmp bytes.Buffer - - for k, v := range cs.constraints { - tmp.Reset() - vlen := len(v) - for kk, c := range v { - tmp.WriteString(c.string()) - - // Space separate the AND conditions - if vlen > 1 && kk < vlen-1 { - tmp.WriteString(" ") - } - } - buf[k] = tmp.String() - } - - return strings.Join(buf, " || ") -} - var constraintOps map[string]cfunc var constraintMsg map[string]string var constraintRegex *regexp.Regexp -var constraintRangeRegex *regexp.Regexp - -// Used to find individual constraints within a multi-constraint string -var findConstraintRegex *regexp.Regexp - -// Used to validate an segment of ANDs is valid -var validConstraintRegex *regexp.Regexp - -const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` + - `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + - `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` func init() { constraintOps = map[string]cfunc{ @@ -192,20 +148,16 @@ func init() { constraintRangeRegex = regexp.MustCompile(fmt.Sprintf( `\s*(%s)\s+-\s+(%s)\s*`, cvRegex, cvRegex)) - - findConstraintRegex = regexp.MustCompile(fmt.Sprintf( - `(%s)\s*(%s)`, - strings.Join(ops, "|"), - cvRegex)) - - validConstraintRegex = regexp.MustCompile(fmt.Sprintf( - `^(\s*(%s)\s*(%s)\s*\,?)+$`, - strings.Join(ops, "|"), - cvRegex)) } // An individual constraint type constraint struct { + // The callback function for the restraint. It performs the logic for + // the constraint. + function cfunc + + msg string + // The version used in the constraint check. For example, if a constraint // is '<= 2.0.0' the con a version instance representing 2.0.0. con *Version @@ -213,9 +165,6 @@ type constraint struct { // The original parsed version (e.g., 4.x from != 4.x) orig string - // The original operator for the constraint - origfunc string - // When an x is used as part of the version (e.g., 1.x) minorDirty bool dirty bool @@ -224,64 +173,36 @@ type constraint struct { // Check if a version meets the constraint func (c *constraint) check(v *Version) bool { - return constraintOps[c.origfunc](v, c) -} - -// String prints an individual constraint into a string -func (c *constraint) string() string { - return c.origfunc + c.orig + return c.function(v, c) } type cfunc func(v *Version, c *constraint) bool func parseConstraint(c string) (*constraint, error) { - if len(c) > 0 { - m := constraintRegex.FindStringSubmatch(c) - if m == nil { - return nil, fmt.Errorf("improper constraint: %s", c) - } - - cs := &constraint{ - orig: m[2], - origfunc: m[1], - } - - ver := m[2] - minorDirty := false - patchDirty := false - dirty := false - if isX(m[3]) || m[3] == "" { - ver = "0.0.0" - dirty = true - } else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" { - minorDirty = true - dirty = true - ver = fmt.Sprintf("%s.0.0%s", m[3], m[6]) - } else if isX(strings.TrimPrefix(m[5], ".")) || m[5] == "" { - dirty = true - patchDirty = true - ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6]) - } - - con, err := NewVersion(ver) - if err != nil { - - // The constraintRegex should catch any regex parsing errors. So, - // we should never get here. - return nil, errors.New("constraint Parser Error") - } - - cs.con = con - cs.minorDirty = minorDirty - cs.patchDirty = patchDirty - cs.dirty = dirty - - return cs, nil - } - - // The rest is the special case where an empty string was passed in which - // is equivalent to * or >=0.0.0 - con, err := StrictNewVersion("0.0.0") + m := constraintRegex.FindStringSubmatch(c) + if m == nil { + return nil, fmt.Errorf("improper constraint: %s", c) + } + + ver := m[2] + orig := ver + minorDirty := false + patchDirty := false + dirty := false + if isX(m[3]) { + ver = "0.0.0" + dirty = true + } else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" { + minorDirty = true + dirty = true + ver = fmt.Sprintf("%s.0.0%s", m[3], m[6]) + } else if isX(strings.TrimPrefix(m[5], ".")) { + dirty = true + patchDirty = true + ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6]) + } + + con, err := NewVersion(ver) if err != nil { // The constraintRegex should catch any regex parsing errors. So, @@ -290,12 +211,13 @@ func parseConstraint(c string) (*constraint, error) { } cs := &constraint{ + function: constraintOps[m[1]], + msg: constraintMsg[m[1]], con: con, - orig: c, - origfunc: "", - minorDirty: false, - patchDirty: false, - dirty: true, + orig: orig, + minorDirty: minorDirty, + patchDirty: patchDirty, + dirty: dirty, } return cs, nil } @@ -318,15 +240,9 @@ func constraintNotEqual(v *Version, c *constraint) bool { return true } else if c.minorDirty { return false - } else if c.con.Patch() != v.Patch() && !c.patchDirty { - return true - } else if c.patchDirty { - // Need to handle prereleases if present - if v.Prerelease() != "" || c.con.Prerelease() != "" { - return comparePrerelease(v.Prerelease(), c.con.Prerelease()) != 0 - } - return false } + + return false } return !v.Equal(c.con) @@ -341,26 +257,6 @@ func constraintGreaterThan(v *Version, c *constraint) bool { return false } - if !c.dirty { - return v.Compare(c.con) == 1 - } - - if v.Major() > c.con.Major() { - return true - } else if v.Major() < c.con.Major() { - return false - } else if c.minorDirty { - // This is a range case such as >11. When the version is something like - // 11.1.0 is it not > 11. For that we would need 12 or higher - return false - } else if c.patchDirty { - // This is for ranges such as >11.1. A version of 11.1.1 is not greater - // which one of 11.2.1 is greater - return v.Minor() > c.con.Minor() - } - - // If we have gotten here we are not comparing pre-preleases and can use the - // Compare function to accomplish that. return v.Compare(c.con) == 1 } @@ -372,7 +268,17 @@ func constraintLessThan(v *Version, c *constraint) bool { return false } - return v.Compare(c.con) < 0 + if !c.dirty { + return v.Compare(c.con) < 0 + } + + if v.Major() > c.con.Major() { + return false + } else if v.Minor() > c.con.Minor() && !c.minorDirty { + return false + } + + return true } func constraintGreaterThanEqual(v *Version, c *constraint) bool { @@ -455,21 +361,19 @@ func constraintTildeOrEqual(v *Version, c *constraint) bool { } if c.dirty { + c.msg = constraintMsg["~"] return constraintTilde(v, c) } return v.Equal(c.con) } -// ^* --> (any) -// ^1.2.3 --> >=1.2.3 <2.0.0 -// ^1.2 --> >=1.2.0 <2.0.0 -// ^1 --> >=1.0.0 <2.0.0 -// ^0.2.3 --> >=0.2.3 <0.3.0 -// ^0.2 --> >=0.2.0 <0.3.0 -// ^0.0.3 --> >=0.0.3 <0.0.4 -// ^0.0 --> >=0.0.0 <0.1.0 -// ^0 --> >=0.0.0 <1.0.0 +// ^* --> (any) +// ^2, ^2.x, ^2.x.x --> >=2.0.0, <3.0.0 +// ^2.0, ^2.0.x --> >=2.0.0, <3.0.0 +// ^1.2, ^1.2.x --> >=1.2.0, <2.0.0 +// ^1.2.3 --> >=1.2.3, <2.0.0 +// ^1.2.0 --> >=1.2.0, <2.0.0 func constraintCaret(v *Version, c *constraint) bool { // If there is a pre-release on the version but the constraint isn't looking // for them assume that pre-releases are not compatible. See issue 21 for @@ -478,31 +382,23 @@ func constraintCaret(v *Version, c *constraint) bool { return false } - // This less than handles prereleases if v.LessThan(c.con) { return false } - // ^ when the major > 0 is >=x.y.z < x+1 - if c.con.Major() > 0 || c.minorDirty { - - // ^ has to be within a major range for > 0. Everything less than was - // filtered out with the LessThan call above. This filters out those - // that greater but not within the same major range. - return v.Major() == c.con.Major() - } - - // ^ when the major is 0 and minor > 0 is >=0.y.z < 0.y+1 - // If the con Minor is > 0 it is not dirty - if c.con.Minor() > 0 || c.patchDirty { - return v.Minor() == c.con.Minor() + if v.Major() != c.con.Major() { + return false } - // At this point the major is 0 and the minor is 0 and not dirty. The patch - // is not dirty so we need to check if they are equal. If they are not equal - return c.con.Patch() == v.Patch() + return true } +var constraintRangeRegex *regexp.Regexp + +const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` + + `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + + `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + func isX(x string) bool { switch x { case "x", "*", "X": diff --git a/vendor/github.com/Masterminds/semver/doc.go b/vendor/github.com/Masterminds/semver/doc.go index 34d78f220..6a6c24c6d 100644 --- a/vendor/github.com/Masterminds/semver/doc.go +++ b/vendor/github.com/Masterminds/semver/doc.go @@ -10,23 +10,14 @@ Specifically it provides the ability to: Parsing Semantic Versions -There are two functions that can parse semantic versions. The `StrictNewVersion` -function only parses valid version 2 semantic versions as outlined in the -specification. The `NewVersion` function attempts to coerce a version into a -semantic version and parse it. For example, if there is a leading v or a version -listed without all 3 parts (e.g. 1.2) it will attempt to coerce it into a valid -semantic version (e.g., 1.2.0). In both cases a `Version` object is returned -that can be sorted, compared, and used in constraints. - -When parsing a version an optional error can be returned if there is an issue -parsing the version. For example, +To parse a semantic version use the `NewVersion` function. For example, v, err := semver.NewVersion("1.2.3-beta.1+build345") -The version object has methods to get the parts of the version, compare it to -other versions, convert the version back into a string, and get the original -string. For more details please see the documentation -at https://godoc.org/github.com/Masterminds/semver. +If there is an error the version wasn't parseable. The version object has methods +to get the parts of the version, compare it to other versions, convert the +version back into a string, and get the original string. For more details +please see the documentation at https://godoc.org/github.com/Masterminds/semver. Sorting Semantic Versions @@ -46,44 +37,19 @@ For example, sort.Sort(semver.Collection(vs)) -Checking Version Constraints and Comparing Versions - -There are two methods for comparing versions. One uses comparison methods on -`Version` instances and the other is using Constraints. There are some important -differences to notes between these two methods of comparison. - -1. When two versions are compared using functions such as `Compare`, `LessThan`, - and others it will follow the specification and always include prereleases - within the comparison. It will provide an answer valid with the comparison - spec section at https://semver.org/#spec-item-11 -2. When constraint checking is used for checks or validation it will follow a - different set of rules that are common for ranges with tools like npm/js - and Rust/Cargo. This includes considering prereleases to be invalid if the - ranges does not include on. If you want to have it include pre-releases a - simple solution is to include `-0` in your range. -3. Constraint ranges can have some complex rules including the shorthard use of - ~ and ^. For more details on those see the options below. - -There are differences between the two methods or checking versions because the -comparison methods on `Version` follow the specification while comparison ranges -are not part of the specification. Different packages and tools have taken it -upon themselves to come up with range rules. This has resulted in differences. -For example, npm/js and Cargo/Rust follow similar patterns which PHP has a -different pattern for ^. The comparison features in this package follow the -npm/js and Cargo/Rust lead because applications using it have followed similar -patters with their versions. +Checking Version Constraints Checking a version against version constraints is one of the most featureful parts of the package. c, err := semver.NewConstraint(">= 1.2.3") if err != nil { - // Handle constraint not being parsable. + // Handle constraint not being parseable. } v, err := semver.NewVersion("1.3") if err != nil { - // Handle version not being parsable. + // Handle version not being parseable. } // Check if the version meets the constraints. The a variable will be true. a := c.Check(v) @@ -91,11 +57,10 @@ parts of the package. Basic Comparisons There are two elements to the comparisons. First, a comparison string is a list -of comma or space separated AND comparisons. These are then separated by || (OR) -comparisons. For example, `">= 1.2 < 3.0.0 || >= 4.2.3"` is looking for a +of comma separated and comparisons. These are then separated by || separated or +comparisons. For example, `">= 1.2, < 3.0.0 || >= 4.2.3"` is looking for a comparison that's greater than or equal to 1.2 and less than 3.0.0 or is -greater than or equal to 4.2.3. This can also be written as -`">= 1.2, < 3.0.0 || >= 4.2.3"` +greater than or equal to 4.2.3. The basic comparisons are: @@ -112,15 +77,15 @@ There are multiple methods to handle ranges and the first is hyphens ranges. These look like: * `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5` - * `2.3.4 - 4.5` which is equivalent to `>= 2.3.4 <= 4.5` + * `2.3.4 - 4.5` which is equivalent to `>= 2.3.4, <= 4.5` Wildcards In Comparisons The `x`, `X`, and `*` characters can be used as a wildcard character. This works for all comparison operators. When used on the `=` operator it falls -back to the tilde operation. For example, +back to the pack level comparison (see tilde below). For example, - * `1.2.x` is equivalent to `>= 1.2.0 < 1.3.0` + * `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` * `>= 1.2.x` is equivalent to `>= 1.2.0` * `<= 2.x` is equivalent to `<= 3` * `*` is equivalent to `>= 0.0.0` @@ -131,54 +96,20 @@ The tilde (`~`) comparison operator is for patch level ranges when a minor version is specified and major level changes when the minor number is missing. For example, - * `~1.2.3` is equivalent to `>= 1.2.3 < 1.3.0` + * `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0` * `~1` is equivalent to `>= 1, < 2` - * `~2.3` is equivalent to `>= 2.3 < 2.4` - * `~1.2.x` is equivalent to `>= 1.2.0 < 1.3.0` - * `~1.x` is equivalent to `>= 1 < 2` + * `~2.3` is equivalent to `>= 2.3, < 2.4` + * `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` + * `~1.x` is equivalent to `>= 1, < 2` Caret Range Comparisons (Major) -The caret (`^`) comparison operator is for major level changes once a stable -(1.0.0) release has occurred. Prior to a 1.0.0 release the minor versions acts -as the API stability level. This is useful when comparisons of API versions as a -major change is API breaking. For example, +The caret (`^`) comparison operator is for major level changes. This is useful +when comparisons of API versions as a major change is API breaking. For example, * `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0` * `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0` * `^2.3` is equivalent to `>= 2.3, < 3` * `^2.x` is equivalent to `>= 2.0.0, < 3` - * `^0.2.3` is equivalent to `>=0.2.3 <0.3.0` - * `^0.2` is equivalent to `>=0.2.0 <0.3.0` - * `^0.0.3` is equivalent to `>=0.0.3 <0.0.4` - * `^0.0` is equivalent to `>=0.0.0 <0.1.0` - * `^0` is equivalent to `>=0.0.0 <1.0.0` - -Validation - -In addition to testing a version against a constraint, a version can be validated -against a constraint. When validation fails a slice of errors containing why a -version didn't meet the constraint is returned. For example, - - c, err := semver.NewConstraint("<= 1.2.3, >= 1.4") - if err != nil { - // Handle constraint not being parseable. - } - - v, _ := semver.NewVersion("1.3") - if err != nil { - // Handle version not being parseable. - } - - // Validate a version against a constraint. - a, msgs := c.Validate(v) - // a is false - for _, m := range msgs { - fmt.Println(m) - - // Loops over the errors which would read - // "1.3 is greater than 1.2.3" - // "1.3 is less than 1.4" - } */ package semver diff --git a/vendor/github.com/Masterminds/semver/fuzz.go b/vendor/github.com/Masterminds/semver/fuzz.go deleted file mode 100644 index a242ad705..000000000 --- a/vendor/github.com/Masterminds/semver/fuzz.go +++ /dev/null @@ -1,22 +0,0 @@ -// +build gofuzz - -package semver - -func Fuzz(data []byte) int { - d := string(data) - - // Test NewVersion - _, _ = NewVersion(d) - - // Test StrictNewVersion - _, _ = StrictNewVersion(d) - - // Test NewConstraint - _, _ = NewConstraint(d) - - // The return value should be 0 normally, 1 if the priority in future tests - // should be increased, and -1 if future tests should skip passing in that - // data. We do not have a reason to change priority so 0 is always returned. - // There are example tests that do this. - return 0 -} diff --git a/vendor/github.com/Masterminds/semver/version.go b/vendor/github.com/Masterminds/semver/version.go index 1bb95f263..400d4f934 100644 --- a/vendor/github.com/Masterminds/semver/version.go +++ b/vendor/github.com/Masterminds/semver/version.go @@ -13,23 +13,13 @@ import ( // The compiled version of the regex created at init() is cached here so it // only needs to be created once. var versionRegex *regexp.Regexp +var validPrereleaseRegex *regexp.Regexp var ( // ErrInvalidSemVer is returned a version is found to be invalid when // being parsed. ErrInvalidSemVer = errors.New("Invalid Semantic Version") - // ErrEmptyString is returned when an empty string is passed in for parsing. - ErrEmptyString = errors.New("Version string empty") - - // ErrInvalidCharacters is returned when invalid characters are found as - // part of a version - ErrInvalidCharacters = errors.New("Invalid characters in version") - - // ErrSegmentStartsZero is returned when a version segment starts with 0. - // This is invalid in SemVer. - ErrSegmentStartsZero = errors.New("Version segment starts with 0") - // ErrInvalidMetadata is returned when the metadata is an invalid format ErrInvalidMetadata = errors.New("Invalid Metadata string") @@ -37,121 +27,30 @@ var ( ErrInvalidPrerelease = errors.New("Invalid Prerelease string") ) -// semVerRegex is the regular expression used to parse a semantic version. -const semVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` + +// SemVerRegex is the regular expression used to parse a semantic version. +const SemVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` + `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +// ValidPrerelease is the regular expression which validates +// both prerelease and metadata values. +const ValidPrerelease string = `^([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*)$` + // Version represents a single semantic version. type Version struct { - major, minor, patch uint64 + major, minor, patch int64 pre string metadata string original string } func init() { - versionRegex = regexp.MustCompile("^" + semVerRegex + "$") -} - -const num string = "0123456789" -const allowed string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" + num - -// StrictNewVersion parses a given version and returns an instance of Version or -// an error if unable to parse the version. Only parses valid semantic versions. -// Performs checking that can find errors within the version. -// If you want to coerce a version, such as 1 or 1.2, and perse that as the 1.x -// releases of semver provided use the NewSemver() function. -func StrictNewVersion(v string) (*Version, error) { - // Parsing here does not use RegEx in order to increase performance and reduce - // allocations. - - if len(v) == 0 { - return nil, ErrEmptyString - } - - // Split the parts into [0]major, [1]minor, and [2]patch,prerelease,build - parts := strings.SplitN(v, ".", 3) - if len(parts) != 3 { - return nil, ErrInvalidSemVer - } - - sv := &Version{ - original: v, - } - - // check for prerelease or build metadata - var extra []string - if strings.ContainsAny(parts[2], "-+") { - // Start with the build metadata first as it needs to be on the right - extra = strings.SplitN(parts[2], "+", 2) - if len(extra) > 1 { - // build metadata found - sv.metadata = extra[1] - parts[2] = extra[0] - } - - extra = strings.SplitN(parts[2], "-", 2) - if len(extra) > 1 { - // prerelease found - sv.pre = extra[1] - parts[2] = extra[0] - } - } - - // Validate the number segments are valid. This includes only having positive - // numbers and no leading 0's. - for _, p := range parts { - if !containsOnly(p, num) { - return nil, ErrInvalidCharacters - } - - if len(p) > 1 && p[0] == '0' { - return nil, ErrSegmentStartsZero - } - } - - // Extract the major, minor, and patch elements onto the returned Version - var err error - sv.major, err = strconv.ParseUint(parts[0], 10, 64) - if err != nil { - return nil, err - } - - sv.minor, err = strconv.ParseUint(parts[1], 10, 64) - if err != nil { - return nil, err - } - - sv.patch, err = strconv.ParseUint(parts[2], 10, 64) - if err != nil { - return nil, err - } - - // No prerelease or build metadata found so returning now as a fastpath. - if sv.pre == "" && sv.metadata == "" { - return sv, nil - } - - if sv.pre != "" { - if err = validatePrerelease(sv.pre); err != nil { - return nil, err - } - } - - if sv.metadata != "" { - if err = validateMetadata(sv.metadata); err != nil { - return nil, err - } - } - - return sv, nil + versionRegex = regexp.MustCompile("^" + SemVerRegex + "$") + validPrereleaseRegex = regexp.MustCompile(ValidPrerelease) } // NewVersion parses a given version and returns an instance of Version or -// an error if unable to parse the version. If the version is SemVer-ish it -// attempts to convert it to SemVer. If you want to validate it was a strict -// semantic version at parse time see StrictNewVersion(). +// an error if unable to parse the version. func NewVersion(v string) (*Version, error) { m := versionRegex.FindStringSubmatch(v) if m == nil { @@ -164,45 +63,33 @@ func NewVersion(v string) (*Version, error) { original: v, } - var err error - sv.major, err = strconv.ParseUint(m[1], 10, 64) + var temp int64 + temp, err := strconv.ParseInt(m[1], 10, 64) if err != nil { return nil, fmt.Errorf("Error parsing version segment: %s", err) } + sv.major = temp if m[2] != "" { - sv.minor, err = strconv.ParseUint(strings.TrimPrefix(m[2], "."), 10, 64) + temp, err = strconv.ParseInt(strings.TrimPrefix(m[2], "."), 10, 64) if err != nil { return nil, fmt.Errorf("Error parsing version segment: %s", err) } + sv.minor = temp } else { sv.minor = 0 } if m[3] != "" { - sv.patch, err = strconv.ParseUint(strings.TrimPrefix(m[3], "."), 10, 64) + temp, err = strconv.ParseInt(strings.TrimPrefix(m[3], "."), 10, 64) if err != nil { return nil, fmt.Errorf("Error parsing version segment: %s", err) } + sv.patch = temp } else { sv.patch = 0 } - // Perform some basic due diligence on the extra parts to ensure they are - // valid. - - if sv.pre != "" { - if err = validatePrerelease(sv.pre); err != nil { - return nil, err - } - } - - if sv.metadata != "" { - if err = validateMetadata(sv.metadata); err != nil { - return nil, err - } - } - return sv, nil } @@ -220,7 +107,7 @@ func MustParse(v string) *Version { // See the Original() method to retrieve the original value. Semantic Versions // don't contain a leading v per the spec. Instead it's optional on // implementation. -func (v Version) String() string { +func (v *Version) String() string { var buf bytes.Buffer fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch) @@ -240,32 +127,32 @@ func (v *Version) Original() string { } // Major returns the major version. -func (v Version) Major() uint64 { +func (v *Version) Major() int64 { return v.major } // Minor returns the minor version. -func (v Version) Minor() uint64 { +func (v *Version) Minor() int64 { return v.minor } // Patch returns the patch version. -func (v Version) Patch() uint64 { +func (v *Version) Patch() int64 { return v.patch } // Prerelease returns the pre-release version. -func (v Version) Prerelease() string { +func (v *Version) Prerelease() string { return v.pre } // Metadata returns the metadata on the version. -func (v Version) Metadata() string { +func (v *Version) Metadata() string { return v.metadata } // originalVPrefix returns the original 'v' prefix if any. -func (v Version) originalVPrefix() string { +func (v *Version) originalVPrefix() string { // Note, only lowercase v is supported as a prefix by the parser. if v.original != "" && v.original[:1] == "v" { @@ -278,7 +165,7 @@ func (v Version) originalVPrefix() string { // If the current version does not have prerelease/metadata information, // it unsets metadata and prerelease values, increments patch number. // If the current version has any of prerelease or metadata information, -// it unsets both values and keeps current patch value +// it unsets both values and keeps curent patch value func (v Version) IncPatch() Version { vNext := v // according to http://semver.org/#spec-item-9 @@ -330,13 +217,11 @@ func (v Version) IncMajor() Version { } // SetPrerelease defines the prerelease value. -// Value must not include the required 'hyphen' prefix. +// Value must not include the required 'hypen' prefix. func (v Version) SetPrerelease(prerelease string) (Version, error) { vNext := v - if len(prerelease) > 0 { - if err := validatePrerelease(prerelease); err != nil { - return vNext, err - } + if len(prerelease) > 0 && !validPrereleaseRegex.MatchString(prerelease) { + return vNext, ErrInvalidPrerelease } vNext.pre = prerelease vNext.original = v.originalVPrefix() + "" + vNext.String() @@ -347,10 +232,8 @@ func (v Version) SetPrerelease(prerelease string) (Version, error) { // Value must not include the required 'plus' prefix. func (v Version) SetMetadata(metadata string) (Version, error) { vNext := v - if len(metadata) > 0 { - if err := validateMetadata(metadata); err != nil { - return vNext, err - } + if len(metadata) > 0 && !validPrereleaseRegex.MatchString(metadata) { + return vNext, ErrInvalidMetadata } vNext.metadata = metadata vNext.original = v.originalVPrefix() + "" + vNext.String() @@ -378,9 +261,7 @@ func (v *Version) Equal(o *Version) bool { // the version smaller, equal, or larger than the other version. // // Versions are compared by X.Y.Z. Build metadata is ignored. Prerelease is -// lower than the version without a prerelease. Compare always takes into account -// prereleases. If you want to work with ranges using typical range syntaxes that -// skip prereleases if the range is not looking for them use constraints. +// lower than the version without a prerelease. func (v *Version) Compare(o *Version) int { // Compare the major, minor, and patch version for differences. If a // difference is found return the comparison. @@ -427,15 +308,16 @@ func (v *Version) UnmarshalJSON(b []byte) error { v.pre = temp.pre v.metadata = temp.metadata v.original = temp.original + temp = nil return nil } // MarshalJSON implements JSON.Marshaler interface. -func (v Version) MarshalJSON() ([]byte, error) { +func (v *Version) MarshalJSON() ([]byte, error) { return json.Marshal(v.String()) } -func compareSegment(v, o uint64) int { +func compareSegment(v, o int64) int { if v < o { return -1 } @@ -541,43 +423,3 @@ func comparePrePart(s, o string) int { return -1 } - -// Like strings.ContainsAny but does an only instead of any. -func containsOnly(s string, comp string) bool { - return strings.IndexFunc(s, func(r rune) bool { - return !strings.ContainsRune(comp, r) - }) == -1 -} - -// From the spec, "Identifiers MUST comprise only -// ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. -// Numeric identifiers MUST NOT include leading zeroes.". These segments can -// be dot separated. -func validatePrerelease(p string) error { - eparts := strings.Split(p, ".") - for _, p := range eparts { - if containsOnly(p, num) { - if len(p) > 1 && p[0] == '0' { - return ErrSegmentStartsZero - } - } else if !containsOnly(p, allowed) { - return ErrInvalidPrerelease - } - } - - return nil -} - -// From the spec, "Build metadata MAY be denoted by -// appending a plus sign and a series of dot separated identifiers immediately -// following the patch or pre-release version. Identifiers MUST comprise only -// ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty." -func validateMetadata(m string) error { - eparts := strings.Split(m, ".") - for _, p := range eparts { - if !containsOnly(p, allowed) { - return ErrInvalidMetadata - } - } - return nil -} diff --git a/vendor/github.com/Masterminds/semver/version_fuzz.go b/vendor/github.com/Masterminds/semver/version_fuzz.go new file mode 100644 index 000000000..b42bcd62b --- /dev/null +++ b/vendor/github.com/Masterminds/semver/version_fuzz.go @@ -0,0 +1,10 @@ +// +build gofuzz + +package semver + +func Fuzz(data []byte) int { + if _, err := NewVersion(string(data)); err != nil { + return 0 + } + return 1 +}