├── .gx └── lastpubver ├── .travis.yml ├── LICENSE ├── README.md ├── examples └── main.go ├── go.mod ├── json.go ├── json_test.go ├── package.json ├── range.go ├── range_test.go ├── semver.go ├── semver_test.go ├── sort.go ├── sort_test.go ├── sql.go ├── sql_test.go └── v4 ├── examples └── main.go ├── go.mod ├── json.go ├── json_test.go ├── range.go ├── range_test.go ├── semver.go ├── semver_test.go ├── sort.go ├── sort_test.go ├── sql.go └── sql_test.go /.gx/lastpubver: -------------------------------------------------------------------------------- 1 | 3.6.1: Qmc2TuykruVht9jiXbBXdGenbQhdsUMWCn61B8QFakH2Aw 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | matrix: 3 | include: 4 | - go: 1.4.x 5 | - go: 1.5.x 6 | - go: 1.6.x 7 | - go: 1.7.x 8 | - go: 1.8.x 9 | - go: 1.9.x 10 | - go: 1.10.x 11 | - go: 1.11.x 12 | - go: 1.12.x 13 | - go: tip 14 | allow_failures: 15 | - go: tip 16 | install: 17 | - go get golang.org/x/tools/cmd/cover 18 | - go get github.com/mattn/goveralls 19 | script: 20 | - echo "Test and track coverage" ; $HOME/gopath/bin/goveralls -package "." -service=travis-ci 21 | -repotoken=$COVERALLS_TOKEN 22 | - echo "Build examples" ; cd examples && go build 23 | - echo "Check if gofmt'd" ; diff -u <(echo -n) <(gofmt -d -s .) 24 | env: 25 | global: 26 | secure: HroGEAUQpVq9zX1b1VIkraLiywhGbzvNnTZq2TMxgK7JHP8xqNplAeF1izrR2i4QLL9nsY+9WtYss4QuPvEtZcVHUobw6XnL6radF7jS1LgfYZ9Y7oF+zogZ2I5QUMRLGA7rcxQ05s7mKq3XZQfeqaNts4bms/eZRefWuaFZbkw= 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014 Benedikt Lang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | semver 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) 2 | ====== 3 | 4 | semver is a [Semantic Versioning](http://semver.org/) library written in golang. It fully covers spec version `2.0.0`. 5 | 6 | Versioning 7 | ---------- 8 | Old v1-v3 versions exist in the root of the repository for compatiblity reasons and will only receive bug fixes. 9 | 10 | The current stable version is [*v4*](v4/) and is fully go-mod compatible. 11 | 12 | Usage 13 | ----- 14 | ```bash 15 | $ go get github.com/blang/semver/v4 16 | # Or use fixed versions 17 | $ go get github.com/blang/semver/v4@v4.0.0 18 | ``` 19 | Note: Always vendor your dependencies or fix on a specific version tag. 20 | 21 | ```go 22 | import github.com/blang/semver/v4 23 | v1, err := semver.Make("1.0.0-beta") 24 | v2, err := semver.Make("2.0.0-beta") 25 | v1.Compare(v2) 26 | ``` 27 | 28 | Also check the [GoDocs](http://godoc.org/github.com/blang/semver/v4). 29 | 30 | Why should I use this lib? 31 | ----- 32 | 33 | - Fully spec compatible 34 | - No reflection 35 | - No regex 36 | - Fully tested (Coverage >99%) 37 | - Readable parsing/validation errors 38 | - Fast (See [Benchmarks](#benchmarks)) 39 | - Only Stdlib 40 | - Uses values instead of pointers 41 | - Many features, see below 42 | 43 | 44 | Features 45 | ----- 46 | 47 | - Parsing and validation at all levels 48 | - Comparator-like comparisons 49 | - Compare Helper Methods 50 | - InPlace manipulation 51 | - Ranges `>=1.0.0 <2.0.0 || >=3.0.0 !3.0.1-beta.1` 52 | - Wildcards `>=1.x`, `<=2.5.x` 53 | - Sortable (implements sort.Interface) 54 | - database/sql compatible (sql.Scanner/Valuer) 55 | - encoding/json compatible (json.Marshaler/Unmarshaler) 56 | 57 | Ranges 58 | ------ 59 | 60 | A `Range` is a set of conditions which specify which versions satisfy the range. 61 | 62 | A condition is composed of an operator and a version. The supported operators are: 63 | 64 | - `<1.0.0` Less than `1.0.0` 65 | - `<=1.0.0` Less than or equal to `1.0.0` 66 | - `>1.0.0` Greater than `1.0.0` 67 | - `>=1.0.0` Greater than or equal to `1.0.0` 68 | - `1.0.0`, `=1.0.0`, `==1.0.0` Equal to `1.0.0` 69 | - `!1.0.0`, `!=1.0.0` Not equal to `1.0.0`. Excludes version `1.0.0`. 70 | 71 | Note that spaces between the operator and the version will be gracefully tolerated. 72 | 73 | A `Range` can link multiple `Ranges` separated by space: 74 | 75 | Ranges can be linked by logical AND: 76 | 77 | - `>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` 78 | - `>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` 79 | 80 | Ranges can also be linked by logical OR: 81 | 82 | - `<2.0.0 || >=3.0.0` would match `1.x.x` and `3.x.x` but not `2.x.x` 83 | 84 | AND has a higher precedence than OR. It's not possible to use brackets. 85 | 86 | Ranges can be combined by both AND and OR 87 | 88 | - `>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` 89 | 90 | Range usage: 91 | 92 | ``` 93 | v, err := semver.Parse("1.2.3") 94 | expectedRange, err := semver.ParseRange(">1.0.0 <2.0.0 || >=3.0.0") 95 | if expectedRange(v) { 96 | //valid 97 | } 98 | 99 | ``` 100 | 101 | Example 102 | ----- 103 | 104 | Have a look at full examples in [v4/examples/main.go](v4/examples/main.go) 105 | 106 | ```go 107 | import github.com/blang/semver/v4 108 | 109 | v, err := semver.Make("0.0.1-alpha.preview+123.github") 110 | fmt.Printf("Major: %d\n", v.Major) 111 | fmt.Printf("Minor: %d\n", v.Minor) 112 | fmt.Printf("Patch: %d\n", v.Patch) 113 | fmt.Printf("Pre: %s\n", v.Pre) 114 | fmt.Printf("Build: %s\n", v.Build) 115 | 116 | // Prerelease versions array 117 | if len(v.Pre) > 0 { 118 | fmt.Println("Prerelease versions:") 119 | for i, pre := range v.Pre { 120 | fmt.Printf("%d: %q\n", i, pre) 121 | } 122 | } 123 | 124 | // Build meta data array 125 | if len(v.Build) > 0 { 126 | fmt.Println("Build meta data:") 127 | for i, build := range v.Build { 128 | fmt.Printf("%d: %q\n", i, build) 129 | } 130 | } 131 | 132 | v001, err := semver.Make("0.0.1") 133 | // Compare using helpers: v.GT(v2), v.LT, v.GTE, v.LTE 134 | v001.GT(v) == true 135 | v.LT(v001) == true 136 | v.GTE(v) == true 137 | v.LTE(v) == true 138 | 139 | // Or use v.Compare(v2) for comparisons (-1, 0, 1): 140 | v001.Compare(v) == 1 141 | v.Compare(v001) == -1 142 | v.Compare(v) == 0 143 | 144 | // Manipulate Version in place: 145 | v.Pre[0], err = semver.NewPRVersion("beta") 146 | if err != nil { 147 | fmt.Printf("Error parsing pre release version: %q", err) 148 | } 149 | 150 | fmt.Println("\nValidate versions:") 151 | v.Build[0] = "?" 152 | 153 | err = v.Validate() 154 | if err != nil { 155 | fmt.Printf("Validation failed: %s\n", err) 156 | } 157 | ``` 158 | 159 | 160 | Benchmarks 161 | ----- 162 | 163 | BenchmarkParseSimple-4 5000000 390 ns/op 48 B/op 1 allocs/op 164 | BenchmarkParseComplex-4 1000000 1813 ns/op 256 B/op 7 allocs/op 165 | BenchmarkParseAverage-4 1000000 1171 ns/op 163 B/op 4 allocs/op 166 | BenchmarkStringSimple-4 20000000 119 ns/op 16 B/op 1 allocs/op 167 | BenchmarkStringLarger-4 10000000 206 ns/op 32 B/op 2 allocs/op 168 | BenchmarkStringComplex-4 5000000 324 ns/op 80 B/op 3 allocs/op 169 | BenchmarkStringAverage-4 5000000 273 ns/op 53 B/op 2 allocs/op 170 | BenchmarkValidateSimple-4 200000000 9.33 ns/op 0 B/op 0 allocs/op 171 | BenchmarkValidateComplex-4 3000000 469 ns/op 0 B/op 0 allocs/op 172 | BenchmarkValidateAverage-4 5000000 256 ns/op 0 B/op 0 allocs/op 173 | BenchmarkCompareSimple-4 100000000 11.8 ns/op 0 B/op 0 allocs/op 174 | BenchmarkCompareComplex-4 50000000 30.8 ns/op 0 B/op 0 allocs/op 175 | BenchmarkCompareAverage-4 30000000 41.5 ns/op 0 B/op 0 allocs/op 176 | BenchmarkSort-4 3000000 419 ns/op 256 B/op 2 allocs/op 177 | BenchmarkRangeParseSimple-4 2000000 850 ns/op 192 B/op 5 allocs/op 178 | BenchmarkRangeParseAverage-4 1000000 1677 ns/op 400 B/op 10 allocs/op 179 | BenchmarkRangeParseComplex-4 300000 5214 ns/op 1440 B/op 30 allocs/op 180 | BenchmarkRangeMatchSimple-4 50000000 25.6 ns/op 0 B/op 0 allocs/op 181 | BenchmarkRangeMatchAverage-4 30000000 56.4 ns/op 0 B/op 0 allocs/op 182 | BenchmarkRangeMatchComplex-4 10000000 153 ns/op 0 B/op 0 allocs/op 183 | 184 | See benchmark cases at [semver_test.go](semver_test.go) 185 | 186 | 187 | Motivation 188 | ----- 189 | 190 | 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. 191 | 192 | 193 | Contribution 194 | ----- 195 | 196 | Feel free to make a pull request. For bigger changes create a issue first to discuss about it. 197 | 198 | 199 | License 200 | ----- 201 | 202 | See [LICENSE](LICENSE) file. 203 | -------------------------------------------------------------------------------- /examples/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/blang/semver" 7 | ) 8 | 9 | func main() { 10 | v, err := semver.Parse("0.0.1-alpha.preview.222+123.github") 11 | if err != nil { 12 | fmt.Printf("Error while parsing (not valid): %q", err) 13 | } 14 | fmt.Printf("Version to string: %q\n", v) 15 | 16 | fmt.Printf("Major: %d\n", v.Major) 17 | fmt.Printf("Minor: %d\n", v.Minor) 18 | fmt.Printf("Patch: %d\n", v.Patch) 19 | 20 | // Prerelease versions 21 | if len(v.Pre) > 0 { 22 | fmt.Println("Prerelease versions:") 23 | for i, pre := range v.Pre { 24 | fmt.Printf("%d: %q\n", i, pre) 25 | } 26 | } 27 | 28 | // Build meta data 29 | if len(v.Build) > 0 { 30 | fmt.Println("Build meta data:") 31 | for i, build := range v.Build { 32 | fmt.Printf("%d: %q\n", i, build) 33 | } 34 | } 35 | 36 | // Make == Parse (Value), New for Pointer 37 | v001, _ := semver.Make("0.0.1") 38 | 39 | fmt.Println("\nUse Version.Compare for comparisons (-1, 0, 1):") 40 | fmt.Printf("%q is greater than %q: Compare == %d\n", v001, v, v001.Compare(v)) 41 | fmt.Printf("%q is less than %q: Compare == %d\n", v, v001, v.Compare(v001)) 42 | fmt.Printf("%q is equal to %q: Compare == %d\n", v, v, v.Compare(v)) 43 | 44 | fmt.Println("\nUse comparison helpers returning booleans:") 45 | fmt.Printf("%q is greater than %q: %t\n", v001, v, v001.GT(v)) 46 | fmt.Printf("%q is greater than equal %q: %t\n", v001, v, v001.GTE(v)) 47 | fmt.Printf("%q is greater than equal %q: %t\n", v, v, v.GTE(v)) 48 | fmt.Printf("%q is less than %q: %t\n", v, v001, v.LT(v001)) 49 | fmt.Printf("%q is less than equal %q: %t\n", v, v001, v.LTE(v001)) 50 | fmt.Printf("%q is less than equal %q: %t\n", v, v, v.LTE(v)) 51 | 52 | fmt.Println("\nManipulate Version in place:") 53 | v.Pre[0], err = semver.NewPRVersion("beta") 54 | if err != nil { 55 | fmt.Printf("Error parsing pre release version: %q", err) 56 | } 57 | fmt.Printf("Version to string: %q\n", v) 58 | 59 | fmt.Println("\nCompare Prerelease versions:") 60 | pre1, _ := semver.NewPRVersion("123") 61 | pre2, _ := semver.NewPRVersion("alpha") 62 | pre3, _ := semver.NewPRVersion("124") 63 | fmt.Printf("%q is less than %q: Compare == %d\n", pre1, pre2, pre1.Compare(pre2)) 64 | fmt.Printf("%q is greater than %q: Compare == %d\n", pre3, pre1, pre3.Compare(pre1)) 65 | fmt.Printf("%q is equal to %q: Compare == %d\n", pre1, pre1, pre1.Compare(pre1)) 66 | 67 | fmt.Println("\nValidate versions:") 68 | v.Build[0] = "?" 69 | 70 | err = v.Validate() 71 | if err != nil { 72 | fmt.Printf("Validation failed: %s\n", err) 73 | } 74 | 75 | fmt.Println("Create valid build meta data:") 76 | b1, _ := semver.NewBuildVersion("build123") 77 | v.Build[0] = b1 78 | fmt.Printf("Version with new build version %q\n", v) 79 | 80 | _, err = semver.NewBuildVersion("build?123") 81 | if err != nil { 82 | fmt.Printf("Create build version failed: %s\n", err) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/blang/semver 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /json.go: -------------------------------------------------------------------------------- 1 | package semver 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // MarshalJSON implements the encoding/json.Marshaler interface. 8 | func (v Version) MarshalJSON() ([]byte, error) { 9 | return json.Marshal(v.String()) 10 | } 11 | 12 | // UnmarshalJSON implements the encoding/json.Unmarshaler interface. 13 | func (v *Version) UnmarshalJSON(data []byte) (err error) { 14 | var versionString string 15 | 16 | if err = json.Unmarshal(data, &versionString); err != nil { 17 | return 18 | } 19 | 20 | *v, err = Parse(versionString) 21 | 22 | return 23 | } 24 | -------------------------------------------------------------------------------- /json_test.go: -------------------------------------------------------------------------------- 1 | package semver 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestJSONMarshal(t *testing.T) { 10 | versionString := "3.1.4-alpha.1.5.9+build.2.6.5" 11 | v, err := Parse(versionString) 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | 16 | versionJSON, err := json.Marshal(v) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | quotedVersionString := strconv.Quote(versionString) 22 | 23 | if string(versionJSON) != quotedVersionString { 24 | t.Fatalf("JSON marshaled semantic version not equal: expected %q, got %q", quotedVersionString, string(versionJSON)) 25 | } 26 | } 27 | 28 | func TestJSONUnmarshal(t *testing.T) { 29 | versionString := "3.1.4-alpha.1.5.9+build.2.6.5" 30 | quotedVersionString := strconv.Quote(versionString) 31 | 32 | var v Version 33 | if err := json.Unmarshal([]byte(quotedVersionString), &v); err != nil { 34 | t.Fatal(err) 35 | } 36 | 37 | if v.String() != versionString { 38 | t.Fatalf("JSON unmarshaled semantic version not equal: expected %q, got %q", versionString, v.String()) 39 | } 40 | 41 | badVersionString := strconv.Quote("3.1.4.1.5.9.2.6.5-other-digits-of-pi") 42 | if err := json.Unmarshal([]byte(badVersionString), &v); err == nil { 43 | t.Fatal("expected JSON unmarshal error, got nil") 44 | } 45 | 46 | if err := json.Unmarshal([]byte("3.1"), &v); err == nil { 47 | t.Fatal("expected JSON unmarshal error, got nil") 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "blang", 3 | "bugs": { 4 | "URL": "https://github.com/blang/semver/issues", 5 | "url": "https://github.com/blang/semver/issues" 6 | }, 7 | "gx": { 8 | "dvcsimport": "github.com/blang/semver" 9 | }, 10 | "gxVersion": "0.10.0", 11 | "language": "go", 12 | "license": "MIT", 13 | "name": "semver", 14 | "releaseCmd": "git commit -a -m \"gx publish $VERSION\"", 15 | "version": "3.6.1" 16 | } 17 | 18 | -------------------------------------------------------------------------------- /range.go: -------------------------------------------------------------------------------- 1 | package semver 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | "unicode" 8 | ) 9 | 10 | type wildcardType int 11 | 12 | const ( 13 | noneWildcard wildcardType = iota 14 | majorWildcard wildcardType = 1 15 | minorWildcard wildcardType = 2 16 | patchWildcard wildcardType = 3 17 | ) 18 | 19 | func wildcardTypefromInt(i int) wildcardType { 20 | switch i { 21 | case 1: 22 | return majorWildcard 23 | case 2: 24 | return minorWildcard 25 | case 3: 26 | return patchWildcard 27 | default: 28 | return noneWildcard 29 | } 30 | } 31 | 32 | type comparator func(Version, Version) bool 33 | 34 | var ( 35 | compEQ comparator = func(v1 Version, v2 Version) bool { 36 | return v1.Compare(v2) == 0 37 | } 38 | compNE = func(v1 Version, v2 Version) bool { 39 | return v1.Compare(v2) != 0 40 | } 41 | compGT = func(v1 Version, v2 Version) bool { 42 | return v1.Compare(v2) == 1 43 | } 44 | compGE = func(v1 Version, v2 Version) bool { 45 | return v1.Compare(v2) >= 0 46 | } 47 | compLT = func(v1 Version, v2 Version) bool { 48 | return v1.Compare(v2) == -1 49 | } 50 | compLE = func(v1 Version, v2 Version) bool { 51 | return v1.Compare(v2) <= 0 52 | } 53 | ) 54 | 55 | type versionRange struct { 56 | v Version 57 | c comparator 58 | } 59 | 60 | // rangeFunc creates a Range from the given versionRange. 61 | func (vr *versionRange) rangeFunc() Range { 62 | return Range(func(v Version) bool { 63 | return vr.c(v, vr.v) 64 | }) 65 | } 66 | 67 | // Range represents a range of versions. 68 | // A Range can be used to check if a Version satisfies it: 69 | // 70 | // range, err := semver.ParseRange(">1.0.0 <2.0.0") 71 | // range(semver.MustParse("1.1.1") // returns true 72 | type Range func(Version) bool 73 | 74 | // OR combines the existing Range with another Range using logical OR. 75 | func (rf Range) OR(f Range) Range { 76 | return Range(func(v Version) bool { 77 | return rf(v) || f(v) 78 | }) 79 | } 80 | 81 | // AND combines the existing Range with another Range using logical AND. 82 | func (rf Range) AND(f Range) Range { 83 | return Range(func(v Version) bool { 84 | return rf(v) && f(v) 85 | }) 86 | } 87 | 88 | // ParseRange parses a range and returns a Range. 89 | // If the range could not be parsed an error is returned. 90 | // 91 | // Valid ranges are: 92 | // - "<1.0.0" 93 | // - "<=1.0.0" 94 | // - ">1.0.0" 95 | // - ">=1.0.0" 96 | // - "1.0.0", "=1.0.0", "==1.0.0" 97 | // - "!1.0.0", "!=1.0.0" 98 | // 99 | // A Range can consist of multiple ranges separated by space: 100 | // Ranges can be linked by logical AND: 101 | // - ">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" 102 | // - ">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 103 | // 104 | // Ranges can also be linked by logical OR: 105 | // - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x" 106 | // 107 | // AND has a higher precedence than OR. It's not possible to use brackets. 108 | // 109 | // Ranges can be combined by both AND and OR 110 | // 111 | // - `>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` 112 | func ParseRange(s string) (Range, error) { 113 | parts := splitAndTrim(s) 114 | orParts, err := splitORParts(parts) 115 | if err != nil { 116 | return nil, err 117 | } 118 | expandedParts, err := expandWildcardVersion(orParts) 119 | if err != nil { 120 | return nil, err 121 | } 122 | var orFn Range 123 | for _, p := range expandedParts { 124 | var andFn Range 125 | for _, ap := range p { 126 | opStr, vStr, err := splitComparatorVersion(ap) 127 | if err != nil { 128 | return nil, err 129 | } 130 | vr, err := buildVersionRange(opStr, vStr) 131 | if err != nil { 132 | return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err) 133 | } 134 | rf := vr.rangeFunc() 135 | 136 | // Set function 137 | if andFn == nil { 138 | andFn = rf 139 | } else { // Combine with existing function 140 | andFn = andFn.AND(rf) 141 | } 142 | } 143 | if orFn == nil { 144 | orFn = andFn 145 | } else { 146 | orFn = orFn.OR(andFn) 147 | } 148 | 149 | } 150 | return orFn, nil 151 | } 152 | 153 | // splitORParts splits the already cleaned parts by '||'. 154 | // Checks for invalid positions of the operator and returns an 155 | // error if found. 156 | func splitORParts(parts []string) ([][]string, error) { 157 | var ORparts [][]string 158 | last := 0 159 | for i, p := range parts { 160 | if p == "||" { 161 | if i == 0 { 162 | return nil, fmt.Errorf("First element in range is '||'") 163 | } 164 | ORparts = append(ORparts, parts[last:i]) 165 | last = i + 1 166 | } 167 | } 168 | if last == len(parts) { 169 | return nil, fmt.Errorf("Last element in range is '||'") 170 | } 171 | ORparts = append(ORparts, parts[last:]) 172 | return ORparts, nil 173 | } 174 | 175 | // buildVersionRange takes a slice of 2: operator and version 176 | // and builds a versionRange, otherwise an error. 177 | func buildVersionRange(opStr, vStr string) (*versionRange, error) { 178 | c := parseComparator(opStr) 179 | if c == nil { 180 | return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, "")) 181 | } 182 | v, err := Parse(vStr) 183 | if err != nil { 184 | return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err) 185 | } 186 | 187 | return &versionRange{ 188 | v: v, 189 | c: c, 190 | }, nil 191 | 192 | } 193 | 194 | // inArray checks if a byte is contained in an array of bytes 195 | func inArray(s byte, list []byte) bool { 196 | for _, el := range list { 197 | if el == s { 198 | return true 199 | } 200 | } 201 | return false 202 | } 203 | 204 | // splitAndTrim splits a range string by spaces and cleans whitespaces 205 | func splitAndTrim(s string) (result []string) { 206 | last := 0 207 | var lastChar byte 208 | excludeFromSplit := []byte{'>', '<', '='} 209 | for i := 0; i < len(s); i++ { 210 | if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) { 211 | if last < i-1 { 212 | result = append(result, s[last:i]) 213 | } 214 | last = i + 1 215 | } else if s[i] != ' ' { 216 | lastChar = s[i] 217 | } 218 | } 219 | if last < len(s)-1 { 220 | result = append(result, s[last:]) 221 | } 222 | 223 | for i, v := range result { 224 | result[i] = strings.Replace(v, " ", "", -1) 225 | } 226 | 227 | // parts := strings.Split(s, " ") 228 | // for _, x := range parts { 229 | // if s := strings.TrimSpace(x); len(s) != 0 { 230 | // result = append(result, s) 231 | // } 232 | // } 233 | return 234 | } 235 | 236 | // splitComparatorVersion splits the comparator from the version. 237 | // Input must be free of leading or trailing spaces. 238 | func splitComparatorVersion(s string) (string, string, error) { 239 | i := strings.IndexFunc(s, unicode.IsDigit) 240 | if i == -1 { 241 | return "", "", fmt.Errorf("Could not get version from string: %q", s) 242 | } 243 | return strings.TrimSpace(s[0:i]), s[i:], nil 244 | } 245 | 246 | // getWildcardType will return the type of wildcard that the 247 | // passed version contains 248 | func getWildcardType(vStr string) wildcardType { 249 | parts := strings.Split(vStr, ".") 250 | nparts := len(parts) 251 | wildcard := parts[nparts-1] 252 | 253 | possibleWildcardType := wildcardTypefromInt(nparts) 254 | if wildcard == "x" { 255 | return possibleWildcardType 256 | } 257 | 258 | return noneWildcard 259 | } 260 | 261 | // createVersionFromWildcard will convert a wildcard version 262 | // into a regular version, replacing 'x's with '0's, handling 263 | // special cases like '1.x.x' and '1.x' 264 | func createVersionFromWildcard(vStr string) string { 265 | // handle 1.x.x 266 | vStr2 := strings.Replace(vStr, ".x.x", ".x", 1) 267 | vStr2 = strings.Replace(vStr2, ".x", ".0", 1) 268 | parts := strings.Split(vStr2, ".") 269 | 270 | // handle 1.x 271 | if len(parts) == 2 { 272 | return vStr2 + ".0" 273 | } 274 | 275 | return vStr2 276 | } 277 | 278 | // incrementMajorVersion will increment the major version 279 | // of the passed version 280 | func incrementMajorVersion(vStr string) (string, error) { 281 | parts := strings.Split(vStr, ".") 282 | i, err := strconv.Atoi(parts[0]) 283 | if err != nil { 284 | return "", err 285 | } 286 | parts[0] = strconv.Itoa(i + 1) 287 | 288 | return strings.Join(parts, "."), nil 289 | } 290 | 291 | // incrementMajorVersion will increment the minor version 292 | // of the passed version 293 | func incrementMinorVersion(vStr string) (string, error) { 294 | parts := strings.Split(vStr, ".") 295 | i, err := strconv.Atoi(parts[1]) 296 | if err != nil { 297 | return "", err 298 | } 299 | parts[1] = strconv.Itoa(i + 1) 300 | 301 | return strings.Join(parts, "."), nil 302 | } 303 | 304 | // expandWildcardVersion will expand wildcards inside versions 305 | // following these rules: 306 | // 307 | // * when dealing with patch wildcards: 308 | // >= 1.2.x will become >= 1.2.0 309 | // <= 1.2.x will become < 1.3.0 310 | // > 1.2.x will become >= 1.3.0 311 | // < 1.2.x will become < 1.2.0 312 | // != 1.2.x will become < 1.2.0 >= 1.3.0 313 | // 314 | // * when dealing with minor wildcards: 315 | // >= 1.x will become >= 1.0.0 316 | // <= 1.x will become < 2.0.0 317 | // > 1.x will become >= 2.0.0 318 | // < 1.0 will become < 1.0.0 319 | // != 1.x will become < 1.0.0 >= 2.0.0 320 | // 321 | // * when dealing with wildcards without 322 | // version operator: 323 | // 1.2.x will become >= 1.2.0 < 1.3.0 324 | // 1.x will become >= 1.0.0 < 2.0.0 325 | func expandWildcardVersion(parts [][]string) ([][]string, error) { 326 | var expandedParts [][]string 327 | for _, p := range parts { 328 | var newParts []string 329 | for _, ap := range p { 330 | if strings.Contains(ap, "x") { 331 | opStr, vStr, err := splitComparatorVersion(ap) 332 | if err != nil { 333 | return nil, err 334 | } 335 | 336 | versionWildcardType := getWildcardType(vStr) 337 | flatVersion := createVersionFromWildcard(vStr) 338 | 339 | var resultOperator string 340 | var shouldIncrementVersion bool 341 | switch opStr { 342 | case ">": 343 | resultOperator = ">=" 344 | shouldIncrementVersion = true 345 | case ">=": 346 | resultOperator = ">=" 347 | case "<": 348 | resultOperator = "<" 349 | case "<=": 350 | resultOperator = "<" 351 | shouldIncrementVersion = true 352 | case "", "=", "==": 353 | newParts = append(newParts, ">="+flatVersion) 354 | resultOperator = "<" 355 | shouldIncrementVersion = true 356 | case "!=", "!": 357 | newParts = append(newParts, "<"+flatVersion) 358 | resultOperator = ">=" 359 | shouldIncrementVersion = true 360 | } 361 | 362 | var resultVersion string 363 | if shouldIncrementVersion { 364 | switch versionWildcardType { 365 | case patchWildcard: 366 | resultVersion, _ = incrementMinorVersion(flatVersion) 367 | case minorWildcard: 368 | resultVersion, _ = incrementMajorVersion(flatVersion) 369 | } 370 | } else { 371 | resultVersion = flatVersion 372 | } 373 | 374 | ap = resultOperator + resultVersion 375 | } 376 | newParts = append(newParts, ap) 377 | } 378 | expandedParts = append(expandedParts, newParts) 379 | } 380 | 381 | return expandedParts, nil 382 | } 383 | 384 | func parseComparator(s string) comparator { 385 | switch s { 386 | case "==": 387 | fallthrough 388 | case "": 389 | fallthrough 390 | case "=": 391 | return compEQ 392 | case ">": 393 | return compGT 394 | case ">=": 395 | return compGE 396 | case "<": 397 | return compLT 398 | case "<=": 399 | return compLE 400 | case "!": 401 | fallthrough 402 | case "!=": 403 | return compNE 404 | } 405 | 406 | return nil 407 | } 408 | 409 | // MustParseRange is like ParseRange but panics if the range cannot be parsed. 410 | func MustParseRange(s string) Range { 411 | r, err := ParseRange(s) 412 | if err != nil { 413 | panic(`semver: ParseRange(` + s + `): ` + err.Error()) 414 | } 415 | return r 416 | } 417 | -------------------------------------------------------------------------------- /range_test.go: -------------------------------------------------------------------------------- 1 | package semver 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | type wildcardTypeTest struct { 10 | input string 11 | wildcardType wildcardType 12 | } 13 | 14 | type comparatorTest struct { 15 | input string 16 | comparator func(comparator) bool 17 | } 18 | 19 | func TestParseComparator(t *testing.T) { 20 | compatorTests := []comparatorTest{ 21 | {">", testGT}, 22 | {">=", testGE}, 23 | {"<", testLT}, 24 | {"<=", testLE}, 25 | {"", testEQ}, 26 | {"=", testEQ}, 27 | {"==", testEQ}, 28 | {"!=", testNE}, 29 | {"!", testNE}, 30 | {"-", nil}, 31 | {"<==", nil}, 32 | {"<<", nil}, 33 | {">>", nil}, 34 | } 35 | 36 | for _, tc := range compatorTests { 37 | if c := parseComparator(tc.input); c == nil { 38 | if tc.comparator != nil { 39 | t.Errorf("Comparator nil for case %q\n", tc.input) 40 | } 41 | } else if !tc.comparator(c) { 42 | t.Errorf("Invalid comparator for case %q\n", tc.input) 43 | } 44 | } 45 | } 46 | 47 | var ( 48 | v1 = MustParse("1.2.2") 49 | v2 = MustParse("1.2.3") 50 | v3 = MustParse("1.2.4") 51 | ) 52 | 53 | func testEQ(f comparator) bool { 54 | return f(v1, v1) && !f(v1, v2) 55 | } 56 | 57 | func testNE(f comparator) bool { 58 | return !f(v1, v1) && f(v1, v2) 59 | } 60 | 61 | func testGT(f comparator) bool { 62 | return f(v2, v1) && f(v3, v2) && !f(v1, v2) && !f(v1, v1) 63 | } 64 | 65 | func testGE(f comparator) bool { 66 | return f(v2, v1) && f(v3, v2) && !f(v1, v2) 67 | } 68 | 69 | func testLT(f comparator) bool { 70 | return f(v1, v2) && f(v2, v3) && !f(v2, v1) && !f(v1, v1) 71 | } 72 | 73 | func testLE(f comparator) bool { 74 | return f(v1, v2) && f(v2, v3) && !f(v2, v1) 75 | } 76 | 77 | func TestSplitAndTrim(t *testing.T) { 78 | tests := []struct { 79 | i string 80 | s []string 81 | }{ 82 | {"1.2.3 1.2.3", []string{"1.2.3", "1.2.3"}}, 83 | {" 1.2.3 1.2.3 ", []string{"1.2.3", "1.2.3"}}, // Spaces 84 | {" >= 1.2.3 <= 1.2.3 ", []string{">=1.2.3", "<=1.2.3"}}, // Spaces between operator and version 85 | {"1.2.3 || >=1.2.3 <1.2.3", []string{"1.2.3", "||", ">=1.2.3", "<1.2.3"}}, 86 | {" 1.2.3 || >=1.2.3 <1.2.3 ", []string{"1.2.3", "||", ">=1.2.3", "<1.2.3"}}, 87 | } 88 | 89 | for _, tc := range tests { 90 | p := splitAndTrim(tc.i) 91 | if !reflect.DeepEqual(p, tc.s) { 92 | t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p) 93 | } 94 | } 95 | } 96 | 97 | func TestSplitComparatorVersion(t *testing.T) { 98 | tests := []struct { 99 | i string 100 | p []string 101 | }{ 102 | {">1.2.3", []string{">", "1.2.3"}}, 103 | {">=1.2.3", []string{">=", "1.2.3"}}, 104 | {"<1.2.3", []string{"<", "1.2.3"}}, 105 | {"<=1.2.3", []string{"<=", "1.2.3"}}, 106 | {"1.2.3", []string{"", "1.2.3"}}, 107 | {"=1.2.3", []string{"=", "1.2.3"}}, 108 | {"==1.2.3", []string{"==", "1.2.3"}}, 109 | {"!=1.2.3", []string{"!=", "1.2.3"}}, 110 | {"!1.2.3", []string{"!", "1.2.3"}}, 111 | {"error", nil}, 112 | } 113 | for _, tc := range tests { 114 | if op, v, err := splitComparatorVersion(tc.i); err != nil { 115 | if tc.p != nil { 116 | t.Errorf("Invalid for case %q: Expected %q, got error %q", tc.i, tc.p, err) 117 | } 118 | } else if op != tc.p[0] { 119 | t.Errorf("Invalid operator for case %q: Expected %q, got: %q", tc.i, tc.p[0], op) 120 | } else if v != tc.p[1] { 121 | t.Errorf("Invalid version for case %q: Expected %q, got: %q", tc.i, tc.p[1], v) 122 | } 123 | 124 | } 125 | } 126 | 127 | func TestBuildVersionRange(t *testing.T) { 128 | tests := []struct { 129 | opStr string 130 | vStr string 131 | c func(comparator) bool 132 | v string 133 | }{ 134 | {">", "1.2.3", testGT, "1.2.3"}, 135 | {">=", "1.2.3", testGE, "1.2.3"}, 136 | {"<", "1.2.3", testLT, "1.2.3"}, 137 | {"<=", "1.2.3", testLE, "1.2.3"}, 138 | {"", "1.2.3", testEQ, "1.2.3"}, 139 | {"=", "1.2.3", testEQ, "1.2.3"}, 140 | {"==", "1.2.3", testEQ, "1.2.3"}, 141 | {"!=", "1.2.3", testNE, "1.2.3"}, 142 | {"!", "1.2.3", testNE, "1.2.3"}, 143 | {">>", "1.2.3", nil, ""}, // Invalid comparator 144 | {"=", "invalid", nil, ""}, // Invalid version 145 | } 146 | 147 | for _, tc := range tests { 148 | if r, err := buildVersionRange(tc.opStr, tc.vStr); err != nil { 149 | if tc.c != nil { 150 | t.Errorf("Invalid for case %q: Expected %q, got error %q", strings.Join([]string{tc.opStr, tc.vStr}, ""), tc.v, err) 151 | } 152 | } else if r == nil { 153 | t.Errorf("Invalid for case %q: got nil", strings.Join([]string{tc.opStr, tc.vStr}, "")) 154 | } else { 155 | // test version 156 | if tv := MustParse(tc.v); !r.v.EQ(tv) { 157 | t.Errorf("Invalid for case %q: Expected version %q, got: %q", strings.Join([]string{tc.opStr, tc.vStr}, ""), tv, r.v) 158 | } 159 | // test comparator 160 | if r.c == nil { 161 | t.Errorf("Invalid for case %q: got nil comparator", strings.Join([]string{tc.opStr, tc.vStr}, "")) 162 | continue 163 | } 164 | if !tc.c(r.c) { 165 | t.Errorf("Invalid comparator for case %q\n", strings.Join([]string{tc.opStr, tc.vStr}, "")) 166 | } 167 | } 168 | } 169 | 170 | } 171 | 172 | func TestSplitORParts(t *testing.T) { 173 | tests := []struct { 174 | i []string 175 | o [][]string 176 | }{ 177 | {[]string{">1.2.3", "||", "<1.2.3", "||", "=1.2.3"}, [][]string{ 178 | {">1.2.3"}, 179 | {"<1.2.3"}, 180 | {"=1.2.3"}, 181 | }}, 182 | {[]string{">1.2.3", "<1.2.3", "||", "=1.2.3"}, [][]string{ 183 | {">1.2.3", "<1.2.3"}, 184 | {"=1.2.3"}, 185 | }}, 186 | {[]string{">1.2.3", "||"}, nil}, 187 | {[]string{"||", ">1.2.3"}, nil}, 188 | } 189 | for _, tc := range tests { 190 | o, err := splitORParts(tc.i) 191 | if err != nil && tc.o != nil { 192 | t.Errorf("Unexpected error for case %q: %s", tc.i, err) 193 | } 194 | if !reflect.DeepEqual(tc.o, o) { 195 | t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.o, o) 196 | } 197 | } 198 | } 199 | 200 | func TestGetWildcardType(t *testing.T) { 201 | wildcardTypeTests := []wildcardTypeTest{ 202 | {"x", majorWildcard}, 203 | {"1.x", minorWildcard}, 204 | {"1.2.x", patchWildcard}, 205 | {"fo.o.b.ar", noneWildcard}, 206 | } 207 | 208 | for _, tc := range wildcardTypeTests { 209 | o := getWildcardType(tc.input) 210 | if o != tc.wildcardType { 211 | t.Errorf("Invalid for case: %q: Expected %q, got: %q", tc.input, tc.wildcardType, o) 212 | } 213 | } 214 | } 215 | 216 | func TestCreateVersionFromWildcard(t *testing.T) { 217 | tests := []struct { 218 | i string 219 | s string 220 | }{ 221 | {"1.2.x", "1.2.0"}, 222 | {"1.x", "1.0.0"}, 223 | } 224 | 225 | for _, tc := range tests { 226 | p := createVersionFromWildcard(tc.i) 227 | if p != tc.s { 228 | t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p) 229 | } 230 | } 231 | } 232 | 233 | func TestIncrementMajorVersion(t *testing.T) { 234 | tests := []struct { 235 | i string 236 | s string 237 | }{ 238 | {"1.2.3", "2.2.3"}, 239 | {"1.2", "2.2"}, 240 | {"foo.bar", ""}, 241 | } 242 | 243 | for _, tc := range tests { 244 | p, _ := incrementMajorVersion(tc.i) 245 | if p != tc.s { 246 | t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p) 247 | } 248 | } 249 | } 250 | 251 | func TestIncrementMinorVersion(t *testing.T) { 252 | tests := []struct { 253 | i string 254 | s string 255 | }{ 256 | {"1.2.3", "1.3.3"}, 257 | {"1.2", "1.3"}, 258 | {"foo.bar", ""}, 259 | } 260 | 261 | for _, tc := range tests { 262 | p, _ := incrementMinorVersion(tc.i) 263 | if p != tc.s { 264 | t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p) 265 | } 266 | } 267 | } 268 | 269 | func TestExpandWildcardVersion(t *testing.T) { 270 | tests := []struct { 271 | i [][]string 272 | o [][]string 273 | }{ 274 | {[][]string{{"foox"}}, nil}, 275 | {[][]string{{">=1.2.x"}}, [][]string{{">=1.2.0"}}}, 276 | {[][]string{{"<=1.2.x"}}, [][]string{{"<1.3.0"}}}, 277 | {[][]string{{">1.2.x"}}, [][]string{{">=1.3.0"}}}, 278 | {[][]string{{"<1.2.x"}}, [][]string{{"<1.2.0"}}}, 279 | {[][]string{{"!=1.2.x"}}, [][]string{{"<1.2.0", ">=1.3.0"}}}, 280 | {[][]string{{">=1.x"}}, [][]string{{">=1.0.0"}}}, 281 | {[][]string{{"<=1.x"}}, [][]string{{"<2.0.0"}}}, 282 | {[][]string{{">1.x"}}, [][]string{{">=2.0.0"}}}, 283 | {[][]string{{"<1.x"}}, [][]string{{"<1.0.0"}}}, 284 | {[][]string{{"!=1.x"}}, [][]string{{"<1.0.0", ">=2.0.0"}}}, 285 | {[][]string{{"1.2.x"}}, [][]string{{">=1.2.0", "<1.3.0"}}}, 286 | {[][]string{{"1.x"}}, [][]string{{">=1.0.0", "<2.0.0"}}}, 287 | } 288 | 289 | for _, tc := range tests { 290 | o, _ := expandWildcardVersion(tc.i) 291 | if !reflect.DeepEqual(tc.o, o) { 292 | t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.o, o) 293 | } 294 | } 295 | } 296 | 297 | func TestVersionRangeToRange(t *testing.T) { 298 | vr := versionRange{ 299 | v: MustParse("1.2.3"), 300 | c: compLT, 301 | } 302 | rf := vr.rangeFunc() 303 | if !rf(MustParse("1.2.2")) || rf(MustParse("1.2.3")) { 304 | t.Errorf("Invalid conversion to range func") 305 | } 306 | } 307 | 308 | func TestRangeAND(t *testing.T) { 309 | v := MustParse("1.2.2") 310 | v1 := MustParse("1.2.1") 311 | v2 := MustParse("1.2.3") 312 | rf1 := Range(func(v Version) bool { 313 | return v.GT(v1) 314 | }) 315 | rf2 := Range(func(v Version) bool { 316 | return v.LT(v2) 317 | }) 318 | rf := rf1.AND(rf2) 319 | if rf(v1) { 320 | t.Errorf("Invalid rangefunc, accepted: %s", v1) 321 | } 322 | if rf(v2) { 323 | t.Errorf("Invalid rangefunc, accepted: %s", v2) 324 | } 325 | if !rf(v) { 326 | t.Errorf("Invalid rangefunc, did not accept: %s", v) 327 | } 328 | } 329 | 330 | func TestRangeOR(t *testing.T) { 331 | tests := []struct { 332 | v Version 333 | b bool 334 | }{ 335 | {MustParse("1.2.0"), true}, 336 | {MustParse("1.2.2"), false}, 337 | {MustParse("1.2.4"), true}, 338 | } 339 | v1 := MustParse("1.2.1") 340 | v2 := MustParse("1.2.3") 341 | rf1 := Range(func(v Version) bool { 342 | return v.LT(v1) 343 | }) 344 | rf2 := Range(func(v Version) bool { 345 | return v.GT(v2) 346 | }) 347 | rf := rf1.OR(rf2) 348 | for _, tc := range tests { 349 | if r := rf(tc.v); r != tc.b { 350 | t.Errorf("Invalid for case %q: Expected %t, got %t", tc.v, tc.b, r) 351 | } 352 | } 353 | } 354 | 355 | func TestParseRange(t *testing.T) { 356 | type tv struct { 357 | v string 358 | b bool 359 | } 360 | tests := []struct { 361 | i string 362 | t []tv 363 | }{ 364 | // Simple expressions 365 | {">1.2.3", []tv{ 366 | {"1.2.2", false}, 367 | {"1.2.3", false}, 368 | {"1.2.4", true}, 369 | }}, 370 | {">=1.2.3", []tv{ 371 | {"1.2.3", true}, 372 | {"1.2.4", true}, 373 | {"1.2.2", false}, 374 | }}, 375 | {"<1.2.3", []tv{ 376 | {"1.2.2", true}, 377 | {"1.2.3", false}, 378 | {"1.2.4", false}, 379 | }}, 380 | {"<=1.2.3", []tv{ 381 | {"1.2.2", true}, 382 | {"1.2.3", true}, 383 | {"1.2.4", false}, 384 | }}, 385 | {"1.2.3", []tv{ 386 | {"1.2.2", false}, 387 | {"1.2.3", true}, 388 | {"1.2.4", false}, 389 | }}, 390 | {"=1.2.3", []tv{ 391 | {"1.2.2", false}, 392 | {"1.2.3", true}, 393 | {"1.2.4", false}, 394 | }}, 395 | {"==1.2.3", []tv{ 396 | {"1.2.2", false}, 397 | {"1.2.3", true}, 398 | {"1.2.4", false}, 399 | }}, 400 | {"!=1.2.3", []tv{ 401 | {"1.2.2", true}, 402 | {"1.2.3", false}, 403 | {"1.2.4", true}, 404 | }}, 405 | {"!1.2.3", []tv{ 406 | {"1.2.2", true}, 407 | {"1.2.3", false}, 408 | {"1.2.4", true}, 409 | }}, 410 | // Simple Expression errors 411 | {">>1.2.3", nil}, 412 | {"!1.2.3", nil}, 413 | {"1.0", nil}, 414 | {"string", nil}, 415 | {"", nil}, 416 | {"fo.ob.ar.x", nil}, 417 | // AND Expressions 418 | {">1.2.2 <1.2.4", []tv{ 419 | {"1.2.2", false}, 420 | {"1.2.3", true}, 421 | {"1.2.4", false}, 422 | }}, 423 | {"<1.2.2 <1.2.4", []tv{ 424 | {"1.2.1", true}, 425 | {"1.2.2", false}, 426 | {"1.2.3", false}, 427 | {"1.2.4", false}, 428 | }}, 429 | {">1.2.2 <1.2.5 !=1.2.4", []tv{ 430 | {"1.2.2", false}, 431 | {"1.2.3", true}, 432 | {"1.2.4", false}, 433 | {"1.2.5", false}, 434 | }}, 435 | {">1.2.2 <1.2.5 !1.2.4", []tv{ 436 | {"1.2.2", false}, 437 | {"1.2.3", true}, 438 | {"1.2.4", false}, 439 | {"1.2.5", false}, 440 | }}, 441 | // OR Expressions 442 | {">1.2.2 || <1.2.4", []tv{ 443 | {"1.2.2", true}, 444 | {"1.2.3", true}, 445 | {"1.2.4", true}, 446 | }}, 447 | {"<1.2.2 || >1.2.4", []tv{ 448 | {"1.2.2", false}, 449 | {"1.2.3", false}, 450 | {"1.2.4", false}, 451 | }}, 452 | // Wildcard expressions 453 | {">1.x", []tv{ 454 | {"0.1.9", false}, 455 | {"1.2.6", false}, 456 | {"1.9.0", false}, 457 | {"2.0.0", true}, 458 | }}, 459 | {">1.2.x", []tv{ 460 | {"1.1.9", false}, 461 | {"1.2.6", false}, 462 | {"1.3.0", true}, 463 | }}, 464 | // Combined Expressions 465 | {">1.2.2 <1.2.4 || >=2.0.0", []tv{ 466 | {"1.2.2", false}, 467 | {"1.2.3", true}, 468 | {"1.2.4", false}, 469 | {"2.0.0", true}, 470 | {"2.0.1", true}, 471 | }}, 472 | {"1.x || >=2.0.x <2.2.x", []tv{ 473 | {"0.9.2", false}, 474 | {"1.2.2", true}, 475 | {"2.0.0", true}, 476 | {"2.1.8", true}, 477 | {"2.2.0", false}, 478 | }}, 479 | {">1.2.2 <1.2.4 || >=2.0.0 <3.0.0", []tv{ 480 | {"1.2.2", false}, 481 | {"1.2.3", true}, 482 | {"1.2.4", false}, 483 | {"2.0.0", true}, 484 | {"2.0.1", true}, 485 | {"2.9.9", true}, 486 | {"3.0.0", false}, 487 | }}, 488 | } 489 | 490 | for _, tc := range tests { 491 | r, err := ParseRange(tc.i) 492 | if err != nil && tc.t != nil { 493 | t.Errorf("Error parsing range %q: %s", tc.i, err) 494 | continue 495 | } 496 | for _, tvc := range tc.t { 497 | v := MustParse(tvc.v) 498 | if res := r(v); res != tvc.b { 499 | t.Errorf("Invalid for case %q matching %q: Expected %t, got: %t", tc.i, tvc.v, tvc.b, res) 500 | } 501 | } 502 | 503 | } 504 | } 505 | 506 | func TestMustParseRange(t *testing.T) { 507 | testCase := ">1.2.2 <1.2.4 || >=2.0.0 <3.0.0" 508 | r := MustParseRange(testCase) 509 | if !r(MustParse("1.2.3")) { 510 | t.Errorf("Unexpected range behavior on MustParseRange") 511 | } 512 | } 513 | 514 | func TestMustParseRange_panic(t *testing.T) { 515 | defer func() { 516 | if recover() == nil { 517 | t.Errorf("Should have panicked") 518 | } 519 | }() 520 | _ = MustParseRange("invalid version") 521 | } 522 | 523 | func BenchmarkRangeParseSimple(b *testing.B) { 524 | const VERSION = ">1.0.0" 525 | b.ReportAllocs() 526 | b.ResetTimer() 527 | for n := 0; n < b.N; n++ { 528 | _, _ = ParseRange(VERSION) 529 | } 530 | } 531 | 532 | func BenchmarkRangeParseAverage(b *testing.B) { 533 | const VERSION = ">=1.0.0 <2.0.0" 534 | b.ReportAllocs() 535 | b.ResetTimer() 536 | for n := 0; n < b.N; n++ { 537 | _, _ = ParseRange(VERSION) 538 | } 539 | } 540 | 541 | func BenchmarkRangeParseComplex(b *testing.B) { 542 | const VERSION = ">=1.0.0 <2.0.0 || >=3.0.1 <4.0.0 !=3.0.3 || >=5.0.0" 543 | b.ReportAllocs() 544 | b.ResetTimer() 545 | for n := 0; n < b.N; n++ { 546 | _, _ = ParseRange(VERSION) 547 | } 548 | } 549 | 550 | func BenchmarkRangeMatchSimple(b *testing.B) { 551 | const VERSION = ">1.0.0" 552 | r, _ := ParseRange(VERSION) 553 | v := MustParse("2.0.0") 554 | b.ReportAllocs() 555 | b.ResetTimer() 556 | for n := 0; n < b.N; n++ { 557 | r(v) 558 | } 559 | } 560 | 561 | func BenchmarkRangeMatchAverage(b *testing.B) { 562 | const VERSION = ">=1.0.0 <2.0.0" 563 | r, _ := ParseRange(VERSION) 564 | v := MustParse("1.2.3") 565 | b.ReportAllocs() 566 | b.ResetTimer() 567 | for n := 0; n < b.N; n++ { 568 | r(v) 569 | } 570 | } 571 | 572 | func BenchmarkRangeMatchComplex(b *testing.B) { 573 | const VERSION = ">=1.0.0 <2.0.0 || >=3.0.1 <4.0.0 !=3.0.3 || >=5.0.0" 574 | r, _ := ParseRange(VERSION) 575 | v := MustParse("5.0.1") 576 | b.ReportAllocs() 577 | b.ResetTimer() 578 | for n := 0; n < b.N; n++ { 579 | r(v) 580 | } 581 | } 582 | -------------------------------------------------------------------------------- /semver.go: -------------------------------------------------------------------------------- 1 | package semver 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | numbers string = "0123456789" 12 | alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" 13 | alphanum = alphas + numbers 14 | ) 15 | 16 | // SpecVersion is the latest fully supported spec version of semver 17 | var SpecVersion = Version{ 18 | Major: 2, 19 | Minor: 0, 20 | Patch: 0, 21 | } 22 | 23 | // Version represents a semver compatible version 24 | type Version struct { 25 | Major uint64 26 | Minor uint64 27 | Patch uint64 28 | Pre []PRVersion 29 | Build []string //No Precedence 30 | } 31 | 32 | // Version to string 33 | func (v Version) String() string { 34 | b := make([]byte, 0, 5) 35 | b = strconv.AppendUint(b, v.Major, 10) 36 | b = append(b, '.') 37 | b = strconv.AppendUint(b, v.Minor, 10) 38 | b = append(b, '.') 39 | b = strconv.AppendUint(b, v.Patch, 10) 40 | 41 | if len(v.Pre) > 0 { 42 | b = append(b, '-') 43 | b = append(b, v.Pre[0].String()...) 44 | 45 | for _, pre := range v.Pre[1:] { 46 | b = append(b, '.') 47 | b = append(b, pre.String()...) 48 | } 49 | } 50 | 51 | if len(v.Build) > 0 { 52 | b = append(b, '+') 53 | b = append(b, v.Build[0]...) 54 | 55 | for _, build := range v.Build[1:] { 56 | b = append(b, '.') 57 | b = append(b, build...) 58 | } 59 | } 60 | 61 | return string(b) 62 | } 63 | 64 | // FinalizeVersion discards prerelease and build number and only returns 65 | // major, minor and patch number. 66 | func (v Version) FinalizeVersion() string { 67 | b := make([]byte, 0, 5) 68 | b = strconv.AppendUint(b, v.Major, 10) 69 | b = append(b, '.') 70 | b = strconv.AppendUint(b, v.Minor, 10) 71 | b = append(b, '.') 72 | b = strconv.AppendUint(b, v.Patch, 10) 73 | return string(b) 74 | } 75 | 76 | // Equals checks if v is equal to o. 77 | func (v Version) Equals(o Version) bool { 78 | return (v.Compare(o) == 0) 79 | } 80 | 81 | // EQ checks if v is equal to o. 82 | func (v Version) EQ(o Version) bool { 83 | return (v.Compare(o) == 0) 84 | } 85 | 86 | // NE checks if v is not equal to o. 87 | func (v Version) NE(o Version) bool { 88 | return (v.Compare(o) != 0) 89 | } 90 | 91 | // GT checks if v is greater than o. 92 | func (v Version) GT(o Version) bool { 93 | return (v.Compare(o) == 1) 94 | } 95 | 96 | // GTE checks if v is greater than or equal to o. 97 | func (v Version) GTE(o Version) bool { 98 | return (v.Compare(o) >= 0) 99 | } 100 | 101 | // GE checks if v is greater than or equal to o. 102 | func (v Version) GE(o Version) bool { 103 | return (v.Compare(o) >= 0) 104 | } 105 | 106 | // LT checks if v is less than o. 107 | func (v Version) LT(o Version) bool { 108 | return (v.Compare(o) == -1) 109 | } 110 | 111 | // LTE checks if v is less than or equal to o. 112 | func (v Version) LTE(o Version) bool { 113 | return (v.Compare(o) <= 0) 114 | } 115 | 116 | // LE checks if v is less than or equal to o. 117 | func (v Version) LE(o Version) bool { 118 | return (v.Compare(o) <= 0) 119 | } 120 | 121 | // Compare compares Versions v to o: 122 | // -1 == v is less than o 123 | // 0 == v is equal to o 124 | // 1 == v is greater than o 125 | func (v Version) Compare(o Version) int { 126 | if v.Major != o.Major { 127 | if v.Major > o.Major { 128 | return 1 129 | } 130 | return -1 131 | } 132 | if v.Minor != o.Minor { 133 | if v.Minor > o.Minor { 134 | return 1 135 | } 136 | return -1 137 | } 138 | if v.Patch != o.Patch { 139 | if v.Patch > o.Patch { 140 | return 1 141 | } 142 | return -1 143 | } 144 | 145 | // Quick comparison if a version has no prerelease versions 146 | if len(v.Pre) == 0 && len(o.Pre) == 0 { 147 | return 0 148 | } else if len(v.Pre) == 0 && len(o.Pre) > 0 { 149 | return 1 150 | } else if len(v.Pre) > 0 && len(o.Pre) == 0 { 151 | return -1 152 | } 153 | 154 | i := 0 155 | for ; i < len(v.Pre) && i < len(o.Pre); i++ { 156 | if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 { 157 | continue 158 | } else if comp == 1 { 159 | return 1 160 | } else { 161 | return -1 162 | } 163 | } 164 | 165 | // If all pr versions are the equal but one has further prversion, this one greater 166 | if i == len(v.Pre) && i == len(o.Pre) { 167 | return 0 168 | } else if i == len(v.Pre) && i < len(o.Pre) { 169 | return -1 170 | } else { 171 | return 1 172 | } 173 | 174 | } 175 | 176 | // IncrementPatch increments the patch version 177 | func (v *Version) IncrementPatch() error { 178 | v.Patch++ 179 | return nil 180 | } 181 | 182 | // IncrementMinor increments the minor version 183 | func (v *Version) IncrementMinor() error { 184 | v.Minor++ 185 | v.Patch = 0 186 | return nil 187 | } 188 | 189 | // IncrementMajor increments the major version 190 | func (v *Version) IncrementMajor() error { 191 | v.Major++ 192 | v.Minor = 0 193 | v.Patch = 0 194 | return nil 195 | } 196 | 197 | // Validate validates v and returns error in case 198 | func (v Version) Validate() error { 199 | // Major, Minor, Patch already validated using uint64 200 | 201 | for _, pre := range v.Pre { 202 | if !pre.IsNum { //Numeric prerelease versions already uint64 203 | if len(pre.VersionStr) == 0 { 204 | return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr) 205 | } 206 | if !containsOnly(pre.VersionStr, alphanum) { 207 | return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr) 208 | } 209 | } 210 | } 211 | 212 | for _, build := range v.Build { 213 | if len(build) == 0 { 214 | return fmt.Errorf("Build meta data can not be empty %q", build) 215 | } 216 | if !containsOnly(build, alphanum) { 217 | return fmt.Errorf("Invalid character(s) found in build meta data %q", build) 218 | } 219 | } 220 | 221 | return nil 222 | } 223 | 224 | // New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error 225 | func New(s string) (*Version, error) { 226 | v, err := Parse(s) 227 | vp := &v 228 | return vp, err 229 | } 230 | 231 | // Make is an alias for Parse, parses version string and returns a validated Version or error 232 | func Make(s string) (Version, error) { 233 | return Parse(s) 234 | } 235 | 236 | // ParseTolerant allows for certain version specifications that do not strictly adhere to semver 237 | // specs to be parsed by this library. It does so by normalizing versions before passing them to 238 | // Parse(). It currently trims spaces, removes a "v" prefix, adds a 0 patch number to versions 239 | // with only major and minor components specified, and removes leading 0s. 240 | func ParseTolerant(s string) (Version, error) { 241 | s = strings.TrimSpace(s) 242 | s = strings.TrimPrefix(s, "v") 243 | 244 | // Split into major.minor.(patch+pr+meta) 245 | parts := strings.SplitN(s, ".", 3) 246 | // Remove leading zeros. 247 | for i, p := range parts { 248 | if len(p) > 1 { 249 | p = strings.TrimLeft(p, "0") 250 | if len(p) == 0 || !strings.ContainsAny(p[0:1], "0123456789") { 251 | p = "0" + p 252 | } 253 | parts[i] = p 254 | } 255 | } 256 | // Fill up shortened versions. 257 | if len(parts) < 3 { 258 | if strings.ContainsAny(parts[len(parts)-1], "+-") { 259 | return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data") 260 | } 261 | for len(parts) < 3 { 262 | parts = append(parts, "0") 263 | } 264 | } 265 | s = strings.Join(parts, ".") 266 | 267 | return Parse(s) 268 | } 269 | 270 | // Parse parses version string and returns a validated Version or error 271 | func Parse(s string) (Version, error) { 272 | if len(s) == 0 { 273 | return Version{}, errors.New("Version string empty") 274 | } 275 | 276 | // Split into major.minor.(patch+pr+meta) 277 | parts := strings.SplitN(s, ".", 3) 278 | if len(parts) != 3 { 279 | return Version{}, errors.New("No Major.Minor.Patch elements found") 280 | } 281 | 282 | // Major 283 | if !containsOnly(parts[0], numbers) { 284 | return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0]) 285 | } 286 | if hasLeadingZeroes(parts[0]) { 287 | return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0]) 288 | } 289 | major, err := strconv.ParseUint(parts[0], 10, 64) 290 | if err != nil { 291 | return Version{}, err 292 | } 293 | 294 | // Minor 295 | if !containsOnly(parts[1], numbers) { 296 | return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1]) 297 | } 298 | if hasLeadingZeroes(parts[1]) { 299 | return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1]) 300 | } 301 | minor, err := strconv.ParseUint(parts[1], 10, 64) 302 | if err != nil { 303 | return Version{}, err 304 | } 305 | 306 | v := Version{} 307 | v.Major = major 308 | v.Minor = minor 309 | 310 | var build, prerelease []string 311 | patchStr := parts[2] 312 | 313 | if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 { 314 | build = strings.Split(patchStr[buildIndex+1:], ".") 315 | patchStr = patchStr[:buildIndex] 316 | } 317 | 318 | if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 { 319 | prerelease = strings.Split(patchStr[preIndex+1:], ".") 320 | patchStr = patchStr[:preIndex] 321 | } 322 | 323 | if !containsOnly(patchStr, numbers) { 324 | return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr) 325 | } 326 | if hasLeadingZeroes(patchStr) { 327 | return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr) 328 | } 329 | patch, err := strconv.ParseUint(patchStr, 10, 64) 330 | if err != nil { 331 | return Version{}, err 332 | } 333 | 334 | v.Patch = patch 335 | 336 | // Prerelease 337 | for _, prstr := range prerelease { 338 | parsedPR, err := NewPRVersion(prstr) 339 | if err != nil { 340 | return Version{}, err 341 | } 342 | v.Pre = append(v.Pre, parsedPR) 343 | } 344 | 345 | // Build meta data 346 | for _, str := range build { 347 | if len(str) == 0 { 348 | return Version{}, errors.New("Build meta data is empty") 349 | } 350 | if !containsOnly(str, alphanum) { 351 | return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str) 352 | } 353 | v.Build = append(v.Build, str) 354 | } 355 | 356 | return v, nil 357 | } 358 | 359 | // MustParse is like Parse but panics if the version cannot be parsed. 360 | func MustParse(s string) Version { 361 | v, err := Parse(s) 362 | if err != nil { 363 | panic(`semver: Parse(` + s + `): ` + err.Error()) 364 | } 365 | return v 366 | } 367 | 368 | // PRVersion represents a PreRelease Version 369 | type PRVersion struct { 370 | VersionStr string 371 | VersionNum uint64 372 | IsNum bool 373 | } 374 | 375 | // NewPRVersion creates a new valid prerelease version 376 | func NewPRVersion(s string) (PRVersion, error) { 377 | if len(s) == 0 { 378 | return PRVersion{}, errors.New("Prerelease is empty") 379 | } 380 | v := PRVersion{} 381 | if containsOnly(s, numbers) { 382 | if hasLeadingZeroes(s) { 383 | return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s) 384 | } 385 | num, err := strconv.ParseUint(s, 10, 64) 386 | 387 | // Might never be hit, but just in case 388 | if err != nil { 389 | return PRVersion{}, err 390 | } 391 | v.VersionNum = num 392 | v.IsNum = true 393 | } else if containsOnly(s, alphanum) { 394 | v.VersionStr = s 395 | v.IsNum = false 396 | } else { 397 | return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s) 398 | } 399 | return v, nil 400 | } 401 | 402 | // IsNumeric checks if prerelease-version is numeric 403 | func (v PRVersion) IsNumeric() bool { 404 | return v.IsNum 405 | } 406 | 407 | // Compare compares two PreRelease Versions v and o: 408 | // -1 == v is less than o 409 | // 0 == v is equal to o 410 | // 1 == v is greater than o 411 | func (v PRVersion) Compare(o PRVersion) int { 412 | if v.IsNum && !o.IsNum { 413 | return -1 414 | } else if !v.IsNum && o.IsNum { 415 | return 1 416 | } else if v.IsNum && o.IsNum { 417 | if v.VersionNum == o.VersionNum { 418 | return 0 419 | } else if v.VersionNum > o.VersionNum { 420 | return 1 421 | } else { 422 | return -1 423 | } 424 | } else { // both are Alphas 425 | if v.VersionStr == o.VersionStr { 426 | return 0 427 | } else if v.VersionStr > o.VersionStr { 428 | return 1 429 | } else { 430 | return -1 431 | } 432 | } 433 | } 434 | 435 | // PreRelease version to string 436 | func (v PRVersion) String() string { 437 | if v.IsNum { 438 | return strconv.FormatUint(v.VersionNum, 10) 439 | } 440 | return v.VersionStr 441 | } 442 | 443 | func containsOnly(s string, set string) bool { 444 | return strings.IndexFunc(s, func(r rune) bool { 445 | return !strings.ContainsRune(set, r) 446 | }) == -1 447 | } 448 | 449 | func hasLeadingZeroes(s string) bool { 450 | return len(s) > 1 && s[0] == '0' 451 | } 452 | 453 | // NewBuildVersion creates a new valid build version 454 | func NewBuildVersion(s string) (string, error) { 455 | if len(s) == 0 { 456 | return "", errors.New("Buildversion is empty") 457 | } 458 | if !containsOnly(s, alphanum) { 459 | return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s) 460 | } 461 | return s, nil 462 | } 463 | 464 | // FinalizeVersion returns the major, minor and patch number only and discards 465 | // prerelease and build number. 466 | func FinalizeVersion(s string) (string, error) { 467 | v, err := Parse(s) 468 | if err != nil { 469 | return "", err 470 | } 471 | v.Pre = nil 472 | v.Build = nil 473 | 474 | finalVer := v.String() 475 | return finalVer, nil 476 | } 477 | -------------------------------------------------------------------------------- /semver_test.go: -------------------------------------------------------------------------------- 1 | package semver 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func prstr(s string) PRVersion { 8 | return PRVersion{s, 0, false} 9 | } 10 | 11 | func prnum(i uint64) PRVersion { 12 | return PRVersion{"", i, true} 13 | } 14 | 15 | type formatTest struct { 16 | v Version 17 | result string 18 | } 19 | 20 | var formatTests = []formatTest{ 21 | {Version{1, 2, 3, nil, nil}, "1.2.3"}, 22 | {Version{0, 0, 1, nil, nil}, "0.0.1"}, 23 | {Version{0, 0, 1, []PRVersion{prstr("alpha"), prstr("preview")}, []string{"123", "456"}}, "0.0.1-alpha.preview+123.456"}, 24 | {Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, []string{"123", "456"}}, "1.2.3-alpha.1+123.456"}, 25 | {Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, nil}, "1.2.3-alpha.1"}, 26 | {Version{1, 2, 3, nil, []string{"123", "456"}}, "1.2.3+123.456"}, 27 | // Prereleases and build metadata hyphens 28 | {Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, []string{"123", "b-uild"}}, "1.2.3-alpha.b-eta+123.b-uild"}, 29 | {Version{1, 2, 3, nil, []string{"123", "b-uild"}}, "1.2.3+123.b-uild"}, 30 | {Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, nil}, "1.2.3-alpha.b-eta"}, 31 | } 32 | 33 | var tolerantFormatTests = []formatTest{ 34 | {Version{1, 2, 3, nil, nil}, "v1.2.3"}, 35 | {Version{1, 2, 0, []PRVersion{prstr("alpha")}, nil}, "1.2.0-alpha"}, 36 | {Version{1, 2, 0, nil, nil}, "1.2.00"}, 37 | {Version{1, 2, 3, nil, nil}, " 1.2.3 "}, 38 | {Version{1, 2, 3, nil, nil}, "01.02.03"}, 39 | {Version{0, 0, 3, nil, nil}, "00.0.03"}, 40 | {Version{0, 0, 3, nil, nil}, "000.0.03"}, 41 | {Version{1, 2, 0, nil, nil}, "1.2"}, 42 | {Version{1, 0, 0, nil, nil}, "1"}, 43 | } 44 | 45 | func TestStringer(t *testing.T) { 46 | for _, test := range formatTests { 47 | if res := test.v.String(); res != test.result { 48 | t.Errorf("Stringer, expected %q but got %q", test.result, res) 49 | } 50 | } 51 | } 52 | 53 | func TestParse(t *testing.T) { 54 | for _, test := range formatTests { 55 | if v, err := Parse(test.result); err != nil { 56 | t.Errorf("Error parsing %q: %q", test.result, err) 57 | } else if comp := v.Compare(test.v); comp != 0 { 58 | t.Errorf("Parsing, expected %q but got %q, comp: %d ", test.v, v, comp) 59 | } else if err := v.Validate(); err != nil { 60 | t.Errorf("Error validating parsed version %q: %q", test.v, err) 61 | } 62 | } 63 | } 64 | 65 | func TestParseTolerant(t *testing.T) { 66 | for _, test := range tolerantFormatTests { 67 | if v, err := ParseTolerant(test.result); err != nil { 68 | t.Errorf("Error parsing %q: %q", test.result, err) 69 | } else if comp := v.Compare(test.v); comp != 0 { 70 | t.Errorf("Parsing, expected %q but got %q, comp: %d ", test.v, v, comp) 71 | } else if err := v.Validate(); err != nil { 72 | t.Errorf("Error validating parsed version %q: %q", test.v, err) 73 | } 74 | } 75 | } 76 | 77 | func TestMustParse(t *testing.T) { 78 | _ = MustParse("32.2.1-alpha") 79 | } 80 | 81 | func TestMustParse_panic(t *testing.T) { 82 | defer func() { 83 | if recover() == nil { 84 | t.Errorf("Should have panicked") 85 | } 86 | }() 87 | _ = MustParse("invalid version") 88 | } 89 | 90 | func TestValidate(t *testing.T) { 91 | for _, test := range formatTests { 92 | if err := test.v.Validate(); err != nil { 93 | t.Errorf("Error validating %q: %q", test.v, err) 94 | } 95 | } 96 | } 97 | 98 | var finalizeVersionMethod = []formatTest{ 99 | {Version{1, 2, 3, nil, nil}, "1.2.3"}, 100 | {Version{0, 0, 1, nil, nil}, "0.0.1"}, 101 | {Version{0, 0, 1, []PRVersion{prstr("alpha"), prstr("preview")}, []string{"123", "456"}}, "0.0.1"}, 102 | {Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, []string{"123", "456"}}, "1.2.3"}, 103 | {Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, nil}, "1.2.3"}, 104 | {Version{1, 2, 3, nil, []string{"123", "456"}}, "1.2.3"}, 105 | // Prereleases and build metadata hyphens 106 | {Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, []string{"123", "b-uild"}}, "1.2.3"}, 107 | {Version{1, 2, 3, nil, []string{"123", "b-uild"}}, "1.2.3"}, 108 | {Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, nil}, "1.2.3"}, 109 | } 110 | 111 | func TestFinalizeVersionMethod(t *testing.T) { 112 | for _, test := range finalizeVersionMethod { 113 | out := test.v.FinalizeVersion() 114 | if out != test.result { 115 | t.Errorf("Finalized version error, expected %q but got %q", test.result, out) 116 | } 117 | } 118 | } 119 | 120 | type compareTest struct { 121 | v1 Version 122 | v2 Version 123 | result int 124 | } 125 | 126 | var compareTests = []compareTest{ 127 | {Version{1, 0, 0, nil, nil}, Version{1, 0, 0, nil, nil}, 0}, 128 | {Version{2, 0, 0, nil, nil}, Version{1, 0, 0, nil, nil}, 1}, 129 | {Version{0, 1, 0, nil, nil}, Version{0, 1, 0, nil, nil}, 0}, 130 | {Version{0, 2, 0, nil, nil}, Version{0, 1, 0, nil, nil}, 1}, 131 | {Version{0, 0, 1, nil, nil}, Version{0, 0, 1, nil, nil}, 0}, 132 | {Version{0, 0, 2, nil, nil}, Version{0, 0, 1, nil, nil}, 1}, 133 | {Version{1, 2, 3, nil, nil}, Version{1, 2, 3, nil, nil}, 0}, 134 | {Version{2, 2, 4, nil, nil}, Version{1, 2, 4, nil, nil}, 1}, 135 | {Version{1, 3, 3, nil, nil}, Version{1, 2, 3, nil, nil}, 1}, 136 | {Version{1, 2, 4, nil, nil}, Version{1, 2, 3, nil, nil}, 1}, 137 | 138 | // Spec Examples #11 139 | {Version{1, 0, 0, nil, nil}, Version{2, 0, 0, nil, nil}, -1}, 140 | {Version{2, 0, 0, nil, nil}, Version{2, 1, 0, nil, nil}, -1}, 141 | {Version{2, 1, 0, nil, nil}, Version{2, 1, 1, nil, nil}, -1}, 142 | 143 | // Spec Examples #9 144 | {Version{1, 0, 0, nil, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil}, 1}, 145 | {Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha"), prnum(1)}, nil}, -1}, 146 | {Version{1, 0, 0, []PRVersion{prstr("alpha"), prnum(1)}, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha"), prstr("beta")}, nil}, -1}, 147 | {Version{1, 0, 0, []PRVersion{prstr("alpha"), prstr("beta")}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta")}, nil}, -1}, 148 | {Version{1, 0, 0, []PRVersion{prstr("beta")}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(2)}, nil}, -1}, 149 | {Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(2)}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(11)}, nil}, -1}, 150 | {Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(11)}, nil}, Version{1, 0, 0, []PRVersion{prstr("rc"), prnum(1)}, nil}, -1}, 151 | {Version{1, 0, 0, []PRVersion{prstr("rc"), prnum(1)}, nil}, Version{1, 0, 0, nil, nil}, -1}, 152 | 153 | // Ignore Build metadata 154 | {Version{1, 0, 0, nil, []string{"1", "2", "3"}}, Version{1, 0, 0, nil, nil}, 0}, 155 | } 156 | 157 | func TestCompare(t *testing.T) { 158 | for _, test := range compareTests { 159 | if res := test.v1.Compare(test.v2); res != test.result { 160 | t.Errorf("Comparing %q : %q, expected %d but got %d", test.v1, test.v2, test.result, res) 161 | } 162 | // Test counterpart 163 | if res := test.v2.Compare(test.v1); res != -test.result { 164 | t.Errorf("Comparing %q : %q, expected %d but got %d", test.v2, test.v1, -test.result, res) 165 | } 166 | } 167 | } 168 | 169 | type wrongformatTest struct { 170 | v *Version 171 | str string 172 | } 173 | 174 | var wrongformatTests = []wrongformatTest{ 175 | {nil, ""}, 176 | {nil, "."}, 177 | {nil, "1."}, 178 | {nil, ".1"}, 179 | {nil, "a.b.c"}, 180 | {nil, "1.a.b"}, 181 | {nil, "1.1.a"}, 182 | {nil, "1.a.1"}, 183 | {nil, "a.1.1"}, 184 | {nil, ".."}, 185 | {nil, "1.."}, 186 | {nil, "1.1."}, 187 | {nil, "1..1"}, 188 | {nil, "1.1.+123"}, 189 | {nil, "1.1.-beta"}, 190 | {nil, "-1.1.1"}, 191 | {nil, "1.-1.1"}, 192 | {nil, "1.1.-1"}, 193 | // giant numbers 194 | {nil, "20000000000000000000.1.1"}, 195 | {nil, "1.20000000000000000000.1"}, 196 | {nil, "1.1.20000000000000000000"}, 197 | {nil, "1.1.1-20000000000000000000"}, 198 | // Leading zeroes 199 | {nil, "01.1.1"}, 200 | {nil, "001.1.1"}, 201 | {nil, "1.01.1"}, 202 | {nil, "1.001.1"}, 203 | {nil, "1.1.01"}, 204 | {nil, "1.1.001"}, 205 | {nil, "1.1.1-01"}, 206 | {nil, "1.1.1-001"}, 207 | {nil, "1.1.1-beta.01"}, 208 | {nil, "1.1.1-beta.001"}, 209 | {&Version{0, 0, 0, []PRVersion{prstr("!")}, nil}, "0.0.0-!"}, 210 | {&Version{0, 0, 0, nil, []string{"!"}}, "0.0.0+!"}, 211 | // empty prversion 212 | {&Version{0, 0, 0, []PRVersion{prstr(""), prstr("alpha")}, nil}, "0.0.0-.alpha"}, 213 | // empty build meta data 214 | {&Version{0, 0, 0, []PRVersion{prstr("alpha")}, []string{""}}, "0.0.0-alpha+"}, 215 | {&Version{0, 0, 0, []PRVersion{prstr("alpha")}, []string{"test", ""}}, "0.0.0-alpha+test."}, 216 | } 217 | 218 | func TestWrongFormat(t *testing.T) { 219 | for _, test := range wrongformatTests { 220 | 221 | if res, err := Parse(test.str); err == nil { 222 | t.Errorf("Parsing wrong format version %q, expected error but got %q", test.str, res) 223 | } 224 | 225 | if test.v != nil { 226 | if err := test.v.Validate(); err == nil { 227 | t.Errorf("Validating wrong format version %q (%q), expected error", test.v, test.str) 228 | } 229 | } 230 | } 231 | } 232 | 233 | var wrongTolerantFormatTests = []wrongformatTest{ 234 | {nil, "1.0+abc"}, 235 | {nil, "1.0-rc.1"}, 236 | } 237 | 238 | func TestWrongTolerantFormat(t *testing.T) { 239 | for _, test := range wrongTolerantFormatTests { 240 | if res, err := ParseTolerant(test.str); err == nil { 241 | t.Errorf("Parsing wrong format version %q, expected error but got %q", test.str, res) 242 | } 243 | } 244 | } 245 | 246 | func TestCompareHelper(t *testing.T) { 247 | v := Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil} 248 | v1 := Version{1, 0, 0, nil, nil} 249 | if !v.EQ(v) { 250 | t.Errorf("%q should be equal to %q", v, v) 251 | } 252 | if !v.Equals(v) { 253 | t.Errorf("%q should be equal to %q", v, v) 254 | } 255 | if !v1.NE(v) { 256 | t.Errorf("%q should not be equal to %q", v1, v) 257 | } 258 | if !v.GTE(v) { 259 | t.Errorf("%q should be greater than or equal to %q", v, v) 260 | } 261 | if !v.LTE(v) { 262 | t.Errorf("%q should be less than or equal to %q", v, v) 263 | } 264 | if !v.LT(v1) { 265 | t.Errorf("%q should be less than %q", v, v1) 266 | } 267 | if !v.LTE(v1) { 268 | t.Errorf("%q should be less than or equal %q", v, v1) 269 | } 270 | if !v.LE(v1) { 271 | t.Errorf("%q should be less than or equal %q", v, v1) 272 | } 273 | if !v1.GT(v) { 274 | t.Errorf("%q should be greater than %q", v1, v) 275 | } 276 | if !v1.GTE(v) { 277 | t.Errorf("%q should be greater than or equal %q", v1, v) 278 | } 279 | if !v1.GE(v) { 280 | t.Errorf("%q should be greater than or equal %q", v1, v) 281 | } 282 | } 283 | 284 | const ( 285 | MAJOR = iota 286 | MINOR 287 | PATCH 288 | ) 289 | 290 | type incrementTest struct { 291 | version Version 292 | incrementType int 293 | expectingError bool 294 | expectedVersion Version 295 | } 296 | 297 | var incrementTests = []incrementTest{ 298 | {Version{1, 2, 3, nil, nil}, PATCH, false, Version{1, 2, 4, nil, nil}}, 299 | {Version{1, 2, 3, nil, nil}, MINOR, false, Version{1, 3, 0, nil, nil}}, 300 | {Version{1, 2, 3, nil, nil}, MAJOR, false, Version{2, 0, 0, nil, nil}}, 301 | {Version{0, 1, 2, nil, nil}, PATCH, false, Version{0, 1, 3, nil, nil}}, 302 | {Version{0, 1, 2, nil, nil}, MINOR, false, Version{0, 2, 0, nil, nil}}, 303 | {Version{0, 1, 2, nil, nil}, MAJOR, false, Version{1, 0, 0, nil, nil}}, 304 | } 305 | 306 | func TestIncrements(t *testing.T) { 307 | for _, test := range incrementTests { 308 | var originalVersion = Version{ 309 | test.version.Major, 310 | test.version.Minor, 311 | test.version.Patch, 312 | test.version.Pre, 313 | test.version.Build, 314 | } 315 | var err error 316 | switch test.incrementType { 317 | case PATCH: 318 | err = test.version.IncrementPatch() 319 | case MINOR: 320 | err = test.version.IncrementMinor() 321 | case MAJOR: 322 | err = test.version.IncrementMajor() 323 | } 324 | if test.expectingError { 325 | if err != nil { 326 | t.Errorf("Increment version, expecting %q, got error %q", test.expectedVersion, err) 327 | } 328 | if test.version.EQ(originalVersion) { 329 | t.Errorf("Increment version, expecting %q, got %q", test.expectedVersion, test.version) 330 | } 331 | } else { 332 | if (err != nil) && !test.expectingError { 333 | t.Errorf("Increment version %q, not expecting error, got %q", test.version, err) 334 | } 335 | if test.version.NE(test.expectedVersion) { 336 | t.Errorf("Increment version, expecting %q, got %q", test.expectedVersion, test.version) 337 | } 338 | } 339 | } 340 | } 341 | 342 | func TestPreReleaseVersions(t *testing.T) { 343 | p1, err := NewPRVersion("123") 344 | if !p1.IsNumeric() { 345 | t.Errorf("Expected numeric prversion, got %q", p1) 346 | } 347 | if p1.VersionNum != 123 { 348 | t.Error("Wrong prversion number") 349 | } 350 | if err != nil { 351 | t.Errorf("Not expected error %q", err) 352 | } 353 | p2, err := NewPRVersion("alpha") 354 | if p2.IsNumeric() { 355 | t.Errorf("Expected non-numeric prversion, got %q", p2) 356 | } 357 | if p2.VersionStr != "alpha" { 358 | t.Error("Wrong prversion string") 359 | } 360 | if err != nil { 361 | t.Errorf("Not expected error %q", err) 362 | } 363 | } 364 | 365 | func TestBuildMetaDataVersions(t *testing.T) { 366 | _, err := NewBuildVersion("123") 367 | if err != nil { 368 | t.Errorf("Unexpected error %q", err) 369 | } 370 | 371 | _, err = NewBuildVersion("build") 372 | if err != nil { 373 | t.Errorf("Unexpected error %q", err) 374 | } 375 | 376 | _, err = NewBuildVersion("test?") 377 | if err == nil { 378 | t.Error("Expected error, got none") 379 | } 380 | 381 | _, err = NewBuildVersion("") 382 | if err == nil { 383 | t.Error("Expected error, got none") 384 | } 385 | } 386 | 387 | func TestNewHelper(t *testing.T) { 388 | v, err := New("1.2.3") 389 | if err != nil { 390 | t.Fatalf("Unexpected error %q", err) 391 | } 392 | 393 | // New returns pointer 394 | if v == nil { 395 | t.Fatal("Version is nil") 396 | } 397 | if v.Compare(Version{1, 2, 3, nil, nil}) != 0 { 398 | t.Fatal("Unexpected comparison problem") 399 | } 400 | } 401 | 402 | func TestMakeHelper(t *testing.T) { 403 | v, err := Make("1.2.3") 404 | if err != nil { 405 | t.Fatalf("Unexpected error %q", err) 406 | } 407 | if v.Compare(Version{1, 2, 3, nil, nil}) != 0 { 408 | t.Fatal("Unexpected comparison problem") 409 | } 410 | } 411 | 412 | type finalizeTest struct { 413 | input string 414 | output string 415 | } 416 | 417 | var finalizeTests = []finalizeTest{ 418 | {"", ""}, 419 | {"1.2.3", "1.2.3"}, 420 | {"0.0.1", "0.0.1"}, 421 | {"0.0.1-alpha.preview+123.456", "0.0.1"}, 422 | {"1.2.3-alpha.1+123.456", "1.2.3"}, 423 | {"1.2.3-alpha.1", "1.2.3"}, 424 | {"1.2.3+123.456", "1.2.3"}, 425 | {"1.2.3-alpha.b-eta+123.b-uild", "1.2.3"}, 426 | {"1.2.3+123.b-uild", "1.2.3"}, 427 | {"1.2.3-alpha.b-eta", "1.2.3"}, 428 | {"1.2-alpha", ""}, 429 | } 430 | 431 | func TestFinalizeVersion(t *testing.T) { 432 | for _, test := range finalizeTests { 433 | finalVer, err := FinalizeVersion(test.input) 434 | if finalVer == "" { 435 | if err == nil { 436 | t.Errorf("Finalize Version error, expected error but got nil") 437 | } 438 | } else if finalVer != test.output && err != nil { 439 | t.Errorf("Finalize Version error expected %q but got %q", test.output, finalVer) 440 | } 441 | } 442 | } 443 | 444 | func BenchmarkParseSimple(b *testing.B) { 445 | const VERSION = "0.0.1" 446 | b.ReportAllocs() 447 | b.ResetTimer() 448 | for n := 0; n < b.N; n++ { 449 | _, _ = Parse(VERSION) 450 | } 451 | } 452 | 453 | func BenchmarkParseComplex(b *testing.B) { 454 | const VERSION = "0.0.1-alpha.preview+123.456" 455 | b.ReportAllocs() 456 | b.ResetTimer() 457 | for n := 0; n < b.N; n++ { 458 | _, _ = Parse(VERSION) 459 | } 460 | } 461 | 462 | func BenchmarkParseAverage(b *testing.B) { 463 | l := len(formatTests) 464 | b.ReportAllocs() 465 | b.ResetTimer() 466 | for n := 0; n < b.N; n++ { 467 | _, _ = Parse(formatTests[n%l].result) 468 | } 469 | } 470 | 471 | func BenchmarkParseTolerantAverage(b *testing.B) { 472 | l := len(tolerantFormatTests) 473 | b.ReportAllocs() 474 | b.ResetTimer() 475 | for n := 0; n < b.N; n++ { 476 | _, _ = ParseTolerant(tolerantFormatTests[n%l].result) 477 | } 478 | } 479 | 480 | func BenchmarkStringSimple(b *testing.B) { 481 | const VERSION = "0.0.1" 482 | v, _ := Parse(VERSION) 483 | b.ReportAllocs() 484 | b.ResetTimer() 485 | for n := 0; n < b.N; n++ { 486 | _ = v.String() 487 | } 488 | } 489 | 490 | func BenchmarkStringLarger(b *testing.B) { 491 | const VERSION = "11.15.2012" 492 | v, _ := Parse(VERSION) 493 | b.ReportAllocs() 494 | b.ResetTimer() 495 | for n := 0; n < b.N; n++ { 496 | _ = v.String() 497 | } 498 | } 499 | 500 | func BenchmarkStringComplex(b *testing.B) { 501 | const VERSION = "0.0.1-alpha.preview+123.456" 502 | v, _ := Parse(VERSION) 503 | b.ReportAllocs() 504 | b.ResetTimer() 505 | for n := 0; n < b.N; n++ { 506 | _ = v.String() 507 | } 508 | } 509 | 510 | func BenchmarkStringAverage(b *testing.B) { 511 | l := len(formatTests) 512 | b.ReportAllocs() 513 | b.ResetTimer() 514 | for n := 0; n < b.N; n++ { 515 | _ = formatTests[n%l].v.String() 516 | } 517 | } 518 | 519 | func BenchmarkValidateSimple(b *testing.B) { 520 | const VERSION = "0.0.1" 521 | v, _ := Parse(VERSION) 522 | b.ReportAllocs() 523 | b.ResetTimer() 524 | for n := 0; n < b.N; n++ { 525 | _ = v.Validate() 526 | } 527 | } 528 | 529 | func BenchmarkValidateComplex(b *testing.B) { 530 | const VERSION = "0.0.1-alpha.preview+123.456" 531 | v, _ := Parse(VERSION) 532 | b.ReportAllocs() 533 | b.ResetTimer() 534 | for n := 0; n < b.N; n++ { 535 | _ = v.Validate() 536 | } 537 | } 538 | 539 | func BenchmarkValidateAverage(b *testing.B) { 540 | l := len(formatTests) 541 | b.ReportAllocs() 542 | b.ResetTimer() 543 | for n := 0; n < b.N; n++ { 544 | _ = formatTests[n%l].v.Validate() 545 | } 546 | } 547 | 548 | func BenchmarkCompareSimple(b *testing.B) { 549 | const VERSION = "0.0.1" 550 | v, _ := Parse(VERSION) 551 | b.ReportAllocs() 552 | b.ResetTimer() 553 | for n := 0; n < b.N; n++ { 554 | v.Compare(v) 555 | } 556 | } 557 | 558 | func BenchmarkCompareComplex(b *testing.B) { 559 | const VERSION = "0.0.1-alpha.preview+123.456" 560 | v, _ := Parse(VERSION) 561 | b.ReportAllocs() 562 | b.ResetTimer() 563 | for n := 0; n < b.N; n++ { 564 | v.Compare(v) 565 | } 566 | } 567 | 568 | func BenchmarkCompareAverage(b *testing.B) { 569 | l := len(compareTests) 570 | b.ReportAllocs() 571 | b.ResetTimer() 572 | for n := 0; n < b.N; n++ { 573 | compareTests[n%l].v1.Compare((compareTests[n%l].v2)) 574 | } 575 | } 576 | -------------------------------------------------------------------------------- /sort.go: -------------------------------------------------------------------------------- 1 | package semver 2 | 3 | import ( 4 | "sort" 5 | ) 6 | 7 | // Versions represents multiple versions. 8 | type Versions []Version 9 | 10 | // Len returns length of version collection 11 | func (s Versions) Len() int { 12 | return len(s) 13 | } 14 | 15 | // Swap swaps two versions inside the collection by its indices 16 | func (s Versions) Swap(i, j int) { 17 | s[i], s[j] = s[j], s[i] 18 | } 19 | 20 | // Less checks if version at index i is less than version at index j 21 | func (s Versions) Less(i, j int) bool { 22 | return s[i].LT(s[j]) 23 | } 24 | 25 | // Sort sorts a slice of versions 26 | func Sort(versions []Version) { 27 | sort.Sort(Versions(versions)) 28 | } 29 | -------------------------------------------------------------------------------- /sort_test.go: -------------------------------------------------------------------------------- 1 | package semver 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestSort(t *testing.T) { 9 | v100, _ := Parse("1.0.0") 10 | v010, _ := Parse("0.1.0") 11 | v001, _ := Parse("0.0.1") 12 | versions := []Version{v010, v100, v001} 13 | Sort(versions) 14 | 15 | correct := []Version{v001, v010, v100} 16 | if !reflect.DeepEqual(versions, correct) { 17 | t.Fatalf("Sort returned wrong order: %s", versions) 18 | } 19 | } 20 | 21 | func BenchmarkSort(b *testing.B) { 22 | v100, _ := Parse("1.0.0") 23 | v010, _ := Parse("0.1.0") 24 | v001, _ := Parse("0.0.1") 25 | b.ReportAllocs() 26 | b.ResetTimer() 27 | for n := 0; n < b.N; n++ { 28 | Sort([]Version{v010, v100, v001}) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sql.go: -------------------------------------------------------------------------------- 1 | package semver 2 | 3 | import ( 4 | "database/sql/driver" 5 | "fmt" 6 | ) 7 | 8 | // Scan implements the database/sql.Scanner interface. 9 | func (v *Version) Scan(src interface{}) (err error) { 10 | var str string 11 | switch src := src.(type) { 12 | case string: 13 | str = src 14 | case []byte: 15 | str = string(src) 16 | default: 17 | return fmt.Errorf("version.Scan: cannot convert %T to string", src) 18 | } 19 | 20 | if t, err := Parse(str); err == nil { 21 | *v = t 22 | } 23 | 24 | return 25 | } 26 | 27 | // Value implements the database/sql/driver.Valuer interface. 28 | func (v Version) Value() (driver.Value, error) { 29 | return v.String(), nil 30 | } 31 | -------------------------------------------------------------------------------- /sql_test.go: -------------------------------------------------------------------------------- 1 | package semver 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type scanTest struct { 8 | val interface{} 9 | shouldError bool 10 | expected string 11 | } 12 | 13 | var scanTests = []scanTest{ 14 | {"1.2.3", false, "1.2.3"}, 15 | {[]byte("1.2.3"), false, "1.2.3"}, 16 | {7, true, ""}, 17 | {7e4, true, ""}, 18 | {true, true, ""}, 19 | } 20 | 21 | func TestScanString(t *testing.T) { 22 | for _, tc := range scanTests { 23 | s := &Version{} 24 | err := s.Scan(tc.val) 25 | if tc.shouldError { 26 | if err == nil { 27 | t.Fatalf("Scan did not return an error on %v (%T)", tc.val, tc.val) 28 | } 29 | } else { 30 | if err != nil { 31 | t.Fatalf("Scan returned an unexpected error: %s (%T) on %v (%T)", tc.val, tc.val, tc.val, tc.val) 32 | } 33 | if val, _ := s.Value(); val != tc.expected { 34 | t.Errorf("Wrong Value returned, expected %q, got %q", tc.expected, val) 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /v4/examples/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/blang/semver/v4" 7 | ) 8 | 9 | func main() { 10 | v, err := semver.Parse("0.0.1-alpha.preview.222+123.github") 11 | if err != nil { 12 | fmt.Printf("Error while parsing (not valid): %q", err) 13 | } 14 | fmt.Printf("Version to string: %q\n", v) 15 | 16 | fmt.Printf("Major: %d\n", v.Major) 17 | fmt.Printf("Minor: %d\n", v.Minor) 18 | fmt.Printf("Patch: %d\n", v.Patch) 19 | 20 | // Prerelease versions 21 | if len(v.Pre) > 0 { 22 | fmt.Println("Prerelease versions:") 23 | for i, pre := range v.Pre { 24 | fmt.Printf("%d: %q\n", i, pre) 25 | } 26 | } 27 | 28 | // Build meta data 29 | if len(v.Build) > 0 { 30 | fmt.Println("Build meta data:") 31 | for i, build := range v.Build { 32 | fmt.Printf("%d: %q\n", i, build) 33 | } 34 | } 35 | 36 | // Make == Parse (Value), New for Pointer 37 | v001, _ := semver.Make("0.0.1") 38 | 39 | fmt.Println("\nUse Version.Compare for comparisons (-1, 0, 1):") 40 | fmt.Printf("%q is greater than %q: Compare == %d\n", v001, v, v001.Compare(v)) 41 | fmt.Printf("%q is less than %q: Compare == %d\n", v, v001, v.Compare(v001)) 42 | fmt.Printf("%q is equal to %q: Compare == %d\n", v, v, v.Compare(v)) 43 | 44 | fmt.Println("\nUse comparison helpers returning booleans:") 45 | fmt.Printf("%q is greater than %q: %t\n", v001, v, v001.GT(v)) 46 | fmt.Printf("%q is greater than equal %q: %t\n", v001, v, v001.GTE(v)) 47 | fmt.Printf("%q is greater than equal %q: %t\n", v, v, v.GTE(v)) 48 | fmt.Printf("%q is less than %q: %t\n", v, v001, v.LT(v001)) 49 | fmt.Printf("%q is less than equal %q: %t\n", v, v001, v.LTE(v001)) 50 | fmt.Printf("%q is less than equal %q: %t\n", v, v, v.LTE(v)) 51 | 52 | fmt.Println("\nManipulate Version in place:") 53 | v.Pre[0], err = semver.NewPRVersion("beta") 54 | if err != nil { 55 | fmt.Printf("Error parsing pre release version: %q", err) 56 | } 57 | fmt.Printf("Version to string: %q\n", v) 58 | 59 | fmt.Println("\nCompare Prerelease versions:") 60 | pre1, _ := semver.NewPRVersion("123") 61 | pre2, _ := semver.NewPRVersion("alpha") 62 | pre3, _ := semver.NewPRVersion("124") 63 | fmt.Printf("%q is less than %q: Compare == %d\n", pre1, pre2, pre1.Compare(pre2)) 64 | fmt.Printf("%q is greater than %q: Compare == %d\n", pre3, pre1, pre3.Compare(pre1)) 65 | fmt.Printf("%q is equal to %q: Compare == %d\n", pre1, pre1, pre1.Compare(pre1)) 66 | 67 | fmt.Println("\nValidate versions:") 68 | v.Build[0] = "?" 69 | 70 | err = v.Validate() 71 | if err != nil { 72 | fmt.Printf("Validation failed: %s\n", err) 73 | } 74 | 75 | fmt.Println("Create valid build meta data:") 76 | b1, _ := semver.NewBuildVersion("build123") 77 | v.Build[0] = b1 78 | fmt.Printf("Version with new build version %q\n", v) 79 | 80 | _, err = semver.NewBuildVersion("build?123") 81 | if err != nil { 82 | fmt.Printf("Create build version failed: %s\n", err) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /v4/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/blang/semver/v4 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /v4/json.go: -------------------------------------------------------------------------------- 1 | package semver 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // MarshalJSON implements the encoding/json.Marshaler interface. 8 | func (v Version) MarshalJSON() ([]byte, error) { 9 | return json.Marshal(v.String()) 10 | } 11 | 12 | // UnmarshalJSON implements the encoding/json.Unmarshaler interface. 13 | func (v *Version) UnmarshalJSON(data []byte) (err error) { 14 | var versionString string 15 | 16 | if err = json.Unmarshal(data, &versionString); err != nil { 17 | return 18 | } 19 | 20 | *v, err = Parse(versionString) 21 | 22 | return 23 | } 24 | -------------------------------------------------------------------------------- /v4/json_test.go: -------------------------------------------------------------------------------- 1 | package semver 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestJSONMarshal(t *testing.T) { 10 | versionString := "3.1.4-alpha.1.5.9+build.2.6.5" 11 | v, err := Parse(versionString) 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | 16 | versionJSON, err := json.Marshal(v) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | quotedVersionString := strconv.Quote(versionString) 22 | 23 | if string(versionJSON) != quotedVersionString { 24 | t.Fatalf("JSON marshaled semantic version not equal: expected %q, got %q", quotedVersionString, string(versionJSON)) 25 | } 26 | } 27 | 28 | func TestJSONUnmarshal(t *testing.T) { 29 | versionString := "3.1.4-alpha.1.5.9+build.2.6.5" 30 | quotedVersionString := strconv.Quote(versionString) 31 | 32 | var v Version 33 | if err := json.Unmarshal([]byte(quotedVersionString), &v); err != nil { 34 | t.Fatal(err) 35 | } 36 | 37 | if v.String() != versionString { 38 | t.Fatalf("JSON unmarshaled semantic version not equal: expected %q, got %q", versionString, v.String()) 39 | } 40 | 41 | badVersionString := strconv.Quote("3.1.4.1.5.9.2.6.5-other-digits-of-pi") 42 | if err := json.Unmarshal([]byte(badVersionString), &v); err == nil { 43 | t.Fatal("expected JSON unmarshal error, got nil") 44 | } 45 | 46 | if err := json.Unmarshal([]byte("3.1"), &v); err == nil { 47 | t.Fatal("expected JSON unmarshal error, got nil") 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /v4/range.go: -------------------------------------------------------------------------------- 1 | package semver 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | "unicode" 8 | ) 9 | 10 | type wildcardType int 11 | 12 | const ( 13 | noneWildcard wildcardType = iota 14 | majorWildcard wildcardType = 1 15 | minorWildcard wildcardType = 2 16 | patchWildcard wildcardType = 3 17 | ) 18 | 19 | func wildcardTypefromInt(i int) wildcardType { 20 | switch i { 21 | case 1: 22 | return majorWildcard 23 | case 2: 24 | return minorWildcard 25 | case 3: 26 | return patchWildcard 27 | default: 28 | return noneWildcard 29 | } 30 | } 31 | 32 | type comparator func(Version, Version) bool 33 | 34 | var ( 35 | compEQ comparator = func(v1 Version, v2 Version) bool { 36 | return v1.Compare(v2) == 0 37 | } 38 | compNE = func(v1 Version, v2 Version) bool { 39 | return v1.Compare(v2) != 0 40 | } 41 | compGT = func(v1 Version, v2 Version) bool { 42 | return v1.Compare(v2) == 1 43 | } 44 | compGE = func(v1 Version, v2 Version) bool { 45 | return v1.Compare(v2) >= 0 46 | } 47 | compLT = func(v1 Version, v2 Version) bool { 48 | return v1.Compare(v2) == -1 49 | } 50 | compLE = func(v1 Version, v2 Version) bool { 51 | return v1.Compare(v2) <= 0 52 | } 53 | ) 54 | 55 | type versionRange struct { 56 | v Version 57 | c comparator 58 | } 59 | 60 | // rangeFunc creates a Range from the given versionRange. 61 | func (vr *versionRange) rangeFunc() Range { 62 | return Range(func(v Version) bool { 63 | return vr.c(v, vr.v) 64 | }) 65 | } 66 | 67 | // Range represents a range of versions. 68 | // A Range can be used to check if a Version satisfies it: 69 | // 70 | // range, err := semver.ParseRange(">1.0.0 <2.0.0") 71 | // range(semver.MustParse("1.1.1") // returns true 72 | type Range func(Version) bool 73 | 74 | // OR combines the existing Range with another Range using logical OR. 75 | func (rf Range) OR(f Range) Range { 76 | return Range(func(v Version) bool { 77 | return rf(v) || f(v) 78 | }) 79 | } 80 | 81 | // AND combines the existing Range with another Range using logical AND. 82 | func (rf Range) AND(f Range) Range { 83 | return Range(func(v Version) bool { 84 | return rf(v) && f(v) 85 | }) 86 | } 87 | 88 | // ParseRange parses a range and returns a Range. 89 | // If the range could not be parsed an error is returned. 90 | // 91 | // Valid ranges are: 92 | // - "<1.0.0" 93 | // - "<=1.0.0" 94 | // - ">1.0.0" 95 | // - ">=1.0.0" 96 | // - "1.0.0", "=1.0.0", "==1.0.0" 97 | // - "!1.0.0", "!=1.0.0" 98 | // 99 | // A Range can consist of multiple ranges separated by space: 100 | // Ranges can be linked by logical AND: 101 | // - ">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" 102 | // - ">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 103 | // 104 | // Ranges can also be linked by logical OR: 105 | // - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x" 106 | // 107 | // AND has a higher precedence than OR. It's not possible to use brackets. 108 | // 109 | // Ranges can be combined by both AND and OR 110 | // 111 | // - `>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` 112 | func ParseRange(s string) (Range, error) { 113 | parts := splitAndTrim(s) 114 | orParts, err := splitORParts(parts) 115 | if err != nil { 116 | return nil, err 117 | } 118 | expandedParts, err := expandWildcardVersion(orParts) 119 | if err != nil { 120 | return nil, err 121 | } 122 | var orFn Range 123 | for _, p := range expandedParts { 124 | var andFn Range 125 | for _, ap := range p { 126 | opStr, vStr, err := splitComparatorVersion(ap) 127 | if err != nil { 128 | return nil, err 129 | } 130 | vr, err := buildVersionRange(opStr, vStr) 131 | if err != nil { 132 | return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err) 133 | } 134 | rf := vr.rangeFunc() 135 | 136 | // Set function 137 | if andFn == nil { 138 | andFn = rf 139 | } else { // Combine with existing function 140 | andFn = andFn.AND(rf) 141 | } 142 | } 143 | if orFn == nil { 144 | orFn = andFn 145 | } else { 146 | orFn = orFn.OR(andFn) 147 | } 148 | 149 | } 150 | return orFn, nil 151 | } 152 | 153 | // splitORParts splits the already cleaned parts by '||'. 154 | // Checks for invalid positions of the operator and returns an 155 | // error if found. 156 | func splitORParts(parts []string) ([][]string, error) { 157 | var ORparts [][]string 158 | last := 0 159 | for i, p := range parts { 160 | if p == "||" { 161 | if i == 0 { 162 | return nil, fmt.Errorf("First element in range is '||'") 163 | } 164 | ORparts = append(ORparts, parts[last:i]) 165 | last = i + 1 166 | } 167 | } 168 | if last == len(parts) { 169 | return nil, fmt.Errorf("Last element in range is '||'") 170 | } 171 | ORparts = append(ORparts, parts[last:]) 172 | return ORparts, nil 173 | } 174 | 175 | // buildVersionRange takes a slice of 2: operator and version 176 | // and builds a versionRange, otherwise an error. 177 | func buildVersionRange(opStr, vStr string) (*versionRange, error) { 178 | c := parseComparator(opStr) 179 | if c == nil { 180 | return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, "")) 181 | } 182 | v, err := Parse(vStr) 183 | if err != nil { 184 | return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err) 185 | } 186 | 187 | return &versionRange{ 188 | v: v, 189 | c: c, 190 | }, nil 191 | 192 | } 193 | 194 | // inArray checks if a byte is contained in an array of bytes 195 | func inArray(s byte, list []byte) bool { 196 | for _, el := range list { 197 | if el == s { 198 | return true 199 | } 200 | } 201 | return false 202 | } 203 | 204 | // splitAndTrim splits a range string by spaces and cleans whitespaces 205 | func splitAndTrim(s string) (result []string) { 206 | last := 0 207 | var lastChar byte 208 | excludeFromSplit := []byte{'>', '<', '='} 209 | for i := 0; i < len(s); i++ { 210 | if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) { 211 | if last < i-1 { 212 | result = append(result, s[last:i]) 213 | } 214 | last = i + 1 215 | } else if s[i] != ' ' { 216 | lastChar = s[i] 217 | } 218 | } 219 | if last < len(s)-1 { 220 | result = append(result, s[last:]) 221 | } 222 | 223 | for i, v := range result { 224 | result[i] = strings.Replace(v, " ", "", -1) 225 | } 226 | 227 | // parts := strings.Split(s, " ") 228 | // for _, x := range parts { 229 | // if s := strings.TrimSpace(x); len(s) != 0 { 230 | // result = append(result, s) 231 | // } 232 | // } 233 | return 234 | } 235 | 236 | // splitComparatorVersion splits the comparator from the version. 237 | // Input must be free of leading or trailing spaces. 238 | func splitComparatorVersion(s string) (string, string, error) { 239 | i := strings.IndexFunc(s, unicode.IsDigit) 240 | if i == -1 { 241 | return "", "", fmt.Errorf("Could not get version from string: %q", s) 242 | } 243 | return strings.TrimSpace(s[0:i]), s[i:], nil 244 | } 245 | 246 | // getWildcardType will return the type of wildcard that the 247 | // passed version contains 248 | func getWildcardType(vStr string) wildcardType { 249 | parts := strings.Split(vStr, ".") 250 | nparts := len(parts) 251 | wildcard := parts[nparts-1] 252 | 253 | possibleWildcardType := wildcardTypefromInt(nparts) 254 | if wildcard == "x" { 255 | return possibleWildcardType 256 | } 257 | 258 | return noneWildcard 259 | } 260 | 261 | // createVersionFromWildcard will convert a wildcard version 262 | // into a regular version, replacing 'x's with '0's, handling 263 | // special cases like '1.x.x' and '1.x' 264 | func createVersionFromWildcard(vStr string) string { 265 | // handle 1.x.x 266 | vStr2 := strings.Replace(vStr, ".x.x", ".x", 1) 267 | vStr2 = strings.Replace(vStr2, ".x", ".0", 1) 268 | parts := strings.Split(vStr2, ".") 269 | 270 | // handle 1.x 271 | if len(parts) == 2 { 272 | return vStr2 + ".0" 273 | } 274 | 275 | return vStr2 276 | } 277 | 278 | // incrementMajorVersion will increment the major version 279 | // of the passed version 280 | func incrementMajorVersion(vStr string) (string, error) { 281 | parts := strings.Split(vStr, ".") 282 | i, err := strconv.Atoi(parts[0]) 283 | if err != nil { 284 | return "", err 285 | } 286 | parts[0] = strconv.Itoa(i + 1) 287 | 288 | return strings.Join(parts, "."), nil 289 | } 290 | 291 | // incrementMajorVersion will increment the minor version 292 | // of the passed version 293 | func incrementMinorVersion(vStr string) (string, error) { 294 | parts := strings.Split(vStr, ".") 295 | i, err := strconv.Atoi(parts[1]) 296 | if err != nil { 297 | return "", err 298 | } 299 | parts[1] = strconv.Itoa(i + 1) 300 | 301 | return strings.Join(parts, "."), nil 302 | } 303 | 304 | // expandWildcardVersion will expand wildcards inside versions 305 | // following these rules: 306 | // 307 | // * when dealing with patch wildcards: 308 | // >= 1.2.x will become >= 1.2.0 309 | // <= 1.2.x will become < 1.3.0 310 | // > 1.2.x will become >= 1.3.0 311 | // < 1.2.x will become < 1.2.0 312 | // != 1.2.x will become < 1.2.0 >= 1.3.0 313 | // 314 | // * when dealing with minor wildcards: 315 | // >= 1.x will become >= 1.0.0 316 | // <= 1.x will become < 2.0.0 317 | // > 1.x will become >= 2.0.0 318 | // < 1.0 will become < 1.0.0 319 | // != 1.x will become < 1.0.0 >= 2.0.0 320 | // 321 | // * when dealing with wildcards without 322 | // version operator: 323 | // 1.2.x will become >= 1.2.0 < 1.3.0 324 | // 1.x will become >= 1.0.0 < 2.0.0 325 | func expandWildcardVersion(parts [][]string) ([][]string, error) { 326 | var expandedParts [][]string 327 | for _, p := range parts { 328 | var newParts []string 329 | for _, ap := range p { 330 | if strings.Contains(ap, "x") { 331 | opStr, vStr, err := splitComparatorVersion(ap) 332 | if err != nil { 333 | return nil, err 334 | } 335 | 336 | versionWildcardType := getWildcardType(vStr) 337 | flatVersion := createVersionFromWildcard(vStr) 338 | 339 | var resultOperator string 340 | var shouldIncrementVersion bool 341 | switch opStr { 342 | case ">": 343 | resultOperator = ">=" 344 | shouldIncrementVersion = true 345 | case ">=": 346 | resultOperator = ">=" 347 | case "<": 348 | resultOperator = "<" 349 | case "<=": 350 | resultOperator = "<" 351 | shouldIncrementVersion = true 352 | case "", "=", "==": 353 | newParts = append(newParts, ">="+flatVersion) 354 | resultOperator = "<" 355 | shouldIncrementVersion = true 356 | case "!=", "!": 357 | newParts = append(newParts, "<"+flatVersion) 358 | resultOperator = ">=" 359 | shouldIncrementVersion = true 360 | } 361 | 362 | var resultVersion string 363 | if shouldIncrementVersion { 364 | switch versionWildcardType { 365 | case patchWildcard: 366 | resultVersion, _ = incrementMinorVersion(flatVersion) 367 | case minorWildcard: 368 | resultVersion, _ = incrementMajorVersion(flatVersion) 369 | } 370 | } else { 371 | resultVersion = flatVersion 372 | } 373 | 374 | ap = resultOperator + resultVersion 375 | } 376 | newParts = append(newParts, ap) 377 | } 378 | expandedParts = append(expandedParts, newParts) 379 | } 380 | 381 | return expandedParts, nil 382 | } 383 | 384 | func parseComparator(s string) comparator { 385 | switch s { 386 | case "==": 387 | fallthrough 388 | case "": 389 | fallthrough 390 | case "=": 391 | return compEQ 392 | case ">": 393 | return compGT 394 | case ">=": 395 | return compGE 396 | case "<": 397 | return compLT 398 | case "<=": 399 | return compLE 400 | case "!": 401 | fallthrough 402 | case "!=": 403 | return compNE 404 | } 405 | 406 | return nil 407 | } 408 | 409 | // MustParseRange is like ParseRange but panics if the range cannot be parsed. 410 | func MustParseRange(s string) Range { 411 | r, err := ParseRange(s) 412 | if err != nil { 413 | panic(`semver: ParseRange(` + s + `): ` + err.Error()) 414 | } 415 | return r 416 | } 417 | -------------------------------------------------------------------------------- /v4/range_test.go: -------------------------------------------------------------------------------- 1 | package semver 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | type wildcardTypeTest struct { 10 | input string 11 | wildcardType wildcardType 12 | } 13 | 14 | type comparatorTest struct { 15 | input string 16 | comparator func(comparator) bool 17 | } 18 | 19 | func TestParseComparator(t *testing.T) { 20 | compatorTests := []comparatorTest{ 21 | {">", testGT}, 22 | {">=", testGE}, 23 | {"<", testLT}, 24 | {"<=", testLE}, 25 | {"", testEQ}, 26 | {"=", testEQ}, 27 | {"==", testEQ}, 28 | {"!=", testNE}, 29 | {"!", testNE}, 30 | {"-", nil}, 31 | {"<==", nil}, 32 | {"<<", nil}, 33 | {">>", nil}, 34 | } 35 | 36 | for _, tc := range compatorTests { 37 | if c := parseComparator(tc.input); c == nil { 38 | if tc.comparator != nil { 39 | t.Errorf("Comparator nil for case %q\n", tc.input) 40 | } 41 | } else if !tc.comparator(c) { 42 | t.Errorf("Invalid comparator for case %q\n", tc.input) 43 | } 44 | } 45 | } 46 | 47 | var ( 48 | v1 = MustParse("1.2.2") 49 | v2 = MustParse("1.2.3") 50 | v3 = MustParse("1.2.4") 51 | ) 52 | 53 | func testEQ(f comparator) bool { 54 | return f(v1, v1) && !f(v1, v2) 55 | } 56 | 57 | func testNE(f comparator) bool { 58 | return !f(v1, v1) && f(v1, v2) 59 | } 60 | 61 | func testGT(f comparator) bool { 62 | return f(v2, v1) && f(v3, v2) && !f(v1, v2) && !f(v1, v1) 63 | } 64 | 65 | func testGE(f comparator) bool { 66 | return f(v2, v1) && f(v3, v2) && !f(v1, v2) 67 | } 68 | 69 | func testLT(f comparator) bool { 70 | return f(v1, v2) && f(v2, v3) && !f(v2, v1) && !f(v1, v1) 71 | } 72 | 73 | func testLE(f comparator) bool { 74 | return f(v1, v2) && f(v2, v3) && !f(v2, v1) 75 | } 76 | 77 | func TestSplitAndTrim(t *testing.T) { 78 | tests := []struct { 79 | i string 80 | s []string 81 | }{ 82 | {"1.2.3 1.2.3", []string{"1.2.3", "1.2.3"}}, 83 | {" 1.2.3 1.2.3 ", []string{"1.2.3", "1.2.3"}}, // Spaces 84 | {" >= 1.2.3 <= 1.2.3 ", []string{">=1.2.3", "<=1.2.3"}}, // Spaces between operator and version 85 | {"1.2.3 || >=1.2.3 <1.2.3", []string{"1.2.3", "||", ">=1.2.3", "<1.2.3"}}, 86 | {" 1.2.3 || >=1.2.3 <1.2.3 ", []string{"1.2.3", "||", ">=1.2.3", "<1.2.3"}}, 87 | } 88 | 89 | for _, tc := range tests { 90 | p := splitAndTrim(tc.i) 91 | if !reflect.DeepEqual(p, tc.s) { 92 | t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p) 93 | } 94 | } 95 | } 96 | 97 | func TestSplitComparatorVersion(t *testing.T) { 98 | tests := []struct { 99 | i string 100 | p []string 101 | }{ 102 | {">1.2.3", []string{">", "1.2.3"}}, 103 | {">=1.2.3", []string{">=", "1.2.3"}}, 104 | {"<1.2.3", []string{"<", "1.2.3"}}, 105 | {"<=1.2.3", []string{"<=", "1.2.3"}}, 106 | {"1.2.3", []string{"", "1.2.3"}}, 107 | {"=1.2.3", []string{"=", "1.2.3"}}, 108 | {"==1.2.3", []string{"==", "1.2.3"}}, 109 | {"!=1.2.3", []string{"!=", "1.2.3"}}, 110 | {"!1.2.3", []string{"!", "1.2.3"}}, 111 | {"error", nil}, 112 | } 113 | for _, tc := range tests { 114 | if op, v, err := splitComparatorVersion(tc.i); err != nil { 115 | if tc.p != nil { 116 | t.Errorf("Invalid for case %q: Expected %q, got error %q", tc.i, tc.p, err) 117 | } 118 | } else if op != tc.p[0] { 119 | t.Errorf("Invalid operator for case %q: Expected %q, got: %q", tc.i, tc.p[0], op) 120 | } else if v != tc.p[1] { 121 | t.Errorf("Invalid version for case %q: Expected %q, got: %q", tc.i, tc.p[1], v) 122 | } 123 | 124 | } 125 | } 126 | 127 | func TestBuildVersionRange(t *testing.T) { 128 | tests := []struct { 129 | opStr string 130 | vStr string 131 | c func(comparator) bool 132 | v string 133 | }{ 134 | {">", "1.2.3", testGT, "1.2.3"}, 135 | {">=", "1.2.3", testGE, "1.2.3"}, 136 | {"<", "1.2.3", testLT, "1.2.3"}, 137 | {"<=", "1.2.3", testLE, "1.2.3"}, 138 | {"", "1.2.3", testEQ, "1.2.3"}, 139 | {"=", "1.2.3", testEQ, "1.2.3"}, 140 | {"==", "1.2.3", testEQ, "1.2.3"}, 141 | {"!=", "1.2.3", testNE, "1.2.3"}, 142 | {"!", "1.2.3", testNE, "1.2.3"}, 143 | {">>", "1.2.3", nil, ""}, // Invalid comparator 144 | {"=", "invalid", nil, ""}, // Invalid version 145 | } 146 | 147 | for _, tc := range tests { 148 | if r, err := buildVersionRange(tc.opStr, tc.vStr); err != nil { 149 | if tc.c != nil { 150 | t.Errorf("Invalid for case %q: Expected %q, got error %q", strings.Join([]string{tc.opStr, tc.vStr}, ""), tc.v, err) 151 | } 152 | } else if r == nil { 153 | t.Errorf("Invalid for case %q: got nil", strings.Join([]string{tc.opStr, tc.vStr}, "")) 154 | } else { 155 | // test version 156 | if tv := MustParse(tc.v); !r.v.EQ(tv) { 157 | t.Errorf("Invalid for case %q: Expected version %q, got: %q", strings.Join([]string{tc.opStr, tc.vStr}, ""), tv, r.v) 158 | } 159 | // test comparator 160 | if r.c == nil { 161 | t.Errorf("Invalid for case %q: got nil comparator", strings.Join([]string{tc.opStr, tc.vStr}, "")) 162 | continue 163 | } 164 | if !tc.c(r.c) { 165 | t.Errorf("Invalid comparator for case %q\n", strings.Join([]string{tc.opStr, tc.vStr}, "")) 166 | } 167 | } 168 | } 169 | 170 | } 171 | 172 | func TestSplitORParts(t *testing.T) { 173 | tests := []struct { 174 | i []string 175 | o [][]string 176 | }{ 177 | {[]string{">1.2.3", "||", "<1.2.3", "||", "=1.2.3"}, [][]string{ 178 | {">1.2.3"}, 179 | {"<1.2.3"}, 180 | {"=1.2.3"}, 181 | }}, 182 | {[]string{">1.2.3", "<1.2.3", "||", "=1.2.3"}, [][]string{ 183 | {">1.2.3", "<1.2.3"}, 184 | {"=1.2.3"}, 185 | }}, 186 | {[]string{">1.2.3", "||"}, nil}, 187 | {[]string{"||", ">1.2.3"}, nil}, 188 | } 189 | for _, tc := range tests { 190 | o, err := splitORParts(tc.i) 191 | if err != nil && tc.o != nil { 192 | t.Errorf("Unexpected error for case %q: %s", tc.i, err) 193 | } 194 | if !reflect.DeepEqual(tc.o, o) { 195 | t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.o, o) 196 | } 197 | } 198 | } 199 | 200 | func TestGetWildcardType(t *testing.T) { 201 | wildcardTypeTests := []wildcardTypeTest{ 202 | {"x", majorWildcard}, 203 | {"1.x", minorWildcard}, 204 | {"1.2.x", patchWildcard}, 205 | {"fo.o.b.ar", noneWildcard}, 206 | } 207 | 208 | for _, tc := range wildcardTypeTests { 209 | o := getWildcardType(tc.input) 210 | if o != tc.wildcardType { 211 | t.Errorf("Invalid for case: %q: Expected %q, got: %q", tc.input, tc.wildcardType, o) 212 | } 213 | } 214 | } 215 | 216 | func TestCreateVersionFromWildcard(t *testing.T) { 217 | tests := []struct { 218 | i string 219 | s string 220 | }{ 221 | {"1.2.x", "1.2.0"}, 222 | {"1.x", "1.0.0"}, 223 | } 224 | 225 | for _, tc := range tests { 226 | p := createVersionFromWildcard(tc.i) 227 | if p != tc.s { 228 | t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p) 229 | } 230 | } 231 | } 232 | 233 | func TestIncrementMajorVersion(t *testing.T) { 234 | tests := []struct { 235 | i string 236 | s string 237 | }{ 238 | {"1.2.3", "2.2.3"}, 239 | {"1.2", "2.2"}, 240 | {"foo.bar", ""}, 241 | } 242 | 243 | for _, tc := range tests { 244 | p, _ := incrementMajorVersion(tc.i) 245 | if p != tc.s { 246 | t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p) 247 | } 248 | } 249 | } 250 | 251 | func TestIncrementMinorVersion(t *testing.T) { 252 | tests := []struct { 253 | i string 254 | s string 255 | }{ 256 | {"1.2.3", "1.3.3"}, 257 | {"1.2", "1.3"}, 258 | {"foo.bar", ""}, 259 | } 260 | 261 | for _, tc := range tests { 262 | p, _ := incrementMinorVersion(tc.i) 263 | if p != tc.s { 264 | t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p) 265 | } 266 | } 267 | } 268 | 269 | func TestExpandWildcardVersion(t *testing.T) { 270 | tests := []struct { 271 | i [][]string 272 | o [][]string 273 | }{ 274 | {[][]string{{"foox"}}, nil}, 275 | {[][]string{{">=1.2.x"}}, [][]string{{">=1.2.0"}}}, 276 | {[][]string{{"<=1.2.x"}}, [][]string{{"<1.3.0"}}}, 277 | {[][]string{{">1.2.x"}}, [][]string{{">=1.3.0"}}}, 278 | {[][]string{{"<1.2.x"}}, [][]string{{"<1.2.0"}}}, 279 | {[][]string{{"!=1.2.x"}}, [][]string{{"<1.2.0", ">=1.3.0"}}}, 280 | {[][]string{{">=1.x"}}, [][]string{{">=1.0.0"}}}, 281 | {[][]string{{"<=1.x"}}, [][]string{{"<2.0.0"}}}, 282 | {[][]string{{">1.x"}}, [][]string{{">=2.0.0"}}}, 283 | {[][]string{{"<1.x"}}, [][]string{{"<1.0.0"}}}, 284 | {[][]string{{"!=1.x"}}, [][]string{{"<1.0.0", ">=2.0.0"}}}, 285 | {[][]string{{"1.2.x"}}, [][]string{{">=1.2.0", "<1.3.0"}}}, 286 | {[][]string{{"1.x"}}, [][]string{{">=1.0.0", "<2.0.0"}}}, 287 | } 288 | 289 | for _, tc := range tests { 290 | o, _ := expandWildcardVersion(tc.i) 291 | if !reflect.DeepEqual(tc.o, o) { 292 | t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.o, o) 293 | } 294 | } 295 | } 296 | 297 | func TestVersionRangeToRange(t *testing.T) { 298 | vr := versionRange{ 299 | v: MustParse("1.2.3"), 300 | c: compLT, 301 | } 302 | rf := vr.rangeFunc() 303 | if !rf(MustParse("1.2.2")) || rf(MustParse("1.2.3")) { 304 | t.Errorf("Invalid conversion to range func") 305 | } 306 | } 307 | 308 | func TestRangeAND(t *testing.T) { 309 | v := MustParse("1.2.2") 310 | v1 := MustParse("1.2.1") 311 | v2 := MustParse("1.2.3") 312 | rf1 := Range(func(v Version) bool { 313 | return v.GT(v1) 314 | }) 315 | rf2 := Range(func(v Version) bool { 316 | return v.LT(v2) 317 | }) 318 | rf := rf1.AND(rf2) 319 | if rf(v1) { 320 | t.Errorf("Invalid rangefunc, accepted: %s", v1) 321 | } 322 | if rf(v2) { 323 | t.Errorf("Invalid rangefunc, accepted: %s", v2) 324 | } 325 | if !rf(v) { 326 | t.Errorf("Invalid rangefunc, did not accept: %s", v) 327 | } 328 | } 329 | 330 | func TestRangeOR(t *testing.T) { 331 | tests := []struct { 332 | v Version 333 | b bool 334 | }{ 335 | {MustParse("1.2.0"), true}, 336 | {MustParse("1.2.2"), false}, 337 | {MustParse("1.2.4"), true}, 338 | } 339 | v1 := MustParse("1.2.1") 340 | v2 := MustParse("1.2.3") 341 | rf1 := Range(func(v Version) bool { 342 | return v.LT(v1) 343 | }) 344 | rf2 := Range(func(v Version) bool { 345 | return v.GT(v2) 346 | }) 347 | rf := rf1.OR(rf2) 348 | for _, tc := range tests { 349 | if r := rf(tc.v); r != tc.b { 350 | t.Errorf("Invalid for case %q: Expected %t, got %t", tc.v, tc.b, r) 351 | } 352 | } 353 | } 354 | 355 | func TestParseRange(t *testing.T) { 356 | type tv struct { 357 | v string 358 | b bool 359 | } 360 | tests := []struct { 361 | i string 362 | t []tv 363 | }{ 364 | // Simple expressions 365 | {">1.2.3", []tv{ 366 | {"1.2.2", false}, 367 | {"1.2.3", false}, 368 | {"1.2.4", true}, 369 | }}, 370 | {">=1.2.3", []tv{ 371 | {"1.2.3", true}, 372 | {"1.2.4", true}, 373 | {"1.2.2", false}, 374 | }}, 375 | {"<1.2.3", []tv{ 376 | {"1.2.2", true}, 377 | {"1.2.3", false}, 378 | {"1.2.4", false}, 379 | }}, 380 | {"<=1.2.3", []tv{ 381 | {"1.2.2", true}, 382 | {"1.2.3", true}, 383 | {"1.2.4", false}, 384 | }}, 385 | {"1.2.3", []tv{ 386 | {"1.2.2", false}, 387 | {"1.2.3", true}, 388 | {"1.2.4", false}, 389 | }}, 390 | {"=1.2.3", []tv{ 391 | {"1.2.2", false}, 392 | {"1.2.3", true}, 393 | {"1.2.4", false}, 394 | }}, 395 | {"==1.2.3", []tv{ 396 | {"1.2.2", false}, 397 | {"1.2.3", true}, 398 | {"1.2.4", false}, 399 | }}, 400 | {"!=1.2.3", []tv{ 401 | {"1.2.2", true}, 402 | {"1.2.3", false}, 403 | {"1.2.4", true}, 404 | }}, 405 | {"!1.2.3", []tv{ 406 | {"1.2.2", true}, 407 | {"1.2.3", false}, 408 | {"1.2.4", true}, 409 | }}, 410 | // Simple Expression errors 411 | {">>1.2.3", nil}, 412 | {"!1.2.3", nil}, 413 | {"1.0", nil}, 414 | {"string", nil}, 415 | {"", nil}, 416 | {"fo.ob.ar.x", nil}, 417 | // AND Expressions 418 | {">1.2.2 <1.2.4", []tv{ 419 | {"1.2.2", false}, 420 | {"1.2.3", true}, 421 | {"1.2.4", false}, 422 | }}, 423 | {"<1.2.2 <1.2.4", []tv{ 424 | {"1.2.1", true}, 425 | {"1.2.2", false}, 426 | {"1.2.3", false}, 427 | {"1.2.4", false}, 428 | }}, 429 | {">1.2.2 <1.2.5 !=1.2.4", []tv{ 430 | {"1.2.2", false}, 431 | {"1.2.3", true}, 432 | {"1.2.4", false}, 433 | {"1.2.5", false}, 434 | }}, 435 | {">1.2.2 <1.2.5 !1.2.4", []tv{ 436 | {"1.2.2", false}, 437 | {"1.2.3", true}, 438 | {"1.2.4", false}, 439 | {"1.2.5", false}, 440 | }}, 441 | // OR Expressions 442 | {">1.2.2 || <1.2.4", []tv{ 443 | {"1.2.2", true}, 444 | {"1.2.3", true}, 445 | {"1.2.4", true}, 446 | }}, 447 | {"<1.2.2 || >1.2.4", []tv{ 448 | {"1.2.2", false}, 449 | {"1.2.3", false}, 450 | {"1.2.4", false}, 451 | }}, 452 | // Wildcard expressions 453 | {">1.x", []tv{ 454 | {"0.1.9", false}, 455 | {"1.2.6", false}, 456 | {"1.9.0", false}, 457 | {"2.0.0", true}, 458 | }}, 459 | {">1.2.x", []tv{ 460 | {"1.1.9", false}, 461 | {"1.2.6", false}, 462 | {"1.3.0", true}, 463 | }}, 464 | // Combined Expressions 465 | {">1.2.2 <1.2.4 || >=2.0.0", []tv{ 466 | {"1.2.2", false}, 467 | {"1.2.3", true}, 468 | {"1.2.4", false}, 469 | {"2.0.0", true}, 470 | {"2.0.1", true}, 471 | }}, 472 | {"1.x || >=2.0.x <2.2.x", []tv{ 473 | {"0.9.2", false}, 474 | {"1.2.2", true}, 475 | {"2.0.0", true}, 476 | {"2.1.8", true}, 477 | {"2.2.0", false}, 478 | }}, 479 | {">1.2.2 <1.2.4 || >=2.0.0 <3.0.0", []tv{ 480 | {"1.2.2", false}, 481 | {"1.2.3", true}, 482 | {"1.2.4", false}, 483 | {"2.0.0", true}, 484 | {"2.0.1", true}, 485 | {"2.9.9", true}, 486 | {"3.0.0", false}, 487 | }}, 488 | } 489 | 490 | for _, tc := range tests { 491 | r, err := ParseRange(tc.i) 492 | if err != nil && tc.t != nil { 493 | t.Errorf("Error parsing range %q: %s", tc.i, err) 494 | continue 495 | } 496 | for _, tvc := range tc.t { 497 | v := MustParse(tvc.v) 498 | if res := r(v); res != tvc.b { 499 | t.Errorf("Invalid for case %q matching %q: Expected %t, got: %t", tc.i, tvc.v, tvc.b, res) 500 | } 501 | } 502 | 503 | } 504 | } 505 | 506 | func TestMustParseRange(t *testing.T) { 507 | testCase := ">1.2.2 <1.2.4 || >=2.0.0 <3.0.0" 508 | r := MustParseRange(testCase) 509 | if !r(MustParse("1.2.3")) { 510 | t.Errorf("Unexpected range behavior on MustParseRange") 511 | } 512 | } 513 | 514 | func TestMustParseRange_panic(t *testing.T) { 515 | defer func() { 516 | if recover() == nil { 517 | t.Errorf("Should have panicked") 518 | } 519 | }() 520 | _ = MustParseRange("invalid version") 521 | } 522 | 523 | func BenchmarkRangeParseSimple(b *testing.B) { 524 | const VERSION = ">1.0.0" 525 | b.ReportAllocs() 526 | b.ResetTimer() 527 | for n := 0; n < b.N; n++ { 528 | _, _ = ParseRange(VERSION) 529 | } 530 | } 531 | 532 | func BenchmarkRangeParseAverage(b *testing.B) { 533 | const VERSION = ">=1.0.0 <2.0.0" 534 | b.ReportAllocs() 535 | b.ResetTimer() 536 | for n := 0; n < b.N; n++ { 537 | _, _ = ParseRange(VERSION) 538 | } 539 | } 540 | 541 | func BenchmarkRangeParseComplex(b *testing.B) { 542 | const VERSION = ">=1.0.0 <2.0.0 || >=3.0.1 <4.0.0 !=3.0.3 || >=5.0.0" 543 | b.ReportAllocs() 544 | b.ResetTimer() 545 | for n := 0; n < b.N; n++ { 546 | _, _ = ParseRange(VERSION) 547 | } 548 | } 549 | 550 | func BenchmarkRangeMatchSimple(b *testing.B) { 551 | const VERSION = ">1.0.0" 552 | r, _ := ParseRange(VERSION) 553 | v := MustParse("2.0.0") 554 | b.ReportAllocs() 555 | b.ResetTimer() 556 | for n := 0; n < b.N; n++ { 557 | r(v) 558 | } 559 | } 560 | 561 | func BenchmarkRangeMatchAverage(b *testing.B) { 562 | const VERSION = ">=1.0.0 <2.0.0" 563 | r, _ := ParseRange(VERSION) 564 | v := MustParse("1.2.3") 565 | b.ReportAllocs() 566 | b.ResetTimer() 567 | for n := 0; n < b.N; n++ { 568 | r(v) 569 | } 570 | } 571 | 572 | func BenchmarkRangeMatchComplex(b *testing.B) { 573 | const VERSION = ">=1.0.0 <2.0.0 || >=3.0.1 <4.0.0 !=3.0.3 || >=5.0.0" 574 | r, _ := ParseRange(VERSION) 575 | v := MustParse("5.0.1") 576 | b.ReportAllocs() 577 | b.ResetTimer() 578 | for n := 0; n < b.N; n++ { 579 | r(v) 580 | } 581 | } 582 | -------------------------------------------------------------------------------- /v4/semver.go: -------------------------------------------------------------------------------- 1 | package semver 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | numbers string = "0123456789" 12 | alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" 13 | alphanum = alphas + numbers 14 | ) 15 | 16 | // SpecVersion is the latest fully supported spec version of semver 17 | var SpecVersion = Version{ 18 | Major: 2, 19 | Minor: 0, 20 | Patch: 0, 21 | } 22 | 23 | // Version represents a semver compatible version 24 | type Version struct { 25 | Major uint64 26 | Minor uint64 27 | Patch uint64 28 | Pre []PRVersion 29 | Build []string //No Precedence 30 | } 31 | 32 | // Version to string 33 | func (v Version) String() string { 34 | b := make([]byte, 0, 5) 35 | b = strconv.AppendUint(b, v.Major, 10) 36 | b = append(b, '.') 37 | b = strconv.AppendUint(b, v.Minor, 10) 38 | b = append(b, '.') 39 | b = strconv.AppendUint(b, v.Patch, 10) 40 | 41 | if len(v.Pre) > 0 { 42 | b = append(b, '-') 43 | b = append(b, v.Pre[0].String()...) 44 | 45 | for _, pre := range v.Pre[1:] { 46 | b = append(b, '.') 47 | b = append(b, pre.String()...) 48 | } 49 | } 50 | 51 | if len(v.Build) > 0 { 52 | b = append(b, '+') 53 | b = append(b, v.Build[0]...) 54 | 55 | for _, build := range v.Build[1:] { 56 | b = append(b, '.') 57 | b = append(b, build...) 58 | } 59 | } 60 | 61 | return string(b) 62 | } 63 | 64 | // FinalizeVersion discards prerelease and build number and only returns 65 | // major, minor and patch number. 66 | func (v Version) FinalizeVersion() string { 67 | b := make([]byte, 0, 5) 68 | b = strconv.AppendUint(b, v.Major, 10) 69 | b = append(b, '.') 70 | b = strconv.AppendUint(b, v.Minor, 10) 71 | b = append(b, '.') 72 | b = strconv.AppendUint(b, v.Patch, 10) 73 | return string(b) 74 | } 75 | 76 | // Equals checks if v is equal to o. 77 | func (v Version) Equals(o Version) bool { 78 | return (v.Compare(o) == 0) 79 | } 80 | 81 | // EQ checks if v is equal to o. 82 | func (v Version) EQ(o Version) bool { 83 | return (v.Compare(o) == 0) 84 | } 85 | 86 | // NE checks if v is not equal to o. 87 | func (v Version) NE(o Version) bool { 88 | return (v.Compare(o) != 0) 89 | } 90 | 91 | // GT checks if v is greater than o. 92 | func (v Version) GT(o Version) bool { 93 | return (v.Compare(o) == 1) 94 | } 95 | 96 | // GTE checks if v is greater than or equal to o. 97 | func (v Version) GTE(o Version) bool { 98 | return (v.Compare(o) >= 0) 99 | } 100 | 101 | // GE checks if v is greater than or equal to o. 102 | func (v Version) GE(o Version) bool { 103 | return (v.Compare(o) >= 0) 104 | } 105 | 106 | // LT checks if v is less than o. 107 | func (v Version) LT(o Version) bool { 108 | return (v.Compare(o) == -1) 109 | } 110 | 111 | // LTE checks if v is less than or equal to o. 112 | func (v Version) LTE(o Version) bool { 113 | return (v.Compare(o) <= 0) 114 | } 115 | 116 | // LE checks if v is less than or equal to o. 117 | func (v Version) LE(o Version) bool { 118 | return (v.Compare(o) <= 0) 119 | } 120 | 121 | // Compare compares Versions v to o: 122 | // -1 == v is less than o 123 | // 0 == v is equal to o 124 | // 1 == v is greater than o 125 | func (v Version) Compare(o Version) int { 126 | if v.Major != o.Major { 127 | if v.Major > o.Major { 128 | return 1 129 | } 130 | return -1 131 | } 132 | if v.Minor != o.Minor { 133 | if v.Minor > o.Minor { 134 | return 1 135 | } 136 | return -1 137 | } 138 | if v.Patch != o.Patch { 139 | if v.Patch > o.Patch { 140 | return 1 141 | } 142 | return -1 143 | } 144 | 145 | // Quick comparison if a version has no prerelease versions 146 | if len(v.Pre) == 0 && len(o.Pre) == 0 { 147 | return 0 148 | } else if len(v.Pre) == 0 && len(o.Pre) > 0 { 149 | return 1 150 | } else if len(v.Pre) > 0 && len(o.Pre) == 0 { 151 | return -1 152 | } 153 | 154 | i := 0 155 | for ; i < len(v.Pre) && i < len(o.Pre); i++ { 156 | if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 { 157 | continue 158 | } else if comp == 1 { 159 | return 1 160 | } else { 161 | return -1 162 | } 163 | } 164 | 165 | // If all pr versions are the equal but one has further prversion, this one greater 166 | if i == len(v.Pre) && i == len(o.Pre) { 167 | return 0 168 | } else if i == len(v.Pre) && i < len(o.Pre) { 169 | return -1 170 | } else { 171 | return 1 172 | } 173 | 174 | } 175 | 176 | // IncrementPatch increments the patch version 177 | func (v *Version) IncrementPatch() error { 178 | v.Patch++ 179 | return nil 180 | } 181 | 182 | // IncrementMinor increments the minor version 183 | func (v *Version) IncrementMinor() error { 184 | v.Minor++ 185 | v.Patch = 0 186 | return nil 187 | } 188 | 189 | // IncrementMajor increments the major version 190 | func (v *Version) IncrementMajor() error { 191 | v.Major++ 192 | v.Minor = 0 193 | v.Patch = 0 194 | return nil 195 | } 196 | 197 | // Validate validates v and returns error in case 198 | func (v Version) Validate() error { 199 | // Major, Minor, Patch already validated using uint64 200 | 201 | for _, pre := range v.Pre { 202 | if !pre.IsNum { //Numeric prerelease versions already uint64 203 | if len(pre.VersionStr) == 0 { 204 | return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr) 205 | } 206 | if !containsOnly(pre.VersionStr, alphanum) { 207 | return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr) 208 | } 209 | } 210 | } 211 | 212 | for _, build := range v.Build { 213 | if len(build) == 0 { 214 | return fmt.Errorf("Build meta data can not be empty %q", build) 215 | } 216 | if !containsOnly(build, alphanum) { 217 | return fmt.Errorf("Invalid character(s) found in build meta data %q", build) 218 | } 219 | } 220 | 221 | return nil 222 | } 223 | 224 | // New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error 225 | func New(s string) (*Version, error) { 226 | v, err := Parse(s) 227 | vp := &v 228 | return vp, err 229 | } 230 | 231 | // Make is an alias for Parse, parses version string and returns a validated Version or error 232 | func Make(s string) (Version, error) { 233 | return Parse(s) 234 | } 235 | 236 | // ParseTolerant allows for certain version specifications that do not strictly adhere to semver 237 | // specs to be parsed by this library. It does so by normalizing versions before passing them to 238 | // Parse(). It currently trims spaces, removes a "v" prefix, adds a 0 patch number to versions 239 | // with only major and minor components specified, and removes leading 0s. 240 | func ParseTolerant(s string) (Version, error) { 241 | s = strings.TrimSpace(s) 242 | s = strings.TrimPrefix(s, "v") 243 | 244 | // Split into major.minor.(patch+pr+meta) 245 | parts := strings.SplitN(s, ".", 3) 246 | // Remove leading zeros. 247 | for i, p := range parts { 248 | if len(p) > 1 { 249 | p = strings.TrimLeft(p, "0") 250 | if len(p) == 0 || !strings.ContainsAny(p[0:1], "0123456789") { 251 | p = "0" + p 252 | } 253 | parts[i] = p 254 | } 255 | } 256 | // Fill up shortened versions. 257 | if len(parts) < 3 { 258 | if strings.ContainsAny(parts[len(parts)-1], "+-") { 259 | return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data") 260 | } 261 | for len(parts) < 3 { 262 | parts = append(parts, "0") 263 | } 264 | } 265 | s = strings.Join(parts, ".") 266 | 267 | return Parse(s) 268 | } 269 | 270 | // Parse parses version string and returns a validated Version or error 271 | func Parse(s string) (Version, error) { 272 | if len(s) == 0 { 273 | return Version{}, errors.New("Version string empty") 274 | } 275 | 276 | // Split into major.minor.(patch+pr+meta) 277 | parts := strings.SplitN(s, ".", 3) 278 | if len(parts) != 3 { 279 | return Version{}, errors.New("No Major.Minor.Patch elements found") 280 | } 281 | 282 | // Major 283 | if !containsOnly(parts[0], numbers) { 284 | return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0]) 285 | } 286 | if hasLeadingZeroes(parts[0]) { 287 | return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0]) 288 | } 289 | major, err := strconv.ParseUint(parts[0], 10, 64) 290 | if err != nil { 291 | return Version{}, err 292 | } 293 | 294 | // Minor 295 | if !containsOnly(parts[1], numbers) { 296 | return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1]) 297 | } 298 | if hasLeadingZeroes(parts[1]) { 299 | return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1]) 300 | } 301 | minor, err := strconv.ParseUint(parts[1], 10, 64) 302 | if err != nil { 303 | return Version{}, err 304 | } 305 | 306 | v := Version{} 307 | v.Major = major 308 | v.Minor = minor 309 | 310 | var build, prerelease []string 311 | patchStr := parts[2] 312 | 313 | if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 { 314 | build = strings.Split(patchStr[buildIndex+1:], ".") 315 | patchStr = patchStr[:buildIndex] 316 | } 317 | 318 | if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 { 319 | prerelease = strings.Split(patchStr[preIndex+1:], ".") 320 | patchStr = patchStr[:preIndex] 321 | } 322 | 323 | if !containsOnly(patchStr, numbers) { 324 | return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr) 325 | } 326 | if hasLeadingZeroes(patchStr) { 327 | return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr) 328 | } 329 | patch, err := strconv.ParseUint(patchStr, 10, 64) 330 | if err != nil { 331 | return Version{}, err 332 | } 333 | 334 | v.Patch = patch 335 | 336 | // Prerelease 337 | for _, prstr := range prerelease { 338 | parsedPR, err := NewPRVersion(prstr) 339 | if err != nil { 340 | return Version{}, err 341 | } 342 | v.Pre = append(v.Pre, parsedPR) 343 | } 344 | 345 | // Build meta data 346 | for _, str := range build { 347 | if len(str) == 0 { 348 | return Version{}, errors.New("Build meta data is empty") 349 | } 350 | if !containsOnly(str, alphanum) { 351 | return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str) 352 | } 353 | v.Build = append(v.Build, str) 354 | } 355 | 356 | return v, nil 357 | } 358 | 359 | // MustParse is like Parse but panics if the version cannot be parsed. 360 | func MustParse(s string) Version { 361 | v, err := Parse(s) 362 | if err != nil { 363 | panic(`semver: Parse(` + s + `): ` + err.Error()) 364 | } 365 | return v 366 | } 367 | 368 | // PRVersion represents a PreRelease Version 369 | type PRVersion struct { 370 | VersionStr string 371 | VersionNum uint64 372 | IsNum bool 373 | } 374 | 375 | // NewPRVersion creates a new valid prerelease version 376 | func NewPRVersion(s string) (PRVersion, error) { 377 | if len(s) == 0 { 378 | return PRVersion{}, errors.New("Prerelease is empty") 379 | } 380 | v := PRVersion{} 381 | if containsOnly(s, numbers) { 382 | if hasLeadingZeroes(s) { 383 | return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s) 384 | } 385 | num, err := strconv.ParseUint(s, 10, 64) 386 | 387 | // Might never be hit, but just in case 388 | if err != nil { 389 | return PRVersion{}, err 390 | } 391 | v.VersionNum = num 392 | v.IsNum = true 393 | } else if containsOnly(s, alphanum) { 394 | v.VersionStr = s 395 | v.IsNum = false 396 | } else { 397 | return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s) 398 | } 399 | return v, nil 400 | } 401 | 402 | // IsNumeric checks if prerelease-version is numeric 403 | func (v PRVersion) IsNumeric() bool { 404 | return v.IsNum 405 | } 406 | 407 | // Compare compares two PreRelease Versions v and o: 408 | // -1 == v is less than o 409 | // 0 == v is equal to o 410 | // 1 == v is greater than o 411 | func (v PRVersion) Compare(o PRVersion) int { 412 | if v.IsNum && !o.IsNum { 413 | return -1 414 | } else if !v.IsNum && o.IsNum { 415 | return 1 416 | } else if v.IsNum && o.IsNum { 417 | if v.VersionNum == o.VersionNum { 418 | return 0 419 | } else if v.VersionNum > o.VersionNum { 420 | return 1 421 | } else { 422 | return -1 423 | } 424 | } else { // both are Alphas 425 | if v.VersionStr == o.VersionStr { 426 | return 0 427 | } else if v.VersionStr > o.VersionStr { 428 | return 1 429 | } else { 430 | return -1 431 | } 432 | } 433 | } 434 | 435 | // PreRelease version to string 436 | func (v PRVersion) String() string { 437 | if v.IsNum { 438 | return strconv.FormatUint(v.VersionNum, 10) 439 | } 440 | return v.VersionStr 441 | } 442 | 443 | func containsOnly(s string, set string) bool { 444 | return strings.IndexFunc(s, func(r rune) bool { 445 | return !strings.ContainsRune(set, r) 446 | }) == -1 447 | } 448 | 449 | func hasLeadingZeroes(s string) bool { 450 | return len(s) > 1 && s[0] == '0' 451 | } 452 | 453 | // NewBuildVersion creates a new valid build version 454 | func NewBuildVersion(s string) (string, error) { 455 | if len(s) == 0 { 456 | return "", errors.New("Buildversion is empty") 457 | } 458 | if !containsOnly(s, alphanum) { 459 | return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s) 460 | } 461 | return s, nil 462 | } 463 | 464 | // FinalizeVersion returns the major, minor and patch number only and discards 465 | // prerelease and build number. 466 | func FinalizeVersion(s string) (string, error) { 467 | v, err := Parse(s) 468 | if err != nil { 469 | return "", err 470 | } 471 | v.Pre = nil 472 | v.Build = nil 473 | 474 | finalVer := v.String() 475 | return finalVer, nil 476 | } 477 | -------------------------------------------------------------------------------- /v4/semver_test.go: -------------------------------------------------------------------------------- 1 | package semver 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func prstr(s string) PRVersion { 8 | return PRVersion{s, 0, false} 9 | } 10 | 11 | func prnum(i uint64) PRVersion { 12 | return PRVersion{"", i, true} 13 | } 14 | 15 | type formatTest struct { 16 | v Version 17 | result string 18 | } 19 | 20 | var formatTests = []formatTest{ 21 | {Version{1, 2, 3, nil, nil}, "1.2.3"}, 22 | {Version{0, 0, 1, nil, nil}, "0.0.1"}, 23 | {Version{0, 0, 1, []PRVersion{prstr("alpha"), prstr("preview")}, []string{"123", "456"}}, "0.0.1-alpha.preview+123.456"}, 24 | {Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, []string{"123", "456"}}, "1.2.3-alpha.1+123.456"}, 25 | {Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, nil}, "1.2.3-alpha.1"}, 26 | {Version{1, 2, 3, nil, []string{"123", "456"}}, "1.2.3+123.456"}, 27 | // Prereleases and build metadata hyphens 28 | {Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, []string{"123", "b-uild"}}, "1.2.3-alpha.b-eta+123.b-uild"}, 29 | {Version{1, 2, 3, nil, []string{"123", "b-uild"}}, "1.2.3+123.b-uild"}, 30 | {Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, nil}, "1.2.3-alpha.b-eta"}, 31 | } 32 | 33 | var tolerantFormatTests = []formatTest{ 34 | {Version{1, 2, 3, nil, nil}, "v1.2.3"}, 35 | {Version{1, 2, 0, []PRVersion{prstr("alpha")}, nil}, "1.2.0-alpha"}, 36 | {Version{1, 2, 0, nil, nil}, "1.2.00"}, 37 | {Version{1, 2, 3, nil, nil}, " 1.2.3 "}, 38 | {Version{1, 2, 3, nil, nil}, "01.02.03"}, 39 | {Version{0, 0, 3, nil, nil}, "00.0.03"}, 40 | {Version{0, 0, 3, nil, nil}, "000.0.03"}, 41 | {Version{1, 2, 0, nil, nil}, "1.2"}, 42 | {Version{1, 0, 0, nil, nil}, "1"}, 43 | } 44 | 45 | func TestStringer(t *testing.T) { 46 | for _, test := range formatTests { 47 | if res := test.v.String(); res != test.result { 48 | t.Errorf("Stringer, expected %q but got %q", test.result, res) 49 | } 50 | } 51 | } 52 | 53 | func TestParse(t *testing.T) { 54 | for _, test := range formatTests { 55 | if v, err := Parse(test.result); err != nil { 56 | t.Errorf("Error parsing %q: %q", test.result, err) 57 | } else if comp := v.Compare(test.v); comp != 0 { 58 | t.Errorf("Parsing, expected %q but got %q, comp: %d ", test.v, v, comp) 59 | } else if err := v.Validate(); err != nil { 60 | t.Errorf("Error validating parsed version %q: %q", test.v, err) 61 | } 62 | } 63 | } 64 | 65 | func TestParseTolerant(t *testing.T) { 66 | for _, test := range tolerantFormatTests { 67 | if v, err := ParseTolerant(test.result); err != nil { 68 | t.Errorf("Error parsing %q: %q", test.result, err) 69 | } else if comp := v.Compare(test.v); comp != 0 { 70 | t.Errorf("Parsing, expected %q but got %q, comp: %d ", test.v, v, comp) 71 | } else if err := v.Validate(); err != nil { 72 | t.Errorf("Error validating parsed version %q: %q", test.v, err) 73 | } 74 | } 75 | } 76 | 77 | func TestMustParse(t *testing.T) { 78 | _ = MustParse("32.2.1-alpha") 79 | } 80 | 81 | func TestMustParse_panic(t *testing.T) { 82 | defer func() { 83 | if recover() == nil { 84 | t.Errorf("Should have panicked") 85 | } 86 | }() 87 | _ = MustParse("invalid version") 88 | } 89 | 90 | func TestValidate(t *testing.T) { 91 | for _, test := range formatTests { 92 | if err := test.v.Validate(); err != nil { 93 | t.Errorf("Error validating %q: %q", test.v, err) 94 | } 95 | } 96 | } 97 | 98 | var finalizeVersionMethod = []formatTest{ 99 | {Version{1, 2, 3, nil, nil}, "1.2.3"}, 100 | {Version{0, 0, 1, nil, nil}, "0.0.1"}, 101 | {Version{0, 0, 1, []PRVersion{prstr("alpha"), prstr("preview")}, []string{"123", "456"}}, "0.0.1"}, 102 | {Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, []string{"123", "456"}}, "1.2.3"}, 103 | {Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, nil}, "1.2.3"}, 104 | {Version{1, 2, 3, nil, []string{"123", "456"}}, "1.2.3"}, 105 | // Prereleases and build metadata hyphens 106 | {Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, []string{"123", "b-uild"}}, "1.2.3"}, 107 | {Version{1, 2, 3, nil, []string{"123", "b-uild"}}, "1.2.3"}, 108 | {Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, nil}, "1.2.3"}, 109 | } 110 | 111 | func TestFinalizeVersionMethod(t *testing.T) { 112 | for _, test := range finalizeVersionMethod { 113 | out := test.v.FinalizeVersion() 114 | if out != test.result { 115 | t.Errorf("Finalized version error, expected %q but got %q", test.result, out) 116 | } 117 | } 118 | } 119 | 120 | type compareTest struct { 121 | v1 Version 122 | v2 Version 123 | result int 124 | } 125 | 126 | var compareTests = []compareTest{ 127 | {Version{1, 0, 0, nil, nil}, Version{1, 0, 0, nil, nil}, 0}, 128 | {Version{2, 0, 0, nil, nil}, Version{1, 0, 0, nil, nil}, 1}, 129 | {Version{0, 1, 0, nil, nil}, Version{0, 1, 0, nil, nil}, 0}, 130 | {Version{0, 2, 0, nil, nil}, Version{0, 1, 0, nil, nil}, 1}, 131 | {Version{0, 0, 1, nil, nil}, Version{0, 0, 1, nil, nil}, 0}, 132 | {Version{0, 0, 2, nil, nil}, Version{0, 0, 1, nil, nil}, 1}, 133 | {Version{1, 2, 3, nil, nil}, Version{1, 2, 3, nil, nil}, 0}, 134 | {Version{2, 2, 4, nil, nil}, Version{1, 2, 4, nil, nil}, 1}, 135 | {Version{1, 3, 3, nil, nil}, Version{1, 2, 3, nil, nil}, 1}, 136 | {Version{1, 2, 4, nil, nil}, Version{1, 2, 3, nil, nil}, 1}, 137 | 138 | // Spec Examples #11 139 | {Version{1, 0, 0, nil, nil}, Version{2, 0, 0, nil, nil}, -1}, 140 | {Version{2, 0, 0, nil, nil}, Version{2, 1, 0, nil, nil}, -1}, 141 | {Version{2, 1, 0, nil, nil}, Version{2, 1, 1, nil, nil}, -1}, 142 | 143 | // Spec Examples #9 144 | {Version{1, 0, 0, nil, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil}, 1}, 145 | {Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha"), prnum(1)}, nil}, -1}, 146 | {Version{1, 0, 0, []PRVersion{prstr("alpha"), prnum(1)}, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha"), prstr("beta")}, nil}, -1}, 147 | {Version{1, 0, 0, []PRVersion{prstr("alpha"), prstr("beta")}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta")}, nil}, -1}, 148 | {Version{1, 0, 0, []PRVersion{prstr("beta")}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(2)}, nil}, -1}, 149 | {Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(2)}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(11)}, nil}, -1}, 150 | {Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(11)}, nil}, Version{1, 0, 0, []PRVersion{prstr("rc"), prnum(1)}, nil}, -1}, 151 | {Version{1, 0, 0, []PRVersion{prstr("rc"), prnum(1)}, nil}, Version{1, 0, 0, nil, nil}, -1}, 152 | 153 | // Ignore Build metadata 154 | {Version{1, 0, 0, nil, []string{"1", "2", "3"}}, Version{1, 0, 0, nil, nil}, 0}, 155 | } 156 | 157 | func TestCompare(t *testing.T) { 158 | for _, test := range compareTests { 159 | if res := test.v1.Compare(test.v2); res != test.result { 160 | t.Errorf("Comparing %q : %q, expected %d but got %d", test.v1, test.v2, test.result, res) 161 | } 162 | // Test counterpart 163 | if res := test.v2.Compare(test.v1); res != -test.result { 164 | t.Errorf("Comparing %q : %q, expected %d but got %d", test.v2, test.v1, -test.result, res) 165 | } 166 | } 167 | } 168 | 169 | type wrongformatTest struct { 170 | v *Version 171 | str string 172 | } 173 | 174 | var wrongformatTests = []wrongformatTest{ 175 | {nil, ""}, 176 | {nil, "."}, 177 | {nil, "1."}, 178 | {nil, ".1"}, 179 | {nil, "a.b.c"}, 180 | {nil, "1.a.b"}, 181 | {nil, "1.1.a"}, 182 | {nil, "1.a.1"}, 183 | {nil, "a.1.1"}, 184 | {nil, ".."}, 185 | {nil, "1.."}, 186 | {nil, "1.1."}, 187 | {nil, "1..1"}, 188 | {nil, "1.1.+123"}, 189 | {nil, "1.1.-beta"}, 190 | {nil, "-1.1.1"}, 191 | {nil, "1.-1.1"}, 192 | {nil, "1.1.-1"}, 193 | // giant numbers 194 | {nil, "20000000000000000000.1.1"}, 195 | {nil, "1.20000000000000000000.1"}, 196 | {nil, "1.1.20000000000000000000"}, 197 | {nil, "1.1.1-20000000000000000000"}, 198 | // Leading zeroes 199 | {nil, "01.1.1"}, 200 | {nil, "001.1.1"}, 201 | {nil, "1.01.1"}, 202 | {nil, "1.001.1"}, 203 | {nil, "1.1.01"}, 204 | {nil, "1.1.001"}, 205 | {nil, "1.1.1-01"}, 206 | {nil, "1.1.1-001"}, 207 | {nil, "1.1.1-beta.01"}, 208 | {nil, "1.1.1-beta.001"}, 209 | {&Version{0, 0, 0, []PRVersion{prstr("!")}, nil}, "0.0.0-!"}, 210 | {&Version{0, 0, 0, nil, []string{"!"}}, "0.0.0+!"}, 211 | // empty prversion 212 | {&Version{0, 0, 0, []PRVersion{prstr(""), prstr("alpha")}, nil}, "0.0.0-.alpha"}, 213 | // empty build meta data 214 | {&Version{0, 0, 0, []PRVersion{prstr("alpha")}, []string{""}}, "0.0.0-alpha+"}, 215 | {&Version{0, 0, 0, []PRVersion{prstr("alpha")}, []string{"test", ""}}, "0.0.0-alpha+test."}, 216 | } 217 | 218 | func TestWrongFormat(t *testing.T) { 219 | for _, test := range wrongformatTests { 220 | 221 | if res, err := Parse(test.str); err == nil { 222 | t.Errorf("Parsing wrong format version %q, expected error but got %q", test.str, res) 223 | } 224 | 225 | if test.v != nil { 226 | if err := test.v.Validate(); err == nil { 227 | t.Errorf("Validating wrong format version %q (%q), expected error", test.v, test.str) 228 | } 229 | } 230 | } 231 | } 232 | 233 | var wrongTolerantFormatTests = []wrongformatTest{ 234 | {nil, "1.0+abc"}, 235 | {nil, "1.0-rc.1"}, 236 | } 237 | 238 | func TestWrongTolerantFormat(t *testing.T) { 239 | for _, test := range wrongTolerantFormatTests { 240 | if res, err := ParseTolerant(test.str); err == nil { 241 | t.Errorf("Parsing wrong format version %q, expected error but got %q", test.str, res) 242 | } 243 | } 244 | } 245 | 246 | func TestCompareHelper(t *testing.T) { 247 | v := Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil} 248 | v1 := Version{1, 0, 0, nil, nil} 249 | if !v.EQ(v) { 250 | t.Errorf("%q should be equal to %q", v, v) 251 | } 252 | if !v.Equals(v) { 253 | t.Errorf("%q should be equal to %q", v, v) 254 | } 255 | if !v1.NE(v) { 256 | t.Errorf("%q should not be equal to %q", v1, v) 257 | } 258 | if !v.GTE(v) { 259 | t.Errorf("%q should be greater than or equal to %q", v, v) 260 | } 261 | if !v.LTE(v) { 262 | t.Errorf("%q should be less than or equal to %q", v, v) 263 | } 264 | if !v.LT(v1) { 265 | t.Errorf("%q should be less than %q", v, v1) 266 | } 267 | if !v.LTE(v1) { 268 | t.Errorf("%q should be less than or equal %q", v, v1) 269 | } 270 | if !v.LE(v1) { 271 | t.Errorf("%q should be less than or equal %q", v, v1) 272 | } 273 | if !v1.GT(v) { 274 | t.Errorf("%q should be greater than %q", v1, v) 275 | } 276 | if !v1.GTE(v) { 277 | t.Errorf("%q should be greater than or equal %q", v1, v) 278 | } 279 | if !v1.GE(v) { 280 | t.Errorf("%q should be greater than or equal %q", v1, v) 281 | } 282 | } 283 | 284 | const ( 285 | MAJOR = iota 286 | MINOR 287 | PATCH 288 | ) 289 | 290 | type incrementTest struct { 291 | version Version 292 | incrementType int 293 | expectingError bool 294 | expectedVersion Version 295 | } 296 | 297 | var incrementTests = []incrementTest{ 298 | {Version{1, 2, 3, nil, nil}, PATCH, false, Version{1, 2, 4, nil, nil}}, 299 | {Version{1, 2, 3, nil, nil}, MINOR, false, Version{1, 3, 0, nil, nil}}, 300 | {Version{1, 2, 3, nil, nil}, MAJOR, false, Version{2, 0, 0, nil, nil}}, 301 | {Version{0, 1, 2, nil, nil}, PATCH, false, Version{0, 1, 3, nil, nil}}, 302 | {Version{0, 1, 2, nil, nil}, MINOR, false, Version{0, 2, 0, nil, nil}}, 303 | {Version{0, 1, 2, nil, nil}, MAJOR, false, Version{1, 0, 0, nil, nil}}, 304 | } 305 | 306 | func TestIncrements(t *testing.T) { 307 | for _, test := range incrementTests { 308 | var originalVersion = Version{ 309 | test.version.Major, 310 | test.version.Minor, 311 | test.version.Patch, 312 | test.version.Pre, 313 | test.version.Build, 314 | } 315 | var err error 316 | switch test.incrementType { 317 | case PATCH: 318 | err = test.version.IncrementPatch() 319 | case MINOR: 320 | err = test.version.IncrementMinor() 321 | case MAJOR: 322 | err = test.version.IncrementMajor() 323 | } 324 | if test.expectingError { 325 | if err != nil { 326 | t.Errorf("Increment version, expecting %q, got error %q", test.expectedVersion, err) 327 | } 328 | if test.version.EQ(originalVersion) { 329 | t.Errorf("Increment version, expecting %q, got %q", test.expectedVersion, test.version) 330 | } 331 | } else { 332 | if (err != nil) && !test.expectingError { 333 | t.Errorf("Increment version %q, not expecting error, got %q", test.version, err) 334 | } 335 | if test.version.NE(test.expectedVersion) { 336 | t.Errorf("Increment version, expecting %q, got %q", test.expectedVersion, test.version) 337 | } 338 | } 339 | } 340 | } 341 | 342 | func TestPreReleaseVersions(t *testing.T) { 343 | p1, err := NewPRVersion("123") 344 | if !p1.IsNumeric() { 345 | t.Errorf("Expected numeric prversion, got %q", p1) 346 | } 347 | if p1.VersionNum != 123 { 348 | t.Error("Wrong prversion number") 349 | } 350 | if err != nil { 351 | t.Errorf("Not expected error %q", err) 352 | } 353 | p2, err := NewPRVersion("alpha") 354 | if p2.IsNumeric() { 355 | t.Errorf("Expected non-numeric prversion, got %q", p2) 356 | } 357 | if p2.VersionStr != "alpha" { 358 | t.Error("Wrong prversion string") 359 | } 360 | if err != nil { 361 | t.Errorf("Not expected error %q", err) 362 | } 363 | } 364 | 365 | func TestBuildMetaDataVersions(t *testing.T) { 366 | _, err := NewBuildVersion("123") 367 | if err != nil { 368 | t.Errorf("Unexpected error %q", err) 369 | } 370 | 371 | _, err = NewBuildVersion("build") 372 | if err != nil { 373 | t.Errorf("Unexpected error %q", err) 374 | } 375 | 376 | _, err = NewBuildVersion("test?") 377 | if err == nil { 378 | t.Error("Expected error, got none") 379 | } 380 | 381 | _, err = NewBuildVersion("") 382 | if err == nil { 383 | t.Error("Expected error, got none") 384 | } 385 | } 386 | 387 | func TestNewHelper(t *testing.T) { 388 | v, err := New("1.2.3") 389 | if err != nil { 390 | t.Fatalf("Unexpected error %q", err) 391 | } 392 | 393 | // New returns pointer 394 | if v == nil { 395 | t.Fatal("Version is nil") 396 | } 397 | if v.Compare(Version{1, 2, 3, nil, nil}) != 0 { 398 | t.Fatal("Unexpected comparison problem") 399 | } 400 | } 401 | 402 | func TestMakeHelper(t *testing.T) { 403 | v, err := Make("1.2.3") 404 | if err != nil { 405 | t.Fatalf("Unexpected error %q", err) 406 | } 407 | if v.Compare(Version{1, 2, 3, nil, nil}) != 0 { 408 | t.Fatal("Unexpected comparison problem") 409 | } 410 | } 411 | 412 | type finalizeTest struct { 413 | input string 414 | output string 415 | } 416 | 417 | var finalizeTests = []finalizeTest{ 418 | {"", ""}, 419 | {"1.2.3", "1.2.3"}, 420 | {"0.0.1", "0.0.1"}, 421 | {"0.0.1-alpha.preview+123.456", "0.0.1"}, 422 | {"1.2.3-alpha.1+123.456", "1.2.3"}, 423 | {"1.2.3-alpha.1", "1.2.3"}, 424 | {"1.2.3+123.456", "1.2.3"}, 425 | {"1.2.3-alpha.b-eta+123.b-uild", "1.2.3"}, 426 | {"1.2.3+123.b-uild", "1.2.3"}, 427 | {"1.2.3-alpha.b-eta", "1.2.3"}, 428 | {"1.2-alpha", ""}, 429 | } 430 | 431 | func TestFinalizeVersion(t *testing.T) { 432 | for _, test := range finalizeTests { 433 | finalVer, err := FinalizeVersion(test.input) 434 | if finalVer == "" { 435 | if err == nil { 436 | t.Errorf("Finalize Version error, expected error but got nil") 437 | } 438 | } else if finalVer != test.output && err != nil { 439 | t.Errorf("Finalize Version error expected %q but got %q", test.output, finalVer) 440 | } 441 | } 442 | } 443 | 444 | func BenchmarkParseSimple(b *testing.B) { 445 | const VERSION = "0.0.1" 446 | b.ReportAllocs() 447 | b.ResetTimer() 448 | for n := 0; n < b.N; n++ { 449 | _, _ = Parse(VERSION) 450 | } 451 | } 452 | 453 | func BenchmarkParseComplex(b *testing.B) { 454 | const VERSION = "0.0.1-alpha.preview+123.456" 455 | b.ReportAllocs() 456 | b.ResetTimer() 457 | for n := 0; n < b.N; n++ { 458 | _, _ = Parse(VERSION) 459 | } 460 | } 461 | 462 | func BenchmarkParseAverage(b *testing.B) { 463 | l := len(formatTests) 464 | b.ReportAllocs() 465 | b.ResetTimer() 466 | for n := 0; n < b.N; n++ { 467 | _, _ = Parse(formatTests[n%l].result) 468 | } 469 | } 470 | 471 | func BenchmarkParseTolerantAverage(b *testing.B) { 472 | l := len(tolerantFormatTests) 473 | b.ReportAllocs() 474 | b.ResetTimer() 475 | for n := 0; n < b.N; n++ { 476 | _, _ = ParseTolerant(tolerantFormatTests[n%l].result) 477 | } 478 | } 479 | 480 | func BenchmarkStringSimple(b *testing.B) { 481 | const VERSION = "0.0.1" 482 | v, _ := Parse(VERSION) 483 | b.ReportAllocs() 484 | b.ResetTimer() 485 | for n := 0; n < b.N; n++ { 486 | _ = v.String() 487 | } 488 | } 489 | 490 | func BenchmarkStringLarger(b *testing.B) { 491 | const VERSION = "11.15.2012" 492 | v, _ := Parse(VERSION) 493 | b.ReportAllocs() 494 | b.ResetTimer() 495 | for n := 0; n < b.N; n++ { 496 | _ = v.String() 497 | } 498 | } 499 | 500 | func BenchmarkStringComplex(b *testing.B) { 501 | const VERSION = "0.0.1-alpha.preview+123.456" 502 | v, _ := Parse(VERSION) 503 | b.ReportAllocs() 504 | b.ResetTimer() 505 | for n := 0; n < b.N; n++ { 506 | _ = v.String() 507 | } 508 | } 509 | 510 | func BenchmarkStringAverage(b *testing.B) { 511 | l := len(formatTests) 512 | b.ReportAllocs() 513 | b.ResetTimer() 514 | for n := 0; n < b.N; n++ { 515 | _ = formatTests[n%l].v.String() 516 | } 517 | } 518 | 519 | func BenchmarkValidateSimple(b *testing.B) { 520 | const VERSION = "0.0.1" 521 | v, _ := Parse(VERSION) 522 | b.ReportAllocs() 523 | b.ResetTimer() 524 | for n := 0; n < b.N; n++ { 525 | _ = v.Validate() 526 | } 527 | } 528 | 529 | func BenchmarkValidateComplex(b *testing.B) { 530 | const VERSION = "0.0.1-alpha.preview+123.456" 531 | v, _ := Parse(VERSION) 532 | b.ReportAllocs() 533 | b.ResetTimer() 534 | for n := 0; n < b.N; n++ { 535 | _ = v.Validate() 536 | } 537 | } 538 | 539 | func BenchmarkValidateAverage(b *testing.B) { 540 | l := len(formatTests) 541 | b.ReportAllocs() 542 | b.ResetTimer() 543 | for n := 0; n < b.N; n++ { 544 | _ = formatTests[n%l].v.Validate() 545 | } 546 | } 547 | 548 | func BenchmarkCompareSimple(b *testing.B) { 549 | const VERSION = "0.0.1" 550 | v, _ := Parse(VERSION) 551 | b.ReportAllocs() 552 | b.ResetTimer() 553 | for n := 0; n < b.N; n++ { 554 | v.Compare(v) 555 | } 556 | } 557 | 558 | func BenchmarkCompareComplex(b *testing.B) { 559 | const VERSION = "0.0.1-alpha.preview+123.456" 560 | v, _ := Parse(VERSION) 561 | b.ReportAllocs() 562 | b.ResetTimer() 563 | for n := 0; n < b.N; n++ { 564 | v.Compare(v) 565 | } 566 | } 567 | 568 | func BenchmarkCompareAverage(b *testing.B) { 569 | l := len(compareTests) 570 | b.ReportAllocs() 571 | b.ResetTimer() 572 | for n := 0; n < b.N; n++ { 573 | compareTests[n%l].v1.Compare((compareTests[n%l].v2)) 574 | } 575 | } 576 | -------------------------------------------------------------------------------- /v4/sort.go: -------------------------------------------------------------------------------- 1 | package semver 2 | 3 | import ( 4 | "sort" 5 | ) 6 | 7 | // Versions represents multiple versions. 8 | type Versions []Version 9 | 10 | // Len returns length of version collection 11 | func (s Versions) Len() int { 12 | return len(s) 13 | } 14 | 15 | // Swap swaps two versions inside the collection by its indices 16 | func (s Versions) Swap(i, j int) { 17 | s[i], s[j] = s[j], s[i] 18 | } 19 | 20 | // Less checks if version at index i is less than version at index j 21 | func (s Versions) Less(i, j int) bool { 22 | return s[i].LT(s[j]) 23 | } 24 | 25 | // Sort sorts a slice of versions 26 | func Sort(versions []Version) { 27 | sort.Sort(Versions(versions)) 28 | } 29 | -------------------------------------------------------------------------------- /v4/sort_test.go: -------------------------------------------------------------------------------- 1 | package semver 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestSort(t *testing.T) { 9 | v100, _ := Parse("1.0.0") 10 | v010, _ := Parse("0.1.0") 11 | v001, _ := Parse("0.0.1") 12 | versions := []Version{v010, v100, v001} 13 | Sort(versions) 14 | 15 | correct := []Version{v001, v010, v100} 16 | if !reflect.DeepEqual(versions, correct) { 17 | t.Fatalf("Sort returned wrong order: %s", versions) 18 | } 19 | } 20 | 21 | func BenchmarkSort(b *testing.B) { 22 | v100, _ := Parse("1.0.0") 23 | v010, _ := Parse("0.1.0") 24 | v001, _ := Parse("0.0.1") 25 | b.ReportAllocs() 26 | b.ResetTimer() 27 | for n := 0; n < b.N; n++ { 28 | Sort([]Version{v010, v100, v001}) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /v4/sql.go: -------------------------------------------------------------------------------- 1 | package semver 2 | 3 | import ( 4 | "database/sql/driver" 5 | "fmt" 6 | ) 7 | 8 | // Scan implements the database/sql.Scanner interface. 9 | func (v *Version) Scan(src interface{}) (err error) { 10 | var str string 11 | switch src := src.(type) { 12 | case string: 13 | str = src 14 | case []byte: 15 | str = string(src) 16 | default: 17 | return fmt.Errorf("version.Scan: cannot convert %T to string", src) 18 | } 19 | 20 | if t, err := Parse(str); err == nil { 21 | *v = t 22 | } 23 | 24 | return 25 | } 26 | 27 | // Value implements the database/sql/driver.Valuer interface. 28 | func (v Version) Value() (driver.Value, error) { 29 | return v.String(), nil 30 | } 31 | -------------------------------------------------------------------------------- /v4/sql_test.go: -------------------------------------------------------------------------------- 1 | package semver 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type scanTest struct { 8 | val interface{} 9 | shouldError bool 10 | expected string 11 | } 12 | 13 | var scanTests = []scanTest{ 14 | {"1.2.3", false, "1.2.3"}, 15 | {[]byte("1.2.3"), false, "1.2.3"}, 16 | {7, true, ""}, 17 | {7e4, true, ""}, 18 | {true, true, ""}, 19 | } 20 | 21 | func TestScanString(t *testing.T) { 22 | for _, tc := range scanTests { 23 | s := &Version{} 24 | err := s.Scan(tc.val) 25 | if tc.shouldError { 26 | if err == nil { 27 | t.Fatalf("Scan did not return an error on %v (%T)", tc.val, tc.val) 28 | } 29 | } else { 30 | if err != nil { 31 | t.Fatalf("Scan returned an unexpected error: %s (%T) on %v (%T)", tc.val, tc.val, tc.val, tc.val) 32 | } 33 | if val, _ := s.Value(); val != tc.expected { 34 | t.Errorf("Wrong Value returned, expected %q, got %q", tc.expected, val) 35 | } 36 | } 37 | } 38 | } 39 | --------------------------------------------------------------------------------