diff --git a/src/Gopkg.lock b/src/Gopkg.lock index 2b1138151..dc01670c3 100644 --- a/src/Gopkg.lock +++ b/src/Gopkg.lock @@ -58,6 +58,12 @@ packages = ["."] revision = "e87155e8f0c05bf323d0b13470e1b97af0cb5652" +[[projects]] + name = "github.com/blang/semver" + packages = ["."] + revision = "2ee87856327ba09384cabd113bc6b5d174e9ec0f" + version = "v3.5.1" + [[projects]] name = "github.com/davecgh/go-spew" packages = ["spew"] @@ -371,6 +377,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "db970de67fb6d82511e2fb991860473786678b3c1dfc4fb835046fafbaa61f57" + inputs-digest = "0392e2659013ae00be46cd4f0b9d56aa0765b6297d672a56175249c465e98a8d" solver-name = "gps-cdcl" solver-version = 1 diff --git a/src/Gopkg.toml b/src/Gopkg.toml index eacbc4ea8..0adeaba58 100644 --- a/src/Gopkg.toml +++ b/src/Gopkg.toml @@ -90,4 +90,8 @@ ignored = ["github.com/vmware/harbor/tests*"] [[constraint]] name = "github.com/ghodss/yaml" - version = "=1.0.0" \ No newline at end of file + version = "=1.0.0" + +[[constraint]] + name = "github.com/blang/semver" + version = "=3.5.1" \ No newline at end of file diff --git a/src/chartserver/chart_operator.go b/src/chartserver/chart_operator.go index c1d5bac9a..f5007fc0d 100644 --- a/src/chartserver/chart_operator.go +++ b/src/chartserver/chart_operator.go @@ -1,12 +1,182 @@ package chartserver -import "k8s.io/helm/pkg/proto/hapi/chart" +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/Masterminds/semver" + + "k8s.io/helm/pkg/chartutil" + helm_repo "k8s.io/helm/pkg/repo" +) + +const ( + readmeFileName = "README.md" +) + +//ChartVersionDetails keeps the detailed data info of the chart version +type ChartVersionDetails struct { + Metadata *helm_repo.ChartVersion `json:"metadata"` + Dependencies []*chartutil.Dependency `json:"dependencies"` + Values map[string]interface{} `json:"values"` + Files map[string]string `json:"files"` +} + +//ChartInfo keeps the information of the chart +type ChartInfo struct { + Name string + TotalVersions uint32 `json:"total_versions"` + Created time.Time + Icon string + Home string +} //ChartOperator is designed to process the contents of //the specified chart version to get more details type ChartOperator struct{} //GetChartDetails parse the details from the provided content bytes -func (cho *ChartOperator) GetChartDetails(content []byte) *chart.Chart { - return nil +func (cho *ChartOperator) GetChartDetails(content []byte) (*ChartVersionDetails, error) { + if content == nil || len(content) == 0 { + return nil, errors.New("zero content") + } + + //Load chart from in-memory content + reader := bytes.NewReader(content) + chartData, err := chartutil.LoadArchive(reader) + if err != nil { + return nil, err + } + + //Parse the requirements of chart + requirements, err := chartutil.LoadRequirements(chartData) + if err != nil { + return nil, err + } + + var values map[string]interface{} + files := make(map[string]string) + //Parse values + if chartData.Values != nil { + values = parseRawValues([]byte(chartData.Values.GetRaw())) + //Append values.yaml file + files["values.yaml"] = chartData.Values.Raw + } + + //Append other files like 'README.md' + for _, v := range chartData.GetFiles() { + if v.TypeUrl == readmeFileName { + files[readmeFileName] = string(v.GetValue()) + break + } + } + + theChart := &ChartVersionDetails{ + Dependencies: requirements.Dependencies, + Values: values, + Files: files, + } + + return theChart, nil +} + +//GetChartList returns a reorganized chart list +func (cho *ChartOperator) GetChartList(content []byte) ([]*ChartInfo, error) { + if content == nil || len(content) == 0 { + return nil, errors.New("zero content") + } + + allCharts := make(map[string]helm_repo.ChartVersions) + if err := json.Unmarshal(content, &allCharts); err != nil { + return nil, err + } + + chartList := make([]*ChartInfo, 0) + for key, chartVersions := range allCharts { + lVersion, oVersion := getTheTwoCharts(chartVersions) + if lVersion != nil && oVersion != nil { + chartInfo := &ChartInfo{ + Name: key, + TotalVersions: uint32(len(chartVersions)), + } + chartInfo.Created = oVersion.Created + chartInfo.Home = lVersion.Home + chartInfo.Icon = lVersion.Icon + chartList = append(chartList, chartInfo) + } + } + + return chartList, nil +} + +//Get the latest and oldest chart versions +func getTheTwoCharts(chartVersions helm_repo.ChartVersions) (latestChart *helm_repo.ChartVersion, oldestChart *helm_repo.ChartVersion) { + if len(chartVersions) == 1 { + return chartVersions[0], chartVersions[0] + } + + for _, chartVersion := range chartVersions { + currentV, err := semver.NewVersion(chartVersion.Version) + if err != nil { + //ignore it + continue + } + + //Find latest chart + if latestChart == nil { + latestChart = chartVersion + } else { + lVersion, _ := semver.NewVersion(latestChart.Version) + if lVersion.LessThan(currentV) { + latestChart = chartVersion + } + } + + if oldestChart == nil { + oldestChart = chartVersion + } else { + if oldestChart.Created.After(chartVersion.Created) { + oldestChart = chartVersion + } + } + } + + return latestChart, oldestChart +} + +//Parse the raw values to value map +func parseRawValues(rawValue []byte) map[string]interface{} { + valueMap := make(map[string]interface{}) + + if len(rawValue) == 0 { + return valueMap + } + + values, err := chartutil.ReadValues(rawValue) + if err != nil || len(values) == 0 { + return valueMap + } + + readValue(values, "", valueMap) + + return valueMap +} + +//Recursively read value +func readValue(values map[string]interface{}, keyPrefix string, valueMap map[string]interface{}) { + for key, value := range values { + longKey := key + if keyPrefix != "" { + longKey = fmt.Sprintf("%s.%s", keyPrefix, key) + } + + if subValues, ok := value.(map[string]interface{}); ok { + readValue(subValues, longKey, valueMap) + } else { + valueMap[longKey] = value + } + } } diff --git a/src/chartserver/client.go b/src/chartserver/client.go new file mode 100644 index 000000000..92e620273 --- /dev/null +++ b/src/chartserver/client.go @@ -0,0 +1,67 @@ +package chartserver + +import ( + "errors" + "fmt" + "io/ioutil" + "net/http" + "os" + "strings" + "time" +) + +const ( + clientTimeout = 10 * time.Second + maxIdleConnections = 10 + idleConnectionTimeout = 30 * time.Second +) + +//ChartClient is a http client to get the content from the external http server +type ChartClient struct { + httpClient *http.Client +} + +//NewChartClient is constructor of ChartClient +func NewChartClient() *ChartClient { //Create http client with customized timeouts + client := &http.Client{ + Timeout: clientTimeout, + Transport: &http.Transport{ + MaxIdleConns: maxIdleConnections, + IdleConnTimeout: idleConnectionTimeout, + }, + } + + return &ChartClient{client} +} + +//GetContent get the bytes from the specified url +func (cc *ChartClient) GetContent(url string) ([]byte, error) { + if len(strings.TrimSpace(url)) == 0 { + return nil, errors.New("empty url is not allowed") + } + + request, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, err + } + + //Set basic auth + request.SetBasicAuth(userName, os.Getenv(passwordKey)) + + response, err := cc.httpClient.Do(request) + if err != nil { + return nil, err + } + + if response.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to retrieve content from url %s", url) + } + + content, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + defer response.Body.Close() + + return content, nil +} diff --git a/src/chartserver/controller.go b/src/chartserver/controller.go index 709365f72..0bf8b0fa5 100644 --- a/src/chartserver/controller.go +++ b/src/chartserver/controller.go @@ -2,15 +2,7 @@ package chartserver import ( "errors" - "net/http" "net/url" - "time" -) - -const ( - clientTimeout = 10 * time.Second - maxIdleConnections = 10 - idleConnectionTimeout = 30 * time.Second ) //Controller is used to handle flows of related requests based on the corresponding handlers @@ -40,13 +32,7 @@ func NewController(backendServer *url.URL) (*Controller, error) { proxy := NewProxyEngine(backendServer) //Create http client with customized timeouts - client := &http.Client{ - Timeout: clientTimeout, - Transport: &http.Transport{ - MaxIdleConns: maxIdleConnections, - IdleConnTimeout: idleConnectionTimeout, - }, - } + client := NewChartClient() //Initialize chart operator for use operator := &ChartOperator{} @@ -60,8 +46,10 @@ func NewController(backendServer *url.URL) (*Controller, error) { backendServerAddress: backendServer, }, manipulationHandler: &ManipulationHandler{ - trafficProxy: proxy, - chartOperator: operator, + trafficProxy: proxy, + chartOperator: operator, + apiClient: client, + backendServerAddress: backendServer, }, }, nil } diff --git a/src/chartserver/manipulation_handler.go b/src/chartserver/manipulation_handler.go index 37b212d71..eae4ee7e9 100644 --- a/src/chartserver/manipulation_handler.go +++ b/src/chartserver/manipulation_handler.go @@ -1,7 +1,14 @@ package chartserver import ( + "encoding/json" + "fmt" "net/http" + "net/url" + "strings" + + "github.com/ghodss/yaml" + helm_repo "k8s.io/helm/pkg/repo" ) //ManipulationHandler includes all the handler methods for the purpose of manipulating the @@ -13,11 +20,38 @@ type ManipulationHandler struct { //Parse and process the chart version to provide required info data chartOperator *ChartOperator + + //HTTP client used to call the realted APIs of the backend chart repositories + apiClient *ChartClient + + //Point to the url of the backend server + backendServerAddress *url.URL } //ListCharts lists all the charts under the specified namespace func (mh *ManipulationHandler) ListCharts(w http.ResponseWriter, req *http.Request) { - mh.trafficProxy.ServeHTTP(w, req) + rootURL := strings.TrimSuffix(mh.backendServerAddress.String(), "/") + fullURL := fmt.Sprintf("%s%s", rootURL, req.RequestURI) + + content, err := mh.apiClient.GetContent(fullURL) + if err != nil { + writeInternalError(w, err) + return + } + + chartList, err := mh.chartOperator.GetChartList(content) + if err != nil { + writeInternalError(w, err) + return + } + + jsonData, err := json.Marshal(chartList) + if err != nil { + writeInternalError(w, err) + return + } + + writeJSONData(w, jsonData) } //GetChart returns all the chart versions under the specified chart @@ -29,8 +63,35 @@ func (mh *ManipulationHandler) GetChart(w http.ResponseWriter, req *http.Request //This handler should return the details of the chart version, //maybe including metadata,dependencies and values etc. func (mh *ManipulationHandler) GetChartVersion(w http.ResponseWriter, req *http.Request) { - w.WriteHeader(http.StatusNotImplemented) - w.Write([]byte("not implemented")) + chartV, err := mh.getChartVersion(req.RequestURI) + if err != nil { + writeInternalError(w, err) + return + } + + //TODO: + namespace := "repo2" + content, err := mh.getChartVersionContent(namespace, chartV.URLs[0]) + if err != nil { + writeInternalError(w, err) + return + } + + //Process bytes and get more details of chart version + chartDetails, err := mh.chartOperator.GetChartDetails(content) + if err != nil { + writeInternalError(w, err) + return + } + chartDetails.Metadata = chartV + + bytes, err := json.Marshal(chartDetails) + if err != nil { + writeInternalError(w, err) + return + } + + writeJSONData(w, bytes) } //UploadChartVersion will save the new version of the chart to the backend storage @@ -47,3 +108,29 @@ func (mh *ManipulationHandler) UploadProvenanceFile(w http.ResponseWriter, req * func (mh *ManipulationHandler) DeleteChartVersion(w http.ResponseWriter, req *http.Request) { mh.trafficProxy.ServeHTTP(w, req) } + +//Get the basic metadata of chart version +func (mh *ManipulationHandler) getChartVersion(path string) (*helm_repo.ChartVersion, error) { + rootURL := strings.TrimSuffix(mh.backendServerAddress.String(), "/") + fullURL := fmt.Sprintf("%s%s", rootURL, path) + + content, err := mh.apiClient.GetContent(fullURL) + if err != nil { + return nil, err + } + + chartVersion := &helm_repo.ChartVersion{} + if err := yaml.Unmarshal(content, chartVersion); err != nil { + return nil, err + } + + return chartVersion, nil +} + +//Get the content bytes of the chart version +func (mh *ManipulationHandler) getChartVersionContent(namespace string, path string) ([]byte, error) { + rootURL := strings.TrimSuffix(mh.backendServerAddress.String(), "/") + fullPath := fmt.Sprintf("%s/%s/%s", rootURL, namespace, path) + + return mh.apiClient.GetContent(fullPath) +} diff --git a/src/chartserver/repo_handler.go b/src/chartserver/repo_handler.go index 372296599..39a572820 100644 --- a/src/chartserver/repo_handler.go +++ b/src/chartserver/repo_handler.go @@ -4,10 +4,8 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" "net/http" "net/url" - "os" "strings" "sync" "sync/atomic" @@ -30,7 +28,7 @@ type RepositoryHandler struct { trafficProxy *ProxyEngine //HTTP client used to call the realted APIs of the backend chart repositories - apiClient *http.Client + apiClient *ChartClient //Point to the url of the backend server backendServerAddress *url.URL @@ -60,14 +58,14 @@ func (rh *RepositoryHandler) GetIndexFile(w http.ResponseWriter, req *http.Reque //Get project manager references projectMgr, err := filter.GetProjectManager(req) if err != nil { - writeError(w, http.StatusInternalServerError, err) + writeInternalError(w, err) return } //Get all the projects results, err := projectMgr.List(nil) if err != nil { - writeError(w, http.StatusInternalServerError, err) + writeInternalError(w, err) return } @@ -161,7 +159,7 @@ LOOP: writeError(w, http.StatusInternalServerError, err) return case <-req.Context().Done(): - writeError(w, http.StatusInternalServerError, errors.New("request aborted")) + writeInternalError(w, errors.New("request aborted")) return } } @@ -187,7 +185,7 @@ LOOP: bytes, err := yaml.Marshal(mergedIndexFile) if err != nil { - writeError(w, http.StatusInternalServerError, err) + writeInternalError(w, err) return } @@ -206,29 +204,12 @@ func (rh *RepositoryHandler) getIndexYamlWithNS(namespace string) (*helm_repo.In rootURL := strings.TrimSuffix(rh.backendServerAddress.String(), "/") url := fmt.Sprintf("%s/%s/index.yaml", rootURL, namespace) - request, err := http.NewRequest(http.MethodGet, url, nil) + content, err := rh.apiClient.GetContent(url) if err != nil { return nil, err } - //Set basic auth - request.SetBasicAuth(userName, os.Getenv(passwordKey)) - - response, err := rh.apiClient.Do(request) - if err != nil { - return nil, err - } - - if response.StatusCode != http.StatusOK { - return nil, fmt.Errorf("failed to retrieve the index.yaml under repo %s", namespace) - } - - content, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, err - } - defer response.Body.Close() - + //Traverse to index file object for merging indexFile := helm_repo.NewIndexFile() if err := yaml.Unmarshal(content, indexFile); err != nil { return nil, err @@ -276,12 +257,3 @@ func emptyIndexFile() []byte { return rawData } - -func writeError(w http.ResponseWriter, code int, err error) { - errorObj := make(map[string]string) - errorObj["error"] = err.Error() - errorContent, _ := json.Marshal(errorObj) - - w.WriteHeader(code) - w.Write(errorContent) -} diff --git a/src/chartserver/utils.go b/src/chartserver/utils.go new file mode 100644 index 000000000..fb69376d6 --- /dev/null +++ b/src/chartserver/utils.go @@ -0,0 +1,33 @@ +package chartserver + +import ( + "encoding/json" + "net/http" +) + +const ( + contentTypeHeader = "content-type" + contentTypeJSON = "application/json" +) + +//Write error to http client +func writeError(w http.ResponseWriter, code int, err error) { + errorObj := make(map[string]string) + errorObj["error"] = err.Error() + errorContent, _ := json.Marshal(errorObj) + + w.WriteHeader(code) + w.Write(errorContent) +} + +//StatusCode == 500 +func writeInternalError(w http.ResponseWriter, err error) { + writeError(w, http.StatusInternalServerError, err) +} + +//Write JSON data to http client +func writeJSONData(w http.ResponseWriter, data []byte) { + w.Header().Set(contentTypeHeader, contentTypeJSON) + w.WriteHeader(http.StatusOK) + w.Write(data) +} diff --git a/src/vendor/github.com/blang/semver/.gx/lastpubver b/src/vendor/github.com/blang/semver/.gx/lastpubver new file mode 100644 index 000000000..27ed26783 --- /dev/null +++ b/src/vendor/github.com/blang/semver/.gx/lastpubver @@ -0,0 +1 @@ +3.5.1: QmYRGECuvQnRX73fcvPnGbYijBcGN2HbKZQ7jh26qmLiHG diff --git a/src/vendor/github.com/blang/semver/.travis.yml b/src/vendor/github.com/blang/semver/.travis.yml new file mode 100644 index 000000000..102fb9a69 --- /dev/null +++ b/src/vendor/github.com/blang/semver/.travis.yml @@ -0,0 +1,21 @@ +language: go +matrix: + include: + - go: 1.4.3 + - go: 1.5.4 + - go: 1.6.3 + - go: 1.7 + - go: tip + allow_failures: + - go: tip +install: +- go get golang.org/x/tools/cmd/cover +- go get github.com/mattn/goveralls +script: +- echo "Test and track coverage" ; $HOME/gopath/bin/goveralls -package "." -service=travis-ci + -repotoken $COVERALLS_TOKEN +- echo "Build examples" ; cd examples && go build +- echo "Check if gofmt'd" ; diff -u <(echo -n) <(gofmt -d -s .) +env: + global: + secure: HroGEAUQpVq9zX1b1VIkraLiywhGbzvNnTZq2TMxgK7JHP8xqNplAeF1izrR2i4QLL9nsY+9WtYss4QuPvEtZcVHUobw6XnL6radF7jS1LgfYZ9Y7oF+zogZ2I5QUMRLGA7rcxQ05s7mKq3XZQfeqaNts4bms/eZRefWuaFZbkw= diff --git a/src/vendor/github.com/blang/semver/LICENSE b/src/vendor/github.com/blang/semver/LICENSE new file mode 100644 index 000000000..5ba5c86fc --- /dev/null +++ b/src/vendor/github.com/blang/semver/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2014 Benedikt Lang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/src/vendor/github.com/blang/semver/README.md b/src/vendor/github.com/blang/semver/README.md new file mode 100644 index 000000000..08b2e4a3d --- /dev/null +++ b/src/vendor/github.com/blang/semver/README.md @@ -0,0 +1,194 @@ +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?status.png)](https://godoc.org/github.com/blang/semver) [![Coverage Status](https://img.shields.io/coveralls/blang/semver.svg)](https://coveralls.io/r/blang/semver?branch=master) +====== + +semver is a [Semantic Versioning](http://semver.org/) library written in golang. It fully covers spec version `2.0.0`. + +Usage +----- +```bash +$ go get github.com/blang/semver +``` +Note: Always vendor your dependencies or fix on a specific version tag. + +```go +import github.com/blang/semver +v1, err := semver.Make("1.0.0-beta") +v2, err := semver.Make("2.0.0-beta") +v1.Compare(v2) +``` + +Also check the [GoDocs](http://godoc.org/github.com/blang/semver). + +Why should I use this lib? +----- + +- Fully spec compatible +- No reflection +- No regex +- Fully tested (Coverage >99%) +- Readable parsing/validation errors +- Fast (See [Benchmarks](#benchmarks)) +- Only Stdlib +- Uses values instead of pointers +- Many features, see below + + +Features +----- + +- Parsing and validation at all levels +- Comparator-like comparisons +- Compare Helper Methods +- InPlace manipulation +- Ranges `>=1.0.0 <2.0.0 || >=3.0.0 !3.0.1-beta.1` +- Wildcards `>=1.x`, `<=2.5.x` +- Sortable (implements sort.Interface) +- database/sql compatible (sql.Scanner/Valuer) +- encoding/json compatible (json.Marshaler/Unmarshaler) + +Ranges +------ + +A `Range` is a set of conditions which specify which versions satisfy the range. + +A condition is composed of an operator and a version. The supported operators are: + +- `<1.0.0` Less than `1.0.0` +- `<=1.0.0` Less than or equal to `1.0.0` +- `>1.0.0` Greater than `1.0.0` +- `>=1.0.0` Greater than or equal to `1.0.0` +- `1.0.0`, `=1.0.0`, `==1.0.0` Equal to `1.0.0` +- `!1.0.0`, `!=1.0.0` Not equal to `1.0.0`. Excludes version `1.0.0`. + +Note that spaces between the operator and the version will be gracefully tolerated. + +A `Range` can link multiple `Ranges` separated by space: + +Ranges can be linked by logical AND: + + - `>1.0.0 <2.0.0` would match between both ranges, so `1.1.1` and `1.8.7` but not `1.0.0` or `2.0.0` + - `>1.0.0 <3.0.0 !2.0.3-beta.2` would match every version between `1.0.0` and `3.0.0` except `2.0.3-beta.2` + +Ranges can also be linked by logical OR: + + - `<2.0.0 || >=3.0.0` would match `1.x.x` and `3.x.x` but not `2.x.x` + +AND has a higher precedence than OR. It's not possible to use brackets. + +Ranges can be combined by both AND and OR + + - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1` + +Range usage: + +``` +v, err := semver.Parse("1.2.3") +range, err := semver.ParseRange(">1.0.0 <2.0.0 || >=3.0.0") +if range(v) { + //valid +} + +``` + +Example +----- + +Have a look at full examples in [examples/main.go](examples/main.go) + +```go +import github.com/blang/semver + +v, err := semver.Make("0.0.1-alpha.preview+123.github") +fmt.Printf("Major: %d\n", v.Major) +fmt.Printf("Minor: %d\n", v.Minor) +fmt.Printf("Patch: %d\n", v.Patch) +fmt.Printf("Pre: %s\n", v.Pre) +fmt.Printf("Build: %s\n", v.Build) + +// Prerelease versions array +if len(v.Pre) > 0 { + fmt.Println("Prerelease versions:") + for i, pre := range v.Pre { + fmt.Printf("%d: %q\n", i, pre) + } +} + +// Build meta data array +if len(v.Build) > 0 { + fmt.Println("Build meta data:") + for i, build := range v.Build { + fmt.Printf("%d: %q\n", i, build) + } +} + +v001, err := semver.Make("0.0.1") +// Compare using helpers: v.GT(v2), v.LT, v.GTE, v.LTE +v001.GT(v) == true +v.LT(v001) == true +v.GTE(v) == true +v.LTE(v) == true + +// Or use v.Compare(v2) for comparisons (-1, 0, 1): +v001.Compare(v) == 1 +v.Compare(v001) == -1 +v.Compare(v) == 0 + +// Manipulate Version in place: +v.Pre[0], err = semver.NewPRVersion("beta") +if err != nil { + fmt.Printf("Error parsing pre release version: %q", err) +} + +fmt.Println("\nValidate versions:") +v.Build[0] = "?" + +err = v.Validate() +if err != nil { + fmt.Printf("Validation failed: %s\n", err) +} +``` + + +Benchmarks +----- + + BenchmarkParseSimple-4 5000000 390 ns/op 48 B/op 1 allocs/op + BenchmarkParseComplex-4 1000000 1813 ns/op 256 B/op 7 allocs/op + BenchmarkParseAverage-4 1000000 1171 ns/op 163 B/op 4 allocs/op + BenchmarkStringSimple-4 20000000 119 ns/op 16 B/op 1 allocs/op + BenchmarkStringLarger-4 10000000 206 ns/op 32 B/op 2 allocs/op + BenchmarkStringComplex-4 5000000 324 ns/op 80 B/op 3 allocs/op + BenchmarkStringAverage-4 5000000 273 ns/op 53 B/op 2 allocs/op + BenchmarkValidateSimple-4 200000000 9.33 ns/op 0 B/op 0 allocs/op + BenchmarkValidateComplex-4 3000000 469 ns/op 0 B/op 0 allocs/op + BenchmarkValidateAverage-4 5000000 256 ns/op 0 B/op 0 allocs/op + BenchmarkCompareSimple-4 100000000 11.8 ns/op 0 B/op 0 allocs/op + BenchmarkCompareComplex-4 50000000 30.8 ns/op 0 B/op 0 allocs/op + BenchmarkCompareAverage-4 30000000 41.5 ns/op 0 B/op 0 allocs/op + BenchmarkSort-4 3000000 419 ns/op 256 B/op 2 allocs/op + BenchmarkRangeParseSimple-4 2000000 850 ns/op 192 B/op 5 allocs/op + BenchmarkRangeParseAverage-4 1000000 1677 ns/op 400 B/op 10 allocs/op + BenchmarkRangeParseComplex-4 300000 5214 ns/op 1440 B/op 30 allocs/op + BenchmarkRangeMatchSimple-4 50000000 25.6 ns/op 0 B/op 0 allocs/op + BenchmarkRangeMatchAverage-4 30000000 56.4 ns/op 0 B/op 0 allocs/op + BenchmarkRangeMatchComplex-4 10000000 153 ns/op 0 B/op 0 allocs/op + +See benchmark cases at [semver_test.go](semver_test.go) + + +Motivation +----- + +I simply couldn't find any lib supporting the full spec. Others were just wrong or used reflection and regex which i don't like. + + +Contribution +----- + +Feel free to make a pull request. For bigger changes create a issue first to discuss about it. + + +License +----- + +See [LICENSE](LICENSE) file. diff --git a/src/vendor/github.com/blang/semver/examples/main.go b/src/vendor/github.com/blang/semver/examples/main.go new file mode 100644 index 000000000..f36c983d9 --- /dev/null +++ b/src/vendor/github.com/blang/semver/examples/main.go @@ -0,0 +1,83 @@ +package main + +import ( + "fmt" + "github.com/blang/semver" +) + +func main() { + v, err := semver.Parse("0.0.1-alpha.preview.222+123.github") + if err != nil { + fmt.Printf("Error while parsing (not valid): %q", err) + } + fmt.Printf("Version to string: %q\n", v) + + fmt.Printf("Major: %d\n", v.Major) + fmt.Printf("Minor: %d\n", v.Minor) + fmt.Printf("Patch: %d\n", v.Patch) + + // Prerelease versions + if len(v.Pre) > 0 { + fmt.Println("Prerelease versions:") + for i, pre := range v.Pre { + fmt.Printf("%d: %q\n", i, pre) + } + } + + // Build meta data + if len(v.Build) > 0 { + fmt.Println("Build meta data:") + for i, build := range v.Build { + fmt.Printf("%d: %q\n", i, build) + } + } + + // Make == Parse (Value), New for Pointer + v001, err := semver.Make("0.0.1") + + fmt.Println("\nUse Version.Compare for comparisons (-1, 0, 1):") + fmt.Printf("%q is greater than %q: Compare == %d\n", v001, v, v001.Compare(v)) + fmt.Printf("%q is less than %q: Compare == %d\n", v, v001, v.Compare(v001)) + fmt.Printf("%q is equal to %q: Compare == %d\n", v, v, v.Compare(v)) + + fmt.Println("\nUse comparison helpers returning booleans:") + fmt.Printf("%q is greater than %q: %t\n", v001, v, v001.GT(v)) + fmt.Printf("%q is greater than equal %q: %t\n", v001, v, v001.GTE(v)) + fmt.Printf("%q is greater than equal %q: %t\n", v, v, v.GTE(v)) + fmt.Printf("%q is less than %q: %t\n", v, v001, v.LT(v001)) + fmt.Printf("%q is less than equal %q: %t\n", v, v001, v.LTE(v001)) + fmt.Printf("%q is less than equal %q: %t\n", v, v, v.LTE(v)) + + fmt.Println("\nManipulate Version in place:") + v.Pre[0], err = semver.NewPRVersion("beta") + if err != nil { + fmt.Printf("Error parsing pre release version: %q", err) + } + fmt.Printf("Version to string: %q\n", v) + + fmt.Println("\nCompare Prerelease versions:") + pre1, _ := semver.NewPRVersion("123") + pre2, _ := semver.NewPRVersion("alpha") + pre3, _ := semver.NewPRVersion("124") + fmt.Printf("%q is less than %q: Compare == %d\n", pre1, pre2, pre1.Compare(pre2)) + fmt.Printf("%q is greater than %q: Compare == %d\n", pre3, pre1, pre3.Compare(pre1)) + fmt.Printf("%q is equal to %q: Compare == %d\n", pre1, pre1, pre1.Compare(pre1)) + + fmt.Println("\nValidate versions:") + v.Build[0] = "?" + + err = v.Validate() + if err != nil { + fmt.Printf("Validation failed: %s\n", err) + } + + fmt.Println("Create valid build meta data:") + b1, _ := semver.NewBuildVersion("build123") + v.Build[0] = b1 + fmt.Printf("Version with new build version %q\n", v) + + _, err = semver.NewBuildVersion("build?123") + if err != nil { + fmt.Printf("Create build version failed: %s\n", err) + } +} diff --git a/src/vendor/github.com/blang/semver/json.go b/src/vendor/github.com/blang/semver/json.go new file mode 100644 index 000000000..a74bf7c44 --- /dev/null +++ b/src/vendor/github.com/blang/semver/json.go @@ -0,0 +1,23 @@ +package semver + +import ( + "encoding/json" +) + +// MarshalJSON implements the encoding/json.Marshaler interface. +func (v Version) MarshalJSON() ([]byte, error) { + return json.Marshal(v.String()) +} + +// UnmarshalJSON implements the encoding/json.Unmarshaler interface. +func (v *Version) UnmarshalJSON(data []byte) (err error) { + var versionString string + + if err = json.Unmarshal(data, &versionString); err != nil { + return + } + + *v, err = Parse(versionString) + + return +} diff --git a/src/vendor/github.com/blang/semver/json_test.go b/src/vendor/github.com/blang/semver/json_test.go new file mode 100644 index 000000000..c635dea16 --- /dev/null +++ b/src/vendor/github.com/blang/semver/json_test.go @@ -0,0 +1,49 @@ +package semver + +import ( + "encoding/json" + "strconv" + "testing" +) + +func TestJSONMarshal(t *testing.T) { + versionString := "3.1.4-alpha.1.5.9+build.2.6.5" + v, err := Parse(versionString) + if err != nil { + t.Fatal(err) + } + + versionJSON, err := json.Marshal(v) + if err != nil { + t.Fatal(err) + } + + quotedVersionString := strconv.Quote(versionString) + + if string(versionJSON) != quotedVersionString { + t.Fatalf("JSON marshaled semantic version not equal: expected %q, got %q", quotedVersionString, string(versionJSON)) + } +} + +func TestJSONUnmarshal(t *testing.T) { + versionString := "3.1.4-alpha.1.5.9+build.2.6.5" + quotedVersionString := strconv.Quote(versionString) + + var v Version + if err := json.Unmarshal([]byte(quotedVersionString), &v); err != nil { + t.Fatal(err) + } + + if v.String() != versionString { + t.Fatalf("JSON unmarshaled semantic version not equal: expected %q, got %q", versionString, v.String()) + } + + badVersionString := strconv.Quote("3.1.4.1.5.9.2.6.5-other-digits-of-pi") + if err := json.Unmarshal([]byte(badVersionString), &v); err == nil { + t.Fatal("expected JSON unmarshal error, got nil") + } + + if err := json.Unmarshal([]byte("3.1"), &v); err == nil { + t.Fatal("expected JSON unmarshal error, got nil") + } +} diff --git a/src/vendor/github.com/blang/semver/package.json b/src/vendor/github.com/blang/semver/package.json new file mode 100644 index 000000000..1cf8ebdd9 --- /dev/null +++ b/src/vendor/github.com/blang/semver/package.json @@ -0,0 +1,17 @@ +{ + "author": "blang", + "bugs": { + "URL": "https://github.com/blang/semver/issues", + "url": "https://github.com/blang/semver/issues" + }, + "gx": { + "dvcsimport": "github.com/blang/semver" + }, + "gxVersion": "0.10.0", + "language": "go", + "license": "MIT", + "name": "semver", + "releaseCmd": "git commit -a -m \"gx publish $VERSION\"", + "version": "3.5.1" +} + diff --git a/src/vendor/github.com/blang/semver/range.go b/src/vendor/github.com/blang/semver/range.go new file mode 100644 index 000000000..fca406d47 --- /dev/null +++ b/src/vendor/github.com/blang/semver/range.go @@ -0,0 +1,416 @@ +package semver + +import ( + "fmt" + "strconv" + "strings" + "unicode" +) + +type wildcardType int + +const ( + noneWildcard wildcardType = iota + majorWildcard wildcardType = 1 + minorWildcard wildcardType = 2 + patchWildcard wildcardType = 3 +) + +func wildcardTypefromInt(i int) wildcardType { + switch i { + case 1: + return majorWildcard + case 2: + return minorWildcard + case 3: + return patchWildcard + default: + return noneWildcard + } +} + +type comparator func(Version, Version) bool + +var ( + compEQ comparator = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) == 0 + } + compNE = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) != 0 + } + compGT = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) == 1 + } + compGE = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) >= 0 + } + compLT = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) == -1 + } + compLE = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) <= 0 + } +) + +type versionRange struct { + v Version + c comparator +} + +// rangeFunc creates a Range from the given versionRange. +func (vr *versionRange) rangeFunc() Range { + return Range(func(v Version) bool { + return vr.c(v, vr.v) + }) +} + +// Range represents a range of versions. +// A Range can be used to check if a Version satisfies it: +// +// range, err := semver.ParseRange(">1.0.0 <2.0.0") +// range(semver.MustParse("1.1.1") // returns true +type Range func(Version) bool + +// OR combines the existing Range with another Range using logical OR. +func (rf Range) OR(f Range) Range { + return Range(func(v Version) bool { + return rf(v) || f(v) + }) +} + +// AND combines the existing Range with another Range using logical AND. +func (rf Range) AND(f Range) Range { + return Range(func(v Version) bool { + return rf(v) && f(v) + }) +} + +// ParseRange parses a range and returns a Range. +// If the range could not be parsed an error is returned. +// +// Valid ranges are: +// - "<1.0.0" +// - "<=1.0.0" +// - ">1.0.0" +// - ">=1.0.0" +// - "1.0.0", "=1.0.0", "==1.0.0" +// - "!1.0.0", "!=1.0.0" +// +// A Range can consist of multiple ranges separated by space: +// Ranges can be linked by logical AND: +// - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7" but not "1.0.0" or "2.0.0" +// - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2 +// +// Ranges can also be linked by logical OR: +// - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x" +// +// AND has a higher precedence than OR. It's not possible to use brackets. +// +// Ranges can be combined by both AND and OR +// +// - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1` +func ParseRange(s string) (Range, error) { + parts := splitAndTrim(s) + orParts, err := splitORParts(parts) + if err != nil { + return nil, err + } + expandedParts, err := expandWildcardVersion(orParts) + if err != nil { + return nil, err + } + var orFn Range + for _, p := range expandedParts { + var andFn Range + for _, ap := range p { + opStr, vStr, err := splitComparatorVersion(ap) + if err != nil { + return nil, err + } + vr, err := buildVersionRange(opStr, vStr) + if err != nil { + return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err) + } + rf := vr.rangeFunc() + + // Set function + if andFn == nil { + andFn = rf + } else { // Combine with existing function + andFn = andFn.AND(rf) + } + } + if orFn == nil { + orFn = andFn + } else { + orFn = orFn.OR(andFn) + } + + } + return orFn, nil +} + +// splitORParts splits the already cleaned parts by '||'. +// Checks for invalid positions of the operator and returns an +// error if found. +func splitORParts(parts []string) ([][]string, error) { + var ORparts [][]string + last := 0 + for i, p := range parts { + if p == "||" { + if i == 0 { + return nil, fmt.Errorf("First element in range is '||'") + } + ORparts = append(ORparts, parts[last:i]) + last = i + 1 + } + } + if last == len(parts) { + return nil, fmt.Errorf("Last element in range is '||'") + } + ORparts = append(ORparts, parts[last:]) + return ORparts, nil +} + +// buildVersionRange takes a slice of 2: operator and version +// and builds a versionRange, otherwise an error. +func buildVersionRange(opStr, vStr string) (*versionRange, error) { + c := parseComparator(opStr) + if c == nil { + return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, "")) + } + v, err := Parse(vStr) + if err != nil { + return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err) + } + + return &versionRange{ + v: v, + c: c, + }, nil + +} + +// inArray checks if a byte is contained in an array of bytes +func inArray(s byte, list []byte) bool { + for _, el := range list { + if el == s { + return true + } + } + return false +} + +// splitAndTrim splits a range string by spaces and cleans whitespaces +func splitAndTrim(s string) (result []string) { + last := 0 + var lastChar byte + excludeFromSplit := []byte{'>', '<', '='} + for i := 0; i < len(s); i++ { + if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) { + if last < i-1 { + result = append(result, s[last:i]) + } + last = i + 1 + } else if s[i] != ' ' { + lastChar = s[i] + } + } + if last < len(s)-1 { + result = append(result, s[last:]) + } + + for i, v := range result { + result[i] = strings.Replace(v, " ", "", -1) + } + + // parts := strings.Split(s, " ") + // for _, x := range parts { + // if s := strings.TrimSpace(x); len(s) != 0 { + // result = append(result, s) + // } + // } + return +} + +// splitComparatorVersion splits the comparator from the version. +// Input must be free of leading or trailing spaces. +func splitComparatorVersion(s string) (string, string, error) { + i := strings.IndexFunc(s, unicode.IsDigit) + if i == -1 { + return "", "", fmt.Errorf("Could not get version from string: %q", s) + } + return strings.TrimSpace(s[0:i]), s[i:], nil +} + +// getWildcardType will return the type of wildcard that the +// passed version contains +func getWildcardType(vStr string) wildcardType { + parts := strings.Split(vStr, ".") + nparts := len(parts) + wildcard := parts[nparts-1] + + possibleWildcardType := wildcardTypefromInt(nparts) + if wildcard == "x" { + return possibleWildcardType + } + + return noneWildcard +} + +// createVersionFromWildcard will convert a wildcard version +// into a regular version, replacing 'x's with '0's, handling +// special cases like '1.x.x' and '1.x' +func createVersionFromWildcard(vStr string) string { + // handle 1.x.x + vStr2 := strings.Replace(vStr, ".x.x", ".x", 1) + vStr2 = strings.Replace(vStr2, ".x", ".0", 1) + parts := strings.Split(vStr2, ".") + + // handle 1.x + if len(parts) == 2 { + return vStr2 + ".0" + } + + return vStr2 +} + +// incrementMajorVersion will increment the major version +// of the passed version +func incrementMajorVersion(vStr string) (string, error) { + parts := strings.Split(vStr, ".") + i, err := strconv.Atoi(parts[0]) + if err != nil { + return "", err + } + parts[0] = strconv.Itoa(i + 1) + + return strings.Join(parts, "."), nil +} + +// incrementMajorVersion will increment the minor version +// of the passed version +func incrementMinorVersion(vStr string) (string, error) { + parts := strings.Split(vStr, ".") + i, err := strconv.Atoi(parts[1]) + if err != nil { + return "", err + } + parts[1] = strconv.Itoa(i + 1) + + return strings.Join(parts, "."), nil +} + +// expandWildcardVersion will expand wildcards inside versions +// following these rules: +// +// * when dealing with patch wildcards: +// >= 1.2.x will become >= 1.2.0 +// <= 1.2.x will become < 1.3.0 +// > 1.2.x will become >= 1.3.0 +// < 1.2.x will become < 1.2.0 +// != 1.2.x will become < 1.2.0 >= 1.3.0 +// +// * when dealing with minor wildcards: +// >= 1.x will become >= 1.0.0 +// <= 1.x will become < 2.0.0 +// > 1.x will become >= 2.0.0 +// < 1.0 will become < 1.0.0 +// != 1.x will become < 1.0.0 >= 2.0.0 +// +// * when dealing with wildcards without +// version operator: +// 1.2.x will become >= 1.2.0 < 1.3.0 +// 1.x will become >= 1.0.0 < 2.0.0 +func expandWildcardVersion(parts [][]string) ([][]string, error) { + var expandedParts [][]string + for _, p := range parts { + var newParts []string + for _, ap := range p { + if strings.Index(ap, "x") != -1 { + opStr, vStr, err := splitComparatorVersion(ap) + if err != nil { + return nil, err + } + + versionWildcardType := getWildcardType(vStr) + flatVersion := createVersionFromWildcard(vStr) + + var resultOperator string + var shouldIncrementVersion bool + switch opStr { + case ">": + resultOperator = ">=" + shouldIncrementVersion = true + case ">=": + resultOperator = ">=" + case "<": + resultOperator = "<" + case "<=": + resultOperator = "<" + shouldIncrementVersion = true + case "", "=", "==": + newParts = append(newParts, ">="+flatVersion) + resultOperator = "<" + shouldIncrementVersion = true + case "!=", "!": + newParts = append(newParts, "<"+flatVersion) + resultOperator = ">=" + shouldIncrementVersion = true + } + + var resultVersion string + if shouldIncrementVersion { + switch versionWildcardType { + case patchWildcard: + resultVersion, _ = incrementMinorVersion(flatVersion) + case minorWildcard: + resultVersion, _ = incrementMajorVersion(flatVersion) + } + } else { + resultVersion = flatVersion + } + + ap = resultOperator + resultVersion + } + newParts = append(newParts, ap) + } + expandedParts = append(expandedParts, newParts) + } + + return expandedParts, nil +} + +func parseComparator(s string) comparator { + switch s { + case "==": + fallthrough + case "": + fallthrough + case "=": + return compEQ + case ">": + return compGT + case ">=": + return compGE + case "<": + return compLT + case "<=": + return compLE + case "!": + fallthrough + case "!=": + return compNE + } + + return nil +} + +// MustParseRange is like ParseRange but panics if the range cannot be parsed. +func MustParseRange(s string) Range { + r, err := ParseRange(s) + if err != nil { + panic(`semver: ParseRange(` + s + `): ` + err.Error()) + } + return r +} diff --git a/src/vendor/github.com/blang/semver/range_test.go b/src/vendor/github.com/blang/semver/range_test.go new file mode 100644 index 000000000..83ee9c787 --- /dev/null +++ b/src/vendor/github.com/blang/semver/range_test.go @@ -0,0 +1,581 @@ +package semver + +import ( + "reflect" + "strings" + "testing" +) + +type wildcardTypeTest struct { + input string + wildcardType wildcardType +} + +type comparatorTest struct { + input string + comparator func(comparator) bool +} + +func TestParseComparator(t *testing.T) { + compatorTests := []comparatorTest{ + {">", testGT}, + {">=", testGE}, + {"<", testLT}, + {"<=", testLE}, + {"", testEQ}, + {"=", testEQ}, + {"==", testEQ}, + {"!=", testNE}, + {"!", testNE}, + {"-", nil}, + {"<==", nil}, + {"<<", nil}, + {">>", nil}, + } + + for _, tc := range compatorTests { + if c := parseComparator(tc.input); c == nil { + if tc.comparator != nil { + t.Errorf("Comparator nil for case %q\n", tc.input) + } + } else if !tc.comparator(c) { + t.Errorf("Invalid comparator for case %q\n", tc.input) + } + } +} + +var ( + v1 = MustParse("1.2.2") + v2 = MustParse("1.2.3") + v3 = MustParse("1.2.4") +) + +func testEQ(f comparator) bool { + return f(v1, v1) && !f(v1, v2) +} + +func testNE(f comparator) bool { + return !f(v1, v1) && f(v1, v2) +} + +func testGT(f comparator) bool { + return f(v2, v1) && f(v3, v2) && !f(v1, v2) && !f(v1, v1) +} + +func testGE(f comparator) bool { + return f(v2, v1) && f(v3, v2) && !f(v1, v2) +} + +func testLT(f comparator) bool { + return f(v1, v2) && f(v2, v3) && !f(v2, v1) && !f(v1, v1) +} + +func testLE(f comparator) bool { + return f(v1, v2) && f(v2, v3) && !f(v2, v1) +} + +func TestSplitAndTrim(t *testing.T) { + tests := []struct { + i string + s []string + }{ + {"1.2.3 1.2.3", []string{"1.2.3", "1.2.3"}}, + {" 1.2.3 1.2.3 ", []string{"1.2.3", "1.2.3"}}, // Spaces + {" >= 1.2.3 <= 1.2.3 ", []string{">=1.2.3", "<=1.2.3"}}, // Spaces between operator and version + {"1.2.3 || >=1.2.3 <1.2.3", []string{"1.2.3", "||", ">=1.2.3", "<1.2.3"}}, + {" 1.2.3 || >=1.2.3 <1.2.3 ", []string{"1.2.3", "||", ">=1.2.3", "<1.2.3"}}, + } + + for _, tc := range tests { + p := splitAndTrim(tc.i) + if !reflect.DeepEqual(p, tc.s) { + t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p) + } + } +} + +func TestSplitComparatorVersion(t *testing.T) { + tests := []struct { + i string + p []string + }{ + {">1.2.3", []string{">", "1.2.3"}}, + {">=1.2.3", []string{">=", "1.2.3"}}, + {"<1.2.3", []string{"<", "1.2.3"}}, + {"<=1.2.3", []string{"<=", "1.2.3"}}, + {"1.2.3", []string{"", "1.2.3"}}, + {"=1.2.3", []string{"=", "1.2.3"}}, + {"==1.2.3", []string{"==", "1.2.3"}}, + {"!=1.2.3", []string{"!=", "1.2.3"}}, + {"!1.2.3", []string{"!", "1.2.3"}}, + {"error", nil}, + } + for _, tc := range tests { + if op, v, err := splitComparatorVersion(tc.i); err != nil { + if tc.p != nil { + t.Errorf("Invalid for case %q: Expected %q, got error %q", tc.i, tc.p, err) + } + } else if op != tc.p[0] { + t.Errorf("Invalid operator for case %q: Expected %q, got: %q", tc.i, tc.p[0], op) + } else if v != tc.p[1] { + t.Errorf("Invalid version for case %q: Expected %q, got: %q", tc.i, tc.p[1], v) + } + + } +} + +func TestBuildVersionRange(t *testing.T) { + tests := []struct { + opStr string + vStr string + c func(comparator) bool + v string + }{ + {">", "1.2.3", testGT, "1.2.3"}, + {">=", "1.2.3", testGE, "1.2.3"}, + {"<", "1.2.3", testLT, "1.2.3"}, + {"<=", "1.2.3", testLE, "1.2.3"}, + {"", "1.2.3", testEQ, "1.2.3"}, + {"=", "1.2.3", testEQ, "1.2.3"}, + {"==", "1.2.3", testEQ, "1.2.3"}, + {"!=", "1.2.3", testNE, "1.2.3"}, + {"!", "1.2.3", testNE, "1.2.3"}, + {">>", "1.2.3", nil, ""}, // Invalid comparator + {"=", "invalid", nil, ""}, // Invalid version + } + + for _, tc := range tests { + if r, err := buildVersionRange(tc.opStr, tc.vStr); err != nil { + if tc.c != nil { + t.Errorf("Invalid for case %q: Expected %q, got error %q", strings.Join([]string{tc.opStr, tc.vStr}, ""), tc.v, err) + } + } else if r == nil { + t.Errorf("Invalid for case %q: got nil", strings.Join([]string{tc.opStr, tc.vStr}, "")) + } else { + // test version + if tv := MustParse(tc.v); !r.v.EQ(tv) { + t.Errorf("Invalid for case %q: Expected version %q, got: %q", strings.Join([]string{tc.opStr, tc.vStr}, ""), tv, r.v) + } + // test comparator + if r.c == nil { + t.Errorf("Invalid for case %q: got nil comparator", strings.Join([]string{tc.opStr, tc.vStr}, "")) + continue + } + if !tc.c(r.c) { + t.Errorf("Invalid comparator for case %q\n", strings.Join([]string{tc.opStr, tc.vStr}, "")) + } + } + } + +} + +func TestSplitORParts(t *testing.T) { + tests := []struct { + i []string + o [][]string + }{ + {[]string{">1.2.3", "||", "<1.2.3", "||", "=1.2.3"}, [][]string{ + []string{">1.2.3"}, + []string{"<1.2.3"}, + []string{"=1.2.3"}, + }}, + {[]string{">1.2.3", "<1.2.3", "||", "=1.2.3"}, [][]string{ + []string{">1.2.3", "<1.2.3"}, + []string{"=1.2.3"}, + }}, + {[]string{">1.2.3", "||"}, nil}, + {[]string{"||", ">1.2.3"}, nil}, + } + for _, tc := range tests { + o, err := splitORParts(tc.i) + if err != nil && tc.o != nil { + t.Errorf("Unexpected error for case %q: %s", tc.i, err) + } + if !reflect.DeepEqual(tc.o, o) { + t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.o, o) + } + } +} + +func TestGetWildcardType(t *testing.T) { + wildcardTypeTests := []wildcardTypeTest{ + {"x", majorWildcard}, + {"1.x", minorWildcard}, + {"1.2.x", patchWildcard}, + {"fo.o.b.ar", noneWildcard}, + } + + for _, tc := range wildcardTypeTests { + o := getWildcardType(tc.input) + if o != tc.wildcardType { + t.Errorf("Invalid for case: %q: Expected %q, got: %q", tc.input, tc.wildcardType, o) + } + } +} + +func TestCreateVersionFromWildcard(t *testing.T) { + tests := []struct { + i string + s string + }{ + {"1.2.x", "1.2.0"}, + {"1.x", "1.0.0"}, + } + + for _, tc := range tests { + p := createVersionFromWildcard(tc.i) + if p != tc.s { + t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p) + } + } +} + +func TestIncrementMajorVersion(t *testing.T) { + tests := []struct { + i string + s string + }{ + {"1.2.3", "2.2.3"}, + {"1.2", "2.2"}, + {"foo.bar", ""}, + } + + for _, tc := range tests { + p, _ := incrementMajorVersion(tc.i) + if p != tc.s { + t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p) + } + } +} + +func TestIncrementMinorVersion(t *testing.T) { + tests := []struct { + i string + s string + }{ + {"1.2.3", "1.3.3"}, + {"1.2", "1.3"}, + {"foo.bar", ""}, + } + + for _, tc := range tests { + p, _ := incrementMinorVersion(tc.i) + if p != tc.s { + t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p) + } + } +} + +func TestExpandWildcardVersion(t *testing.T) { + tests := []struct { + i [][]string + o [][]string + }{ + {[][]string{[]string{"foox"}}, nil}, + {[][]string{[]string{">=1.2.x"}}, [][]string{[]string{">=1.2.0"}}}, + {[][]string{[]string{"<=1.2.x"}}, [][]string{[]string{"<1.3.0"}}}, + {[][]string{[]string{">1.2.x"}}, [][]string{[]string{">=1.3.0"}}}, + {[][]string{[]string{"<1.2.x"}}, [][]string{[]string{"<1.2.0"}}}, + {[][]string{[]string{"!=1.2.x"}}, [][]string{[]string{"<1.2.0", ">=1.3.0"}}}, + {[][]string{[]string{">=1.x"}}, [][]string{[]string{">=1.0.0"}}}, + {[][]string{[]string{"<=1.x"}}, [][]string{[]string{"<2.0.0"}}}, + {[][]string{[]string{">1.x"}}, [][]string{[]string{">=2.0.0"}}}, + {[][]string{[]string{"<1.x"}}, [][]string{[]string{"<1.0.0"}}}, + {[][]string{[]string{"!=1.x"}}, [][]string{[]string{"<1.0.0", ">=2.0.0"}}}, + {[][]string{[]string{"1.2.x"}}, [][]string{[]string{">=1.2.0", "<1.3.0"}}}, + {[][]string{[]string{"1.x"}}, [][]string{[]string{">=1.0.0", "<2.0.0"}}}, + } + + for _, tc := range tests { + o, _ := expandWildcardVersion(tc.i) + if !reflect.DeepEqual(tc.o, o) { + t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.o, o) + } + } +} + +func TestVersionRangeToRange(t *testing.T) { + vr := versionRange{ + v: MustParse("1.2.3"), + c: compLT, + } + rf := vr.rangeFunc() + if !rf(MustParse("1.2.2")) || rf(MustParse("1.2.3")) { + t.Errorf("Invalid conversion to range func") + } +} + +func TestRangeAND(t *testing.T) { + v := MustParse("1.2.2") + v1 := MustParse("1.2.1") + v2 := MustParse("1.2.3") + rf1 := Range(func(v Version) bool { + return v.GT(v1) + }) + rf2 := Range(func(v Version) bool { + return v.LT(v2) + }) + rf := rf1.AND(rf2) + if rf(v1) { + t.Errorf("Invalid rangefunc, accepted: %s", v1) + } + if rf(v2) { + t.Errorf("Invalid rangefunc, accepted: %s", v2) + } + if !rf(v) { + t.Errorf("Invalid rangefunc, did not accept: %s", v) + } +} + +func TestRangeOR(t *testing.T) { + tests := []struct { + v Version + b bool + }{ + {MustParse("1.2.0"), true}, + {MustParse("1.2.2"), false}, + {MustParse("1.2.4"), true}, + } + v1 := MustParse("1.2.1") + v2 := MustParse("1.2.3") + rf1 := Range(func(v Version) bool { + return v.LT(v1) + }) + rf2 := Range(func(v Version) bool { + return v.GT(v2) + }) + rf := rf1.OR(rf2) + for _, tc := range tests { + if r := rf(tc.v); r != tc.b { + t.Errorf("Invalid for case %q: Expected %t, got %t", tc.v, tc.b, r) + } + } +} + +func TestParseRange(t *testing.T) { + type tv struct { + v string + b bool + } + tests := []struct { + i string + t []tv + }{ + // Simple expressions + {">1.2.3", []tv{ + {"1.2.2", false}, + {"1.2.3", false}, + {"1.2.4", true}, + }}, + {">=1.2.3", []tv{ + {"1.2.3", true}, + {"1.2.4", true}, + {"1.2.2", false}, + }}, + {"<1.2.3", []tv{ + {"1.2.2", true}, + {"1.2.3", false}, + {"1.2.4", false}, + }}, + {"<=1.2.3", []tv{ + {"1.2.2", true}, + {"1.2.3", true}, + {"1.2.4", false}, + }}, + {"1.2.3", []tv{ + {"1.2.2", false}, + {"1.2.3", true}, + {"1.2.4", false}, + }}, + {"=1.2.3", []tv{ + {"1.2.2", false}, + {"1.2.3", true}, + {"1.2.4", false}, + }}, + {"==1.2.3", []tv{ + {"1.2.2", false}, + {"1.2.3", true}, + {"1.2.4", false}, + }}, + {"!=1.2.3", []tv{ + {"1.2.2", true}, + {"1.2.3", false}, + {"1.2.4", true}, + }}, + {"!1.2.3", []tv{ + {"1.2.2", true}, + {"1.2.3", false}, + {"1.2.4", true}, + }}, + // Simple Expression errors + {">>1.2.3", nil}, + {"!1.2.3", nil}, + {"1.0", nil}, + {"string", nil}, + {"", nil}, + {"fo.ob.ar.x", nil}, + // AND Expressions + {">1.2.2 <1.2.4", []tv{ + {"1.2.2", false}, + {"1.2.3", true}, + {"1.2.4", false}, + }}, + {"<1.2.2 <1.2.4", []tv{ + {"1.2.1", true}, + {"1.2.2", false}, + {"1.2.3", false}, + {"1.2.4", false}, + }}, + {">1.2.2 <1.2.5 !=1.2.4", []tv{ + {"1.2.2", false}, + {"1.2.3", true}, + {"1.2.4", false}, + {"1.2.5", false}, + }}, + {">1.2.2 <1.2.5 !1.2.4", []tv{ + {"1.2.2", false}, + {"1.2.3", true}, + {"1.2.4", false}, + {"1.2.5", false}, + }}, + // OR Expressions + {">1.2.2 || <1.2.4", []tv{ + {"1.2.2", true}, + {"1.2.3", true}, + {"1.2.4", true}, + }}, + {"<1.2.2 || >1.2.4", []tv{ + {"1.2.2", false}, + {"1.2.3", false}, + {"1.2.4", false}, + }}, + // Wildcard expressions + {">1.x", []tv{ + {"0.1.9", false}, + {"1.2.6", false}, + {"1.9.0", false}, + {"2.0.0", true}, + }}, + {">1.2.x", []tv{ + {"1.1.9", false}, + {"1.2.6", false}, + {"1.3.0", true}, + }}, + // Combined Expressions + {">1.2.2 <1.2.4 || >=2.0.0", []tv{ + {"1.2.2", false}, + {"1.2.3", true}, + {"1.2.4", false}, + {"2.0.0", true}, + {"2.0.1", true}, + }}, + {"1.x || >=2.0.x <2.2.x", []tv{ + {"0.9.2", false}, + {"1.2.2", true}, + {"2.0.0", true}, + {"2.1.8", true}, + {"2.2.0", false}, + }}, + {">1.2.2 <1.2.4 || >=2.0.0 <3.0.0", []tv{ + {"1.2.2", false}, + {"1.2.3", true}, + {"1.2.4", false}, + {"2.0.0", true}, + {"2.0.1", true}, + {"2.9.9", true}, + {"3.0.0", false}, + }}, + } + + for _, tc := range tests { + r, err := ParseRange(tc.i) + if err != nil && tc.t != nil { + t.Errorf("Error parsing range %q: %s", tc.i, err) + continue + } + for _, tvc := range tc.t { + v := MustParse(tvc.v) + if res := r(v); res != tvc.b { + t.Errorf("Invalid for case %q matching %q: Expected %t, got: %t", tc.i, tvc.v, tvc.b, res) + } + } + + } +} + +func TestMustParseRange(t *testing.T) { + testCase := ">1.2.2 <1.2.4 || >=2.0.0 <3.0.0" + r := MustParseRange(testCase) + if !r(MustParse("1.2.3")) { + t.Errorf("Unexpected range behavior on MustParseRange") + } +} + +func TestMustParseRange_panic(t *testing.T) { + defer func() { + if recover() == nil { + t.Errorf("Should have panicked") + } + }() + _ = MustParseRange("invalid version") +} + +func BenchmarkRangeParseSimple(b *testing.B) { + const VERSION = ">1.0.0" + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + ParseRange(VERSION) + } +} + +func BenchmarkRangeParseAverage(b *testing.B) { + const VERSION = ">=1.0.0 <2.0.0" + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + ParseRange(VERSION) + } +} + +func BenchmarkRangeParseComplex(b *testing.B) { + const VERSION = ">=1.0.0 <2.0.0 || >=3.0.1 <4.0.0 !=3.0.3 || >=5.0.0" + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + ParseRange(VERSION) + } +} + +func BenchmarkRangeMatchSimple(b *testing.B) { + const VERSION = ">1.0.0" + r, _ := ParseRange(VERSION) + v := MustParse("2.0.0") + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + r(v) + } +} + +func BenchmarkRangeMatchAverage(b *testing.B) { + const VERSION = ">=1.0.0 <2.0.0" + r, _ := ParseRange(VERSION) + v := MustParse("1.2.3") + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + r(v) + } +} + +func BenchmarkRangeMatchComplex(b *testing.B) { + const VERSION = ">=1.0.0 <2.0.0 || >=3.0.1 <4.0.0 !=3.0.3 || >=5.0.0" + r, _ := ParseRange(VERSION) + v := MustParse("5.0.1") + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + r(v) + } +} diff --git a/src/vendor/github.com/blang/semver/semver.go b/src/vendor/github.com/blang/semver/semver.go new file mode 100644 index 000000000..8ee0842e6 --- /dev/null +++ b/src/vendor/github.com/blang/semver/semver.go @@ -0,0 +1,418 @@ +package semver + +import ( + "errors" + "fmt" + "strconv" + "strings" +) + +const ( + numbers string = "0123456789" + alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" + alphanum = alphas + numbers +) + +// SpecVersion is the latest fully supported spec version of semver +var SpecVersion = Version{ + Major: 2, + Minor: 0, + Patch: 0, +} + +// Version represents a semver compatible version +type Version struct { + Major uint64 + Minor uint64 + Patch uint64 + Pre []PRVersion + Build []string //No Precendence +} + +// Version to string +func (v Version) String() string { + b := make([]byte, 0, 5) + b = strconv.AppendUint(b, v.Major, 10) + b = append(b, '.') + b = strconv.AppendUint(b, v.Minor, 10) + b = append(b, '.') + b = strconv.AppendUint(b, v.Patch, 10) + + if len(v.Pre) > 0 { + b = append(b, '-') + b = append(b, v.Pre[0].String()...) + + for _, pre := range v.Pre[1:] { + b = append(b, '.') + b = append(b, pre.String()...) + } + } + + if len(v.Build) > 0 { + b = append(b, '+') + b = append(b, v.Build[0]...) + + for _, build := range v.Build[1:] { + b = append(b, '.') + b = append(b, build...) + } + } + + return string(b) +} + +// Equals checks if v is equal to o. +func (v Version) Equals(o Version) bool { + return (v.Compare(o) == 0) +} + +// EQ checks if v is equal to o. +func (v Version) EQ(o Version) bool { + return (v.Compare(o) == 0) +} + +// NE checks if v is not equal to o. +func (v Version) NE(o Version) bool { + return (v.Compare(o) != 0) +} + +// GT checks if v is greater than o. +func (v Version) GT(o Version) bool { + return (v.Compare(o) == 1) +} + +// GTE checks if v is greater than or equal to o. +func (v Version) GTE(o Version) bool { + return (v.Compare(o) >= 0) +} + +// GE checks if v is greater than or equal to o. +func (v Version) GE(o Version) bool { + return (v.Compare(o) >= 0) +} + +// LT checks if v is less than o. +func (v Version) LT(o Version) bool { + return (v.Compare(o) == -1) +} + +// LTE checks if v is less than or equal to o. +func (v Version) LTE(o Version) bool { + return (v.Compare(o) <= 0) +} + +// LE checks if v is less than or equal to o. +func (v Version) LE(o Version) bool { + return (v.Compare(o) <= 0) +} + +// Compare compares Versions v to o: +// -1 == v is less than o +// 0 == v is equal to o +// 1 == v is greater than o +func (v Version) Compare(o Version) int { + if v.Major != o.Major { + if v.Major > o.Major { + return 1 + } + return -1 + } + if v.Minor != o.Minor { + if v.Minor > o.Minor { + return 1 + } + return -1 + } + if v.Patch != o.Patch { + if v.Patch > o.Patch { + return 1 + } + return -1 + } + + // Quick comparison if a version has no prerelease versions + if len(v.Pre) == 0 && len(o.Pre) == 0 { + return 0 + } else if len(v.Pre) == 0 && len(o.Pre) > 0 { + return 1 + } else if len(v.Pre) > 0 && len(o.Pre) == 0 { + return -1 + } + + i := 0 + for ; i < len(v.Pre) && i < len(o.Pre); i++ { + if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 { + continue + } else if comp == 1 { + return 1 + } else { + return -1 + } + } + + // If all pr versions are the equal but one has further prversion, this one greater + if i == len(v.Pre) && i == len(o.Pre) { + return 0 + } else if i == len(v.Pre) && i < len(o.Pre) { + return -1 + } else { + return 1 + } + +} + +// Validate validates v and returns error in case +func (v Version) Validate() error { + // Major, Minor, Patch already validated using uint64 + + for _, pre := range v.Pre { + if !pre.IsNum { //Numeric prerelease versions already uint64 + if len(pre.VersionStr) == 0 { + return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr) + } + if !containsOnly(pre.VersionStr, alphanum) { + return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr) + } + } + } + + for _, build := range v.Build { + if len(build) == 0 { + return fmt.Errorf("Build meta data can not be empty %q", build) + } + if !containsOnly(build, alphanum) { + return fmt.Errorf("Invalid character(s) found in build meta data %q", build) + } + } + + return nil +} + +// New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error +func New(s string) (vp *Version, err error) { + v, err := Parse(s) + vp = &v + return +} + +// Make is an alias for Parse, parses version string and returns a validated Version or error +func Make(s string) (Version, error) { + return Parse(s) +} + +// ParseTolerant allows for certain version specifications that do not strictly adhere to semver +// specs to be parsed by this library. It does so by normalizing versions before passing them to +// Parse(). It currently trims spaces, removes a "v" prefix, and adds a 0 patch number to versions +// with only major and minor components specified +func ParseTolerant(s string) (Version, error) { + s = strings.TrimSpace(s) + s = strings.TrimPrefix(s, "v") + + // Split into major.minor.(patch+pr+meta) + parts := strings.SplitN(s, ".", 3) + if len(parts) < 3 { + if strings.ContainsAny(parts[len(parts)-1], "+-") { + return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data") + } + for len(parts) < 3 { + parts = append(parts, "0") + } + s = strings.Join(parts, ".") + } + + return Parse(s) +} + +// Parse parses version string and returns a validated Version or error +func Parse(s string) (Version, error) { + if len(s) == 0 { + return Version{}, errors.New("Version string empty") + } + + // Split into major.minor.(patch+pr+meta) + parts := strings.SplitN(s, ".", 3) + if len(parts) != 3 { + return Version{}, errors.New("No Major.Minor.Patch elements found") + } + + // Major + if !containsOnly(parts[0], numbers) { + return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0]) + } + if hasLeadingZeroes(parts[0]) { + return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0]) + } + major, err := strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return Version{}, err + } + + // Minor + if !containsOnly(parts[1], numbers) { + return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1]) + } + if hasLeadingZeroes(parts[1]) { + return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1]) + } + minor, err := strconv.ParseUint(parts[1], 10, 64) + if err != nil { + return Version{}, err + } + + v := Version{} + v.Major = major + v.Minor = minor + + var build, prerelease []string + patchStr := parts[2] + + if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 { + build = strings.Split(patchStr[buildIndex+1:], ".") + patchStr = patchStr[:buildIndex] + } + + if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 { + prerelease = strings.Split(patchStr[preIndex+1:], ".") + patchStr = patchStr[:preIndex] + } + + if !containsOnly(patchStr, numbers) { + return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr) + } + if hasLeadingZeroes(patchStr) { + return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr) + } + patch, err := strconv.ParseUint(patchStr, 10, 64) + if err != nil { + return Version{}, err + } + + v.Patch = patch + + // Prerelease + for _, prstr := range prerelease { + parsedPR, err := NewPRVersion(prstr) + if err != nil { + return Version{}, err + } + v.Pre = append(v.Pre, parsedPR) + } + + // Build meta data + for _, str := range build { + if len(str) == 0 { + return Version{}, errors.New("Build meta data is empty") + } + if !containsOnly(str, alphanum) { + return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str) + } + v.Build = append(v.Build, str) + } + + return v, nil +} + +// MustParse is like Parse but panics if the version cannot be parsed. +func MustParse(s string) Version { + v, err := Parse(s) + if err != nil { + panic(`semver: Parse(` + s + `): ` + err.Error()) + } + return v +} + +// PRVersion represents a PreRelease Version +type PRVersion struct { + VersionStr string + VersionNum uint64 + IsNum bool +} + +// NewPRVersion creates a new valid prerelease version +func NewPRVersion(s string) (PRVersion, error) { + if len(s) == 0 { + return PRVersion{}, errors.New("Prerelease is empty") + } + v := PRVersion{} + if containsOnly(s, numbers) { + if hasLeadingZeroes(s) { + return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s) + } + num, err := strconv.ParseUint(s, 10, 64) + + // Might never be hit, but just in case + if err != nil { + return PRVersion{}, err + } + v.VersionNum = num + v.IsNum = true + } else if containsOnly(s, alphanum) { + v.VersionStr = s + v.IsNum = false + } else { + return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s) + } + return v, nil +} + +// IsNumeric checks if prerelease-version is numeric +func (v PRVersion) IsNumeric() bool { + return v.IsNum +} + +// Compare compares two PreRelease Versions v and o: +// -1 == v is less than o +// 0 == v is equal to o +// 1 == v is greater than o +func (v PRVersion) Compare(o PRVersion) int { + if v.IsNum && !o.IsNum { + return -1 + } else if !v.IsNum && o.IsNum { + return 1 + } else if v.IsNum && o.IsNum { + if v.VersionNum == o.VersionNum { + return 0 + } else if v.VersionNum > o.VersionNum { + return 1 + } else { + return -1 + } + } else { // both are Alphas + if v.VersionStr == o.VersionStr { + return 0 + } else if v.VersionStr > o.VersionStr { + return 1 + } else { + return -1 + } + } +} + +// PreRelease version to string +func (v PRVersion) String() string { + if v.IsNum { + return strconv.FormatUint(v.VersionNum, 10) + } + return v.VersionStr +} + +func containsOnly(s string, set string) bool { + return strings.IndexFunc(s, func(r rune) bool { + return !strings.ContainsRune(set, r) + }) == -1 +} + +func hasLeadingZeroes(s string) bool { + return len(s) > 1 && s[0] == '0' +} + +// NewBuildVersion creates a new valid build version +func NewBuildVersion(s string) (string, error) { + if len(s) == 0 { + return "", errors.New("Buildversion is empty") + } + if !containsOnly(s, alphanum) { + return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s) + } + return s, nil +} diff --git a/src/vendor/github.com/blang/semver/semver_test.go b/src/vendor/github.com/blang/semver/semver_test.go new file mode 100644 index 000000000..b3e1fd43c --- /dev/null +++ b/src/vendor/github.com/blang/semver/semver_test.go @@ -0,0 +1,458 @@ +package semver + +import ( + "testing" +) + +func prstr(s string) PRVersion { + return PRVersion{s, 0, false} +} + +func prnum(i uint64) PRVersion { + return PRVersion{"", i, true} +} + +type formatTest struct { + v Version + result string +} + +var formatTests = []formatTest{ + {Version{1, 2, 3, nil, nil}, "1.2.3"}, + {Version{0, 0, 1, nil, nil}, "0.0.1"}, + {Version{0, 0, 1, []PRVersion{prstr("alpha"), prstr("preview")}, []string{"123", "456"}}, "0.0.1-alpha.preview+123.456"}, + {Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, []string{"123", "456"}}, "1.2.3-alpha.1+123.456"}, + {Version{1, 2, 3, []PRVersion{prstr("alpha"), prnum(1)}, nil}, "1.2.3-alpha.1"}, + {Version{1, 2, 3, nil, []string{"123", "456"}}, "1.2.3+123.456"}, + // Prereleases and build metadata hyphens + {Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, []string{"123", "b-uild"}}, "1.2.3-alpha.b-eta+123.b-uild"}, + {Version{1, 2, 3, nil, []string{"123", "b-uild"}}, "1.2.3+123.b-uild"}, + {Version{1, 2, 3, []PRVersion{prstr("alpha"), prstr("b-eta")}, nil}, "1.2.3-alpha.b-eta"}, +} + +var tolerantFormatTests = []formatTest{ + {Version{1, 2, 3, nil, nil}, "v1.2.3"}, + {Version{1, 2, 3, nil, nil}, " 1.2.3 "}, + {Version{1, 2, 0, nil, nil}, "1.2"}, + {Version{1, 0, 0, nil, nil}, "1"}, +} + +func TestStringer(t *testing.T) { + for _, test := range formatTests { + if res := test.v.String(); res != test.result { + t.Errorf("Stringer, expected %q but got %q", test.result, res) + } + } +} + +func TestParse(t *testing.T) { + for _, test := range formatTests { + if v, err := Parse(test.result); err != nil { + t.Errorf("Error parsing %q: %q", test.result, err) + } else if comp := v.Compare(test.v); comp != 0 { + t.Errorf("Parsing, expected %q but got %q, comp: %d ", test.v, v, comp) + } else if err := v.Validate(); err != nil { + t.Errorf("Error validating parsed version %q: %q", test.v, err) + } + } +} + +func TestParseTolerant(t *testing.T) { + for _, test := range tolerantFormatTests { + if v, err := ParseTolerant(test.result); err != nil { + t.Errorf("Error parsing %q: %q", test.result, err) + } else if comp := v.Compare(test.v); comp != 0 { + t.Errorf("Parsing, expected %q but got %q, comp: %d ", test.v, v, comp) + } else if err := v.Validate(); err != nil { + t.Errorf("Error validating parsed version %q: %q", test.v, err) + } + } +} + +func TestMustParse(t *testing.T) { + _ = MustParse("32.2.1-alpha") +} + +func TestMustParse_panic(t *testing.T) { + defer func() { + if recover() == nil { + t.Errorf("Should have panicked") + } + }() + _ = MustParse("invalid version") +} + +func TestValidate(t *testing.T) { + for _, test := range formatTests { + if err := test.v.Validate(); err != nil { + t.Errorf("Error validating %q: %q", test.v, err) + } + } +} + +type compareTest struct { + v1 Version + v2 Version + result int +} + +var compareTests = []compareTest{ + {Version{1, 0, 0, nil, nil}, Version{1, 0, 0, nil, nil}, 0}, + {Version{2, 0, 0, nil, nil}, Version{1, 0, 0, nil, nil}, 1}, + {Version{0, 1, 0, nil, nil}, Version{0, 1, 0, nil, nil}, 0}, + {Version{0, 2, 0, nil, nil}, Version{0, 1, 0, nil, nil}, 1}, + {Version{0, 0, 1, nil, nil}, Version{0, 0, 1, nil, nil}, 0}, + {Version{0, 0, 2, nil, nil}, Version{0, 0, 1, nil, nil}, 1}, + {Version{1, 2, 3, nil, nil}, Version{1, 2, 3, nil, nil}, 0}, + {Version{2, 2, 4, nil, nil}, Version{1, 2, 4, nil, nil}, 1}, + {Version{1, 3, 3, nil, nil}, Version{1, 2, 3, nil, nil}, 1}, + {Version{1, 2, 4, nil, nil}, Version{1, 2, 3, nil, nil}, 1}, + + // Spec Examples #11 + {Version{1, 0, 0, nil, nil}, Version{2, 0, 0, nil, nil}, -1}, + {Version{2, 0, 0, nil, nil}, Version{2, 1, 0, nil, nil}, -1}, + {Version{2, 1, 0, nil, nil}, Version{2, 1, 1, nil, nil}, -1}, + + // Spec Examples #9 + {Version{1, 0, 0, nil, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil}, 1}, + {Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha"), prnum(1)}, nil}, -1}, + {Version{1, 0, 0, []PRVersion{prstr("alpha"), prnum(1)}, nil}, Version{1, 0, 0, []PRVersion{prstr("alpha"), prstr("beta")}, nil}, -1}, + {Version{1, 0, 0, []PRVersion{prstr("alpha"), prstr("beta")}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta")}, nil}, -1}, + {Version{1, 0, 0, []PRVersion{prstr("beta")}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(2)}, nil}, -1}, + {Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(2)}, nil}, Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(11)}, nil}, -1}, + {Version{1, 0, 0, []PRVersion{prstr("beta"), prnum(11)}, nil}, Version{1, 0, 0, []PRVersion{prstr("rc"), prnum(1)}, nil}, -1}, + {Version{1, 0, 0, []PRVersion{prstr("rc"), prnum(1)}, nil}, Version{1, 0, 0, nil, nil}, -1}, + + // Ignore Build metadata + {Version{1, 0, 0, nil, []string{"1", "2", "3"}}, Version{1, 0, 0, nil, nil}, 0}, +} + +func TestCompare(t *testing.T) { + for _, test := range compareTests { + if res := test.v1.Compare(test.v2); res != test.result { + t.Errorf("Comparing %q : %q, expected %d but got %d", test.v1, test.v2, test.result, res) + } + //Test counterpart + if res := test.v2.Compare(test.v1); res != -test.result { + t.Errorf("Comparing %q : %q, expected %d but got %d", test.v2, test.v1, -test.result, res) + } + } +} + +type wrongformatTest struct { + v *Version + str string +} + +var wrongformatTests = []wrongformatTest{ + {nil, ""}, + {nil, "."}, + {nil, "1."}, + {nil, ".1"}, + {nil, "a.b.c"}, + {nil, "1.a.b"}, + {nil, "1.1.a"}, + {nil, "1.a.1"}, + {nil, "a.1.1"}, + {nil, ".."}, + {nil, "1.."}, + {nil, "1.1."}, + {nil, "1..1"}, + {nil, "1.1.+123"}, + {nil, "1.1.-beta"}, + {nil, "-1.1.1"}, + {nil, "1.-1.1"}, + {nil, "1.1.-1"}, + // giant numbers + {nil, "20000000000000000000.1.1"}, + {nil, "1.20000000000000000000.1"}, + {nil, "1.1.20000000000000000000"}, + {nil, "1.1.1-20000000000000000000"}, + // Leading zeroes + {nil, "01.1.1"}, + {nil, "001.1.1"}, + {nil, "1.01.1"}, + {nil, "1.001.1"}, + {nil, "1.1.01"}, + {nil, "1.1.001"}, + {nil, "1.1.1-01"}, + {nil, "1.1.1-001"}, + {nil, "1.1.1-beta.01"}, + {nil, "1.1.1-beta.001"}, + {&Version{0, 0, 0, []PRVersion{prstr("!")}, nil}, "0.0.0-!"}, + {&Version{0, 0, 0, nil, []string{"!"}}, "0.0.0+!"}, + // empty prversion + {&Version{0, 0, 0, []PRVersion{prstr(""), prstr("alpha")}, nil}, "0.0.0-.alpha"}, + // empty build meta data + {&Version{0, 0, 0, []PRVersion{prstr("alpha")}, []string{""}}, "0.0.0-alpha+"}, + {&Version{0, 0, 0, []PRVersion{prstr("alpha")}, []string{"test", ""}}, "0.0.0-alpha+test."}, +} + +func TestWrongFormat(t *testing.T) { + for _, test := range wrongformatTests { + + if res, err := Parse(test.str); err == nil { + t.Errorf("Parsing wrong format version %q, expected error but got %q", test.str, res) + } + + if test.v != nil { + if err := test.v.Validate(); err == nil { + t.Errorf("Validating wrong format version %q (%q), expected error", test.v, test.str) + } + } + } +} + +var wrongTolerantFormatTests = []wrongformatTest{ + {nil, "1.0+abc"}, + {nil, "1.0-rc.1"}, +} + +func TestWrongTolerantFormat(t *testing.T) { + for _, test := range wrongTolerantFormatTests { + if res, err := ParseTolerant(test.str); err == nil { + t.Errorf("Parsing wrong format version %q, expected error but got %q", test.str, res) + } + } +} + +func TestCompareHelper(t *testing.T) { + v := Version{1, 0, 0, []PRVersion{prstr("alpha")}, nil} + v1 := Version{1, 0, 0, nil, nil} + if !v.EQ(v) { + t.Errorf("%q should be equal to %q", v, v) + } + if !v.Equals(v) { + t.Errorf("%q should be equal to %q", v, v) + } + if !v1.NE(v) { + t.Errorf("%q should not be equal to %q", v1, v) + } + if !v.GTE(v) { + t.Errorf("%q should be greater than or equal to %q", v, v) + } + if !v.LTE(v) { + t.Errorf("%q should be less than or equal to %q", v, v) + } + if !v.LT(v1) { + t.Errorf("%q should be less than %q", v, v1) + } + if !v.LTE(v1) { + t.Errorf("%q should be less than or equal %q", v, v1) + } + if !v.LE(v1) { + t.Errorf("%q should be less than or equal %q", v, v1) + } + if !v1.GT(v) { + t.Errorf("%q should be greater than %q", v1, v) + } + if !v1.GTE(v) { + t.Errorf("%q should be greater than or equal %q", v1, v) + } + if !v1.GE(v) { + t.Errorf("%q should be greater than or equal %q", v1, v) + } +} + +func TestPreReleaseVersions(t *testing.T) { + p1, err := NewPRVersion("123") + if !p1.IsNumeric() { + t.Errorf("Expected numeric prversion, got %q", p1) + } + if p1.VersionNum != 123 { + t.Error("Wrong prversion number") + } + if err != nil { + t.Errorf("Not expected error %q", err) + } + p2, err := NewPRVersion("alpha") + if p2.IsNumeric() { + t.Errorf("Expected non-numeric prversion, got %q", p2) + } + if p2.VersionStr != "alpha" { + t.Error("Wrong prversion string") + } + if err != nil { + t.Errorf("Not expected error %q", err) + } +} + +func TestBuildMetaDataVersions(t *testing.T) { + _, err := NewBuildVersion("123") + if err != nil { + t.Errorf("Unexpected error %q", err) + } + + _, err = NewBuildVersion("build") + if err != nil { + t.Errorf("Unexpected error %q", err) + } + + _, err = NewBuildVersion("test?") + if err == nil { + t.Error("Expected error, got none") + } + + _, err = NewBuildVersion("") + if err == nil { + t.Error("Expected error, got none") + } +} + +func TestNewHelper(t *testing.T) { + v, err := New("1.2.3") + if err != nil { + t.Fatalf("Unexpected error %q", err) + } + + // New returns pointer + if v == nil { + t.Fatal("Version is nil") + } + if v.Compare(Version{1, 2, 3, nil, nil}) != 0 { + t.Fatal("Unexpected comparison problem") + } +} + +func TestMakeHelper(t *testing.T) { + v, err := Make("1.2.3") + if err != nil { + t.Fatalf("Unexpected error %q", err) + } + if v.Compare(Version{1, 2, 3, nil, nil}) != 0 { + t.Fatal("Unexpected comparison problem") + } +} + +func BenchmarkParseSimple(b *testing.B) { + const VERSION = "0.0.1" + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + Parse(VERSION) + } +} + +func BenchmarkParseComplex(b *testing.B) { + const VERSION = "0.0.1-alpha.preview+123.456" + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + Parse(VERSION) + } +} + +func BenchmarkParseAverage(b *testing.B) { + l := len(formatTests) + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + Parse(formatTests[n%l].result) + } +} + +func BenchmarkParseTolerantAverage(b *testing.B) { + l := len(tolerantFormatTests) + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + ParseTolerant(tolerantFormatTests[n%l].result) + } +} + +func BenchmarkStringSimple(b *testing.B) { + const VERSION = "0.0.1" + v, _ := Parse(VERSION) + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + v.String() + } +} + +func BenchmarkStringLarger(b *testing.B) { + const VERSION = "11.15.2012" + v, _ := Parse(VERSION) + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + v.String() + } +} + +func BenchmarkStringComplex(b *testing.B) { + const VERSION = "0.0.1-alpha.preview+123.456" + v, _ := Parse(VERSION) + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + v.String() + } +} + +func BenchmarkStringAverage(b *testing.B) { + l := len(formatTests) + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + formatTests[n%l].v.String() + } +} + +func BenchmarkValidateSimple(b *testing.B) { + const VERSION = "0.0.1" + v, _ := Parse(VERSION) + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + v.Validate() + } +} + +func BenchmarkValidateComplex(b *testing.B) { + const VERSION = "0.0.1-alpha.preview+123.456" + v, _ := Parse(VERSION) + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + v.Validate() + } +} + +func BenchmarkValidateAverage(b *testing.B) { + l := len(formatTests) + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + formatTests[n%l].v.Validate() + } +} + +func BenchmarkCompareSimple(b *testing.B) { + const VERSION = "0.0.1" + v, _ := Parse(VERSION) + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + v.Compare(v) + } +} + +func BenchmarkCompareComplex(b *testing.B) { + const VERSION = "0.0.1-alpha.preview+123.456" + v, _ := Parse(VERSION) + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + v.Compare(v) + } +} + +func BenchmarkCompareAverage(b *testing.B) { + l := len(compareTests) + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + compareTests[n%l].v1.Compare((compareTests[n%l].v2)) + } +} diff --git a/src/vendor/github.com/blang/semver/sort.go b/src/vendor/github.com/blang/semver/sort.go new file mode 100644 index 000000000..e18f88082 --- /dev/null +++ b/src/vendor/github.com/blang/semver/sort.go @@ -0,0 +1,28 @@ +package semver + +import ( + "sort" +) + +// Versions represents multiple versions. +type Versions []Version + +// Len returns length of version collection +func (s Versions) Len() int { + return len(s) +} + +// Swap swaps two versions inside the collection by its indices +func (s Versions) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +// Less checks if version at index i is less than version at index j +func (s Versions) Less(i, j int) bool { + return s[i].LT(s[j]) +} + +// Sort sorts a slice of versions +func Sort(versions []Version) { + sort.Sort(Versions(versions)) +} diff --git a/src/vendor/github.com/blang/semver/sort_test.go b/src/vendor/github.com/blang/semver/sort_test.go new file mode 100644 index 000000000..68893972a --- /dev/null +++ b/src/vendor/github.com/blang/semver/sort_test.go @@ -0,0 +1,30 @@ +package semver + +import ( + "reflect" + "testing" +) + +func TestSort(t *testing.T) { + v100, _ := Parse("1.0.0") + v010, _ := Parse("0.1.0") + v001, _ := Parse("0.0.1") + versions := []Version{v010, v100, v001} + Sort(versions) + + correct := []Version{v001, v010, v100} + if !reflect.DeepEqual(versions, correct) { + t.Fatalf("Sort returned wrong order: %s", versions) + } +} + +func BenchmarkSort(b *testing.B) { + v100, _ := Parse("1.0.0") + v010, _ := Parse("0.1.0") + v001, _ := Parse("0.0.1") + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + Sort([]Version{v010, v100, v001}) + } +} diff --git a/src/vendor/github.com/blang/semver/sql.go b/src/vendor/github.com/blang/semver/sql.go new file mode 100644 index 000000000..eb4d80266 --- /dev/null +++ b/src/vendor/github.com/blang/semver/sql.go @@ -0,0 +1,30 @@ +package semver + +import ( + "database/sql/driver" + "fmt" +) + +// Scan implements the database/sql.Scanner interface. +func (v *Version) Scan(src interface{}) (err error) { + var str string + switch src := src.(type) { + case string: + str = src + case []byte: + str = string(src) + default: + return fmt.Errorf("Version.Scan: cannot convert %T to string.", src) + } + + if t, err := Parse(str); err == nil { + *v = t + } + + return +} + +// Value implements the database/sql/driver.Valuer interface. +func (v Version) Value() (driver.Value, error) { + return v.String(), nil +} diff --git a/src/vendor/github.com/blang/semver/sql_test.go b/src/vendor/github.com/blang/semver/sql_test.go new file mode 100644 index 000000000..ebf48b584 --- /dev/null +++ b/src/vendor/github.com/blang/semver/sql_test.go @@ -0,0 +1,38 @@ +package semver + +import ( + "testing" +) + +type scanTest struct { + val interface{} + shouldError bool + expected string +} + +var scanTests = []scanTest{ + {"1.2.3", false, "1.2.3"}, + {[]byte("1.2.3"), false, "1.2.3"}, + {7, true, ""}, + {7e4, true, ""}, + {true, true, ""}, +} + +func TestScanString(t *testing.T) { + for _, tc := range scanTests { + s := &Version{} + err := s.Scan(tc.val) + if tc.shouldError { + if err == nil { + t.Fatalf("Scan did not return an error on %v (%T)", tc.val, tc.val) + } + } else { + if err != nil { + t.Fatalf("Scan returned an unexpected error: %s (%T) on %v (%T)", tc.val, tc.val, tc.val, tc.val) + } + if val, _ := s.Value(); val != tc.expected { + t.Errorf("Wrong Value returned, expected %q, got %q", tc.expected, val) + } + } + } +}