pax_global_header00006660000000000000000000000064136625075770014534gustar00rootroot0000000000000052 comment=af3461a9cbcf1f3f5889d21b83f5ef63880c33a8 semver-4.0.0/000077500000000000000000000000001366250757700130365ustar00rootroot00000000000000semver-4.0.0/.gx/000077500000000000000000000000001366250757700135325ustar00rootroot00000000000000semver-4.0.0/.gx/lastpubver000066400000000000000000000000661366250757700156460ustar00rootroot000000000000003.6.1: Qmc2TuykruVht9jiXbBXdGenbQhdsUMWCn61B8QFakH2Aw semver-4.0.0/.travis.yml000066400000000000000000000013341366250757700151500ustar00rootroot00000000000000language: go matrix: include: - go: 1.4.x - go: 1.5.x - go: 1.6.x - go: 1.7.x - go: 1.8.x - go: 1.9.x - go: 1.10.x - go: 1.11.x - go: 1.12.x - go: tip allow_failures: - go: tip install: - go get golang.org/x/tools/cmd/cover - go get github.com/mattn/goveralls script: - echo "Test and track coverage" ; $HOME/gopath/bin/goveralls -package "." -service=travis-ci -repotoken=$COVERALLS_TOKEN - echo "Build examples" ; cd examples && go build - echo "Check if gofmt'd" ; diff -u <(echo -n) <(gofmt -d -s .) env: global: secure: HroGEAUQpVq9zX1b1VIkraLiywhGbzvNnTZq2TMxgK7JHP8xqNplAeF1izrR2i4QLL9nsY+9WtYss4QuPvEtZcVHUobw6XnL6radF7jS1LgfYZ9Y7oF+zogZ2I5QUMRLGA7rcxQ05s7mKq3XZQfeqaNts4bms/eZRefWuaFZbkw= semver-4.0.0/LICENSE000066400000000000000000000021171366250757700140440ustar00rootroot00000000000000The MIT License Copyright (c) 2014 Benedikt Lang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. semver-4.0.0/README.md000066400000000000000000000145201366250757700143170ustar00rootroot00000000000000semver for golang [![Build Status](https://travis-ci.org/blang/semver.svg?branch=master)](https://travis-ci.org/blang/semver) [![GoDoc](https://godoc.org/github.com/blang/semver/v4?status.svg)](https://godoc.org/github.com/blang/semver/v4) [![Coverage Status](https://img.shields.io/coveralls/blang/semver.svg)](https://coveralls.io/r/blang/semver?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/blang/semver)](https://goreportcard.com/report/github.com/blang/semver) ====== semver is a [Semantic Versioning](http://semver.org/) library written in golang. It fully covers spec version `2.0.0`. Versioning ---------- Old v1-v3 versions exist in the root of the repository for compatiblity reasons and will only receive bug fixes. The current stable version is [*v4*](v4/). Usage ----- ```bash $ go get github.com/blang/semver/v4 ``` Note: Always vendor your dependencies or fix on a specific version tag. ```go import github.com/blang/semver/v4 v1, err := semver.Make("1.0.0-beta") v2, err := semver.Make("2.0.0-beta") v1.Compare(v2) ``` Also check the [GoDocs](http://godoc.org/github.com/blang/semver/v4). Why should I use this lib? ----- - Fully spec compatible - No reflection - No regex - Fully tested (Coverage >99%) - Readable parsing/validation errors - Fast (See [Benchmarks](#benchmarks)) - Only Stdlib - Uses values instead of pointers - Many features, see below Features ----- - Parsing and validation at all levels - Comparator-like comparisons - Compare Helper Methods - InPlace manipulation - Ranges `>=1.0.0 <2.0.0 || >=3.0.0 !3.0.1-beta.1` - Wildcards `>=1.x`, `<=2.5.x` - Sortable (implements sort.Interface) - database/sql compatible (sql.Scanner/Valuer) - encoding/json compatible (json.Marshaler/Unmarshaler) Ranges ------ A `Range` is a set of conditions which specify which versions satisfy the range. A condition is composed of an operator and a version. The supported operators are: - `<1.0.0` Less than `1.0.0` - `<=1.0.0` Less than or equal to `1.0.0` - `>1.0.0` Greater than `1.0.0` - `>=1.0.0` Greater than or equal to `1.0.0` - `1.0.0`, `=1.0.0`, `==1.0.0` Equal to `1.0.0` - `!1.0.0`, `!=1.0.0` Not equal to `1.0.0`. Excludes version `1.0.0`. Note that spaces between the operator and the version will be gracefully tolerated. A `Range` can link multiple `Ranges` separated by space: Ranges can be linked by logical AND: - `>1.0.0 <2.0.0` would match between both ranges, so `1.1.1` and `1.8.7` but not `1.0.0` or `2.0.0` - `>1.0.0 <3.0.0 !2.0.3-beta.2` would match every version between `1.0.0` and `3.0.0` except `2.0.3-beta.2` Ranges can also be linked by logical OR: - `<2.0.0 || >=3.0.0` would match `1.x.x` and `3.x.x` but not `2.x.x` AND has a higher precedence than OR. It's not possible to use brackets. Ranges can be combined by both AND and OR - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1` Range usage: ``` v, err := semver.Parse("1.2.3") expectedRange, err := semver.ParseRange(">1.0.0 <2.0.0 || >=3.0.0") if expectedRange(v) { //valid } ``` Example ----- Have a look at full examples in [examples/main.go](examples/main.go) ```go import github.com/blang/semver/v4 v, err := semver.Make("0.0.1-alpha.preview+123.github") fmt.Printf("Major: %d\n", v.Major) fmt.Printf("Minor: %d\n", v.Minor) fmt.Printf("Patch: %d\n", v.Patch) fmt.Printf("Pre: %s\n", v.Pre) fmt.Printf("Build: %s\n", v.Build) // Prerelease versions array if len(v.Pre) > 0 { fmt.Println("Prerelease versions:") for i, pre := range v.Pre { fmt.Printf("%d: %q\n", i, pre) } } // Build meta data array if len(v.Build) > 0 { fmt.Println("Build meta data:") for i, build := range v.Build { fmt.Printf("%d: %q\n", i, build) } } v001, err := semver.Make("0.0.1") // Compare using helpers: v.GT(v2), v.LT, v.GTE, v.LTE v001.GT(v) == true v.LT(v001) == true v.GTE(v) == true v.LTE(v) == true // Or use v.Compare(v2) for comparisons (-1, 0, 1): v001.Compare(v) == 1 v.Compare(v001) == -1 v.Compare(v) == 0 // Manipulate Version in place: v.Pre[0], err = semver.NewPRVersion("beta") if err != nil { fmt.Printf("Error parsing pre release version: %q", err) } fmt.Println("\nValidate versions:") v.Build[0] = "?" err = v.Validate() if err != nil { fmt.Printf("Validation failed: %s\n", err) } ``` Benchmarks ----- BenchmarkParseSimple-4 5000000 390 ns/op 48 B/op 1 allocs/op BenchmarkParseComplex-4 1000000 1813 ns/op 256 B/op 7 allocs/op BenchmarkParseAverage-4 1000000 1171 ns/op 163 B/op 4 allocs/op BenchmarkStringSimple-4 20000000 119 ns/op 16 B/op 1 allocs/op BenchmarkStringLarger-4 10000000 206 ns/op 32 B/op 2 allocs/op BenchmarkStringComplex-4 5000000 324 ns/op 80 B/op 3 allocs/op BenchmarkStringAverage-4 5000000 273 ns/op 53 B/op 2 allocs/op BenchmarkValidateSimple-4 200000000 9.33 ns/op 0 B/op 0 allocs/op BenchmarkValidateComplex-4 3000000 469 ns/op 0 B/op 0 allocs/op BenchmarkValidateAverage-4 5000000 256 ns/op 0 B/op 0 allocs/op BenchmarkCompareSimple-4 100000000 11.8 ns/op 0 B/op 0 allocs/op BenchmarkCompareComplex-4 50000000 30.8 ns/op 0 B/op 0 allocs/op BenchmarkCompareAverage-4 30000000 41.5 ns/op 0 B/op 0 allocs/op BenchmarkSort-4 3000000 419 ns/op 256 B/op 2 allocs/op BenchmarkRangeParseSimple-4 2000000 850 ns/op 192 B/op 5 allocs/op BenchmarkRangeParseAverage-4 1000000 1677 ns/op 400 B/op 10 allocs/op BenchmarkRangeParseComplex-4 300000 5214 ns/op 1440 B/op 30 allocs/op BenchmarkRangeMatchSimple-4 50000000 25.6 ns/op 0 B/op 0 allocs/op BenchmarkRangeMatchAverage-4 30000000 56.4 ns/op 0 B/op 0 allocs/op BenchmarkRangeMatchComplex-4 10000000 153 ns/op 0 B/op 0 allocs/op See benchmark cases at [semver_test.go](semver_test.go) Motivation ----- I simply couldn't find any lib supporting the full spec. Others were just wrong or used reflection and regex which i don't like. Contribution ----- Feel free to make a pull request. For bigger changes create a issue first to discuss about it. License ----- See [LICENSE](LICENSE) file. semver-4.0.0/examples/000077500000000000000000000000001366250757700146545ustar00rootroot00000000000000semver-4.0.0/examples/main.go000066400000000000000000000050001366250757700161220ustar00rootroot00000000000000package main import ( "fmt" "github.com/blang/semver" ) func main() { v, err := semver.Parse("0.0.1-alpha.preview.222+123.github") if err != nil { fmt.Printf("Error while parsing (not valid): %q", err) } fmt.Printf("Version to string: %q\n", v) fmt.Printf("Major: %d\n", v.Major) fmt.Printf("Minor: %d\n", v.Minor) fmt.Printf("Patch: %d\n", v.Patch) // Prerelease versions if len(v.Pre) > 0 { fmt.Println("Prerelease versions:") for i, pre := range v.Pre { fmt.Printf("%d: %q\n", i, pre) } } // Build meta data if len(v.Build) > 0 { fmt.Println("Build meta data:") for i, build := range v.Build { fmt.Printf("%d: %q\n", i, build) } } // Make == Parse (Value), New for Pointer v001, _ := semver.Make("0.0.1") fmt.Println("\nUse Version.Compare for comparisons (-1, 0, 1):") fmt.Printf("%q is greater than %q: Compare == %d\n", v001, v, v001.Compare(v)) fmt.Printf("%q is less than %q: Compare == %d\n", v, v001, v.Compare(v001)) fmt.Printf("%q is equal to %q: Compare == %d\n", v, v, v.Compare(v)) fmt.Println("\nUse comparison helpers returning booleans:") fmt.Printf("%q is greater than %q: %t\n", v001, v, v001.GT(v)) fmt.Printf("%q is greater than equal %q: %t\n", v001, v, v001.GTE(v)) fmt.Printf("%q is greater than equal %q: %t\n", v, v, v.GTE(v)) fmt.Printf("%q is less than %q: %t\n", v, v001, v.LT(v001)) fmt.Printf("%q is less than equal %q: %t\n", v, v001, v.LTE(v001)) fmt.Printf("%q is less than equal %q: %t\n", v, v, v.LTE(v)) fmt.Println("\nManipulate Version in place:") v.Pre[0], err = semver.NewPRVersion("beta") if err != nil { fmt.Printf("Error parsing pre release version: %q", err) } fmt.Printf("Version to string: %q\n", v) fmt.Println("\nCompare Prerelease versions:") pre1, _ := semver.NewPRVersion("123") pre2, _ := semver.NewPRVersion("alpha") pre3, _ := semver.NewPRVersion("124") fmt.Printf("%q is less than %q: Compare == %d\n", pre1, pre2, pre1.Compare(pre2)) fmt.Printf("%q is greater than %q: Compare == %d\n", pre3, pre1, pre3.Compare(pre1)) fmt.Printf("%q is equal to %q: Compare == %d\n", pre1, pre1, pre1.Compare(pre1)) fmt.Println("\nValidate versions:") v.Build[0] = "?" err = v.Validate() if err != nil { fmt.Printf("Validation failed: %s\n", err) } fmt.Println("Create valid build meta data:") b1, _ := semver.NewBuildVersion("build123") v.Build[0] = b1 fmt.Printf("Version with new build version %q\n", v) _, err = semver.NewBuildVersion("build?123") if err != nil { fmt.Printf("Create build version failed: %s\n", err) } } semver-4.0.0/go.mod000066400000000000000000000000501366250757700141370ustar00rootroot00000000000000module github.com/blang/semver go 1.12 semver-4.0.0/json.go000066400000000000000000000007231366250757700143400ustar00rootroot00000000000000package semver import ( "encoding/json" ) // MarshalJSON implements the encoding/json.Marshaler interface. func (v Version) MarshalJSON() ([]byte, error) { return json.Marshal(v.String()) } // UnmarshalJSON implements the encoding/json.Unmarshaler interface. func (v *Version) UnmarshalJSON(data []byte) (err error) { var versionString string if err = json.Unmarshal(data, &versionString); err != nil { return } *v, err = Parse(versionString) return } semver-4.0.0/json_test.go000066400000000000000000000023221366250757700153740ustar00rootroot00000000000000package semver import ( "encoding/json" "strconv" "testing" ) func TestJSONMarshal(t *testing.T) { versionString := "3.1.4-alpha.1.5.9+build.2.6.5" v, err := Parse(versionString) if err != nil { t.Fatal(err) } versionJSON, err := json.Marshal(v) if err != nil { t.Fatal(err) } quotedVersionString := strconv.Quote(versionString) if string(versionJSON) != quotedVersionString { t.Fatalf("JSON marshaled semantic version not equal: expected %q, got %q", quotedVersionString, string(versionJSON)) } } func TestJSONUnmarshal(t *testing.T) { versionString := "3.1.4-alpha.1.5.9+build.2.6.5" quotedVersionString := strconv.Quote(versionString) var v Version if err := json.Unmarshal([]byte(quotedVersionString), &v); err != nil { t.Fatal(err) } if v.String() != versionString { t.Fatalf("JSON unmarshaled semantic version not equal: expected %q, got %q", versionString, v.String()) } badVersionString := strconv.Quote("3.1.4.1.5.9.2.6.5-other-digits-of-pi") if err := json.Unmarshal([]byte(badVersionString), &v); err == nil { t.Fatal("expected JSON unmarshal error, got nil") } if err := json.Unmarshal([]byte("3.1"), &v); err == nil { t.Fatal("expected JSON unmarshal error, got nil") } } semver-4.0.0/package.json000066400000000000000000000005651366250757700153320ustar00rootroot00000000000000{ "author": "blang", "bugs": { "URL": "https://github.com/blang/semver/issues", "url": "https://github.com/blang/semver/issues" }, "gx": { "dvcsimport": "github.com/blang/semver" }, "gxVersion": "0.10.0", "language": "go", "license": "MIT", "name": "semver", "releaseCmd": "git commit -a -m \"gx publish $VERSION\"", "version": "3.6.1" } semver-4.0.0/range.go000066400000000000000000000236601366250757700144700ustar00rootroot00000000000000package semver import ( "fmt" "strconv" "strings" "unicode" ) type wildcardType int const ( noneWildcard wildcardType = iota majorWildcard wildcardType = 1 minorWildcard wildcardType = 2 patchWildcard wildcardType = 3 ) func wildcardTypefromInt(i int) wildcardType { switch i { case 1: return majorWildcard case 2: return minorWildcard case 3: return patchWildcard default: return noneWildcard } } type comparator func(Version, Version) bool var ( compEQ comparator = func(v1 Version, v2 Version) bool { return v1.Compare(v2) == 0 } compNE = func(v1 Version, v2 Version) bool { return v1.Compare(v2) != 0 } compGT = func(v1 Version, v2 Version) bool { return v1.Compare(v2) == 1 } compGE = func(v1 Version, v2 Version) bool { return v1.Compare(v2) >= 0 } compLT = func(v1 Version, v2 Version) bool { return v1.Compare(v2) == -1 } compLE = func(v1 Version, v2 Version) bool { return v1.Compare(v2) <= 0 } ) type versionRange struct { v Version c comparator } // rangeFunc creates a Range from the given versionRange. func (vr *versionRange) rangeFunc() Range { return Range(func(v Version) bool { return vr.c(v, vr.v) }) } // Range represents a range of versions. // A Range can be used to check if a Version satisfies it: // // range, err := semver.ParseRange(">1.0.0 <2.0.0") // range(semver.MustParse("1.1.1") // returns true type Range func(Version) bool // OR combines the existing Range with another Range using logical OR. func (rf Range) OR(f Range) Range { return Range(func(v Version) bool { return rf(v) || f(v) }) } // AND combines the existing Range with another Range using logical AND. func (rf Range) AND(f Range) Range { return Range(func(v Version) bool { return rf(v) && f(v) }) } // ParseRange parses a range and returns a Range. // If the range could not be parsed an error is returned. // // Valid ranges are: // - "<1.0.0" // - "<=1.0.0" // - ">1.0.0" // - ">=1.0.0" // - "1.0.0", "=1.0.0", "==1.0.0" // - "!1.0.0", "!=1.0.0" // // A Range can consist of multiple ranges separated by space: // Ranges can be linked by logical AND: // - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7" but not "1.0.0" or "2.0.0" // - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2 // // Ranges can also be linked by logical OR: // - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x" // // AND has a higher precedence than OR. It's not possible to use brackets. // // Ranges can be combined by both AND and OR // // - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1` func ParseRange(s string) (Range, error) { parts := splitAndTrim(s) orParts, err := splitORParts(parts) if err != nil { return nil, err } expandedParts, err := expandWildcardVersion(orParts) if err != nil { return nil, err } var orFn Range for _, p := range expandedParts { var andFn Range for _, ap := range p { opStr, vStr, err := splitComparatorVersion(ap) if err != nil { return nil, err } vr, err := buildVersionRange(opStr, vStr) if err != nil { return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err) } rf := vr.rangeFunc() // Set function if andFn == nil { andFn = rf } else { // Combine with existing function andFn = andFn.AND(rf) } } if orFn == nil { orFn = andFn } else { orFn = orFn.OR(andFn) } } return orFn, nil } // splitORParts splits the already cleaned parts by '||'. // Checks for invalid positions of the operator and returns an // error if found. func splitORParts(parts []string) ([][]string, error) { var ORparts [][]string last := 0 for i, p := range parts { if p == "||" { if i == 0 { return nil, fmt.Errorf("First element in range is '||'") } ORparts = append(ORparts, parts[last:i]) last = i + 1 } } if last == len(parts) { return nil, fmt.Errorf("Last element in range is '||'") } ORparts = append(ORparts, parts[last:]) return ORparts, nil } // buildVersionRange takes a slice of 2: operator and version // and builds a versionRange, otherwise an error. func buildVersionRange(opStr, vStr string) (*versionRange, error) { c := parseComparator(opStr) if c == nil { return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, "")) } v, err := Parse(vStr) if err != nil { return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err) } return &versionRange{ v: v, c: c, }, nil } // inArray checks if a byte is contained in an array of bytes func inArray(s byte, list []byte) bool { for _, el := range list { if el == s { return true } } return false } // splitAndTrim splits a range string by spaces and cleans whitespaces func splitAndTrim(s string) (result []string) { last := 0 var lastChar byte excludeFromSplit := []byte{'>', '<', '='} for i := 0; i < len(s); i++ { if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) { if last < i-1 { result = append(result, s[last:i]) } last = i + 1 } else if s[i] != ' ' { lastChar = s[i] } } if last < len(s)-1 { result = append(result, s[last:]) } for i, v := range result { result[i] = strings.Replace(v, " ", "", -1) } // parts := strings.Split(s, " ") // for _, x := range parts { // if s := strings.TrimSpace(x); len(s) != 0 { // result = append(result, s) // } // } return } // splitComparatorVersion splits the comparator from the version. // Input must be free of leading or trailing spaces. func splitComparatorVersion(s string) (string, string, error) { i := strings.IndexFunc(s, unicode.IsDigit) if i == -1 { return "", "", fmt.Errorf("Could not get version from string: %q", s) } return strings.TrimSpace(s[0:i]), s[i:], nil } // getWildcardType will return the type of wildcard that the // passed version contains func getWildcardType(vStr string) wildcardType { parts := strings.Split(vStr, ".") nparts := len(parts) wildcard := parts[nparts-1] possibleWildcardType := wildcardTypefromInt(nparts) if wildcard == "x" { return possibleWildcardType } return noneWildcard } // createVersionFromWildcard will convert a wildcard version // into a regular version, replacing 'x's with '0's, handling // special cases like '1.x.x' and '1.x' func createVersionFromWildcard(vStr string) string { // handle 1.x.x vStr2 := strings.Replace(vStr, ".x.x", ".x", 1) vStr2 = strings.Replace(vStr2, ".x", ".0", 1) parts := strings.Split(vStr2, ".") // handle 1.x if len(parts) == 2 { return vStr2 + ".0" } return vStr2 } // incrementMajorVersion will increment the major version // of the passed version func incrementMajorVersion(vStr string) (string, error) { parts := strings.Split(vStr, ".") i, err := strconv.Atoi(parts[0]) if err != nil { return "", err } parts[0] = strconv.Itoa(i + 1) return strings.Join(parts, "."), nil } // incrementMajorVersion will increment the minor version // of the passed version func incrementMinorVersion(vStr string) (string, error) { parts := strings.Split(vStr, ".") i, err := strconv.Atoi(parts[1]) if err != nil { return "", err } parts[1] = strconv.Itoa(i + 1) return strings.Join(parts, "."), nil } // expandWildcardVersion will expand wildcards inside versions // following these rules: // // * when dealing with patch wildcards: // >= 1.2.x will become >= 1.2.0 // <= 1.2.x will become < 1.3.0 // > 1.2.x will become >= 1.3.0 // < 1.2.x will become < 1.2.0 // != 1.2.x will become < 1.2.0 >= 1.3.0 // // * when dealing with minor wildcards: // >= 1.x will become >= 1.0.0 // <= 1.x will become < 2.0.0 // > 1.x will become >= 2.0.0 // < 1.0 will become < 1.0.0 // != 1.x will become < 1.0.0 >= 2.0.0 // // * when dealing with wildcards without // version operator: // 1.2.x will become >= 1.2.0 < 1.3.0 // 1.x will become >= 1.0.0 < 2.0.0 func expandWildcardVersion(parts [][]string) ([][]string, error) { var expandedParts [][]string for _, p := range parts { var newParts []string for _, ap := range p { if strings.Contains(ap, "x") { opStr, vStr, err := splitComparatorVersion(ap) if err != nil { return nil, err } versionWildcardType := getWildcardType(vStr) flatVersion := createVersionFromWildcard(vStr) var resultOperator string var shouldIncrementVersion bool switch opStr { case ">": resultOperator = ">=" shouldIncrementVersion = true case ">=": resultOperator = ">=" case "<": resultOperator = "<" case "<=": resultOperator = "<" shouldIncrementVersion = true case "", "=", "==": newParts = append(newParts, ">="+flatVersion) resultOperator = "<" shouldIncrementVersion = true case "!=", "!": newParts = append(newParts, "<"+flatVersion) resultOperator = ">=" shouldIncrementVersion = true } var resultVersion string if shouldIncrementVersion { switch versionWildcardType { case patchWildcard: resultVersion, _ = incrementMinorVersion(flatVersion) case minorWildcard: resultVersion, _ = incrementMajorVersion(flatVersion) } } else { resultVersion = flatVersion } ap = resultOperator + resultVersion } newParts = append(newParts, ap) } expandedParts = append(expandedParts, newParts) } return expandedParts, nil } func parseComparator(s string) comparator { switch s { case "==": fallthrough case "": fallthrough case "=": return compEQ case ">": return compGT case ">=": return compGE case "<": return compLT case "<=": return compLE case "!": fallthrough case "!=": return compNE } return nil } // MustParseRange is like ParseRange but panics if the range cannot be parsed. func MustParseRange(s string) Range { r, err := ParseRange(s) if err != nil { panic(`semver: ParseRange(` + s + `): ` + err.Error()) } return r } semver-4.0.0/range_test.go000066400000000000000000000312731366250757700155260ustar00rootroot00000000000000package semver import ( "reflect" "strings" "testing" ) type wildcardTypeTest struct { input string wildcardType wildcardType } type comparatorTest struct { input string comparator func(comparator) bool } func TestParseComparator(t *testing.T) { compatorTests := []comparatorTest{ {">", testGT}, {">=", testGE}, {"<", testLT}, {"<=", testLE}, {"", testEQ}, {"=", testEQ}, {"==", testEQ}, {"!=", testNE}, {"!", testNE}, {"-", nil}, {"<==", nil}, {"<<", nil}, {">>", nil}, } for _, tc := range compatorTests { if c := parseComparator(tc.input); c == nil { if tc.comparator != nil { t.Errorf("Comparator nil for case %q\n", tc.input) } } else if !tc.comparator(c) { t.Errorf("Invalid comparator for case %q\n", tc.input) } } } var ( v1 = MustParse("1.2.2") v2 = MustParse("1.2.3") v3 = MustParse("1.2.4") ) func testEQ(f comparator) bool { return f(v1, v1) && !f(v1, v2) } func testNE(f comparator) bool { return !f(v1, v1) && f(v1, v2) } func testGT(f comparator) bool { return f(v2, v1) && f(v3, v2) && !f(v1, v2) && !f(v1, v1) } func testGE(f comparator) bool { return f(v2, v1) && f(v3, v2) && !f(v1, v2) } func testLT(f comparator) bool { return f(v1, v2) && f(v2, v3) && !f(v2, v1) && !f(v1, v1) } func testLE(f comparator) bool { return f(v1, v2) && f(v2, v3) && !f(v2, v1) } func TestSplitAndTrim(t *testing.T) { tests := []struct { i string s []string }{ {"1.2.3 1.2.3", []string{"1.2.3", "1.2.3"}}, {" 1.2.3 1.2.3 ", []string{"1.2.3", "1.2.3"}}, // Spaces {" >= 1.2.3 <= 1.2.3 ", []string{">=1.2.3", "<=1.2.3"}}, // Spaces between operator and version {"1.2.3 || >=1.2.3 <1.2.3", []string{"1.2.3", "||", ">=1.2.3", "<1.2.3"}}, {" 1.2.3 || >=1.2.3 <1.2.3 ", []string{"1.2.3", "||", ">=1.2.3", "<1.2.3"}}, } for _, tc := range tests { p := splitAndTrim(tc.i) if !reflect.DeepEqual(p, tc.s) { t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p) } } } func TestSplitComparatorVersion(t *testing.T) { tests := []struct { i string p []string }{ {">1.2.3", []string{">", "1.2.3"}}, {">=1.2.3", []string{">=", "1.2.3"}}, {"<1.2.3", []string{"<", "1.2.3"}}, {"<=1.2.3", []string{"<=", "1.2.3"}}, {"1.2.3", []string{"", "1.2.3"}}, {"=1.2.3", []string{"=", "1.2.3"}}, {"==1.2.3", []string{"==", "1.2.3"}}, {"!=1.2.3", []string{"!=", "1.2.3"}}, {"!1.2.3", []string{"!", "1.2.3"}}, {"error", nil}, } for _, tc := range tests { if op, v, err := splitComparatorVersion(tc.i); err != nil { if tc.p != nil { t.Errorf("Invalid for case %q: Expected %q, got error %q", tc.i, tc.p, err) } } else if op != tc.p[0] { t.Errorf("Invalid operator for case %q: Expected %q, got: %q", tc.i, tc.p[0], op) } else if v != tc.p[1] { t.Errorf("Invalid version for case %q: Expected %q, got: %q", tc.i, tc.p[1], v) } } } func TestBuildVersionRange(t *testing.T) { tests := []struct { opStr string vStr string c func(comparator) bool v string }{ {">", "1.2.3", testGT, "1.2.3"}, {">=", "1.2.3", testGE, "1.2.3"}, {"<", "1.2.3", testLT, "1.2.3"}, {"<=", "1.2.3", testLE, "1.2.3"}, {"", "1.2.3", testEQ, "1.2.3"}, {"=", "1.2.3", testEQ, "1.2.3"}, {"==", "1.2.3", testEQ, "1.2.3"}, {"!=", "1.2.3", testNE, "1.2.3"}, {"!", "1.2.3", testNE, "1.2.3"}, {">>", "1.2.3", nil, ""}, // Invalid comparator {"=", "invalid", nil, ""}, // Invalid version } for _, tc := range tests { if r, err := buildVersionRange(tc.opStr, tc.vStr); err != nil { if tc.c != nil { t.Errorf("Invalid for case %q: Expected %q, got error %q", strings.Join([]string{tc.opStr, tc.vStr}, ""), tc.v, err) } } else if r == nil { t.Errorf("Invalid for case %q: got nil", strings.Join([]string{tc.opStr, tc.vStr}, "")) } else { // test version if tv := MustParse(tc.v); !r.v.EQ(tv) { t.Errorf("Invalid for case %q: Expected version %q, got: %q", strings.Join([]string{tc.opStr, tc.vStr}, ""), tv, r.v) } // test comparator if r.c == nil { t.Errorf("Invalid for case %q: got nil comparator", strings.Join([]string{tc.opStr, tc.vStr}, "")) continue } if !tc.c(r.c) { t.Errorf("Invalid comparator for case %q\n", strings.Join([]string{tc.opStr, tc.vStr}, "")) } } } } func TestSplitORParts(t *testing.T) { tests := []struct { i []string o [][]string }{ {[]string{">1.2.3", "||", "<1.2.3", "||", "=1.2.3"}, [][]string{ {">1.2.3"}, {"<1.2.3"}, {"=1.2.3"}, }}, {[]string{">1.2.3", "<1.2.3", "||", "=1.2.3"}, [][]string{ {">1.2.3", "<1.2.3"}, {"=1.2.3"}, }}, {[]string{">1.2.3", "||"}, nil}, {[]string{"||", ">1.2.3"}, nil}, } for _, tc := range tests { o, err := splitORParts(tc.i) if err != nil && tc.o != nil { t.Errorf("Unexpected error for case %q: %s", tc.i, err) } if !reflect.DeepEqual(tc.o, o) { t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.o, o) } } } func TestGetWildcardType(t *testing.T) { wildcardTypeTests := []wildcardTypeTest{ {"x", majorWildcard}, {"1.x", minorWildcard}, {"1.2.x", patchWildcard}, {"fo.o.b.ar", noneWildcard}, } for _, tc := range wildcardTypeTests { o := getWildcardType(tc.input) if o != tc.wildcardType { t.Errorf("Invalid for case: %q: Expected %q, got: %q", tc.input, tc.wildcardType, o) } } } func TestCreateVersionFromWildcard(t *testing.T) { tests := []struct { i string s string }{ {"1.2.x", "1.2.0"}, {"1.x", "1.0.0"}, } for _, tc := range tests { p := createVersionFromWildcard(tc.i) if p != tc.s { t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p) } } } func TestIncrementMajorVersion(t *testing.T) { tests := []struct { i string s string }{ {"1.2.3", "2.2.3"}, {"1.2", "2.2"}, {"foo.bar", ""}, } for _, tc := range tests { p, _ := incrementMajorVersion(tc.i) if p != tc.s { t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p) } } } func TestIncrementMinorVersion(t *testing.T) { tests := []struct { i string s string }{ {"1.2.3", "1.3.3"}, {"1.2", "1.3"}, {"foo.bar", ""}, } for _, tc := range tests { p, _ := incrementMinorVersion(tc.i) if p != tc.s { t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p) } } } func TestExpandWildcardVersion(t *testing.T) { tests := []struct { i [][]string o [][]string }{ {[][]string{{"foox"}}, nil}, {[][]string{{">=1.2.x"}}, [][]string{{">=1.2.0"}}}, {[][]string{{"<=1.2.x"}}, [][]string{{"<1.3.0"}}}, {[][]string{{">1.2.x"}}, [][]string{{">=1.3.0"}}}, {[][]string{{"<1.2.x"}}, [][]string{{"<1.2.0"}}}, {[][]string{{"!=1.2.x"}}, [][]string{{"<1.2.0", ">=1.3.0"}}}, {[][]string{{">=1.x"}}, [][]string{{">=1.0.0"}}}, {[][]string{{"<=1.x"}}, [][]string{{"<2.0.0"}}}, {[][]string{{">1.x"}}, [][]string{{">=2.0.0"}}}, {[][]string{{"<1.x"}}, [][]string{{"<1.0.0"}}}, {[][]string{{"!=1.x"}}, [][]string{{"<1.0.0", ">=2.0.0"}}}, {[][]string{{"1.2.x"}}, [][]string{{">=1.2.0", "<1.3.0"}}}, {[][]string{{"1.x"}}, [][]string{{">=1.0.0", "<2.0.0"}}}, } for _, tc := range tests { o, _ := expandWildcardVersion(tc.i) if !reflect.DeepEqual(tc.o, o) { t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.o, o) } } } func TestVersionRangeToRange(t *testing.T) { vr := versionRange{ v: MustParse("1.2.3"), c: compLT, } rf := vr.rangeFunc() if !rf(MustParse("1.2.2")) || rf(MustParse("1.2.3")) { t.Errorf("Invalid conversion to range func") } } func TestRangeAND(t *testing.T) { v := MustParse("1.2.2") v1 := MustParse("1.2.1") v2 := MustParse("1.2.3") rf1 := Range(func(v Version) bool { return v.GT(v1) }) rf2 := Range(func(v Version) bool { return v.LT(v2) }) rf := rf1.AND(rf2) if rf(v1) { t.Errorf("Invalid rangefunc, accepted: %s", v1) } if rf(v2) { t.Errorf("Invalid rangefunc, accepted: %s", v2) } if !rf(v) { t.Errorf("Invalid rangefunc, did not accept: %s", v) } } func TestRangeOR(t *testing.T) { tests := []struct { v Version b bool }{ {MustParse("1.2.0"), true}, {MustParse("1.2.2"), false}, {MustParse("1.2.4"), true}, } v1 := MustParse("1.2.1") v2 := MustParse("1.2.3") rf1 := Range(func(v Version) bool { return v.LT(v1) }) rf2 := Range(func(v Version) bool { return v.GT(v2) }) rf := rf1.OR(rf2) for _, tc := range tests { if r := rf(tc.v); r != tc.b { t.Errorf("Invalid for case %q: Expected %t, got %t", tc.v, tc.b, r) } } } func TestParseRange(t *testing.T) { type tv struct { v string b bool } tests := []struct { i string t []tv }{ // Simple expressions {">1.2.3", []tv{ {"1.2.2", false}, {"1.2.3", false}, {"1.2.4", true}, }}, {">=1.2.3", []tv{ {"1.2.3", true}, {"1.2.4", true}, {"1.2.2", false}, }}, {"<1.2.3", []tv{ {"1.2.2", true}, {"1.2.3", false}, {"1.2.4", false}, }}, {"<=1.2.3", []tv{ {"1.2.2", true}, {"1.2.3", true}, {"1.2.4", false}, }}, {"1.2.3", []tv{ {"1.2.2", false}, {"1.2.3", true}, {"1.2.4", false}, }}, {"=1.2.3", []tv{ {"1.2.2", false}, {"1.2.3", true}, {"1.2.4", false}, }}, {"==1.2.3", []tv{ {"1.2.2", false}, {"1.2.3", true}, {"1.2.4", false}, }}, {"!=1.2.3", []tv{ {"1.2.2", true}, {"1.2.3", false}, {"1.2.4", true}, }}, {"!1.2.3", []tv{ {"1.2.2", true}, {"1.2.3", false}, {"1.2.4", true}, }}, // Simple Expression errors {">>1.2.3", nil}, {"!1.2.3", nil}, {"1.0", nil}, {"string", nil}, {"", nil}, {"fo.ob.ar.x", nil}, // AND Expressions {">1.2.2 <1.2.4", []tv{ {"1.2.2", false}, {"1.2.3", true}, {"1.2.4", false}, }}, {"<1.2.2 <1.2.4", []tv{ {"1.2.1", true}, {"1.2.2", false}, {"1.2.3", false}, {"1.2.4", false}, }}, {">1.2.2 <1.2.5 !=1.2.4", []tv{ {"1.2.2", false}, {"1.2.3", true}, {"1.2.4", false}, {"1.2.5", false}, }}, {">1.2.2 <1.2.5 !1.2.4", []tv{ {"1.2.2", false}, {"1.2.3", true}, {"1.2.4", false}, {"1.2.5", false}, }}, // OR Expressions {">1.2.2 || <1.2.4", []tv{ {"1.2.2", true}, {"1.2.3", true}, {"1.2.4", true}, }}, {"<1.2.2 || >1.2.4", []tv{ {"1.2.2", false}, {"1.2.3", false}, {"1.2.4", false}, }}, // Wildcard expressions {">1.x", []tv{ {"0.1.9", false}, {"1.2.6", false}, {"1.9.0", false}, {"2.0.0", true}, }}, {">1.2.x", []tv{ {"1.1.9", false}, {"1.2.6", false}, {"1.3.0", true}, }}, // Combined Expressions {">1.2.2 <1.2.4 || >=2.0.0", []tv{ {"1.2.2", false}, {"1.2.3", true}, {"1.2.4", false}, {"2.0.0", true}, {"2.0.1", true}, }}, {"1.x || >=2.0.x <2.2.x", []tv{ {"0.9.2", false}, {"1.2.2", true}, {"2.0.0", true}, {"2.1.8", true}, {"2.2.0", false}, }}, {">1.2.2 <1.2.4 || >=2.0.0 <3.0.0", []tv{ {"1.2.2", false}, {"1.2.3", true}, {"1.2.4", false}, {"2.0.0", true}, {"2.0.1", true}, {"2.9.9", true}, {"3.0.0", false}, }}, } for _, tc := range tests { r, err := ParseRange(tc.i) if err != nil && tc.t != nil { t.Errorf("Error parsing range %q: %s", tc.i, err) continue } for _, tvc := range tc.t { v := MustParse(tvc.v) if res := r(v); res != tvc.b { t.Errorf("Invalid for case %q matching %q: Expected %t, got: %t", tc.i, tvc.v, tvc.b, res) } } } } func TestMustParseRange(t *testing.T) { testCase := ">1.2.2 <1.2.4 || >=2.0.0 <3.0.0" r := MustParseRange(testCase) if !r(MustParse("1.2.3")) { t.Errorf("Unexpected range behavior on MustParseRange") } } func TestMustParseRange_panic(t *testing.T) { defer func() { if recover() == nil { t.Errorf("Should have panicked") } }() _ = MustParseRange("invalid version") } func BenchmarkRangeParseSimple(b *testing.B) { const VERSION = ">1.0.0" b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _, _ = ParseRange(VERSION) } } func BenchmarkRangeParseAverage(b *testing.B) { const VERSION = ">=1.0.0 <2.0.0" b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _, _ = ParseRange(VERSION) } } func BenchmarkRangeParseComplex(b *testing.B) { const VERSION = ">=1.0.0 <2.0.0 || >=3.0.1 <4.0.0 !=3.0.3 || >=5.0.0" b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _, _ = ParseRange(VERSION) } } func BenchmarkRangeMatchSimple(b *testing.B) { const VERSION = ">1.0.0" r, _ := ParseRange(VERSION) v := MustParse("2.0.0") b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { r(v) } } func BenchmarkRangeMatchAverage(b *testing.B) { const VERSION = ">=1.0.0 <2.0.0" r, _ := ParseRange(VERSION) v := MustParse("1.2.3") b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { r(v) } } func BenchmarkRangeMatchComplex(b *testing.B) { const VERSION = ">=1.0.0 <2.0.0 || >=3.0.1 <4.0.0 !=3.0.3 || >=5.0.0" r, _ := ParseRange(VERSION) v := MustParse("5.0.1") b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { r(v) } } semver-4.0.0/semver.go000066400000000000000000000257551366250757700147040ustar00rootroot00000000000000package semver import ( "errors" "fmt" "strconv" "strings" ) const ( numbers string = "0123456789" alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" alphanum = alphas + numbers ) // SpecVersion is the latest fully supported spec version of semver var SpecVersion = Version{ Major: 2, Minor: 0, Patch: 0, } // Version represents a semver compatible version type Version struct { Major uint64 Minor uint64 Patch uint64 Pre []PRVersion Build []string //No Precedence } // Version to string func (v Version) String() string { b := make([]byte, 0, 5) b = strconv.AppendUint(b, v.Major, 10) b = append(b, '.') b = strconv.AppendUint(b, v.Minor, 10) b = append(b, '.') b = strconv.AppendUint(b, v.Patch, 10) if len(v.Pre) > 0 { b = append(b, '-') b = append(b, v.Pre[0].String()...) for _, pre := range v.Pre[1:] { b = append(b, '.') b = append(b, pre.String()...) } } if len(v.Build) > 0 { b = append(b, '+') b = append(b, v.Build[0]...) for _, build := range v.Build[1:] { b = append(b, '.') b = append(b, build...) } } return string(b) } // FinalizeVersion discards prerelease and build number and only returns // major, minor and patch number. func (v Version) FinalizeVersion() string { b := make([]byte, 0, 5) b = strconv.AppendUint(b, v.Major, 10) b = append(b, '.') b = strconv.AppendUint(b, v.Minor, 10) b = append(b, '.') b = strconv.AppendUint(b, v.Patch, 10) return string(b) } // Equals checks if v is equal to o. func (v Version) Equals(o Version) bool { return (v.Compare(o) == 0) } // EQ checks if v is equal to o. func (v Version) EQ(o Version) bool { return (v.Compare(o) == 0) } // NE checks if v is not equal to o. func (v Version) NE(o Version) bool { return (v.Compare(o) != 0) } // GT checks if v is greater than o. func (v Version) GT(o Version) bool { return (v.Compare(o) == 1) } // GTE checks if v is greater than or equal to o. func (v Version) GTE(o Version) bool { return (v.Compare(o) >= 0) } // GE checks if v is greater than or equal to o. func (v Version) GE(o Version) bool { return (v.Compare(o) >= 0) } // LT checks if v is less than o. func (v Version) LT(o Version) bool { return (v.Compare(o) == -1) } // LTE checks if v is less than or equal to o. func (v Version) LTE(o Version) bool { return (v.Compare(o) <= 0) } // LE checks if v is less than or equal to o. func (v Version) LE(o Version) bool { return (v.Compare(o) <= 0) } // Compare compares Versions v to o: // -1 == v is less than o // 0 == v is equal to o // 1 == v is greater than o func (v Version) Compare(o Version) int { if v.Major != o.Major { if v.Major > o.Major { return 1 } return -1 } if v.Minor != o.Minor { if v.Minor > o.Minor { return 1 } return -1 } if v.Patch != o.Patch { if v.Patch > o.Patch { return 1 } return -1 } // Quick comparison if a version has no prerelease versions if len(v.Pre) == 0 && len(o.Pre) == 0 { return 0 } else if len(v.Pre) == 0 && len(o.Pre) > 0 { return 1 } else if len(v.Pre) > 0 && len(o.Pre) == 0 { return -1 } i := 0 for ; i < len(v.Pre) && i < len(o.Pre); i++ { if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 { continue } else if comp == 1 { return 1 } else { return -1 } } // If all pr versions are the equal but one has further prversion, this one greater if i == len(v.Pre) && i == len(o.Pre) { return 0 } else if i == len(v.Pre) && i < len(o.Pre) { return -1 } else { return 1 } } // IncrementPatch increments the patch version func (v *Version) IncrementPatch() error { v.Patch++ return nil } // IncrementMinor increments the minor version func (v *Version) IncrementMinor() error { v.Minor++ v.Patch = 0 return nil } // IncrementMajor increments the major version func (v *Version) IncrementMajor() error { v.Major++ v.Minor = 0 v.Patch = 0 return nil } // Validate validates v and returns error in case func (v Version) Validate() error { // Major, Minor, Patch already validated using uint64 for _, pre := range v.Pre { if !pre.IsNum { //Numeric prerelease versions already uint64 if len(pre.VersionStr) == 0 { return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr) } if !containsOnly(pre.VersionStr, alphanum) { return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr) } } } for _, build := range v.Build { if len(build) == 0 { return fmt.Errorf("Build meta data can not be empty %q", build) } if !containsOnly(build, alphanum) { return fmt.Errorf("Invalid character(s) found in build meta data %q", build) } } return nil } // New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error func New(s string) (*Version, error) { v, err := Parse(s) vp := &v return vp, err } // Make is an alias for Parse, parses version string and returns a validated Version or error func Make(s string) (Version, error) { return Parse(s) } // ParseTolerant allows for certain version specifications that do not strictly adhere to semver // specs to be parsed by this library. It does so by normalizing versions before passing them to // Parse(). It currently trims spaces, removes a "v" prefix, adds a 0 patch number to versions // with only major and minor components specified, and removes leading 0s. func ParseTolerant(s string) (Version, error) { s = strings.TrimSpace(s) s = strings.TrimPrefix(s, "v") // Split into major.minor.(patch+pr+meta) parts := strings.SplitN(s, ".", 3) // Remove leading zeros. for i, p := range parts { if len(p) > 1 { p = strings.TrimLeft(p, "0") if len(p) == 0 || !strings.ContainsAny(p[0:1], "0123456789") { p = "0" + p } parts[i] = p } } // Fill up shortened versions. if len(parts) < 3 { if strings.ContainsAny(parts[len(parts)-1], "+-") { return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data") } for len(parts) < 3 { parts = append(parts, "0") } } s = strings.Join(parts, ".") return Parse(s) } // Parse parses version string and returns a validated Version or error func Parse(s string) (Version, error) { if len(s) == 0 { return Version{}, errors.New("Version string empty") } // Split into major.minor.(patch+pr+meta) parts := strings.SplitN(s, ".", 3) if len(parts) != 3 { return Version{}, errors.New("No Major.Minor.Patch elements found") } // Major if !containsOnly(parts[0], numbers) { return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0]) } if hasLeadingZeroes(parts[0]) { return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0]) } major, err := strconv.ParseUint(parts[0], 10, 64) if err != nil { return Version{}, err } // Minor if !containsOnly(parts[1], numbers) { return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1]) } if hasLeadingZeroes(parts[1]) { return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1]) } minor, err := strconv.ParseUint(parts[1], 10, 64) if err != nil { return Version{}, err } v := Version{} v.Major = major v.Minor = minor var build, prerelease []string patchStr := parts[2] if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 { build = strings.Split(patchStr[buildIndex+1:], ".") patchStr = patchStr[:buildIndex] } if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 { prerelease = strings.Split(patchStr[preIndex+1:], ".") patchStr = patchStr[:preIndex] } if !containsOnly(patchStr, numbers) { return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr) } if hasLeadingZeroes(patchStr) { return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr) } patch, err := strconv.ParseUint(patchStr, 10, 64) if err != nil { return Version{}, err } v.Patch = patch // Prerelease for _, prstr := range prerelease { parsedPR, err := NewPRVersion(prstr) if err != nil { return Version{}, err } v.Pre = append(v.Pre, parsedPR) } // Build meta data for _, str := range build { if len(str) == 0 { return Version{}, errors.New("Build meta data is empty") } if !containsOnly(str, alphanum) { return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str) } v.Build = append(v.Build, str) } return v, nil } // MustParse is like Parse but panics if the version cannot be parsed. func MustParse(s string) Version { v, err := Parse(s) if err != nil { panic(`semver: Parse(` + s + `): ` + err.Error()) } return v } // PRVersion represents a PreRelease Version type PRVersion struct { VersionStr string VersionNum uint64 IsNum bool } // NewPRVersion creates a new valid prerelease version func NewPRVersion(s string) (PRVersion, error) { if len(s) == 0 { return PRVersion{}, errors.New("Prerelease is empty") } v := PRVersion{} if containsOnly(s, numbers) { if hasLeadingZeroes(s) { return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s) } num, err := strconv.ParseUint(s, 10, 64) // Might never be hit, but just in case if err != nil { return PRVersion{}, err } v.VersionNum = num v.IsNum = true } else if containsOnly(s, alphanum) { v.VersionStr = s v.IsNum = false } else { return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s) } return v, nil } // IsNumeric checks if prerelease-version is numeric func (v PRVersion) IsNumeric() bool { return v.IsNum } // Compare compares two PreRelease Versions v and o: // -1 == v is less than o // 0 == v is equal to o // 1 == v is greater than o func (v PRVersion) Compare(o PRVersion) int { if v.IsNum && !o.IsNum { return -1 } else if !v.IsNum && o.IsNum { return 1 } else if v.IsNum && o.IsNum { if v.VersionNum == o.VersionNum { return 0 } else if v.VersionNum > o.VersionNum { return 1 } else { return -1 } } else { // both are Alphas if v.VersionStr == o.VersionStr { return 0 } else if v.VersionStr > o.VersionStr { return 1 } else { return -1 } } } // PreRelease version to string func (v PRVersion) String() string { if v.IsNum { return strconv.FormatUint(v.VersionNum, 10) } return v.VersionStr } func containsOnly(s string, set string) bool { return strings.IndexFunc(s, func(r rune) bool { return !strings.ContainsRune(set, r) }) == -1 } func hasLeadingZeroes(s string) bool { return len(s) > 1 && s[0] == '0' } // NewBuildVersion creates a new valid build version func NewBuildVersion(s string) (string, error) { if len(s) == 0 { return "", errors.New("Buildversion is empty") } if !containsOnly(s, alphanum) { return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s) } return s, nil } // FinalizeVersion returns the major, minor and patch number only and discards // prerelease and build number. func FinalizeVersion(s string) (string, error) { v, err := Parse(s) if err != nil { return "", err } v.Pre = nil v.Build = nil finalVer := v.String() return finalVer, nil } semver-4.0.0/semver_test.go000066400000000000000000000365541366250757700157420ustar00rootroot00000000000000package semver import ( "testing" ) func prstr(s string) PRVersion { return PRVersion{s, 0, false} } func prnum(i uint64) PRVersion { return PRVersion{"", i, true} } type formatTest struct { v Version result string } var formatTests = []formatTest{ {Version{1, 2, 3, nil, nil}, "1.2.3"}, {Version{0, 0, 1, nil, nil}, "0.0.1"}, {Version{0, 0, 1, []PRVersion{prstr("alpha"), prstr("preview")}, []string{"123", "456"}}, "0.0.1-alpha.preview+123.456"}, {Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, []string{"123", "456"}}, "1.2.3-alpha.1+123.456"}, {Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, nil}, "1.2.3-alpha.1"}, {Version{1, 2, 3, nil, []string{"123", "456"}}, "1.2.3+123.456"}, // Prereleases and build metadata hyphens {Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, []string{"123", "b-uild"}}, "1.2.3-alpha.b-eta+123.b-uild"}, {Version{1, 2, 3, nil, []string{"123", "b-uild"}}, "1.2.3+123.b-uild"}, {Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, nil}, "1.2.3-alpha.b-eta"}, } var tolerantFormatTests = []formatTest{ {Version{1, 2, 3, nil, nil}, "v1.2.3"}, {Version{1, 2, 0, []PRVersion{prstr("alpha")}, nil}, "1.2.0-alpha"}, {Version{1, 2, 0, nil, nil}, "1.2.00"}, {Version{1, 2, 3, nil, nil}, " 1.2.3 "}, {Version{1, 2, 3, nil, nil}, "01.02.03"}, {Version{0, 0, 3, nil, nil}, "00.0.03"}, {Version{0, 0, 3, nil, nil}, "000.0.03"}, {Version{1, 2, 0, nil, nil}, "1.2"}, {Version{1, 0, 0, nil, nil}, "1"}, } func TestStringer(t *testing.T) { for _, test := range formatTests { if res := test.v.String(); res != test.result { t.Errorf("Stringer, expected %q but got %q", test.result, res) } } } func TestParse(t *testing.T) { for _, test := range formatTests { if v, err := Parse(test.result); err != nil { t.Errorf("Error parsing %q: %q", test.result, err) } else if comp := v.Compare(test.v); comp != 0 { t.Errorf("Parsing, expected %q but got %q, comp: %d ", test.v, v, comp) } else if err := v.Validate(); err != nil { t.Errorf("Error validating parsed version %q: %q", test.v, err) } } } func TestParseTolerant(t *testing.T) { for _, test := range tolerantFormatTests { if v, err := ParseTolerant(test.result); err != nil { t.Errorf("Error parsing %q: %q", test.result, err) } else if comp := v.Compare(test.v); comp != 0 { t.Errorf("Parsing, expected %q but got %q, comp: %d ", test.v, v, comp) } else if err := v.Validate(); err != nil { t.Errorf("Error validating parsed version %q: %q", test.v, err) } } } func TestMustParse(t *testing.T) { _ = MustParse("32.2.1-alpha") } func TestMustParse_panic(t *testing.T) { defer func() { if recover() == nil { t.Errorf("Should have panicked") } }() _ = MustParse("invalid version") } func TestValidate(t *testing.T) { for _, test := range formatTests { if err := test.v.Validate(); err != nil { t.Errorf("Error validating %q: %q", test.v, err) } } } var finalizeVersionMethod = []formatTest{ {Version{1, 2, 3, nil, nil}, "1.2.3"}, {Version{0, 0, 1, nil, nil}, "0.0.1"}, {Version{0, 0, 1, []PRVersion{prstr("alpha"), prstr("preview")}, []string{"123", "456"}}, "0.0.1"}, {Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, []string{"123", "456"}}, "1.2.3"}, {Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, nil}, "1.2.3"}, {Version{1, 2, 3, nil, []string{"123", "456"}}, "1.2.3"}, // Prereleases and build metadata hyphens {Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, []string{"123", "b-uild"}}, "1.2.3"}, {Version{1, 2, 3, nil, []string{"123", "b-uild"}}, "1.2.3"}, {Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, nil}, "1.2.3"}, } func TestFinalizeVersionMethod(t *testing.T) { for _, test := range finalizeVersionMethod { out := test.v.FinalizeVersion() if out != test.result { t.Errorf("Finalized version error, expected %q but got %q", test.result, out) } } } type compareTest struct { v1 Version v2 Version result int } var compareTests = []compareTest{ {Version{1, 0, 0, nil, nil}, Version{1, 0, 0, nil, nil}, 0}, {Version{2, 0, 0, nil, nil}, Version{1, 0, 0, nil, nil}, 1}, {Version{0, 1, 0, nil, nil}, Version{0, 1, 0, nil, nil}, 0}, {Version{0, 2, 0, nil, nil}, Version{0, 1, 0, nil, nil}, 1}, {Version{0, 0, 1, nil, nil}, Version{0, 0, 1, nil, nil}, 0}, {Version{0, 0, 2, nil, nil}, Version{0, 0, 1, nil, nil}, 1}, {Version{1, 2, 3, nil, nil}, Version{1, 2, 3, nil, nil}, 0}, {Version{2, 2, 4, nil, nil}, Version{1, 2, 4, nil, nil}, 1}, {Version{1, 3, 3, nil, nil}, Version{1, 2, 3, nil, nil}, 1}, {Version{1, 2, 4, nil, nil}, Version{1, 2, 3, nil, nil}, 1}, // Spec Examples #11 {Version{1, 0, 0, nil, nil}, Version{2, 0, 0, nil, nil}, -1}, {Version{2, 0, 0, nil, nil}, Version{2, 1, 0, nil, nil}, -1}, {Version{2, 1, 0, nil, nil}, Version{2, 1, 1, nil, nil}, -1}, // Spec Examples #9 {Version{1, 0, 0, nil, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil}, 1}, {Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha"), prnum(1)}, nil}, -1}, {Version{1, 0, 0, []PRVersion{prstr("alpha"), prnum(1)}, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha"), prstr("beta")}, nil}, -1}, {Version{1, 0, 0, []PRVersion{prstr("alpha"), prstr("beta")}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta")}, nil}, -1}, {Version{1, 0, 0, []PRVersion{prstr("beta")}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(2)}, nil}, -1}, {Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(2)}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(11)}, nil}, -1}, {Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(11)}, nil}, Version{1, 0, 0, []PRVersion{prstr("rc"), prnum(1)}, nil}, -1}, {Version{1, 0, 0, []PRVersion{prstr("rc"), prnum(1)}, nil}, Version{1, 0, 0, nil, nil}, -1}, // Ignore Build metadata {Version{1, 0, 0, nil, []string{"1", "2", "3"}}, Version{1, 0, 0, nil, nil}, 0}, } func TestCompare(t *testing.T) { for _, test := range compareTests { if res := test.v1.Compare(test.v2); res != test.result { t.Errorf("Comparing %q : %q, expected %d but got %d", test.v1, test.v2, test.result, res) } // Test counterpart if res := test.v2.Compare(test.v1); res != -test.result { t.Errorf("Comparing %q : %q, expected %d but got %d", test.v2, test.v1, -test.result, res) } } } type wrongformatTest struct { v *Version str string } var wrongformatTests = []wrongformatTest{ {nil, ""}, {nil, "."}, {nil, "1."}, {nil, ".1"}, {nil, "a.b.c"}, {nil, "1.a.b"}, {nil, "1.1.a"}, {nil, "1.a.1"}, {nil, "a.1.1"}, {nil, ".."}, {nil, "1.."}, {nil, "1.1."}, {nil, "1..1"}, {nil, "1.1.+123"}, {nil, "1.1.-beta"}, {nil, "-1.1.1"}, {nil, "1.-1.1"}, {nil, "1.1.-1"}, // giant numbers {nil, "20000000000000000000.1.1"}, {nil, "1.20000000000000000000.1"}, {nil, "1.1.20000000000000000000"}, {nil, "1.1.1-20000000000000000000"}, // Leading zeroes {nil, "01.1.1"}, {nil, "001.1.1"}, {nil, "1.01.1"}, {nil, "1.001.1"}, {nil, "1.1.01"}, {nil, "1.1.001"}, {nil, "1.1.1-01"}, {nil, "1.1.1-001"}, {nil, "1.1.1-beta.01"}, {nil, "1.1.1-beta.001"}, {&Version{0, 0, 0, []PRVersion{prstr("!")}, nil}, "0.0.0-!"}, {&Version{0, 0, 0, nil, []string{"!"}}, "0.0.0+!"}, // empty prversion {&Version{0, 0, 0, []PRVersion{prstr(""), prstr("alpha")}, nil}, "0.0.0-.alpha"}, // empty build meta data {&Version{0, 0, 0, []PRVersion{prstr("alpha")}, []string{""}}, "0.0.0-alpha+"}, {&Version{0, 0, 0, []PRVersion{prstr("alpha")}, []string{"test", ""}}, "0.0.0-alpha+test."}, } func TestWrongFormat(t *testing.T) { for _, test := range wrongformatTests { if res, err := Parse(test.str); err == nil { t.Errorf("Parsing wrong format version %q, expected error but got %q", test.str, res) } if test.v != nil { if err := test.v.Validate(); err == nil { t.Errorf("Validating wrong format version %q (%q), expected error", test.v, test.str) } } } } var wrongTolerantFormatTests = []wrongformatTest{ {nil, "1.0+abc"}, {nil, "1.0-rc.1"}, } func TestWrongTolerantFormat(t *testing.T) { for _, test := range wrongTolerantFormatTests { if res, err := ParseTolerant(test.str); err == nil { t.Errorf("Parsing wrong format version %q, expected error but got %q", test.str, res) } } } func TestCompareHelper(t *testing.T) { v := Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil} v1 := Version{1, 0, 0, nil, nil} if !v.EQ(v) { t.Errorf("%q should be equal to %q", v, v) } if !v.Equals(v) { t.Errorf("%q should be equal to %q", v, v) } if !v1.NE(v) { t.Errorf("%q should not be equal to %q", v1, v) } if !v.GTE(v) { t.Errorf("%q should be greater than or equal to %q", v, v) } if !v.LTE(v) { t.Errorf("%q should be less than or equal to %q", v, v) } if !v.LT(v1) { t.Errorf("%q should be less than %q", v, v1) } if !v.LTE(v1) { t.Errorf("%q should be less than or equal %q", v, v1) } if !v.LE(v1) { t.Errorf("%q should be less than or equal %q", v, v1) } if !v1.GT(v) { t.Errorf("%q should be greater than %q", v1, v) } if !v1.GTE(v) { t.Errorf("%q should be greater than or equal %q", v1, v) } if !v1.GE(v) { t.Errorf("%q should be greater than or equal %q", v1, v) } } const ( MAJOR = iota MINOR PATCH ) type incrementTest struct { version Version incrementType int expectingError bool expectedVersion Version } var incrementTests = []incrementTest{ {Version{1, 2, 3, nil, nil}, PATCH, false, Version{1, 2, 4, nil, nil}}, {Version{1, 2, 3, nil, nil}, MINOR, false, Version{1, 3, 0, nil, nil}}, {Version{1, 2, 3, nil, nil}, MAJOR, false, Version{2, 0, 0, nil, nil}}, {Version{0, 1, 2, nil, nil}, PATCH, false, Version{0, 1, 3, nil, nil}}, {Version{0, 1, 2, nil, nil}, MINOR, false, Version{0, 2, 0, nil, nil}}, {Version{0, 1, 2, nil, nil}, MAJOR, false, Version{1, 0, 0, nil, nil}}, } func TestIncrements(t *testing.T) { for _, test := range incrementTests { var originalVersion = Version{ test.version.Major, test.version.Minor, test.version.Patch, test.version.Pre, test.version.Build, } var err error switch test.incrementType { case PATCH: err = test.version.IncrementPatch() case MINOR: err = test.version.IncrementMinor() case MAJOR: err = test.version.IncrementMajor() } if test.expectingError { if err != nil { t.Errorf("Increment version, expecting %q, got error %q", test.expectedVersion, err) } if test.version.EQ(originalVersion) { t.Errorf("Increment version, expecting %q, got %q", test.expectedVersion, test.version) } } else { if (err != nil) && !test.expectingError { t.Errorf("Increment version %q, not expecting error, got %q", test.version, err) } if test.version.NE(test.expectedVersion) { t.Errorf("Increment version, expecting %q, got %q", test.expectedVersion, test.version) } } } } func TestPreReleaseVersions(t *testing.T) { p1, err := NewPRVersion("123") if !p1.IsNumeric() { t.Errorf("Expected numeric prversion, got %q", p1) } if p1.VersionNum != 123 { t.Error("Wrong prversion number") } if err != nil { t.Errorf("Not expected error %q", err) } p2, err := NewPRVersion("alpha") if p2.IsNumeric() { t.Errorf("Expected non-numeric prversion, got %q", p2) } if p2.VersionStr != "alpha" { t.Error("Wrong prversion string") } if err != nil { t.Errorf("Not expected error %q", err) } } func TestBuildMetaDataVersions(t *testing.T) { _, err := NewBuildVersion("123") if err != nil { t.Errorf("Unexpected error %q", err) } _, err = NewBuildVersion("build") if err != nil { t.Errorf("Unexpected error %q", err) } _, err = NewBuildVersion("test?") if err == nil { t.Error("Expected error, got none") } _, err = NewBuildVersion("") if err == nil { t.Error("Expected error, got none") } } func TestNewHelper(t *testing.T) { v, err := New("1.2.3") if err != nil { t.Fatalf("Unexpected error %q", err) } // New returns pointer if v == nil { t.Fatal("Version is nil") } if v.Compare(Version{1, 2, 3, nil, nil}) != 0 { t.Fatal("Unexpected comparison problem") } } func TestMakeHelper(t *testing.T) { v, err := Make("1.2.3") if err != nil { t.Fatalf("Unexpected error %q", err) } if v.Compare(Version{1, 2, 3, nil, nil}) != 0 { t.Fatal("Unexpected comparison problem") } } type finalizeTest struct { input string output string } var finalizeTests = []finalizeTest{ {"", ""}, {"1.2.3", "1.2.3"}, {"0.0.1", "0.0.1"}, {"0.0.1-alpha.preview+123.456", "0.0.1"}, {"1.2.3-alpha.1+123.456", "1.2.3"}, {"1.2.3-alpha.1", "1.2.3"}, {"1.2.3+123.456", "1.2.3"}, {"1.2.3-alpha.b-eta+123.b-uild", "1.2.3"}, {"1.2.3+123.b-uild", "1.2.3"}, {"1.2.3-alpha.b-eta", "1.2.3"}, {"1.2-alpha", ""}, } func TestFinalizeVersion(t *testing.T) { for _, test := range finalizeTests { finalVer, err := FinalizeVersion(test.input) if finalVer == "" { if err == nil { t.Errorf("Finalize Version error, expected error but got nil") } } else if finalVer != test.output && err != nil { t.Errorf("Finalize Version error expected %q but got %q", test.output, finalVer) } } } func BenchmarkParseSimple(b *testing.B) { const VERSION = "0.0.1" b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _, _ = Parse(VERSION) } } func BenchmarkParseComplex(b *testing.B) { const VERSION = "0.0.1-alpha.preview+123.456" b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _, _ = Parse(VERSION) } } func BenchmarkParseAverage(b *testing.B) { l := len(formatTests) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _, _ = Parse(formatTests[n%l].result) } } func BenchmarkParseTolerantAverage(b *testing.B) { l := len(tolerantFormatTests) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _, _ = ParseTolerant(tolerantFormatTests[n%l].result) } } func BenchmarkStringSimple(b *testing.B) { const VERSION = "0.0.1" v, _ := Parse(VERSION) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _ = v.String() } } func BenchmarkStringLarger(b *testing.B) { const VERSION = "11.15.2012" v, _ := Parse(VERSION) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _ = v.String() } } func BenchmarkStringComplex(b *testing.B) { const VERSION = "0.0.1-alpha.preview+123.456" v, _ := Parse(VERSION) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _ = v.String() } } func BenchmarkStringAverage(b *testing.B) { l := len(formatTests) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _ = formatTests[n%l].v.String() } } func BenchmarkValidateSimple(b *testing.B) { const VERSION = "0.0.1" v, _ := Parse(VERSION) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _ = v.Validate() } } func BenchmarkValidateComplex(b *testing.B) { const VERSION = "0.0.1-alpha.preview+123.456" v, _ := Parse(VERSION) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _ = v.Validate() } } func BenchmarkValidateAverage(b *testing.B) { l := len(formatTests) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _ = formatTests[n%l].v.Validate() } } func BenchmarkCompareSimple(b *testing.B) { const VERSION = "0.0.1" v, _ := Parse(VERSION) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { v.Compare(v) } } func BenchmarkCompareComplex(b *testing.B) { const VERSION = "0.0.1-alpha.preview+123.456" v, _ := Parse(VERSION) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { v.Compare(v) } } func BenchmarkCompareAverage(b *testing.B) { l := len(compareTests) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { compareTests[n%l].v1.Compare((compareTests[n%l].v2)) } } semver-4.0.0/sort.go000066400000000000000000000010531366250757700143530ustar00rootroot00000000000000package semver import ( "sort" ) // Versions represents multiple versions. type Versions []Version // Len returns length of version collection func (s Versions) Len() int { return len(s) } // Swap swaps two versions inside the collection by its indices func (s Versions) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // Less checks if version at index i is less than version at index j func (s Versions) Less(i, j int) bool { return s[i].LT(s[j]) } // Sort sorts a slice of versions func Sort(versions []Version) { sort.Sort(Versions(versions)) } semver-4.0.0/sort_test.go000066400000000000000000000011061366250757700154110ustar00rootroot00000000000000package semver import ( "reflect" "testing" ) func TestSort(t *testing.T) { v100, _ := Parse("1.0.0") v010, _ := Parse("0.1.0") v001, _ := Parse("0.0.1") versions := []Version{v010, v100, v001} Sort(versions) correct := []Version{v001, v010, v100} if !reflect.DeepEqual(versions, correct) { t.Fatalf("Sort returned wrong order: %s", versions) } } func BenchmarkSort(b *testing.B) { v100, _ := Parse("1.0.0") v010, _ := Parse("0.1.0") v001, _ := Parse("0.0.1") b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { Sort([]Version{v010, v100, v001}) } } semver-4.0.0/sql.go000066400000000000000000000010531366250757700141630ustar00rootroot00000000000000package semver import ( "database/sql/driver" "fmt" ) // Scan implements the database/sql.Scanner interface. func (v *Version) Scan(src interface{}) (err error) { var str string switch src := src.(type) { case string: str = src case []byte: str = string(src) default: return fmt.Errorf("version.Scan: cannot convert %T to string", src) } if t, err := Parse(str); err == nil { *v = t } return } // Value implements the database/sql/driver.Valuer interface. func (v Version) Value() (driver.Value, error) { return v.String(), nil } semver-4.0.0/sql_test.go000066400000000000000000000014131366250757700152220ustar00rootroot00000000000000package semver import ( "testing" ) type scanTest struct { val interface{} shouldError bool expected string } var scanTests = []scanTest{ {"1.2.3", false, "1.2.3"}, {[]byte("1.2.3"), false, "1.2.3"}, {7, true, ""}, {7e4, true, ""}, {true, true, ""}, } func TestScanString(t *testing.T) { for _, tc := range scanTests { s := &Version{} err := s.Scan(tc.val) if tc.shouldError { if err == nil { t.Fatalf("Scan did not return an error on %v (%T)", tc.val, tc.val) } } else { if err != nil { t.Fatalf("Scan returned an unexpected error: %s (%T) on %v (%T)", tc.val, tc.val, tc.val, tc.val) } if val, _ := s.Value(); val != tc.expected { t.Errorf("Wrong Value returned, expected %q, got %q", tc.expected, val) } } } } semver-4.0.0/v4/000077500000000000000000000000001366250757700133675ustar00rootroot00000000000000semver-4.0.0/v4/examples/000077500000000000000000000000001366250757700152055ustar00rootroot00000000000000semver-4.0.0/v4/examples/main.go000066400000000000000000000050031366250757700164560ustar00rootroot00000000000000package main import ( "fmt" "github.com/blang/semver/v4" ) func main() { v, err := semver.Parse("0.0.1-alpha.preview.222+123.github") if err != nil { fmt.Printf("Error while parsing (not valid): %q", err) } fmt.Printf("Version to string: %q\n", v) fmt.Printf("Major: %d\n", v.Major) fmt.Printf("Minor: %d\n", v.Minor) fmt.Printf("Patch: %d\n", v.Patch) // Prerelease versions if len(v.Pre) > 0 { fmt.Println("Prerelease versions:") for i, pre := range v.Pre { fmt.Printf("%d: %q\n", i, pre) } } // Build meta data if len(v.Build) > 0 { fmt.Println("Build meta data:") for i, build := range v.Build { fmt.Printf("%d: %q\n", i, build) } } // Make == Parse (Value), New for Pointer v001, _ := semver.Make("0.0.1") fmt.Println("\nUse Version.Compare for comparisons (-1, 0, 1):") fmt.Printf("%q is greater than %q: Compare == %d\n", v001, v, v001.Compare(v)) fmt.Printf("%q is less than %q: Compare == %d\n", v, v001, v.Compare(v001)) fmt.Printf("%q is equal to %q: Compare == %d\n", v, v, v.Compare(v)) fmt.Println("\nUse comparison helpers returning booleans:") fmt.Printf("%q is greater than %q: %t\n", v001, v, v001.GT(v)) fmt.Printf("%q is greater than equal %q: %t\n", v001, v, v001.GTE(v)) fmt.Printf("%q is greater than equal %q: %t\n", v, v, v.GTE(v)) fmt.Printf("%q is less than %q: %t\n", v, v001, v.LT(v001)) fmt.Printf("%q is less than equal %q: %t\n", v, v001, v.LTE(v001)) fmt.Printf("%q is less than equal %q: %t\n", v, v, v.LTE(v)) fmt.Println("\nManipulate Version in place:") v.Pre[0], err = semver.NewPRVersion("beta") if err != nil { fmt.Printf("Error parsing pre release version: %q", err) } fmt.Printf("Version to string: %q\n", v) fmt.Println("\nCompare Prerelease versions:") pre1, _ := semver.NewPRVersion("123") pre2, _ := semver.NewPRVersion("alpha") pre3, _ := semver.NewPRVersion("124") fmt.Printf("%q is less than %q: Compare == %d\n", pre1, pre2, pre1.Compare(pre2)) fmt.Printf("%q is greater than %q: Compare == %d\n", pre3, pre1, pre3.Compare(pre1)) fmt.Printf("%q is equal to %q: Compare == %d\n", pre1, pre1, pre1.Compare(pre1)) fmt.Println("\nValidate versions:") v.Build[0] = "?" err = v.Validate() if err != nil { fmt.Printf("Validation failed: %s\n", err) } fmt.Println("Create valid build meta data:") b1, _ := semver.NewBuildVersion("build123") v.Build[0] = b1 fmt.Printf("Version with new build version %q\n", v) _, err = semver.NewBuildVersion("build?123") if err != nil { fmt.Printf("Create build version failed: %s\n", err) } } semver-4.0.0/v4/go.mod000066400000000000000000000000531366250757700144730ustar00rootroot00000000000000module github.com/blang/semver/v4 go 1.14 semver-4.0.0/v4/json.go000066400000000000000000000007231366250757700146710ustar00rootroot00000000000000package semver import ( "encoding/json" ) // MarshalJSON implements the encoding/json.Marshaler interface. func (v Version) MarshalJSON() ([]byte, error) { return json.Marshal(v.String()) } // UnmarshalJSON implements the encoding/json.Unmarshaler interface. func (v *Version) UnmarshalJSON(data []byte) (err error) { var versionString string if err = json.Unmarshal(data, &versionString); err != nil { return } *v, err = Parse(versionString) return } semver-4.0.0/v4/json_test.go000066400000000000000000000023221366250757700157250ustar00rootroot00000000000000package semver import ( "encoding/json" "strconv" "testing" ) func TestJSONMarshal(t *testing.T) { versionString := "3.1.4-alpha.1.5.9+build.2.6.5" v, err := Parse(versionString) if err != nil { t.Fatal(err) } versionJSON, err := json.Marshal(v) if err != nil { t.Fatal(err) } quotedVersionString := strconv.Quote(versionString) if string(versionJSON) != quotedVersionString { t.Fatalf("JSON marshaled semantic version not equal: expected %q, got %q", quotedVersionString, string(versionJSON)) } } func TestJSONUnmarshal(t *testing.T) { versionString := "3.1.4-alpha.1.5.9+build.2.6.5" quotedVersionString := strconv.Quote(versionString) var v Version if err := json.Unmarshal([]byte(quotedVersionString), &v); err != nil { t.Fatal(err) } if v.String() != versionString { t.Fatalf("JSON unmarshaled semantic version not equal: expected %q, got %q", versionString, v.String()) } badVersionString := strconv.Quote("3.1.4.1.5.9.2.6.5-other-digits-of-pi") if err := json.Unmarshal([]byte(badVersionString), &v); err == nil { t.Fatal("expected JSON unmarshal error, got nil") } if err := json.Unmarshal([]byte("3.1"), &v); err == nil { t.Fatal("expected JSON unmarshal error, got nil") } } semver-4.0.0/v4/range.go000066400000000000000000000236601366250757700150210ustar00rootroot00000000000000package semver import ( "fmt" "strconv" "strings" "unicode" ) type wildcardType int const ( noneWildcard wildcardType = iota majorWildcard wildcardType = 1 minorWildcard wildcardType = 2 patchWildcard wildcardType = 3 ) func wildcardTypefromInt(i int) wildcardType { switch i { case 1: return majorWildcard case 2: return minorWildcard case 3: return patchWildcard default: return noneWildcard } } type comparator func(Version, Version) bool var ( compEQ comparator = func(v1 Version, v2 Version) bool { return v1.Compare(v2) == 0 } compNE = func(v1 Version, v2 Version) bool { return v1.Compare(v2) != 0 } compGT = func(v1 Version, v2 Version) bool { return v1.Compare(v2) == 1 } compGE = func(v1 Version, v2 Version) bool { return v1.Compare(v2) >= 0 } compLT = func(v1 Version, v2 Version) bool { return v1.Compare(v2) == -1 } compLE = func(v1 Version, v2 Version) bool { return v1.Compare(v2) <= 0 } ) type versionRange struct { v Version c comparator } // rangeFunc creates a Range from the given versionRange. func (vr *versionRange) rangeFunc() Range { return Range(func(v Version) bool { return vr.c(v, vr.v) }) } // Range represents a range of versions. // A Range can be used to check if a Version satisfies it: // // range, err := semver.ParseRange(">1.0.0 <2.0.0") // range(semver.MustParse("1.1.1") // returns true type Range func(Version) bool // OR combines the existing Range with another Range using logical OR. func (rf Range) OR(f Range) Range { return Range(func(v Version) bool { return rf(v) || f(v) }) } // AND combines the existing Range with another Range using logical AND. func (rf Range) AND(f Range) Range { return Range(func(v Version) bool { return rf(v) && f(v) }) } // ParseRange parses a range and returns a Range. // If the range could not be parsed an error is returned. // // Valid ranges are: // - "<1.0.0" // - "<=1.0.0" // - ">1.0.0" // - ">=1.0.0" // - "1.0.0", "=1.0.0", "==1.0.0" // - "!1.0.0", "!=1.0.0" // // A Range can consist of multiple ranges separated by space: // Ranges can be linked by logical AND: // - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7" but not "1.0.0" or "2.0.0" // - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2 // // Ranges can also be linked by logical OR: // - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x" // // AND has a higher precedence than OR. It's not possible to use brackets. // // Ranges can be combined by both AND and OR // // - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1` func ParseRange(s string) (Range, error) { parts := splitAndTrim(s) orParts, err := splitORParts(parts) if err != nil { return nil, err } expandedParts, err := expandWildcardVersion(orParts) if err != nil { return nil, err } var orFn Range for _, p := range expandedParts { var andFn Range for _, ap := range p { opStr, vStr, err := splitComparatorVersion(ap) if err != nil { return nil, err } vr, err := buildVersionRange(opStr, vStr) if err != nil { return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err) } rf := vr.rangeFunc() // Set function if andFn == nil { andFn = rf } else { // Combine with existing function andFn = andFn.AND(rf) } } if orFn == nil { orFn = andFn } else { orFn = orFn.OR(andFn) } } return orFn, nil } // splitORParts splits the already cleaned parts by '||'. // Checks for invalid positions of the operator and returns an // error if found. func splitORParts(parts []string) ([][]string, error) { var ORparts [][]string last := 0 for i, p := range parts { if p == "||" { if i == 0 { return nil, fmt.Errorf("First element in range is '||'") } ORparts = append(ORparts, parts[last:i]) last = i + 1 } } if last == len(parts) { return nil, fmt.Errorf("Last element in range is '||'") } ORparts = append(ORparts, parts[last:]) return ORparts, nil } // buildVersionRange takes a slice of 2: operator and version // and builds a versionRange, otherwise an error. func buildVersionRange(opStr, vStr string) (*versionRange, error) { c := parseComparator(opStr) if c == nil { return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, "")) } v, err := Parse(vStr) if err != nil { return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err) } return &versionRange{ v: v, c: c, }, nil } // inArray checks if a byte is contained in an array of bytes func inArray(s byte, list []byte) bool { for _, el := range list { if el == s { return true } } return false } // splitAndTrim splits a range string by spaces and cleans whitespaces func splitAndTrim(s string) (result []string) { last := 0 var lastChar byte excludeFromSplit := []byte{'>', '<', '='} for i := 0; i < len(s); i++ { if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) { if last < i-1 { result = append(result, s[last:i]) } last = i + 1 } else if s[i] != ' ' { lastChar = s[i] } } if last < len(s)-1 { result = append(result, s[last:]) } for i, v := range result { result[i] = strings.Replace(v, " ", "", -1) } // parts := strings.Split(s, " ") // for _, x := range parts { // if s := strings.TrimSpace(x); len(s) != 0 { // result = append(result, s) // } // } return } // splitComparatorVersion splits the comparator from the version. // Input must be free of leading or trailing spaces. func splitComparatorVersion(s string) (string, string, error) { i := strings.IndexFunc(s, unicode.IsDigit) if i == -1 { return "", "", fmt.Errorf("Could not get version from string: %q", s) } return strings.TrimSpace(s[0:i]), s[i:], nil } // getWildcardType will return the type of wildcard that the // passed version contains func getWildcardType(vStr string) wildcardType { parts := strings.Split(vStr, ".") nparts := len(parts) wildcard := parts[nparts-1] possibleWildcardType := wildcardTypefromInt(nparts) if wildcard == "x" { return possibleWildcardType } return noneWildcard } // createVersionFromWildcard will convert a wildcard version // into a regular version, replacing 'x's with '0's, handling // special cases like '1.x.x' and '1.x' func createVersionFromWildcard(vStr string) string { // handle 1.x.x vStr2 := strings.Replace(vStr, ".x.x", ".x", 1) vStr2 = strings.Replace(vStr2, ".x", ".0", 1) parts := strings.Split(vStr2, ".") // handle 1.x if len(parts) == 2 { return vStr2 + ".0" } return vStr2 } // incrementMajorVersion will increment the major version // of the passed version func incrementMajorVersion(vStr string) (string, error) { parts := strings.Split(vStr, ".") i, err := strconv.Atoi(parts[0]) if err != nil { return "", err } parts[0] = strconv.Itoa(i + 1) return strings.Join(parts, "."), nil } // incrementMajorVersion will increment the minor version // of the passed version func incrementMinorVersion(vStr string) (string, error) { parts := strings.Split(vStr, ".") i, err := strconv.Atoi(parts[1]) if err != nil { return "", err } parts[1] = strconv.Itoa(i + 1) return strings.Join(parts, "."), nil } // expandWildcardVersion will expand wildcards inside versions // following these rules: // // * when dealing with patch wildcards: // >= 1.2.x will become >= 1.2.0 // <= 1.2.x will become < 1.3.0 // > 1.2.x will become >= 1.3.0 // < 1.2.x will become < 1.2.0 // != 1.2.x will become < 1.2.0 >= 1.3.0 // // * when dealing with minor wildcards: // >= 1.x will become >= 1.0.0 // <= 1.x will become < 2.0.0 // > 1.x will become >= 2.0.0 // < 1.0 will become < 1.0.0 // != 1.x will become < 1.0.0 >= 2.0.0 // // * when dealing with wildcards without // version operator: // 1.2.x will become >= 1.2.0 < 1.3.0 // 1.x will become >= 1.0.0 < 2.0.0 func expandWildcardVersion(parts [][]string) ([][]string, error) { var expandedParts [][]string for _, p := range parts { var newParts []string for _, ap := range p { if strings.Contains(ap, "x") { opStr, vStr, err := splitComparatorVersion(ap) if err != nil { return nil, err } versionWildcardType := getWildcardType(vStr) flatVersion := createVersionFromWildcard(vStr) var resultOperator string var shouldIncrementVersion bool switch opStr { case ">": resultOperator = ">=" shouldIncrementVersion = true case ">=": resultOperator = ">=" case "<": resultOperator = "<" case "<=": resultOperator = "<" shouldIncrementVersion = true case "", "=", "==": newParts = append(newParts, ">="+flatVersion) resultOperator = "<" shouldIncrementVersion = true case "!=", "!": newParts = append(newParts, "<"+flatVersion) resultOperator = ">=" shouldIncrementVersion = true } var resultVersion string if shouldIncrementVersion { switch versionWildcardType { case patchWildcard: resultVersion, _ = incrementMinorVersion(flatVersion) case minorWildcard: resultVersion, _ = incrementMajorVersion(flatVersion) } } else { resultVersion = flatVersion } ap = resultOperator + resultVersion } newParts = append(newParts, ap) } expandedParts = append(expandedParts, newParts) } return expandedParts, nil } func parseComparator(s string) comparator { switch s { case "==": fallthrough case "": fallthrough case "=": return compEQ case ">": return compGT case ">=": return compGE case "<": return compLT case "<=": return compLE case "!": fallthrough case "!=": return compNE } return nil } // MustParseRange is like ParseRange but panics if the range cannot be parsed. func MustParseRange(s string) Range { r, err := ParseRange(s) if err != nil { panic(`semver: ParseRange(` + s + `): ` + err.Error()) } return r } semver-4.0.0/v4/range_test.go000066400000000000000000000312731366250757700160570ustar00rootroot00000000000000package semver import ( "reflect" "strings" "testing" ) type wildcardTypeTest struct { input string wildcardType wildcardType } type comparatorTest struct { input string comparator func(comparator) bool } func TestParseComparator(t *testing.T) { compatorTests := []comparatorTest{ {">", testGT}, {">=", testGE}, {"<", testLT}, {"<=", testLE}, {"", testEQ}, {"=", testEQ}, {"==", testEQ}, {"!=", testNE}, {"!", testNE}, {"-", nil}, {"<==", nil}, {"<<", nil}, {">>", nil}, } for _, tc := range compatorTests { if c := parseComparator(tc.input); c == nil { if tc.comparator != nil { t.Errorf("Comparator nil for case %q\n", tc.input) } } else if !tc.comparator(c) { t.Errorf("Invalid comparator for case %q\n", tc.input) } } } var ( v1 = MustParse("1.2.2") v2 = MustParse("1.2.3") v3 = MustParse("1.2.4") ) func testEQ(f comparator) bool { return f(v1, v1) && !f(v1, v2) } func testNE(f comparator) bool { return !f(v1, v1) && f(v1, v2) } func testGT(f comparator) bool { return f(v2, v1) && f(v3, v2) && !f(v1, v2) && !f(v1, v1) } func testGE(f comparator) bool { return f(v2, v1) && f(v3, v2) && !f(v1, v2) } func testLT(f comparator) bool { return f(v1, v2) && f(v2, v3) && !f(v2, v1) && !f(v1, v1) } func testLE(f comparator) bool { return f(v1, v2) && f(v2, v3) && !f(v2, v1) } func TestSplitAndTrim(t *testing.T) { tests := []struct { i string s []string }{ {"1.2.3 1.2.3", []string{"1.2.3", "1.2.3"}}, {" 1.2.3 1.2.3 ", []string{"1.2.3", "1.2.3"}}, // Spaces {" >= 1.2.3 <= 1.2.3 ", []string{">=1.2.3", "<=1.2.3"}}, // Spaces between operator and version {"1.2.3 || >=1.2.3 <1.2.3", []string{"1.2.3", "||", ">=1.2.3", "<1.2.3"}}, {" 1.2.3 || >=1.2.3 <1.2.3 ", []string{"1.2.3", "||", ">=1.2.3", "<1.2.3"}}, } for _, tc := range tests { p := splitAndTrim(tc.i) if !reflect.DeepEqual(p, tc.s) { t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p) } } } func TestSplitComparatorVersion(t *testing.T) { tests := []struct { i string p []string }{ {">1.2.3", []string{">", "1.2.3"}}, {">=1.2.3", []string{">=", "1.2.3"}}, {"<1.2.3", []string{"<", "1.2.3"}}, {"<=1.2.3", []string{"<=", "1.2.3"}}, {"1.2.3", []string{"", "1.2.3"}}, {"=1.2.3", []string{"=", "1.2.3"}}, {"==1.2.3", []string{"==", "1.2.3"}}, {"!=1.2.3", []string{"!=", "1.2.3"}}, {"!1.2.3", []string{"!", "1.2.3"}}, {"error", nil}, } for _, tc := range tests { if op, v, err := splitComparatorVersion(tc.i); err != nil { if tc.p != nil { t.Errorf("Invalid for case %q: Expected %q, got error %q", tc.i, tc.p, err) } } else if op != tc.p[0] { t.Errorf("Invalid operator for case %q: Expected %q, got: %q", tc.i, tc.p[0], op) } else if v != tc.p[1] { t.Errorf("Invalid version for case %q: Expected %q, got: %q", tc.i, tc.p[1], v) } } } func TestBuildVersionRange(t *testing.T) { tests := []struct { opStr string vStr string c func(comparator) bool v string }{ {">", "1.2.3", testGT, "1.2.3"}, {">=", "1.2.3", testGE, "1.2.3"}, {"<", "1.2.3", testLT, "1.2.3"}, {"<=", "1.2.3", testLE, "1.2.3"}, {"", "1.2.3", testEQ, "1.2.3"}, {"=", "1.2.3", testEQ, "1.2.3"}, {"==", "1.2.3", testEQ, "1.2.3"}, {"!=", "1.2.3", testNE, "1.2.3"}, {"!", "1.2.3", testNE, "1.2.3"}, {">>", "1.2.3", nil, ""}, // Invalid comparator {"=", "invalid", nil, ""}, // Invalid version } for _, tc := range tests { if r, err := buildVersionRange(tc.opStr, tc.vStr); err != nil { if tc.c != nil { t.Errorf("Invalid for case %q: Expected %q, got error %q", strings.Join([]string{tc.opStr, tc.vStr}, ""), tc.v, err) } } else if r == nil { t.Errorf("Invalid for case %q: got nil", strings.Join([]string{tc.opStr, tc.vStr}, "")) } else { // test version if tv := MustParse(tc.v); !r.v.EQ(tv) { t.Errorf("Invalid for case %q: Expected version %q, got: %q", strings.Join([]string{tc.opStr, tc.vStr}, ""), tv, r.v) } // test comparator if r.c == nil { t.Errorf("Invalid for case %q: got nil comparator", strings.Join([]string{tc.opStr, tc.vStr}, "")) continue } if !tc.c(r.c) { t.Errorf("Invalid comparator for case %q\n", strings.Join([]string{tc.opStr, tc.vStr}, "")) } } } } func TestSplitORParts(t *testing.T) { tests := []struct { i []string o [][]string }{ {[]string{">1.2.3", "||", "<1.2.3", "||", "=1.2.3"}, [][]string{ {">1.2.3"}, {"<1.2.3"}, {"=1.2.3"}, }}, {[]string{">1.2.3", "<1.2.3", "||", "=1.2.3"}, [][]string{ {">1.2.3", "<1.2.3"}, {"=1.2.3"}, }}, {[]string{">1.2.3", "||"}, nil}, {[]string{"||", ">1.2.3"}, nil}, } for _, tc := range tests { o, err := splitORParts(tc.i) if err != nil && tc.o != nil { t.Errorf("Unexpected error for case %q: %s", tc.i, err) } if !reflect.DeepEqual(tc.o, o) { t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.o, o) } } } func TestGetWildcardType(t *testing.T) { wildcardTypeTests := []wildcardTypeTest{ {"x", majorWildcard}, {"1.x", minorWildcard}, {"1.2.x", patchWildcard}, {"fo.o.b.ar", noneWildcard}, } for _, tc := range wildcardTypeTests { o := getWildcardType(tc.input) if o != tc.wildcardType { t.Errorf("Invalid for case: %q: Expected %q, got: %q", tc.input, tc.wildcardType, o) } } } func TestCreateVersionFromWildcard(t *testing.T) { tests := []struct { i string s string }{ {"1.2.x", "1.2.0"}, {"1.x", "1.0.0"}, } for _, tc := range tests { p := createVersionFromWildcard(tc.i) if p != tc.s { t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p) } } } func TestIncrementMajorVersion(t *testing.T) { tests := []struct { i string s string }{ {"1.2.3", "2.2.3"}, {"1.2", "2.2"}, {"foo.bar", ""}, } for _, tc := range tests { p, _ := incrementMajorVersion(tc.i) if p != tc.s { t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p) } } } func TestIncrementMinorVersion(t *testing.T) { tests := []struct { i string s string }{ {"1.2.3", "1.3.3"}, {"1.2", "1.3"}, {"foo.bar", ""}, } for _, tc := range tests { p, _ := incrementMinorVersion(tc.i) if p != tc.s { t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p) } } } func TestExpandWildcardVersion(t *testing.T) { tests := []struct { i [][]string o [][]string }{ {[][]string{{"foox"}}, nil}, {[][]string{{">=1.2.x"}}, [][]string{{">=1.2.0"}}}, {[][]string{{"<=1.2.x"}}, [][]string{{"<1.3.0"}}}, {[][]string{{">1.2.x"}}, [][]string{{">=1.3.0"}}}, {[][]string{{"<1.2.x"}}, [][]string{{"<1.2.0"}}}, {[][]string{{"!=1.2.x"}}, [][]string{{"<1.2.0", ">=1.3.0"}}}, {[][]string{{">=1.x"}}, [][]string{{">=1.0.0"}}}, {[][]string{{"<=1.x"}}, [][]string{{"<2.0.0"}}}, {[][]string{{">1.x"}}, [][]string{{">=2.0.0"}}}, {[][]string{{"<1.x"}}, [][]string{{"<1.0.0"}}}, {[][]string{{"!=1.x"}}, [][]string{{"<1.0.0", ">=2.0.0"}}}, {[][]string{{"1.2.x"}}, [][]string{{">=1.2.0", "<1.3.0"}}}, {[][]string{{"1.x"}}, [][]string{{">=1.0.0", "<2.0.0"}}}, } for _, tc := range tests { o, _ := expandWildcardVersion(tc.i) if !reflect.DeepEqual(tc.o, o) { t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.o, o) } } } func TestVersionRangeToRange(t *testing.T) { vr := versionRange{ v: MustParse("1.2.3"), c: compLT, } rf := vr.rangeFunc() if !rf(MustParse("1.2.2")) || rf(MustParse("1.2.3")) { t.Errorf("Invalid conversion to range func") } } func TestRangeAND(t *testing.T) { v := MustParse("1.2.2") v1 := MustParse("1.2.1") v2 := MustParse("1.2.3") rf1 := Range(func(v Version) bool { return v.GT(v1) }) rf2 := Range(func(v Version) bool { return v.LT(v2) }) rf := rf1.AND(rf2) if rf(v1) { t.Errorf("Invalid rangefunc, accepted: %s", v1) } if rf(v2) { t.Errorf("Invalid rangefunc, accepted: %s", v2) } if !rf(v) { t.Errorf("Invalid rangefunc, did not accept: %s", v) } } func TestRangeOR(t *testing.T) { tests := []struct { v Version b bool }{ {MustParse("1.2.0"), true}, {MustParse("1.2.2"), false}, {MustParse("1.2.4"), true}, } v1 := MustParse("1.2.1") v2 := MustParse("1.2.3") rf1 := Range(func(v Version) bool { return v.LT(v1) }) rf2 := Range(func(v Version) bool { return v.GT(v2) }) rf := rf1.OR(rf2) for _, tc := range tests { if r := rf(tc.v); r != tc.b { t.Errorf("Invalid for case %q: Expected %t, got %t", tc.v, tc.b, r) } } } func TestParseRange(t *testing.T) { type tv struct { v string b bool } tests := []struct { i string t []tv }{ // Simple expressions {">1.2.3", []tv{ {"1.2.2", false}, {"1.2.3", false}, {"1.2.4", true}, }}, {">=1.2.3", []tv{ {"1.2.3", true}, {"1.2.4", true}, {"1.2.2", false}, }}, {"<1.2.3", []tv{ {"1.2.2", true}, {"1.2.3", false}, {"1.2.4", false}, }}, {"<=1.2.3", []tv{ {"1.2.2", true}, {"1.2.3", true}, {"1.2.4", false}, }}, {"1.2.3", []tv{ {"1.2.2", false}, {"1.2.3", true}, {"1.2.4", false}, }}, {"=1.2.3", []tv{ {"1.2.2", false}, {"1.2.3", true}, {"1.2.4", false}, }}, {"==1.2.3", []tv{ {"1.2.2", false}, {"1.2.3", true}, {"1.2.4", false}, }}, {"!=1.2.3", []tv{ {"1.2.2", true}, {"1.2.3", false}, {"1.2.4", true}, }}, {"!1.2.3", []tv{ {"1.2.2", true}, {"1.2.3", false}, {"1.2.4", true}, }}, // Simple Expression errors {">>1.2.3", nil}, {"!1.2.3", nil}, {"1.0", nil}, {"string", nil}, {"", nil}, {"fo.ob.ar.x", nil}, // AND Expressions {">1.2.2 <1.2.4", []tv{ {"1.2.2", false}, {"1.2.3", true}, {"1.2.4", false}, }}, {"<1.2.2 <1.2.4", []tv{ {"1.2.1", true}, {"1.2.2", false}, {"1.2.3", false}, {"1.2.4", false}, }}, {">1.2.2 <1.2.5 !=1.2.4", []tv{ {"1.2.2", false}, {"1.2.3", true}, {"1.2.4", false}, {"1.2.5", false}, }}, {">1.2.2 <1.2.5 !1.2.4", []tv{ {"1.2.2", false}, {"1.2.3", true}, {"1.2.4", false}, {"1.2.5", false}, }}, // OR Expressions {">1.2.2 || <1.2.4", []tv{ {"1.2.2", true}, {"1.2.3", true}, {"1.2.4", true}, }}, {"<1.2.2 || >1.2.4", []tv{ {"1.2.2", false}, {"1.2.3", false}, {"1.2.4", false}, }}, // Wildcard expressions {">1.x", []tv{ {"0.1.9", false}, {"1.2.6", false}, {"1.9.0", false}, {"2.0.0", true}, }}, {">1.2.x", []tv{ {"1.1.9", false}, {"1.2.6", false}, {"1.3.0", true}, }}, // Combined Expressions {">1.2.2 <1.2.4 || >=2.0.0", []tv{ {"1.2.2", false}, {"1.2.3", true}, {"1.2.4", false}, {"2.0.0", true}, {"2.0.1", true}, }}, {"1.x || >=2.0.x <2.2.x", []tv{ {"0.9.2", false}, {"1.2.2", true}, {"2.0.0", true}, {"2.1.8", true}, {"2.2.0", false}, }}, {">1.2.2 <1.2.4 || >=2.0.0 <3.0.0", []tv{ {"1.2.2", false}, {"1.2.3", true}, {"1.2.4", false}, {"2.0.0", true}, {"2.0.1", true}, {"2.9.9", true}, {"3.0.0", false}, }}, } for _, tc := range tests { r, err := ParseRange(tc.i) if err != nil && tc.t != nil { t.Errorf("Error parsing range %q: %s", tc.i, err) continue } for _, tvc := range tc.t { v := MustParse(tvc.v) if res := r(v); res != tvc.b { t.Errorf("Invalid for case %q matching %q: Expected %t, got: %t", tc.i, tvc.v, tvc.b, res) } } } } func TestMustParseRange(t *testing.T) { testCase := ">1.2.2 <1.2.4 || >=2.0.0 <3.0.0" r := MustParseRange(testCase) if !r(MustParse("1.2.3")) { t.Errorf("Unexpected range behavior on MustParseRange") } } func TestMustParseRange_panic(t *testing.T) { defer func() { if recover() == nil { t.Errorf("Should have panicked") } }() _ = MustParseRange("invalid version") } func BenchmarkRangeParseSimple(b *testing.B) { const VERSION = ">1.0.0" b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _, _ = ParseRange(VERSION) } } func BenchmarkRangeParseAverage(b *testing.B) { const VERSION = ">=1.0.0 <2.0.0" b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _, _ = ParseRange(VERSION) } } func BenchmarkRangeParseComplex(b *testing.B) { const VERSION = ">=1.0.0 <2.0.0 || >=3.0.1 <4.0.0 !=3.0.3 || >=5.0.0" b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _, _ = ParseRange(VERSION) } } func BenchmarkRangeMatchSimple(b *testing.B) { const VERSION = ">1.0.0" r, _ := ParseRange(VERSION) v := MustParse("2.0.0") b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { r(v) } } func BenchmarkRangeMatchAverage(b *testing.B) { const VERSION = ">=1.0.0 <2.0.0" r, _ := ParseRange(VERSION) v := MustParse("1.2.3") b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { r(v) } } func BenchmarkRangeMatchComplex(b *testing.B) { const VERSION = ">=1.0.0 <2.0.0 || >=3.0.1 <4.0.0 !=3.0.3 || >=5.0.0" r, _ := ParseRange(VERSION) v := MustParse("5.0.1") b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { r(v) } } semver-4.0.0/v4/semver.go000066400000000000000000000257551366250757700152350ustar00rootroot00000000000000package semver import ( "errors" "fmt" "strconv" "strings" ) const ( numbers string = "0123456789" alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" alphanum = alphas + numbers ) // SpecVersion is the latest fully supported spec version of semver var SpecVersion = Version{ Major: 2, Minor: 0, Patch: 0, } // Version represents a semver compatible version type Version struct { Major uint64 Minor uint64 Patch uint64 Pre []PRVersion Build []string //No Precedence } // Version to string func (v Version) String() string { b := make([]byte, 0, 5) b = strconv.AppendUint(b, v.Major, 10) b = append(b, '.') b = strconv.AppendUint(b, v.Minor, 10) b = append(b, '.') b = strconv.AppendUint(b, v.Patch, 10) if len(v.Pre) > 0 { b = append(b, '-') b = append(b, v.Pre[0].String()...) for _, pre := range v.Pre[1:] { b = append(b, '.') b = append(b, pre.String()...) } } if len(v.Build) > 0 { b = append(b, '+') b = append(b, v.Build[0]...) for _, build := range v.Build[1:] { b = append(b, '.') b = append(b, build...) } } return string(b) } // FinalizeVersion discards prerelease and build number and only returns // major, minor and patch number. func (v Version) FinalizeVersion() string { b := make([]byte, 0, 5) b = strconv.AppendUint(b, v.Major, 10) b = append(b, '.') b = strconv.AppendUint(b, v.Minor, 10) b = append(b, '.') b = strconv.AppendUint(b, v.Patch, 10) return string(b) } // Equals checks if v is equal to o. func (v Version) Equals(o Version) bool { return (v.Compare(o) == 0) } // EQ checks if v is equal to o. func (v Version) EQ(o Version) bool { return (v.Compare(o) == 0) } // NE checks if v is not equal to o. func (v Version) NE(o Version) bool { return (v.Compare(o) != 0) } // GT checks if v is greater than o. func (v Version) GT(o Version) bool { return (v.Compare(o) == 1) } // GTE checks if v is greater than or equal to o. func (v Version) GTE(o Version) bool { return (v.Compare(o) >= 0) } // GE checks if v is greater than or equal to o. func (v Version) GE(o Version) bool { return (v.Compare(o) >= 0) } // LT checks if v is less than o. func (v Version) LT(o Version) bool { return (v.Compare(o) == -1) } // LTE checks if v is less than or equal to o. func (v Version) LTE(o Version) bool { return (v.Compare(o) <= 0) } // LE checks if v is less than or equal to o. func (v Version) LE(o Version) bool { return (v.Compare(o) <= 0) } // Compare compares Versions v to o: // -1 == v is less than o // 0 == v is equal to o // 1 == v is greater than o func (v Version) Compare(o Version) int { if v.Major != o.Major { if v.Major > o.Major { return 1 } return -1 } if v.Minor != o.Minor { if v.Minor > o.Minor { return 1 } return -1 } if v.Patch != o.Patch { if v.Patch > o.Patch { return 1 } return -1 } // Quick comparison if a version has no prerelease versions if len(v.Pre) == 0 && len(o.Pre) == 0 { return 0 } else if len(v.Pre) == 0 && len(o.Pre) > 0 { return 1 } else if len(v.Pre) > 0 && len(o.Pre) == 0 { return -1 } i := 0 for ; i < len(v.Pre) && i < len(o.Pre); i++ { if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 { continue } else if comp == 1 { return 1 } else { return -1 } } // If all pr versions are the equal but one has further prversion, this one greater if i == len(v.Pre) && i == len(o.Pre) { return 0 } else if i == len(v.Pre) && i < len(o.Pre) { return -1 } else { return 1 } } // IncrementPatch increments the patch version func (v *Version) IncrementPatch() error { v.Patch++ return nil } // IncrementMinor increments the minor version func (v *Version) IncrementMinor() error { v.Minor++ v.Patch = 0 return nil } // IncrementMajor increments the major version func (v *Version) IncrementMajor() error { v.Major++ v.Minor = 0 v.Patch = 0 return nil } // Validate validates v and returns error in case func (v Version) Validate() error { // Major, Minor, Patch already validated using uint64 for _, pre := range v.Pre { if !pre.IsNum { //Numeric prerelease versions already uint64 if len(pre.VersionStr) == 0 { return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr) } if !containsOnly(pre.VersionStr, alphanum) { return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr) } } } for _, build := range v.Build { if len(build) == 0 { return fmt.Errorf("Build meta data can not be empty %q", build) } if !containsOnly(build, alphanum) { return fmt.Errorf("Invalid character(s) found in build meta data %q", build) } } return nil } // New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error func New(s string) (*Version, error) { v, err := Parse(s) vp := &v return vp, err } // Make is an alias for Parse, parses version string and returns a validated Version or error func Make(s string) (Version, error) { return Parse(s) } // ParseTolerant allows for certain version specifications that do not strictly adhere to semver // specs to be parsed by this library. It does so by normalizing versions before passing them to // Parse(). It currently trims spaces, removes a "v" prefix, adds a 0 patch number to versions // with only major and minor components specified, and removes leading 0s. func ParseTolerant(s string) (Version, error) { s = strings.TrimSpace(s) s = strings.TrimPrefix(s, "v") // Split into major.minor.(patch+pr+meta) parts := strings.SplitN(s, ".", 3) // Remove leading zeros. for i, p := range parts { if len(p) > 1 { p = strings.TrimLeft(p, "0") if len(p) == 0 || !strings.ContainsAny(p[0:1], "0123456789") { p = "0" + p } parts[i] = p } } // Fill up shortened versions. if len(parts) < 3 { if strings.ContainsAny(parts[len(parts)-1], "+-") { return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data") } for len(parts) < 3 { parts = append(parts, "0") } } s = strings.Join(parts, ".") return Parse(s) } // Parse parses version string and returns a validated Version or error func Parse(s string) (Version, error) { if len(s) == 0 { return Version{}, errors.New("Version string empty") } // Split into major.minor.(patch+pr+meta) parts := strings.SplitN(s, ".", 3) if len(parts) != 3 { return Version{}, errors.New("No Major.Minor.Patch elements found") } // Major if !containsOnly(parts[0], numbers) { return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0]) } if hasLeadingZeroes(parts[0]) { return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0]) } major, err := strconv.ParseUint(parts[0], 10, 64) if err != nil { return Version{}, err } // Minor if !containsOnly(parts[1], numbers) { return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1]) } if hasLeadingZeroes(parts[1]) { return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1]) } minor, err := strconv.ParseUint(parts[1], 10, 64) if err != nil { return Version{}, err } v := Version{} v.Major = major v.Minor = minor var build, prerelease []string patchStr := parts[2] if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 { build = strings.Split(patchStr[buildIndex+1:], ".") patchStr = patchStr[:buildIndex] } if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 { prerelease = strings.Split(patchStr[preIndex+1:], ".") patchStr = patchStr[:preIndex] } if !containsOnly(patchStr, numbers) { return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr) } if hasLeadingZeroes(patchStr) { return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr) } patch, err := strconv.ParseUint(patchStr, 10, 64) if err != nil { return Version{}, err } v.Patch = patch // Prerelease for _, prstr := range prerelease { parsedPR, err := NewPRVersion(prstr) if err != nil { return Version{}, err } v.Pre = append(v.Pre, parsedPR) } // Build meta data for _, str := range build { if len(str) == 0 { return Version{}, errors.New("Build meta data is empty") } if !containsOnly(str, alphanum) { return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str) } v.Build = append(v.Build, str) } return v, nil } // MustParse is like Parse but panics if the version cannot be parsed. func MustParse(s string) Version { v, err := Parse(s) if err != nil { panic(`semver: Parse(` + s + `): ` + err.Error()) } return v } // PRVersion represents a PreRelease Version type PRVersion struct { VersionStr string VersionNum uint64 IsNum bool } // NewPRVersion creates a new valid prerelease version func NewPRVersion(s string) (PRVersion, error) { if len(s) == 0 { return PRVersion{}, errors.New("Prerelease is empty") } v := PRVersion{} if containsOnly(s, numbers) { if hasLeadingZeroes(s) { return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s) } num, err := strconv.ParseUint(s, 10, 64) // Might never be hit, but just in case if err != nil { return PRVersion{}, err } v.VersionNum = num v.IsNum = true } else if containsOnly(s, alphanum) { v.VersionStr = s v.IsNum = false } else { return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s) } return v, nil } // IsNumeric checks if prerelease-version is numeric func (v PRVersion) IsNumeric() bool { return v.IsNum } // Compare compares two PreRelease Versions v and o: // -1 == v is less than o // 0 == v is equal to o // 1 == v is greater than o func (v PRVersion) Compare(o PRVersion) int { if v.IsNum && !o.IsNum { return -1 } else if !v.IsNum && o.IsNum { return 1 } else if v.IsNum && o.IsNum { if v.VersionNum == o.VersionNum { return 0 } else if v.VersionNum > o.VersionNum { return 1 } else { return -1 } } else { // both are Alphas if v.VersionStr == o.VersionStr { return 0 } else if v.VersionStr > o.VersionStr { return 1 } else { return -1 } } } // PreRelease version to string func (v PRVersion) String() string { if v.IsNum { return strconv.FormatUint(v.VersionNum, 10) } return v.VersionStr } func containsOnly(s string, set string) bool { return strings.IndexFunc(s, func(r rune) bool { return !strings.ContainsRune(set, r) }) == -1 } func hasLeadingZeroes(s string) bool { return len(s) > 1 && s[0] == '0' } // NewBuildVersion creates a new valid build version func NewBuildVersion(s string) (string, error) { if len(s) == 0 { return "", errors.New("Buildversion is empty") } if !containsOnly(s, alphanum) { return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s) } return s, nil } // FinalizeVersion returns the major, minor and patch number only and discards // prerelease and build number. func FinalizeVersion(s string) (string, error) { v, err := Parse(s) if err != nil { return "", err } v.Pre = nil v.Build = nil finalVer := v.String() return finalVer, nil } semver-4.0.0/v4/semver_test.go000066400000000000000000000365541366250757700162730ustar00rootroot00000000000000package semver import ( "testing" ) func prstr(s string) PRVersion { return PRVersion{s, 0, false} } func prnum(i uint64) PRVersion { return PRVersion{"", i, true} } type formatTest struct { v Version result string } var formatTests = []formatTest{ {Version{1, 2, 3, nil, nil}, "1.2.3"}, {Version{0, 0, 1, nil, nil}, "0.0.1"}, {Version{0, 0, 1, []PRVersion{prstr("alpha"), prstr("preview")}, []string{"123", "456"}}, "0.0.1-alpha.preview+123.456"}, {Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, []string{"123", "456"}}, "1.2.3-alpha.1+123.456"}, {Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, nil}, "1.2.3-alpha.1"}, {Version{1, 2, 3, nil, []string{"123", "456"}}, "1.2.3+123.456"}, // Prereleases and build metadata hyphens {Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, []string{"123", "b-uild"}}, "1.2.3-alpha.b-eta+123.b-uild"}, {Version{1, 2, 3, nil, []string{"123", "b-uild"}}, "1.2.3+123.b-uild"}, {Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, nil}, "1.2.3-alpha.b-eta"}, } var tolerantFormatTests = []formatTest{ {Version{1, 2, 3, nil, nil}, "v1.2.3"}, {Version{1, 2, 0, []PRVersion{prstr("alpha")}, nil}, "1.2.0-alpha"}, {Version{1, 2, 0, nil, nil}, "1.2.00"}, {Version{1, 2, 3, nil, nil}, " 1.2.3 "}, {Version{1, 2, 3, nil, nil}, "01.02.03"}, {Version{0, 0, 3, nil, nil}, "00.0.03"}, {Version{0, 0, 3, nil, nil}, "000.0.03"}, {Version{1, 2, 0, nil, nil}, "1.2"}, {Version{1, 0, 0, nil, nil}, "1"}, } func TestStringer(t *testing.T) { for _, test := range formatTests { if res := test.v.String(); res != test.result { t.Errorf("Stringer, expected %q but got %q", test.result, res) } } } func TestParse(t *testing.T) { for _, test := range formatTests { if v, err := Parse(test.result); err != nil { t.Errorf("Error parsing %q: %q", test.result, err) } else if comp := v.Compare(test.v); comp != 0 { t.Errorf("Parsing, expected %q but got %q, comp: %d ", test.v, v, comp) } else if err := v.Validate(); err != nil { t.Errorf("Error validating parsed version %q: %q", test.v, err) } } } func TestParseTolerant(t *testing.T) { for _, test := range tolerantFormatTests { if v, err := ParseTolerant(test.result); err != nil { t.Errorf("Error parsing %q: %q", test.result, err) } else if comp := v.Compare(test.v); comp != 0 { t.Errorf("Parsing, expected %q but got %q, comp: %d ", test.v, v, comp) } else if err := v.Validate(); err != nil { t.Errorf("Error validating parsed version %q: %q", test.v, err) } } } func TestMustParse(t *testing.T) { _ = MustParse("32.2.1-alpha") } func TestMustParse_panic(t *testing.T) { defer func() { if recover() == nil { t.Errorf("Should have panicked") } }() _ = MustParse("invalid version") } func TestValidate(t *testing.T) { for _, test := range formatTests { if err := test.v.Validate(); err != nil { t.Errorf("Error validating %q: %q", test.v, err) } } } var finalizeVersionMethod = []formatTest{ {Version{1, 2, 3, nil, nil}, "1.2.3"}, {Version{0, 0, 1, nil, nil}, "0.0.1"}, {Version{0, 0, 1, []PRVersion{prstr("alpha"), prstr("preview")}, []string{"123", "456"}}, "0.0.1"}, {Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, []string{"123", "456"}}, "1.2.3"}, {Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, nil}, "1.2.3"}, {Version{1, 2, 3, nil, []string{"123", "456"}}, "1.2.3"}, // Prereleases and build metadata hyphens {Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, []string{"123", "b-uild"}}, "1.2.3"}, {Version{1, 2, 3, nil, []string{"123", "b-uild"}}, "1.2.3"}, {Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, nil}, "1.2.3"}, } func TestFinalizeVersionMethod(t *testing.T) { for _, test := range finalizeVersionMethod { out := test.v.FinalizeVersion() if out != test.result { t.Errorf("Finalized version error, expected %q but got %q", test.result, out) } } } type compareTest struct { v1 Version v2 Version result int } var compareTests = []compareTest{ {Version{1, 0, 0, nil, nil}, Version{1, 0, 0, nil, nil}, 0}, {Version{2, 0, 0, nil, nil}, Version{1, 0, 0, nil, nil}, 1}, {Version{0, 1, 0, nil, nil}, Version{0, 1, 0, nil, nil}, 0}, {Version{0, 2, 0, nil, nil}, Version{0, 1, 0, nil, nil}, 1}, {Version{0, 0, 1, nil, nil}, Version{0, 0, 1, nil, nil}, 0}, {Version{0, 0, 2, nil, nil}, Version{0, 0, 1, nil, nil}, 1}, {Version{1, 2, 3, nil, nil}, Version{1, 2, 3, nil, nil}, 0}, {Version{2, 2, 4, nil, nil}, Version{1, 2, 4, nil, nil}, 1}, {Version{1, 3, 3, nil, nil}, Version{1, 2, 3, nil, nil}, 1}, {Version{1, 2, 4, nil, nil}, Version{1, 2, 3, nil, nil}, 1}, // Spec Examples #11 {Version{1, 0, 0, nil, nil}, Version{2, 0, 0, nil, nil}, -1}, {Version{2, 0, 0, nil, nil}, Version{2, 1, 0, nil, nil}, -1}, {Version{2, 1, 0, nil, nil}, Version{2, 1, 1, nil, nil}, -1}, // Spec Examples #9 {Version{1, 0, 0, nil, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil}, 1}, {Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha"), prnum(1)}, nil}, -1}, {Version{1, 0, 0, []PRVersion{prstr("alpha"), prnum(1)}, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha"), prstr("beta")}, nil}, -1}, {Version{1, 0, 0, []PRVersion{prstr("alpha"), prstr("beta")}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta")}, nil}, -1}, {Version{1, 0, 0, []PRVersion{prstr("beta")}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(2)}, nil}, -1}, {Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(2)}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(11)}, nil}, -1}, {Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(11)}, nil}, Version{1, 0, 0, []PRVersion{prstr("rc"), prnum(1)}, nil}, -1}, {Version{1, 0, 0, []PRVersion{prstr("rc"), prnum(1)}, nil}, Version{1, 0, 0, nil, nil}, -1}, // Ignore Build metadata {Version{1, 0, 0, nil, []string{"1", "2", "3"}}, Version{1, 0, 0, nil, nil}, 0}, } func TestCompare(t *testing.T) { for _, test := range compareTests { if res := test.v1.Compare(test.v2); res != test.result { t.Errorf("Comparing %q : %q, expected %d but got %d", test.v1, test.v2, test.result, res) } // Test counterpart if res := test.v2.Compare(test.v1); res != -test.result { t.Errorf("Comparing %q : %q, expected %d but got %d", test.v2, test.v1, -test.result, res) } } } type wrongformatTest struct { v *Version str string } var wrongformatTests = []wrongformatTest{ {nil, ""}, {nil, "."}, {nil, "1."}, {nil, ".1"}, {nil, "a.b.c"}, {nil, "1.a.b"}, {nil, "1.1.a"}, {nil, "1.a.1"}, {nil, "a.1.1"}, {nil, ".."}, {nil, "1.."}, {nil, "1.1."}, {nil, "1..1"}, {nil, "1.1.+123"}, {nil, "1.1.-beta"}, {nil, "-1.1.1"}, {nil, "1.-1.1"}, {nil, "1.1.-1"}, // giant numbers {nil, "20000000000000000000.1.1"}, {nil, "1.20000000000000000000.1"}, {nil, "1.1.20000000000000000000"}, {nil, "1.1.1-20000000000000000000"}, // Leading zeroes {nil, "01.1.1"}, {nil, "001.1.1"}, {nil, "1.01.1"}, {nil, "1.001.1"}, {nil, "1.1.01"}, {nil, "1.1.001"}, {nil, "1.1.1-01"}, {nil, "1.1.1-001"}, {nil, "1.1.1-beta.01"}, {nil, "1.1.1-beta.001"}, {&Version{0, 0, 0, []PRVersion{prstr("!")}, nil}, "0.0.0-!"}, {&Version{0, 0, 0, nil, []string{"!"}}, "0.0.0+!"}, // empty prversion {&Version{0, 0, 0, []PRVersion{prstr(""), prstr("alpha")}, nil}, "0.0.0-.alpha"}, // empty build meta data {&Version{0, 0, 0, []PRVersion{prstr("alpha")}, []string{""}}, "0.0.0-alpha+"}, {&Version{0, 0, 0, []PRVersion{prstr("alpha")}, []string{"test", ""}}, "0.0.0-alpha+test."}, } func TestWrongFormat(t *testing.T) { for _, test := range wrongformatTests { if res, err := Parse(test.str); err == nil { t.Errorf("Parsing wrong format version %q, expected error but got %q", test.str, res) } if test.v != nil { if err := test.v.Validate(); err == nil { t.Errorf("Validating wrong format version %q (%q), expected error", test.v, test.str) } } } } var wrongTolerantFormatTests = []wrongformatTest{ {nil, "1.0+abc"}, {nil, "1.0-rc.1"}, } func TestWrongTolerantFormat(t *testing.T) { for _, test := range wrongTolerantFormatTests { if res, err := ParseTolerant(test.str); err == nil { t.Errorf("Parsing wrong format version %q, expected error but got %q", test.str, res) } } } func TestCompareHelper(t *testing.T) { v := Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil} v1 := Version{1, 0, 0, nil, nil} if !v.EQ(v) { t.Errorf("%q should be equal to %q", v, v) } if !v.Equals(v) { t.Errorf("%q should be equal to %q", v, v) } if !v1.NE(v) { t.Errorf("%q should not be equal to %q", v1, v) } if !v.GTE(v) { t.Errorf("%q should be greater than or equal to %q", v, v) } if !v.LTE(v) { t.Errorf("%q should be less than or equal to %q", v, v) } if !v.LT(v1) { t.Errorf("%q should be less than %q", v, v1) } if !v.LTE(v1) { t.Errorf("%q should be less than or equal %q", v, v1) } if !v.LE(v1) { t.Errorf("%q should be less than or equal %q", v, v1) } if !v1.GT(v) { t.Errorf("%q should be greater than %q", v1, v) } if !v1.GTE(v) { t.Errorf("%q should be greater than or equal %q", v1, v) } if !v1.GE(v) { t.Errorf("%q should be greater than or equal %q", v1, v) } } const ( MAJOR = iota MINOR PATCH ) type incrementTest struct { version Version incrementType int expectingError bool expectedVersion Version } var incrementTests = []incrementTest{ {Version{1, 2, 3, nil, nil}, PATCH, false, Version{1, 2, 4, nil, nil}}, {Version{1, 2, 3, nil, nil}, MINOR, false, Version{1, 3, 0, nil, nil}}, {Version{1, 2, 3, nil, nil}, MAJOR, false, Version{2, 0, 0, nil, nil}}, {Version{0, 1, 2, nil, nil}, PATCH, false, Version{0, 1, 3, nil, nil}}, {Version{0, 1, 2, nil, nil}, MINOR, false, Version{0, 2, 0, nil, nil}}, {Version{0, 1, 2, nil, nil}, MAJOR, false, Version{1, 0, 0, nil, nil}}, } func TestIncrements(t *testing.T) { for _, test := range incrementTests { var originalVersion = Version{ test.version.Major, test.version.Minor, test.version.Patch, test.version.Pre, test.version.Build, } var err error switch test.incrementType { case PATCH: err = test.version.IncrementPatch() case MINOR: err = test.version.IncrementMinor() case MAJOR: err = test.version.IncrementMajor() } if test.expectingError { if err != nil { t.Errorf("Increment version, expecting %q, got error %q", test.expectedVersion, err) } if test.version.EQ(originalVersion) { t.Errorf("Increment version, expecting %q, got %q", test.expectedVersion, test.version) } } else { if (err != nil) && !test.expectingError { t.Errorf("Increment version %q, not expecting error, got %q", test.version, err) } if test.version.NE(test.expectedVersion) { t.Errorf("Increment version, expecting %q, got %q", test.expectedVersion, test.version) } } } } func TestPreReleaseVersions(t *testing.T) { p1, err := NewPRVersion("123") if !p1.IsNumeric() { t.Errorf("Expected numeric prversion, got %q", p1) } if p1.VersionNum != 123 { t.Error("Wrong prversion number") } if err != nil { t.Errorf("Not expected error %q", err) } p2, err := NewPRVersion("alpha") if p2.IsNumeric() { t.Errorf("Expected non-numeric prversion, got %q", p2) } if p2.VersionStr != "alpha" { t.Error("Wrong prversion string") } if err != nil { t.Errorf("Not expected error %q", err) } } func TestBuildMetaDataVersions(t *testing.T) { _, err := NewBuildVersion("123") if err != nil { t.Errorf("Unexpected error %q", err) } _, err = NewBuildVersion("build") if err != nil { t.Errorf("Unexpected error %q", err) } _, err = NewBuildVersion("test?") if err == nil { t.Error("Expected error, got none") } _, err = NewBuildVersion("") if err == nil { t.Error("Expected error, got none") } } func TestNewHelper(t *testing.T) { v, err := New("1.2.3") if err != nil { t.Fatalf("Unexpected error %q", err) } // New returns pointer if v == nil { t.Fatal("Version is nil") } if v.Compare(Version{1, 2, 3, nil, nil}) != 0 { t.Fatal("Unexpected comparison problem") } } func TestMakeHelper(t *testing.T) { v, err := Make("1.2.3") if err != nil { t.Fatalf("Unexpected error %q", err) } if v.Compare(Version{1, 2, 3, nil, nil}) != 0 { t.Fatal("Unexpected comparison problem") } } type finalizeTest struct { input string output string } var finalizeTests = []finalizeTest{ {"", ""}, {"1.2.3", "1.2.3"}, {"0.0.1", "0.0.1"}, {"0.0.1-alpha.preview+123.456", "0.0.1"}, {"1.2.3-alpha.1+123.456", "1.2.3"}, {"1.2.3-alpha.1", "1.2.3"}, {"1.2.3+123.456", "1.2.3"}, {"1.2.3-alpha.b-eta+123.b-uild", "1.2.3"}, {"1.2.3+123.b-uild", "1.2.3"}, {"1.2.3-alpha.b-eta", "1.2.3"}, {"1.2-alpha", ""}, } func TestFinalizeVersion(t *testing.T) { for _, test := range finalizeTests { finalVer, err := FinalizeVersion(test.input) if finalVer == "" { if err == nil { t.Errorf("Finalize Version error, expected error but got nil") } } else if finalVer != test.output && err != nil { t.Errorf("Finalize Version error expected %q but got %q", test.output, finalVer) } } } func BenchmarkParseSimple(b *testing.B) { const VERSION = "0.0.1" b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _, _ = Parse(VERSION) } } func BenchmarkParseComplex(b *testing.B) { const VERSION = "0.0.1-alpha.preview+123.456" b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _, _ = Parse(VERSION) } } func BenchmarkParseAverage(b *testing.B) { l := len(formatTests) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _, _ = Parse(formatTests[n%l].result) } } func BenchmarkParseTolerantAverage(b *testing.B) { l := len(tolerantFormatTests) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _, _ = ParseTolerant(tolerantFormatTests[n%l].result) } } func BenchmarkStringSimple(b *testing.B) { const VERSION = "0.0.1" v, _ := Parse(VERSION) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _ = v.String() } } func BenchmarkStringLarger(b *testing.B) { const VERSION = "11.15.2012" v, _ := Parse(VERSION) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _ = v.String() } } func BenchmarkStringComplex(b *testing.B) { const VERSION = "0.0.1-alpha.preview+123.456" v, _ := Parse(VERSION) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _ = v.String() } } func BenchmarkStringAverage(b *testing.B) { l := len(formatTests) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _ = formatTests[n%l].v.String() } } func BenchmarkValidateSimple(b *testing.B) { const VERSION = "0.0.1" v, _ := Parse(VERSION) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _ = v.Validate() } } func BenchmarkValidateComplex(b *testing.B) { const VERSION = "0.0.1-alpha.preview+123.456" v, _ := Parse(VERSION) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _ = v.Validate() } } func BenchmarkValidateAverage(b *testing.B) { l := len(formatTests) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _ = formatTests[n%l].v.Validate() } } func BenchmarkCompareSimple(b *testing.B) { const VERSION = "0.0.1" v, _ := Parse(VERSION) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { v.Compare(v) } } func BenchmarkCompareComplex(b *testing.B) { const VERSION = "0.0.1-alpha.preview+123.456" v, _ := Parse(VERSION) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { v.Compare(v) } } func BenchmarkCompareAverage(b *testing.B) { l := len(compareTests) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { compareTests[n%l].v1.Compare((compareTests[n%l].v2)) } } semver-4.0.0/v4/sort.go000066400000000000000000000010531366250757700147040ustar00rootroot00000000000000package semver import ( "sort" ) // Versions represents multiple versions. type Versions []Version // Len returns length of version collection func (s Versions) Len() int { return len(s) } // Swap swaps two versions inside the collection by its indices func (s Versions) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // Less checks if version at index i is less than version at index j func (s Versions) Less(i, j int) bool { return s[i].LT(s[j]) } // Sort sorts a slice of versions func Sort(versions []Version) { sort.Sort(Versions(versions)) } semver-4.0.0/v4/sort_test.go000066400000000000000000000011061366250757700157420ustar00rootroot00000000000000package semver import ( "reflect" "testing" ) func TestSort(t *testing.T) { v100, _ := Parse("1.0.0") v010, _ := Parse("0.1.0") v001, _ := Parse("0.0.1") versions := []Version{v010, v100, v001} Sort(versions) correct := []Version{v001, v010, v100} if !reflect.DeepEqual(versions, correct) { t.Fatalf("Sort returned wrong order: %s", versions) } } func BenchmarkSort(b *testing.B) { v100, _ := Parse("1.0.0") v010, _ := Parse("0.1.0") v001, _ := Parse("0.0.1") b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { Sort([]Version{v010, v100, v001}) } } semver-4.0.0/v4/sql.go000066400000000000000000000010531366250757700145140ustar00rootroot00000000000000package semver import ( "database/sql/driver" "fmt" ) // Scan implements the database/sql.Scanner interface. func (v *Version) Scan(src interface{}) (err error) { var str string switch src := src.(type) { case string: str = src case []byte: str = string(src) default: return fmt.Errorf("version.Scan: cannot convert %T to string", src) } if t, err := Parse(str); err == nil { *v = t } return } // Value implements the database/sql/driver.Valuer interface. func (v Version) Value() (driver.Value, error) { return v.String(), nil } semver-4.0.0/v4/sql_test.go000066400000000000000000000014131366250757700155530ustar00rootroot00000000000000package semver import ( "testing" ) type scanTest struct { val interface{} shouldError bool expected string } var scanTests = []scanTest{ {"1.2.3", false, "1.2.3"}, {[]byte("1.2.3"), false, "1.2.3"}, {7, true, ""}, {7e4, true, ""}, {true, true, ""}, } func TestScanString(t *testing.T) { for _, tc := range scanTests { s := &Version{} err := s.Scan(tc.val) if tc.shouldError { if err == nil { t.Fatalf("Scan did not return an error on %v (%T)", tc.val, tc.val) } } else { if err != nil { t.Fatalf("Scan returned an unexpected error: %s (%T) on %v (%T)", tc.val, tc.val, tc.val, tc.val) } if val, _ := s.Value(); val != tc.expected { t.Errorf("Wrong Value returned, expected %q, got %q", tc.expected, val) } } } }