pax_global_header00006660000000000000000000000064140021624100014500gustar00rootroot0000000000000052 comment=9804cc9a5c59316ef40d5c0abb06e59661df000e go-semver-1.0.2/000077500000000000000000000000001400216241000134045ustar00rootroot00000000000000go-semver-1.0.2/.circleci/000077500000000000000000000000001400216241000152375ustar00rootroot00000000000000go-semver-1.0.2/.circleci/config.yml000066400000000000000000000052161400216241000172330ustar00rootroot00000000000000version: 2.1 orbs: win: circleci/windows@1.0.0 workflows: workflow: jobs: - go-test: name: Go 1.15 docker-image: circleci/golang:1.14 run-lint: true with-coverage: true - go-test: name: Go 1.14 docker-image: circleci/golang:1.14 - go-test-windows: name: Windows - benchmarks jobs: go-test: parameters: docker-image: type: string run-lint: type: boolean default: false with-coverage: type: boolean default: false docker: - image: <> environment: CIRCLE_TEST_REPORTS: /tmp/circle-reports CIRCLE_ARTIFACTS: /tmp/circle-artifacts steps: - checkout - run: name: install go-junit-report command: go get -u github.com/jstemmer/go-junit-report - run: name: build command: make build - when: condition: <> steps: - run: name: lint command: make lint - run: name: test command: | mkdir -p $CIRCLE_TEST_REPORTS mkdir -p $CIRCLE_ARTIFACTS make test | tee $CIRCLE_ARTIFACTS/report.txt - run: name: Process test results command: go-junit-report < $CIRCLE_ARTIFACTS/report.txt > $CIRCLE_TEST_REPORTS/junit.xml when: always - when: condition: <> steps: - run: name: Verify test coverage command: make test-coverage - run: name: Store coverage results command: cp build/coverage* /tmp/circle-artifacts when: always - store_test_results: path: /tmp/circle-reports - store_artifacts: path: /tmp/circle-artifacts go-test-windows: executor: name: win/vs2019 shell: powershell.exe environment: GOPATH: C:\Users\VssAdministrator\go steps: - checkout - run: go version - run: go build ./... - run: go test ./... benchmarks: docker: - image: circleci/golang:1.14 environment: CIRCLE_ARTIFACTS: /tmp/circle-artifacts steps: - checkout - run: go build ./... - run: name: Run benchmarks command: | mkdir -p $CIRCLE_ARTIFACTS make benchmarks | tee $CIRCLE_ARTIFACTS/benchmarks.txt - store_artifacts: path: /tmp/circle-artifacts go-semver-1.0.2/.gitignore000066400000000000000000000000121400216241000153650ustar00rootroot00000000000000bin build go-semver-1.0.2/.ldrelease/000077500000000000000000000000001400216241000154225ustar00rootroot00000000000000go-semver-1.0.2/.ldrelease/config.yml000066400000000000000000000002001400216241000174020ustar00rootroot00000000000000template: name: go publications: - url: https://godoc.org/github.com/launchdarkly/go-semver description: documentation go-semver-1.0.2/CHANGELOG.md000066400000000000000000000014001400216241000152100ustar00rootroot00000000000000# Change log All notable changes to the project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). ## [1.0.2] - 2021-01-20 ### Fixed: - Valid semantic version strings were being rejected by the parser if they contained a zero digit in any position _after_ the first character of a numeric version component. For instance, "0.1.2" and "1.2.3" were accepted, and "01.2.3" was correctly rejected (leading zeroes for nonzero values are not allowed), but "10.2.3" was incorrectly rejected. ## [1.0.1] - 2020-09-18 ### Fixed: - Removed some unwanted package dependencies. - Added CI build for Go 1.15 and removed build for 1.13; Go 1.13 is now EOL. ## [1.0.0] - 2020-06-15 Initial release. go-semver-1.0.2/CONTRIBUTING.md000066400000000000000000000042641400216241000156430ustar00rootroot00000000000000# Contributing to this project ## Submitting bug reports and feature requests The LaunchDarkly SDK team monitors the [issue tracker](https://github.com/launchdarkly/go-semver/issues) in tis repository. Bug reports and feature requests specific to this project should be filed in this issue tracker. The SDK team will respond to all newly filed issues within two business days. ## Submitting pull requests We encourage pull requests and other contributions from the community. Before submitting pull requests, ensure that all temporary or unintended code is removed. Don't worry about adding reviewers to the pull request; the LaunchDarkly SDK team will add themselves. The SDK team will acknowledge all pull requests within two business days. ## Build instructions ### Prerequisites This project should be built against Go 1.13 or newer. ### Building To build the project without running any tests: ``` make ``` If you wish to clean your working directory between builds, you can clean it by running: ``` make clean ``` To run the linter: ``` make lint ``` ### Testing To build and run all unit tests: ``` make test ``` ## Coding best practices ### Test coverage It is important to keep unit test coverage as close to 100% as possible in this project. You can view the latest code coverage report in CircleCI, as `coverage.html` and `coverage.txt` in the artifacts. You can also generate this information locally with `make test-coverage`. The build will fail if there are any uncovered blocks of code, unless you explicitly add an override by placing a comment that starts with `// COVERAGE` somewhere within that block. Sometimes a gap in coverage is unavoidable, usually because the compiler requires us to provide a code path for some condition that in practice can't happen and can't be tested. Exclude these paths with a `// COVERAGE` comment. ### Avoid heap allocations A major design goal in this implementation is to maximize performance and avoid unwanted heap churn. No operations in this package should allocate any data on the heap; everything should be passed by value, and slices or maps should not be used. The `make benchmarks` target will fail if any benchmark shows heap allocations. go-semver-1.0.2/LICENSE.txt000066400000000000000000000010541400216241000152270ustar00rootroot00000000000000Copyright 2020 Catamorphic, Co. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.go-semver-1.0.2/Makefile000066400000000000000000000027501400216241000150500ustar00rootroot00000000000000 GOLANGCI_LINT_VERSION=v1.27.0 LINTER=./bin/golangci-lint LINTER_VERSION_FILE=./bin/.golangci-lint-version-$(GOLANGCI_LINT_VERSION) ALL_SOURCES := $(shell find * -type f -name "*.go") COVERAGE_PROFILE_RAW=./build/coverage_raw.out COVERAGE_PROFILE_RAW_HTML=./build/coverage_raw.html COVERAGE_PROFILE_FILTERED=./build/coverage.out COVERAGE_PROFILE_FILTERED_HTML=./build/coverage.html .PHONY: build clean test test-coverage lint build: go build ./... clean: go clean test: build go test ./... benchmarks: build mkdir -p ./build go test -benchmem '-run=^$$' -bench . | tee build/benchmarks.out @if grep =1.0.0" or "2.5.x". This package has no external dependencies other than the regular Go runtime. ## Supported Go versions This version of the project has been tested with Go 1.14 and above. ## Contributing We encourage pull requests and other contributions from the community. Check out our [contributing guidelines](CONTRIBUTING.md) for instructions on how to contribute to this project. go-semver-1.0.2/compare.go000066400000000000000000000046001400216241000153610ustar00rootroot00000000000000package semver // ComparePrecedence compares this Version to another Version according to the canonical precedence rules. It // returns -1 if v has lower precedence than other, 1 if v has higher precedence, or 0 if the same. func (v Version) ComparePrecedence(other Version) int { if v.major < other.major { return -1 } if v.major > other.major { return 1 } if v.minor < other.minor { return -1 } if v.minor > other.minor { return 1 } if v.patch < other.patch { return -1 } if v.patch > other.patch { return 1 } if v.prerelease == "" && other.prerelease == "" { return 0 } // *no* prerelease component always has higher precedence than *any* prerelease component if v.prerelease == "" { return 1 } if other.prerelease == "" { return -1 } // compare prerelease components - the build component, if any, has no effect on precedence return comparePrereleaseIdentifiers(v.prerelease, other.prerelease) } func comparePrereleaseIdentifiers(prerel1, prerel2 string) int { // The parser has already validated the syntax of both of these strings, so we know that they both // contain one or more identifiers separated by a period, and that they contain only alphanumerics. // If an identifier contains only digits, and does not have a leading zero, then we treat it as a // number. scanner1 := newSimpleASCIIScanner(prerel1) scanner2 := newSimpleASCIIScanner(prerel2) for { // all components up to this point have been determined to be equal if scanner1.eof() { if scanner2.eof() { return 0 } return -1 // x.y is always less than x.y.z } else { if scanner2.eof() { return 1 } } identifier1, _ := scanner1.readUntil(dotTerminator) identifier2, _ := scanner2.readUntil(dotTerminator) // each sub-identifier is compared numerically if both are numeric; if both are non-numeric, // they're compared as strings; otherwise, the numeric one is the lesser one var n1, n2, d int var isNum1, isNum2 bool n1, isNum1 = parsePositiveNumericString(identifier1) n2, isNum2 = parsePositiveNumericString(identifier2) if isNum1 && isNum2 { if n1 < n2 { d = -1 } else if n1 > n2 { d = 1 } } else { if isNum1 { d = -1 } else if isNum2 { d = 1 } else { // string comparison if identifier1 < identifier2 { d = -1 } else if identifier1 > identifier2 { d = 1 } } } if d != 0 { return d } } } go-semver-1.0.2/compare_benchmark_test.go000066400000000000000000000023061400216241000204330ustar00rootroot00000000000000package semver import "testing" // These benchmarks are based on the ones defined in github.com/blang/semver (see: // https://github.com/blang/semver/blob/master/semver_test.go), not including benchmarks for // any non-standard parsing modes or range comparisons. var ( // use package-level variables so the compiler won't optimize away benchmark logic benchmarkCompareResult int ) func BenchmarkCompareSimple(b *testing.B) { const VERSION = "0.0.1" v, _ := Parse(VERSION) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { benchmarkCompareResult = v.ComparePrecedence(v) if benchmarkCompareResult != 0 { b.Fail() } } } 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++ { benchmarkCompareResult = v.ComparePrecedence(v) if benchmarkCompareResult != 0 { b.Fail() } } } func BenchmarkCompareAverage(b *testing.B) { l := len(compareTests) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { test := compareTests[n%l] benchmarkCompareResult = test.v1.ComparePrecedence(test.v2) if benchmarkCompareResult != test.result { b.Fail() } } } go-semver-1.0.2/compare_test.go000066400000000000000000000045161400216241000164260ustar00rootroot00000000000000package semver import ( "fmt" "testing" "github.com/stretchr/testify/assert" ) // The test data set is based on the one defined in github.com/blang/semver (see: // https://github.com/blang/semver/blob/master/semver_test.go), not including data for any // non-standard parsing modes or range comparisons. type compareTest struct { v1 Version v2 Version result int } var compareTests = []compareTest{ {Version{1, 0, 0, "", ""}, Version{1, 0, 0, "", ""}, 0}, {Version{2, 0, 0, "", ""}, Version{1, 0, 0, "", ""}, 1}, {Version{0, 1, 0, "", ""}, Version{0, 1, 0, "", ""}, 0}, {Version{0, 2, 0, "", ""}, Version{0, 1, 0, "", ""}, 1}, {Version{0, 0, 1, "", ""}, Version{0, 0, 1, "", ""}, 0}, {Version{0, 0, 2, "", ""}, Version{0, 0, 1, "", ""}, 1}, {Version{1, 2, 3, "", ""}, Version{1, 2, 3, "", ""}, 0}, {Version{2, 2, 4, "", ""}, Version{1, 2, 4, "", ""}, 1}, {Version{1, 3, 3, "", ""}, Version{1, 2, 3, "", ""}, 1}, {Version{1, 2, 4, "", ""}, Version{1, 2, 3, "", ""}, 1}, {Version{1, 0, 0, "", ""}, Version{2, 0, 0, "", ""}, -1}, {Version{2, 0, 0, "", ""}, Version{2, 1, 0, "", ""}, -1}, {Version{2, 1, 0, "", ""}, Version{2, 1, 1, "", ""}, -1}, {Version{1, 0, 0, "alpha", ""}, Version{1, 0, 0, "alpha", ""}, 0}, {Version{1, 0, 0, "", ""}, Version{1, 0, 0, "alpha", ""}, 1}, {Version{1, 0, 0, "alpha", ""}, Version{1, 0, 0, "alpha.1", ""}, -1}, {Version{1, 0, 0, "alpha.1", ""}, Version{1, 0, 0, "alpha.beta", ""}, -1}, {Version{1, 0, 0, "alpha.beta", ""}, Version{1, 0, 0, "beta", ""}, -1}, {Version{1, 0, 0, "beta", ""}, Version{1, 0, 0, "beta.2", ""}, -1}, {Version{1, 0, 0, "beta.2", ""}, Version{1, 0, 0, "beta.11", ""}, -1}, {Version{1, 0, 0, "beta.2", ""}, Version{1, 0, 0, "rc.1", ""}, -1}, {Version{1, 0, 0, "rc.1", ""}, Version{1, 0, 0, "", ""}, -1}, {Version{1, 0, 0, "", "1.2.3"}, Version{1, 0, 0, "", ""}, 0}, } func TestSemanticVersionCompare(t *testing.T) { for _, test := range compareTests { opDesc := "==" if test.result < 0 { opDesc = "<" } else if test.result > 0 { opDesc = ">" } t.Run(fmt.Sprintf("%+v should %s %+v", test.v1, opDesc, test.v2), func(t *testing.T) { assert.Equal(t, test.result, test.v1.ComparePrecedence(test.v2)) if test.result != 0 { assert.Equal(t, -test.result, test.v2.ComparePrecedence(test.v1), "%+v should not %s %+v", test.v2, opDesc, test.v1) } }) } } go-semver-1.0.2/go.mod000066400000000000000000000001361400216241000145120ustar00rootroot00000000000000module github.com/launchdarkly/go-semver go 1.13 require github.com/stretchr/testify v1.6.1 go-semver-1.0.2/go.sum000066400000000000000000000020001400216241000145270ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= go-semver-1.0.2/parse.go000066400000000000000000000150771400216241000150570ustar00rootroot00000000000000package semver import ( "errors" ) // ParseMode is an enum-like type used with ParseAs. type ParseMode int var invalidSemverError = errors.New("invalid semantic version") const ( // ParseModeStrict is the default parsing mode, requiring a strictly correct version string with // all three required numeric components. ParseModeStrict = iota // ParseModeAllowMissingMinorAndPatch is a parsing mode in which the version string may omit the patch // version component ("2.1"), or both the minor and patch version components ("2"), in which case // they are assumed to be zero. ParseModeAllowMissingMinorAndPatch = iota ) // Parse attempts to parse a string into a Version. It only accepts strings that strictly match the // specification, so extensions like a "v" prefix are not allowed. // // If parsing fails, it returns a non-nil error as the second return value, and Version{} as the first. func Parse(s string) (Version, error) { return ParseAs(s, ParseModeStrict) } // ParseAs attempts to parse a string into a Version, using the specified ParseMode. // // If parsing fails, it returns a non-nil error as the second return value, and Version{} as the first. func ParseAs(s string, mode ParseMode) (Version, error) { if mode != ParseModeStrict && mode != ParseModeAllowMissingMinorAndPatch { return Version{}, errors.New("invalid ParseMode") } scanner := newSimpleASCIIScanner(s) var result Version var term int8 var ok bool if mode == ParseModeAllowMissingMinorAndPatch { result.major, term, ok = requirePositiveIntegerComponent(&scanner, dotOrHyphenOrPlusTerminator) if !ok { return Version{}, invalidSemverError } if term == '.' { result.minor, term, ok = requirePositiveIntegerComponent(&scanner, dotOrHyphenOrPlusTerminator) if !ok { return Version{}, invalidSemverError } if term == '.' { result.patch, term, ok = requirePositiveIntegerComponent(&scanner, hyphenOrPlusTerminator) if !ok { return Version{}, invalidSemverError } } } } else { result.major, term, ok = requirePositiveIntegerComponent(&scanner, dotTerminator) if !ok || term != '.' { return Version{}, invalidSemverError } result.minor, term, ok = requirePositiveIntegerComponent(&scanner, dotTerminator) if !ok || term != '.' { return Version{}, invalidSemverError } result.patch, term, ok = requirePositiveIntegerComponent(&scanner, hyphenOrPlusTerminator) if !ok { return Version{}, invalidSemverError } } if term == '-' { result.prerelease, term = scanner.readUntil(plusTerminator) if result.prerelease == "" || term == scannerNonASCII || !validatePrerelease(result.prerelease) { return Version{}, invalidSemverError } } if term == '+' { result.build, term = scanner.readUntil(noTerminator) if result.build == "" || term == scannerNonASCII || !validateBuild(result.build) { return Version{}, invalidSemverError } } return result, nil } func requirePositiveIntegerComponent( scanner *simpleASCIIScanner, terminatorFn func(rune) bool, ) (n int, terminatedBy int8, ok bool) { // From spec: // A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative integers, and // MUST NOT contain leading zeroes. substr, terminatedBy := scanner.readUntil(terminatorFn) if terminatedBy == scannerNonASCII { return 0, terminatedBy, false } if n, okNumber := parsePositiveNumericString(substr); okNumber { return n, terminatedBy, true } return 0, terminatedBy, false } func validatePrerelease(s string) bool { // BNF definition from spec: // ::= // ::= // // | "." // ::= | // ::= // // | // | // | // ::= // "0" // | // | // ::= | // ::= | // ::= | "-" // ::= | // ::= "0" | // ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" // ::= [A-Za-z] // // Textual definition from spec: // A pre-release version MAY be denoted by appending a hyphen and a series of dot separated identifiers // immediately following the patch version. Identifiers MUST comprise only ASCII alphanumerics and hyphen // [0-9A-Za-z-]. Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes. // Pre-release versions have a lower precedence than the associated normal version. A pre-release version // indicates that the version is unstable and might not satisfy the intended compatibility requirements as // denoted by its associated normal version. Examples: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, // 1.0.0-x.7.z.92. scanner := newSimpleASCIIScanner(s) for { substr, terminatedBy := scanner.readUntil(dotTerminator) if terminatedBy == scannerNonASCII || substr == "" { return false } if !everyChar(substr, isAlphanumericOrHyphen) { return false } if len(substr) > 1 && everyChar(substr, isDigit) && substr[0] == '0' { // leading zero is not allowed in an all-numeric string, for prerelease (OK in build) return false } if terminatedBy == scannerEOF { break } } return true } func validateBuild(s string) bool { // BNF definition from spec (see validatePrerelease for basic definitions) // // ::= // ::= // // | "." // ::= | // // Textual definition from spec: // Build metadata MAY be denoted by appending a plus sign and a series of dot separated identifiers // immediately following the patch or pre-release version. Identifiers MUST comprise only ASCII // alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. scanner := newSimpleASCIIScanner(s) for { substr, terminatedBy := scanner.readUntil(dotTerminator) if terminatedBy == scannerNonASCII || substr == "" || !everyChar(substr, isAlphanumericOrHyphen) { return false } if terminatedBy == scannerEOF { break } } return true } go-semver-1.0.2/parse_benchmark_test.go000066400000000000000000000025321400216241000201200ustar00rootroot00000000000000package semver import "testing" var ( // use package-level variables so the compiler won't optimize away benchmark logic benchmarkVer Version benchmarkErr error ) // These benchmarks are based on the ones defined in github.com/blang/semver (see: // https://github.com/blang/semver/blob/master/semver_test.go), not including benchmarks for // any non-standard parsing modes. func BenchmarkParseSimple(b *testing.B) { const VERSION = "0.0.1" b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { benchmarkVer, benchmarkErr = Parse(VERSION) if benchmarkErr != nil { b.Fatal(benchmarkErr) } } } 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++ { benchmarkVer, benchmarkErr = Parse(VERSION) if benchmarkErr != nil { b.Fatal(benchmarkErr) } } } func BenchmarkParseAverage(b *testing.B) { l := len(benchmarkFormatTests) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { benchmarkVer, benchmarkErr = Parse(benchmarkFormatTests[n%l]) if benchmarkErr != nil { b.Fatal(benchmarkErr) } } } var benchmarkFormatTests = []string{ "1.2.3", "0.0.1", "0.0.1-alpha.preview+123.456", "1.2.3-alpha.1+123.456", "1.2.3-alpha.1", "1.2.3+123.456", "1.2.3-alpha.b-eta+123.b-uild", "1.2.3+123.b-uild", "1.2.3-alpha.b-eta", } go-semver-1.0.2/parse_test.go000066400000000000000000000175741400216241000161220ustar00rootroot00000000000000package semver import ( "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // In order to catch parsing bugs that depend on the number or order of digits, we use ValuesGenerator // (in values_generator.go) to create permutations of many integer values. However, as a practical // measure to avoid very long test times, we don't use an equally large range for every value: when // generating major, minor, and patch version numbers, we cover a 3-digit range for the major and only // one digit for the others, on the assumption that the same numeric parsing logic is used for each. func assertVersionComponents(t *testing.T, v Version, major int, minor int, patch int, pre string, b string) { assert.Equal(t, major, v.GetMajor()) assert.Equal(t, minor, v.GetMinor()) assert.Equal(t, patch, v.GetPatch()) assert.Equal(t, pre, v.GetPrerelease()) assert.Equal(t, b, v.GetBuild()) } func parsingShouldSucceed(parseFn func(string) (Version, error), s string, major int, minor int, patch int, pre string, b string) func(t *testing.T) { return func(t *testing.T) { v, err := parseFn(s) require.NoError(t, err) assertVersionComponents(t, v, major, minor, patch, pre, b) } } func parsingShouldFail(parseFn func(string) (Version, error), s string) func(t *testing.T) { return func(t *testing.T) { v, err := parseFn(s) assert.Error(t, err) assert.Equal(t, Version{}, v) } } func TestParseStrict(t *testing.T) { strictParsingTests := func(t *testing.T, parseFn func(string) (Version, error)) { parsingTestsForAnyMode(t, parseFn) NewValuesGenerator().AddValue(0, 199).AddValue(0, 9).TestAll2(t, func(t *testing.T, a, b int) { t.Run("patch version cannot be omitted", parsingShouldFail(parseFn, fmt.Sprintf("%d.%d", a, b))) }) NewValuesGenerator().AddValue(0, 199).TestAll1(t, func(t *testing.T, a int) { t.Run("micro and patch versions cannot be omitted", parsingShouldFail(parseFn, fmt.Sprintf("%d", a))) }) } t.Run("Parse(s)", func(t *testing.T) { strictParsingTests(t, Parse) }) t.Run("ParseAs(s, ParseModeStrict)", func(t *testing.T) { strictParsingTests(t, func(s string) (Version, error) { return ParseAs(s, ParseModeStrict) }) }) } func TestParseAllowMissingMinorAndPatch(t *testing.T) { parseFn := func(s string) (Version, error) { return ParseAs(s, ParseModeAllowMissingMinorAndPatch) } parsingTestsForAnyMode(t, parseFn) NewValuesGenerator().AddValue(0, 199).AddValue(0, 9).TestAll2(t, func(t *testing.T, a, b int) { s := fmt.Sprintf("%d.%d", a, b) t.Run("patch version can be omitted", parsingShouldSucceed(parseFn, s, a, b, 0, "", "")) t.Run("patch version can be omitted with prerelease", parsingShouldSucceed(parseFn, s+"-beta1", a, b, 0, "beta1", "")) t.Run("patch version can be omitted with build", parsingShouldSucceed(parseFn, s+"+build1", a, b, 0, "", "build1")) }) NewValuesGenerator().AddValue(0, 199).TestAll1(t, func(t *testing.T, a int) { s := fmt.Sprintf("%d", a) t.Run("micro and patch versions can be omitted", parsingShouldSucceed(parseFn, s, a, 0, 0, "", "")) t.Run("micro and patch versions can be omitted with prerelease", parsingShouldSucceed(parseFn, s+"-beta1", a, 0, 0, "beta1", "")) t.Run("micro and patch versions can be omitted with build", parsingShouldSucceed(parseFn, s+"+build1", a, 0, 0, "", "build1")) }) } func parsingTestsForAnyMode(t *testing.T, parseFn func(string) (Version, error)) { t.Run("valid", func(t *testing.T) { NewValuesGenerator().AddValue(0, 199).AddValue(0, 9).AddValue(0, 9). TestAll3(t, func(t *testing.T, a, b, c int) { s := fmt.Sprintf("%d.%d.%d", a, b, c) t.Run("simple complete version", parsingShouldSucceed(parseFn, s, a, b, c, "", "")) t.Run("version with prerelease (single identifier)", parsingShouldSucceed(parseFn, s+"-beta1", a, b, c, "beta1", "")) t.Run("version with build (single identifier)", parsingShouldSucceed(parseFn, s+"+build2", a, b, c, "", "build2")) t.Run("prerelease identifier of only letters", parsingShouldSucceed(parseFn, s+"-abc", a, b, c, "abc", "")) t.Run("prerelease identifier of only digits", parsingShouldSucceed(parseFn, s+"-123", a, b, c, "123", "")) t.Run("prerelease identifier of only hyphens", parsingShouldSucceed(parseFn, s+"----", a, b, c, "---", "")) t.Run("alphanumeric prerelease identifier with leading zero", parsingShouldSucceed(parseFn, s+"-beta1.0yes", a, b, c, "beta1.0yes", "")) t.Run("build identifier of only letters", parsingShouldSucceed(parseFn, s+"+abc", a, b, c, "", "abc")) t.Run("build identifier of only digits", parsingShouldSucceed(parseFn, s+"+123", a, b, c, "", "123")) t.Run("build identifier of only hyphens", parsingShouldSucceed(parseFn, s+"+---", a, b, c, "", "---")) t.Run("alphanumeric build identifier with leading zero", parsingShouldSucceed(parseFn, s+"+build1.0yes", a, b, c, "", "build1.0yes")) t.Run("leading zero in numeric build identifier", parsingShouldSucceed(parseFn, s+"+build1.02", a, b, c, "", "build1.02")) }) NewValuesGenerator().AddValue(0, 199).AddValue(0, 9).AddValue(0, 9).AddValue(0, 2). TestAll4(t, func(t *testing.T, a, b, c, d int) { s := fmt.Sprintf("%d.%d.%d", a, b, c) dd := fmt.Sprintf("%d", d) t.Run("version with prerelease (multi-identifier)", parsingShouldSucceed(parseFn, s+"-beta1."+dd, a, b, c, "beta1."+dd, "")) t.Run("version with prerelease (multi-identifier with hyphens)", parsingShouldSucceed(parseFn, s+"-beta1-final."+dd, a, b, c, "beta1-final."+dd, "")) t.Run("version with build (multi-identifier)", parsingShouldSucceed(parseFn, s+"+build2."+dd, a, b, c, "", "build2."+dd)) t.Run("version with build (multi-identifier with hyphens)", parsingShouldSucceed(parseFn, s+"+build2-other."+dd, a, b, c, "", "build2-other."+dd)) t.Run("version with prerelease and build", parsingShouldSucceed(parseFn, s+"-beta1.rc2+build2."+dd, a, b, c, "beta1.rc2", "build2."+dd)) }) }) t.Run("invalid", func(t *testing.T) { t.Run("non-numeric major", parsingShouldFail(parseFn, "2x.3.4")) t.Run("non-numeric minor", parsingShouldFail(parseFn, "2.3x.4")) t.Run("non-numeric patch", parsingShouldFail(parseFn, "2.3.4x")) t.Run("empty major", parsingShouldFail(parseFn, ".3.4")) t.Run("empty minor", parsingShouldFail(parseFn, "2..4")) t.Run("empty patch", parsingShouldFail(parseFn, "2.3.")) t.Run("empty prerelease", parsingShouldFail(parseFn, "2.3.4-")) t.Run("empty prerelease identifier", parsingShouldFail(parseFn, "2.3.4-a..b")) t.Run("empty build", parsingShouldFail(parseFn, "2.3.4+a..b")) t.Run("leading zero in major", parsingShouldFail(parseFn, "02.3.4")) t.Run("leading zero in minor", parsingShouldFail(parseFn, "2.03.4")) t.Run("leading zero in patch", parsingShouldFail(parseFn, "2.3.04")) t.Run("leading zero in prerelease numeric identifier", parsingShouldFail(parseFn, "2.3.4-beta1.02")) t.Run("non-alphanumeric prerelease identifier", parsingShouldFail(parseFn, "2.3.4-beta1!")) t.Run("non-alphanumeric build identifier", parsingShouldFail(parseFn, "2.3.4+build!")) t.Run("non-ASCII character before major", parsingShouldFail(parseFn, "🔥2.3.4")) t.Run("non-ASCII character before minor", parsingShouldFail(parseFn, "2.🔥3.4")) t.Run("non-ASCII character before patch", parsingShouldFail(parseFn, "2.3.🔥4")) t.Run("non-ASCII character after major", parsingShouldFail(parseFn, "2🔥.3.4")) t.Run("non-ASCII character after minor", parsingShouldFail(parseFn, "2.3🔥.4")) t.Run("non-ASCII character after patch", parsingShouldFail(parseFn, "2.3.4🔥")) t.Run("non-ASCII character in prerelease", parsingShouldFail(parseFn, "2.3.4-beta🔥no")) t.Run("non-ASCII character in build", parsingShouldFail(parseFn, "2.3.4+build🔥no")) }) } func TestParseAsUnknownMode(t *testing.T) { v, err := ParseAs("1.2.3", ParseMode(2)) assert.Error(t, err) assert.Equal(t, Version{}, v) } go-semver-1.0.2/scan.go000066400000000000000000000021261400216241000146600ustar00rootroot00000000000000package semver import "unicode" // An extremely simple tokenizing helper that only handles ASCII strings. type simpleASCIIScanner struct { source string length int pos int } const ( scannerEOF int8 = -1 scannerNonASCII int8 = -2 ) func noTerminator(rune) bool { return false } func newSimpleASCIIScanner(source string) simpleASCIIScanner { return simpleASCIIScanner{source: source, length: len(source)} } func (s *simpleASCIIScanner) eof() bool { return s.pos >= s.length } func (s *simpleASCIIScanner) peek() int8 { if s.pos >= s.length { return scannerEOF } var ch uint8 = s.source[s.pos] if ch == 0 || ch > unicode.MaxASCII { return scannerNonASCII } return int8(ch) } func (s *simpleASCIIScanner) next() int8 { ch := s.peek() if ch > 0 { s.pos++ } return ch } func (s *simpleASCIIScanner) readUntil(terminatorFn func(rune) bool) (substring string, terminatedBy int8) { startPos := s.pos var ch int8 for { ch = s.next() if ch < 0 || terminatorFn(rune(ch)) { break } } endPos := s.pos if ch > 0 { endPos-- } return s.source[startPos:endPos], ch } go-semver-1.0.2/scan_test.go000066400000000000000000000035411400216241000157210ustar00rootroot00000000000000package semver import ( "testing" "github.com/stretchr/testify/assert" ) func TestSimpleASCIIScanner(t *testing.T) { t.Run("empty string", func(t *testing.T) { s := newSimpleASCIIScanner("") assert.True(t, s.eof()) assert.Equal(t, scannerEOF, s.peek()) assert.Equal(t, scannerEOF, s.next()) substr, term := s.readUntil(noTerminator) assert.True(t, s.eof()) assert.Equal(t, "", substr) assert.Equal(t, scannerEOF, term) }) t.Run("peek/next", func(t *testing.T) { s := newSimpleASCIIScanner("ab") assert.False(t, s.eof()) assert.Equal(t, int8('a'), s.peek()) assert.Equal(t, int8('a'), s.peek()) assert.Equal(t, int8('a'), s.next()) assert.False(t, s.eof()) assert.Equal(t, int8('b'), s.peek()) assert.Equal(t, int8('b'), s.peek()) assert.Equal(t, int8('b'), s.next()) assert.True(t, s.eof()) assert.Equal(t, scannerEOF, s.peek()) assert.Equal(t, scannerEOF, s.peek()) assert.Equal(t, scannerEOF, s.next()) assert.Equal(t, scannerEOF, s.next()) }) t.Run("readUntil", func(t *testing.T) { s1 := newSimpleASCIIScanner("abcd") ss1, term1 := s1.readUntil(noTerminator) assert.Equal(t, "abcd", ss1) assert.Equal(t, scannerEOF, term1) assert.Equal(t, scannerEOF, s1.peek()) s2 := newSimpleASCIIScanner("abcd") _ = s2.next() ss2, term2 := s2.readUntil(noTerminator) assert.Equal(t, "bcd", ss2) assert.Equal(t, scannerEOF, term2) assert.Equal(t, scannerEOF, s2.peek()) s3 := newSimpleASCIIScanner("abcd") _ = s3.next() ss3, term3 := s3.readUntil(func(ch rune) bool { return ch == 'c' }) assert.Equal(t, "b", ss3) assert.Equal(t, int8('c'), term3) assert.Equal(t, int8('d'), s3.peek()) }) t.Run("halts on non-ASCII character", func(t *testing.T) { s := newSimpleASCIIScanner("a🥦b") ss, term := s.readUntil(noTerminator) assert.Equal(t, scannerNonASCII, term) assert.Equal(t, "a", ss) }) } go-semver-1.0.2/syntax.go000066400000000000000000000024221400216241000152610ustar00rootroot00000000000000package semver func dotTerminator(ch rune) bool { return ch == '.' } func hyphenOrPlusTerminator(ch rune) bool { return ch == '-' || ch == '+' } func dotOrHyphenOrPlusTerminator(ch rune) bool { return ch == '.' || ch == '-' || ch == '+' } func plusTerminator(ch rune) bool { return ch == '+' } func isDigit(ch rune) bool { return ch >= '0' && ch <= '9' } func isAlphanumericOrHyphen(ch rune) bool { return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '-' } // Attempts to parse a string as an integer greater than or equal to zero. A zero value must be // only "0"; otherwise leading zeroes are not allowed. Non-ASCII strings are not supported. func parsePositiveNumericString(s string) (int, bool) { max := len(s) if max == 0 { return 0, false } n := 0 for i := 0; i < max; i++ { ch := rune(s[i]) if ch < '0' || ch > '9' { return 0, false } if ch == '0' && i == 0 && max > 1 { return 0, false // leading zeroes aren't allowed } n = n*10 + (int(ch) - int('0')) } return n, true } func everyChar(s string, validatorFn func(rune) bool) bool { n := len(s) for i := 0; i < n; i++ { if !validatorFn(rune(s[i])) { // we can assume it's an ASCII string due to prior validation return false } } return true } go-semver-1.0.2/values_generator.go000066400000000000000000000040561400216241000173050ustar00rootroot00000000000000package semver import ( "fmt" "testing" ) type ValuesGenerator struct { valueDefs []valueConstraint } type valueConstraint struct { min int max int } func NewValuesGenerator() *ValuesGenerator { return &ValuesGenerator{} } func (g *ValuesGenerator) AddValue(min, max int) *ValuesGenerator { g.valueDefs = append(g.valueDefs, valueConstraint{min, max}) return g } func (g ValuesGenerator) MakeAllPermutations() [][]int { var ret [][]int numValues := len(g.valueDefs) values := make([]int, numValues) for i := 0; i < numValues; i++ { values[i] = g.valueDefs[i].min } for { copied := make([]int, numValues) copy(copied, values) ret = append(ret, copied) // increment the values starting at the left, rolling over and incrementing the next to the right for pos := 0; pos < numValues; pos++ { if values[pos] < g.valueDefs[pos].max { values[pos]++ break } if pos == numValues-1 { return ret // we've covered all permutations } values[pos] = g.valueDefs[pos].min } } } func (g ValuesGenerator) TestAll(t *testing.T, action func(*testing.T, []int)) { var permutations = g.MakeAllPermutations() for _, perm := range permutations { values := perm desc := "values" for _, v := range values { desc = desc + fmt.Sprintf(" %d", v) } t.Run(desc, func(t *testing.T) { action(t, values) }) } } func (g ValuesGenerator) TestAll1(t *testing.T, action func(*testing.T, int)) { g.TestAll(t, func(t *testing.T, values []int) { action(t, values[0]) }) } func (g ValuesGenerator) TestAll2(t *testing.T, action func(*testing.T, int, int)) { g.TestAll(t, func(t *testing.T, values []int) { action(t, values[0], values[1]) }) } func (g ValuesGenerator) TestAll3(t *testing.T, action func(*testing.T, int, int, int)) { g.TestAll(t, func(t *testing.T, values []int) { action(t, values[0], values[1], values[2]) }) } func (g ValuesGenerator) TestAll4(t *testing.T, action func(*testing.T, int, int, int, int)) { g.TestAll(t, func(t *testing.T, values []int) { action(t, values[0], values[1], values[2], values[3]) }) } go-semver-1.0.2/version.go000066400000000000000000000017311400216241000154220ustar00rootroot00000000000000package semver // Version is a semantic version as defined by the Semantic Versions 2.0.0 standard (http://semver.org). // // This type provides only parsing and simple precedence comparison, since those are the only features // required by the LaunchDarkly Go SDK. type Version struct { major int minor int patch int prerelease string build string } // GetMajor returns the numeric major version component. func (v Version) GetMajor() int { return v.major } // GetMinor returns the numeric minor version component. func (v Version) GetMinor() int { return v.minor } // GetPatch returns the numeric patch version component. func (v Version) GetPatch() int { return v.patch } // GetPrerelease returns the prerelease version component, or "" if there is none. func (v Version) GetPrerelease() string { return v.prerelease } // GetBuild returns the build version component, or "" if there is none. func (v Version) GetBuild() string { return v.build } go-semver-1.0.2/version_test.go000066400000000000000000000000171400216241000164550ustar00rootroot00000000000000package semver