diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 48b251f..7126370 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -25,44 +25,14 @@ jobs:
only-new-issues: true
args: --verbose
- diff:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-go@v5
- with:
- go-version-file: 'go.mod'
- - run: go mod tidy -diff
- - run: go mod download
- - run: go mod verify
- - run: go generate ./...
- - name: Detect uncommitted changes
- run: |
- changes=$(git status --porcelain)
- if [[ -n "$changes" ]]; then
- {
- echo "## :construction: Uncommitted changes"
- echo "\`\`\`console"
- echo "\$ git status --porcelain"
- echo "$changes"
- echo "\`\`\`"
- } >> "$GITHUB_STEP_SUMMARY"
-
- echo "::group::Uncommitted changes"
- echo "$changes"
- echo "::endgroup::"
-
- exit 1
- fi
-
test:
- needs: diff
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
+ - run: go mod tidy -diff
- run: go mod download
- run: go mod verify
- run: go test -v -count=1 -race -shuffle=on -coverprofile=coverage.out -covermode=atomic ./...
@@ -83,3 +53,34 @@ jobs:
- uses: codecov/codecov-action@v4
with:
use_oidc: ${{ !(github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork) }}
+
+ generate-diff:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-go@v5
+ with:
+ go-version-file: 'go.mod'
+ - run: go mod tidy -diff
+ - run: go mod download
+ - run: go mod verify
+ - run: go generate ./...
+ - name: Detect uncommitted changes
+ run: |
+ changes=$(git status --porcelain)
+ if [[ -n "$changes" ]]; then
+ {
+ echo '## :construction: Uncommitted changes'
+ echo 'Run `$ go generate ./...` to re-generate the files.'
+ echo '```console'
+ echo '$ git status --porcelain'
+ echo "$changes"
+ echo '```'
+ } >> "$GITHUB_STEP_SUMMARY"
+
+ echo "::group::Uncommitted changes"
+ echo "$changes"
+ echo "::endgroup::"
+
+ exit 1
+ fi
diff --git a/.golangci.yml b/.golangci.yml
index 3734bf8..f9020e1 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -16,17 +16,21 @@ linters:
disable:
- dupl
- - depguard
+# -
- testpackage
- varnamelen
- wsl
- - nlreturn
+# - nlreturn
- revive
issues:
exclude-rules:
- path: '(.+)_test\.go'
linters:
- - dupl
- - lll
+ - depguard
- funlen
+ - maintidx
+
+ - path: 'version_test\.go'
+ linters:
+ - lll
diff --git a/README.md b/README.md
index 81012d3..4b33bfc 100644
--- a/README.md
+++ b/README.md
@@ -6,11 +6,11 @@
+[](https://pkg.go.dev/github.com/typisttech/comver)
+[](https://github.com/typisttech/comver/releases/latest)
[](https://github.com/typisttech/comver/actions/workflows/go.yml)
[](https://codecov.io/gh/typisttech/comver)
[](https://goreportcard.com/report/github.com/typisttech/comver)
-[](https://github.com/typisttech/comver/releases/latest)
-[](https://pkg.go.dev/github.com/typisttech/comver)
[](https://github.com/typisttech/comver/blob/master/LICENSE)
[](https://x.com/tangrufus)
[](https://typist.tech/contact/)
@@ -29,138 +29,15 @@
## Usage
> [!NOTE]
-> See full API documentation at [pkg.go.dev](https://pkg.go.dev/github.com/typisttech/comver).
-
-### `Version`
-
-[`NewVersion`](https://pkg.go.dev/github.com/typisttech/comver#NewVersion) parses a given version string, attempts to coerce a version string into a [`Version`](https://pkg.go.dev/github.com/typisttech/comver#Version) object or return an error if unable to parse the version string.
-
-If there is a leading **v** or a version listed without all parts (e.g. **v1.2.p5+foo**) it will attempt to coerce it into a valid composer version (e.g. **1.2.0.0-patch5**). In both cases a [`Version`](https://pkg.go.dev/github.com/typisttech/comver#Version) object is returned that can be sorted, compared, and used in constraints.
+>
+> See full API documentation on [pkg.go.dev](https://pkg.go.dev/github.com/typisttech/comver).
+## Known Issues
-> [!WARNING]
-> Due to implementation complexity, it only supports a subset of [composer versioning](https://github.com/composer/semver/).
+> [!CAUTION]
>
-> Refer to the [`version_test.go`](version_test.go) for examples.
-
-
-```go
-ss := []string{
- "1.2.3",
- "v1.2.p5+foo",
- "v1.2.3.4.p5+foo",
- "2010-01-02",
- "2010-01-02.5",
- "not a version",
- "1.0.0-meh",
- "20100102.0.3.4",
- "1.0.0-alpha.beta",
-}
-
-for _, s := range ss {
- v, err := comver.NewVersion(s)
- if err != nil {
- fmt.Println(s, " => ", err)
- continue
- }
- fmt.Println(s, " => ", v)
-}
-
-// Output:
-// 1.2.3 => 1.2.3.0
-// v1.2.p5+foo => 1.2.0.0-patch5
-// v1.2.3.4.p5+foo => 1.2.3.4-patch5
-// 2010-01-02 => 2010.1.2.0
-// 2010-01-02.5 => 2010.1.2.5
-// not a version => error parsing version string "not a version"
-// 1.0.0-meh => error parsing version string "1.0.0-meh"
-// 20100102.0.3.4 => error parsing version string "20100102.0.3.4"
-// 1.0.0-alpha.beta => error parsing version string "1.0.0-alpha.beta"
-```
-
-### `constraint`
-
-```go
-v1, _ := comver.NewVersion("1")
-v2, _ := comver.NewVersion("2")
-v3, _ := comver.NewVersion("3")
-v4, _ := comver.NewVersion("4")
-
-cs := []any{
- comver.NewGreaterThanConstraint(v1),
- comver.NewGreaterThanOrEqualToConstraint(v2),
- comver.NewLessThanOrEqualToConstraint(v3),
- comver.NewLessThanConstraint(v4),
-}
-
-for _, c := range cs {
- fmt.Println(c)
-}
-
-// Output:
-// >1
-// >=2
-// <=3
-// <4
-```
-
-### `interval`
-
-`interval` represents the intersection (logical AND) of two constraints.
-
-```go
-v1, _ := comver.NewVersion("1")
-v2, _ := comver.NewVersion("2")
-v3, _ := comver.NewVersion("3")
-
-g1l3, _ := comver.NewInterval(
- comver.NewGreaterThanConstraint(v1),
- comver.NewLessThanConstraint(v3),
-)
-
-if g1l3.Check(v2) {
- fmt.Println(v2.Short(), "satisfies", g1l3)
-}
-
-if !g1l3.Check(v3) {
- fmt.Println(v2.Short(), "doesn't satisfy", g1l3)
-}
-
-// Output:
-// 2 satisfies >1 <3
-// 2 doesn't satisfy >1 <3
-```
-
-### `Intervals`
-
-[`Intervals`](https://pkg.go.dev/github.com/typisttech/comver#Intervals) represent the union (logical OR) of multiple intervals.
-
-```go
-v1, _ := comver.NewVersion("1")
-v2, _ := comver.NewVersion("2")
-v3, _ := comver.NewVersion("3")
-v4, _ := comver.NewVersion("4")
-
-g1l3, _ := comver.NewInterval(
-comver.NewGreaterThanConstraint(v1),
-comver.NewLessThanConstraint(v3),
-)
-
-ge2le4, _ := comver.NewInterval(
-comver.NewGreaterThanOrEqualToConstraint(v2),
-comver.NewLessThanOrEqualToConstraint(v4),
-)
-
-is := comver.Intervals{g1l3, ge2le4}
-fmt.Println(is)
-
-is = comver.Compact(is)
-fmt.Println(is)
-
-// Output:
-// >1 <3 || >=2 <=4
-// >1 <=4
-```
+> Due to implementation complexity, it only supports a subset of [composer versioning](https://github.com/composer/semver/).
+> Refer to the [version_test.go](https://github.com/typisttech/comver/blob/main/version_test.go) for examples.
## Credits
diff --git a/and.go b/and.go
new file mode 100644
index 0000000..64803a0
--- /dev/null
+++ b/and.go
@@ -0,0 +1,109 @@
+package comver
+
+import "slices"
+
+const (
+ errNoEndlessGiven stringError = "no endless given"
+ errUnexpectedAndLogic stringError = "unexpected and logic"
+ errImpossibleInterval stringError = "impossible interval"
+)
+
+// And returns a [CeilingFloorConstrainter] instance representing the logical AND of
+// the given [Endless] instances; or return an error if the given [Endless] instances
+// could never be satisfied at the same time.
+func And(es ...Endless) (CeilingFloorConstrainter, error) { //nolint:cyclop,ireturn
+ var nilC CeilingFloorConstrainter
+
+ if len(es) == 0 {
+ return nilC, errNoEndlessGiven
+ }
+
+ es = slices.Clone(es)
+ es = slices.DeleteFunc(es, Endless.wildcard)
+
+ if len(es) == 0 {
+ return NewWildcard(), nil
+ }
+ if len(es) == 1 {
+ return es[0], nil
+ }
+
+ ceiling, ceilingOk := minBoundedCeiling(es...)
+ floor, floorOk := maxBoundedFloor(es...)
+
+ if !ceilingOk && !floorOk {
+ // logic error! This should never happen
+ return nilC, errUnexpectedAndLogic
+ }
+ if ceilingOk && !floorOk {
+ return ceiling, nil
+ }
+ if !ceilingOk { // floorOk is always true here
+ return floor, nil
+ }
+
+ vCmp := floor.floor().versionCompare(ceiling.ceiling().version)
+
+ if vCmp > 0 {
+ return nilC, errImpossibleInterval
+ }
+
+ if vCmp == 0 {
+ if !floor.floor().inclusive() || !ceiling.ceiling().inclusive() {
+ return nilC, errImpossibleInterval
+ }
+
+ return NewExactConstraint(*floor.floor().version), nil
+ }
+
+ return interval{
+ upper: ceiling,
+ lower: floor,
+ }, nil
+}
+
+// MustAnd is like [And] but panics if an error occurs.
+func MustAnd(es ...Endless) CeilingFloorConstrainter { //nolint:ireturn
+ c, err := And(es...)
+ if err != nil {
+ panic(err)
+ }
+
+ return c
+}
+
+func minBoundedCeiling(es ...Endless) (Endless, bool) {
+ es = slices.Clone(es)
+
+ bcs := slices.DeleteFunc(es, func(b Endless) bool {
+ return b.ceiling().version == nil
+ })
+
+ if len(bcs) == 0 {
+ var nilF Endless
+
+ return nilF, false
+ }
+
+ return slices.MinFunc(bcs, func(a, b Endless) int {
+ return a.ceiling().compare(b.ceiling())
+ }), true
+}
+
+func maxBoundedFloor(es ...Endless) (Endless, bool) {
+ es = slices.Clone(es)
+
+ bfs := slices.DeleteFunc(es, func(c Endless) bool {
+ return c.floor().wildcard()
+ })
+
+ if len(bfs) == 0 {
+ var nilF Endless
+
+ return nilF, false
+ }
+
+ return slices.MaxFunc(bfs, func(a, b Endless) int {
+ return a.floor().compare(b.floor())
+ }), true
+}
diff --git a/and_example_test.go b/and_example_test.go
new file mode 100644
index 0000000..956a14e
--- /dev/null
+++ b/and_example_test.go
@@ -0,0 +1,48 @@
+package comver_test
+
+import (
+ "fmt"
+
+ "github.com/typisttech/comver"
+)
+
+func ExampleAnd() {
+ a, _ := comver.And(
+ comver.NewGreaterThanOrEqualTo(comver.MustParse("2")),
+ comver.NewLessThan(comver.MustParse("3")),
+ )
+
+ fmt.Println(a)
+ // Output: >=2 <3
+}
+
+func ExampleAnd_wildcard() {
+ a, _ := comver.And(
+ comver.NewGreaterThanOrEqualTo(comver.MustParse("2")),
+ comver.NewLessThan(comver.MustParse("3")),
+ comver.NewWildcard(),
+ )
+
+ fmt.Println(a)
+ // Output: >=2 <3
+}
+
+func ExampleAnd_exactConstraint() {
+ a, _ := comver.And(
+ comver.NewGreaterThanOrEqualTo(comver.MustParse("2")),
+ comver.NewLessThanOrEqualTo(comver.MustParse("2")),
+ )
+
+ fmt.Println(a)
+ // Output: 2
+}
+
+func ExampleAnd_impossibleInterval() {
+ _, err := comver.And(
+ comver.NewGreaterThanOrEqualTo(comver.MustParse("3")),
+ comver.NewLessThan(comver.MustParse("2")),
+ )
+
+ fmt.Println(err)
+ // Output: impossible interval
+}
diff --git a/and_test.go b/and_test.go
new file mode 100644
index 0000000..2347de2
--- /dev/null
+++ b/and_test.go
@@ -0,0 +1,792 @@
+package comver
+
+import (
+ "errors"
+ "math/rand/v2"
+ "reflect"
+ "slices"
+ "testing"
+)
+
+func TestAnd(t *testing.T) {
+ t.Parallel()
+
+ var nilC CeilingFloorConstrainter
+
+ tests := []struct {
+ name string
+ es []Endless
+ want CeilingFloorConstrainter
+ wantErr error
+ }{
+ {
+ name: "empty",
+ es: []Endless{},
+ want: nilC,
+ wantErr: errNoEndlessGiven,
+ },
+ {
+ name: "single_lessThan",
+ es: []Endless{NewLessThan(MustParse("1"))},
+ want: NewLessThan(MustParse("1")),
+ wantErr: nil,
+ },
+ {
+ name: "single_lessThanOrEqualTo",
+ es: []Endless{NewLessThanOrEqualTo(MustParse("2"))},
+ want: NewLessThanOrEqualTo(MustParse("2")),
+ wantErr: nil,
+ },
+ {
+ name: "single_greaterThan",
+ es: []Endless{NewGreaterThan(MustParse("3"))},
+ want: NewGreaterThan(MustParse("3")),
+ wantErr: nil,
+ },
+ {
+ name: "single_greaterThanOrEqualTo",
+ es: []Endless{NewGreaterThanOrEqualTo(MustParse("4"))},
+ want: NewGreaterThanOrEqualTo(MustParse("4")),
+ wantErr: nil,
+ },
+ {
+ name: "single_wildcard",
+ es: []Endless{NewWildcard()},
+ want: NewWildcard(),
+ wantErr: nil,
+ },
+ {
+ name: "multiple_wildcards",
+ es: []Endless{NewWildcard(), NewWildcard()},
+ want: NewWildcard(),
+ wantErr: nil,
+ },
+ {
+ name: "multiple_ceiling_inclusive",
+ es: []Endless{
+ NewLessThan(MustParse("1")),
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThan(MustParse("2")),
+ NewLessThanOrEqualTo(MustParse("2")),
+ },
+ want: NewLessThan(MustParse("1")),
+ wantErr: nil,
+ },
+ {
+ name: "multiple_ceiling_non_inclusive",
+ es: []Endless{
+ NewLessThan(MustParse("1")),
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThan(MustParse("2")),
+ NewLessThanOrEqualTo(MustParse("2")),
+ },
+ want: NewLessThan(MustParse("1")),
+ wantErr: nil,
+ },
+ {
+ name: "multiple_floors_inclusive",
+ es: []Endless{
+ NewGreaterThanOrEqualTo(MustParse("3")),
+ NewGreaterThan(MustParse("3")),
+ NewGreaterThanOrEqualTo(MustParse("4")),
+ },
+ want: NewGreaterThanOrEqualTo(MustParse("4")),
+ wantErr: nil,
+ },
+ {
+ name: "multiple_floors_non_inclusive",
+ es: []Endless{
+ NewGreaterThanOrEqualTo(MustParse("3")),
+ NewGreaterThan(MustParse("3")),
+ NewGreaterThanOrEqualTo(MustParse("4")),
+ NewGreaterThan(MustParse("4")),
+ },
+ want: NewGreaterThan(MustParse("4")),
+ wantErr: nil,
+ },
+ {
+ name: "impossible_interval",
+ es: []Endless{
+ NewLessThan(MustParse("1")),
+ NewGreaterThan(MustParse("4")),
+ },
+ want: nilC,
+ wantErr: errImpossibleInterval,
+ },
+ {
+ name: "impossible_interval_wildcard",
+ es: []Endless{
+ NewLessThan(MustParse("1")),
+ NewGreaterThan(MustParse("4")),
+ NewWildcard(),
+ },
+ want: nilC,
+ wantErr: errImpossibleInterval,
+ },
+ {
+ name: "multiple_wildcards_multiple_endlesses",
+ es: []Endless{
+ NewLessThan(MustParse("1")),
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThan(MustParse("2")),
+ NewLessThanOrEqualTo(MustParse("2")),
+ NewGreaterThanOrEqualTo(MustParse("3")),
+ NewGreaterThan(MustParse("3")),
+ NewGreaterThanOrEqualTo(MustParse("4")),
+ NewGreaterThan(MustParse("4")),
+ NewWildcard(),
+ NewWildcard(),
+ },
+ want: nilC,
+ wantErr: errImpossibleInterval,
+ },
+ {
+ name: "impossible_interval_same_version",
+ es: []Endless{
+ NewLessThan(MustParse("1")),
+ NewGreaterThan(MustParse("1")),
+ },
+ want: nilC,
+ wantErr: errImpossibleInterval,
+ },
+
+ {
+ name: "exact_version_ceiling_inclusive",
+ es: []Endless{
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewGreaterThan(MustParse("1")),
+ },
+ want: nilC,
+ wantErr: errImpossibleInterval,
+ },
+ {
+ name: "exact_version_floor_inclusive",
+ es: []Endless{
+ NewLessThan(MustParse("1")),
+ NewGreaterThanOrEqualTo(MustParse("1")),
+ },
+ want: nilC,
+ wantErr: errImpossibleInterval,
+ },
+ {
+ name: "exact_version_ceiling_inclusive_floor_inclusive",
+ es: []Endless{
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewGreaterThanOrEqualTo(MustParse("1")),
+ },
+ want: NewExactConstraint(MustParse("1")),
+ wantErr: nil,
+ },
+ {
+ name: "exact_version_wildcard",
+ es: []Endless{
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewGreaterThanOrEqualTo(MustParse("1")),
+ NewWildcard(),
+ },
+ want: NewExactConstraint(MustParse("1")),
+ wantErr: nil,
+ },
+ {
+ name: "impossible_interval_same_version",
+ es: []Endless{
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThan(MustParse("1")),
+ NewLessThan(MustParse("1")),
+ NewGreaterThanOrEqualTo(MustParse("1")),
+ NewGreaterThanOrEqualTo(MustParse("1")),
+ NewGreaterThan(MustParse("1")),
+ NewGreaterThan(MustParse("1")),
+ NewWildcard(),
+ NewWildcard(),
+ },
+ want: nilC,
+ wantErr: errImpossibleInterval,
+ },
+ {
+ name: "impossible_interval_same_version",
+ es: []Endless{
+ NewLessThan(MustParse("1")),
+ NewLessThan(MustParse("1")),
+ NewGreaterThanOrEqualTo(MustParse("1")),
+ NewGreaterThanOrEqualTo(MustParse("1")),
+ NewGreaterThan(MustParse("1")),
+ NewGreaterThan(MustParse("1")),
+ NewWildcard(),
+ NewWildcard(),
+ },
+ want: nilC,
+ wantErr: errImpossibleInterval,
+ },
+ {
+ name: "exact_version_multiple",
+ es: []Endless{
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewGreaterThanOrEqualTo(MustParse("1")),
+ NewGreaterThanOrEqualTo(MustParse("1")),
+ NewWildcard(),
+ NewWildcard(),
+ },
+ want: NewExactConstraint(MustParse("1")),
+ wantErr: nil,
+ },
+ {
+ name: "impossible_interval_same_version",
+ es: []Endless{
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThan(MustParse("1")),
+ NewLessThan(MustParse("1")),
+ NewWildcard(),
+ NewWildcard(),
+ NewGreaterThan(MustParse("1")),
+ },
+ want: nilC,
+ wantErr: errImpossibleInterval,
+ },
+ {
+ name: "impossible_interval_same_version",
+ es: []Endless{
+ NewGreaterThanOrEqualTo(MustParse("1")),
+ NewGreaterThanOrEqualTo(MustParse("1")),
+ NewGreaterThan(MustParse("1")),
+ NewGreaterThan(MustParse("1")),
+ NewWildcard(),
+ NewWildcard(),
+ NewLessThan(MustParse("1")),
+ },
+ want: nilC,
+ wantErr: errImpossibleInterval,
+ },
+ {
+ name: "impossible_interval_same_version",
+ es: []Endless{
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThan(MustParse("1")),
+ NewLessThan(MustParse("1")),
+ NewWildcard(),
+ NewWildcard(),
+ NewGreaterThanOrEqualTo(MustParse("1")),
+ NewGreaterThanOrEqualTo(MustParse("1")),
+ NewGreaterThan(MustParse("1")),
+ NewGreaterThan(MustParse("1")),
+ },
+ want: nilC,
+ wantErr: errImpossibleInterval,
+ },
+ {
+ name: "impossible_interval_same_version",
+ es: []Endless{
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThan(MustParse("1")),
+ NewLessThan(MustParse("1")),
+ NewGreaterThanOrEqualTo(MustParse("1")),
+ NewGreaterThanOrEqualTo(MustParse("1")),
+ NewGreaterThan(MustParse("1")),
+ NewGreaterThan(MustParse("1")),
+ },
+ want: nilC,
+ wantErr: errImpossibleInterval,
+ },
+ {
+ name: "simple",
+ es: []Endless{
+ NewLessThan(MustParse("4")),
+ NewGreaterThan(MustParse("1")),
+ },
+ want: interval{
+ upper: NewLessThan(MustParse("4")),
+ lower: NewGreaterThan(MustParse("1")),
+ },
+ wantErr: nil,
+ },
+ {
+ name: "compact_ceiling",
+ es: []Endless{
+ NewLessThanOrEqualTo(MustParse("4")),
+ NewLessThan(MustParse("4")),
+ NewLessThanOrEqualTo(MustParse("3")),
+ NewLessThan(MustParse("3")),
+ NewGreaterThan(MustParse("1")),
+ },
+ want: interval{
+ upper: NewLessThan(MustParse("3")),
+ lower: NewGreaterThan(MustParse("1")),
+ },
+ wantErr: nil,
+ },
+ {
+ name: "compact_ceiling_inclusive",
+ es: []Endless{
+ NewLessThanOrEqualTo(MustParse("4")),
+ NewLessThan(MustParse("4")),
+ NewLessThanOrEqualTo(MustParse("3")),
+ NewGreaterThan(MustParse("1")),
+ },
+ want: interval{
+ upper: NewLessThanOrEqualTo(MustParse("3")),
+ lower: NewGreaterThan(MustParse("1")),
+ },
+ wantErr: nil,
+ },
+ {
+ name: "compact_floor",
+ es: []Endless{
+ NewLessThan(MustParse("4")),
+ NewGreaterThan(MustParse("2")),
+ NewGreaterThanOrEqualTo(MustParse("2")),
+ NewGreaterThan(MustParse("1")),
+ NewGreaterThanOrEqualTo(MustParse("1")),
+ },
+ want: interval{
+ upper: NewLessThan(MustParse("4")),
+ lower: NewGreaterThan(MustParse("2")),
+ },
+ wantErr: nil,
+ },
+ {
+ name: "compact_floor_non_inclusive",
+ es: []Endless{
+ NewLessThan(MustParse("4")),
+ NewGreaterThanOrEqualTo(MustParse("2")),
+ NewGreaterThan(MustParse("1")),
+ NewGreaterThanOrEqualTo(MustParse("1")),
+ },
+ want: interval{
+ upper: NewLessThan(MustParse("4")),
+ lower: NewGreaterThanOrEqualTo(MustParse("2")),
+ },
+ wantErr: nil,
+ },
+ {
+ name: "compact_ceiling_floor",
+ es: []Endless{
+ NewLessThanOrEqualTo(MustParse("4")),
+ NewLessThan(MustParse("4")),
+ NewLessThanOrEqualTo(MustParse("3")),
+ NewLessThan(MustParse("3")),
+ NewGreaterThan(MustParse("2")),
+ NewGreaterThanOrEqualTo(MustParse("2")),
+ NewGreaterThan(MustParse("1")),
+ NewGreaterThanOrEqualTo(MustParse("1")),
+ },
+ want: interval{
+ upper: NewLessThan(MustParse("3")),
+ lower: NewGreaterThan(MustParse("2")),
+ },
+ wantErr: nil,
+ },
+ {
+ name: "compact_ceiling_floor_wildcard",
+ es: []Endless{
+ NewLessThanOrEqualTo(MustParse("4")),
+ NewLessThan(MustParse("4")),
+ NewLessThanOrEqualTo(MustParse("3")),
+ NewLessThan(MustParse("3")),
+ NewGreaterThan(MustParse("2")),
+ NewGreaterThanOrEqualTo(MustParse("2")),
+ NewGreaterThan(MustParse("1")),
+ NewGreaterThanOrEqualTo(MustParse("1")),
+ NewWildcard(),
+ },
+ want: interval{
+ upper: NewLessThan(MustParse("3")),
+ lower: NewGreaterThan(MustParse("2")),
+ },
+ wantErr: nil,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ rand.Shuffle(len(tt.es), func(i, j int) {
+ tt.es[i], tt.es[j] = tt.es[j], tt.es[i]
+ })
+
+ got, err := And(tt.es...)
+
+ if !errors.Is(err, tt.wantErr) {
+ t.Fatalf("And() error = %#v, wantErr %#v", err, tt.wantErr)
+ }
+
+ if err != nil {
+ return
+ }
+
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("And() got = %v, want %v", got, tt.want)
+ }
+ })
+ }
+
+ t.Run("no_arg", func(t *testing.T) {
+ t.Parallel()
+
+ wantErr := errNoEndlessGiven
+
+ _, err := And()
+
+ if !errors.Is(err, wantErr) {
+ t.Errorf("And() error = %#v, wantErr %#v", err, wantErr)
+ }
+ })
+}
+
+func Test_minBoundedCeiling(t *testing.T) {
+ t.Parallel()
+
+ type testCase struct {
+ name string
+ cs []Endless
+ want Endless
+ wantOk bool
+ }
+ tests := []testCase{
+ {
+ name: "empty",
+ cs: []Endless{},
+ want: NewWildcard(),
+ wantOk: false,
+ },
+ {
+ name: "nil",
+ cs: nil,
+ want: NewWildcard(),
+ wantOk: false,
+ },
+ {
+ name: "single_lessThan",
+ cs: []Endless{NewLessThan(MustParse("1"))},
+ want: NewLessThan(MustParse("1")),
+ wantOk: true,
+ },
+ {
+ name: "single_lessThanOrEqualTo",
+ cs: []Endless{NewLessThanOrEqualTo(MustParse("2"))},
+ want: NewLessThanOrEqualTo(MustParse("2")),
+ wantOk: true,
+ },
+ {
+ name: "single_greaterThan",
+ cs: []Endless{NewGreaterThan(MustParse("3"))},
+ want: NewWildcard(),
+ wantOk: false,
+ },
+ {
+ name: "single_greaterThanOrEqualTo",
+ cs: []Endless{NewGreaterThanOrEqualTo(MustParse("4"))},
+ want: NewWildcard(),
+ wantOk: false,
+ },
+ {
+ name: "single_wildcard",
+ cs: []Endless{NewWildcard()},
+ want: NewWildcard(),
+ wantOk: false,
+ },
+ {
+ name: "multiple_no_ceiling",
+ cs: []Endless{
+ NewGreaterThanOrEqualTo(MustParse("3")),
+ NewGreaterThan(MustParse("3")),
+ NewGreaterThanOrEqualTo(MustParse("4")),
+ NewGreaterThan(MustParse("4")),
+ },
+ want: NewWildcard(),
+ wantOk: false,
+ },
+ {
+ name: "multiple_no_ceiling_wildcard",
+ cs: []Endless{
+ NewGreaterThanOrEqualTo(MustParse("3")),
+ NewGreaterThan(MustParse("3")),
+ NewGreaterThanOrEqualTo(MustParse("4")),
+ NewGreaterThan(MustParse("4")),
+ NewWildcard(),
+ },
+ want: NewWildcard(),
+ wantOk: false,
+ },
+ {
+ name: "multiple_ceilings_non_inclusive",
+ cs: []Endless{
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThan(MustParse("2")),
+ NewLessThanOrEqualTo(MustParse("2")),
+ NewGreaterThanOrEqualTo(MustParse("3")),
+ NewGreaterThan(MustParse("3")),
+ NewGreaterThanOrEqualTo(MustParse("4")),
+ NewGreaterThan(MustParse("4")),
+ },
+ want: NewLessThanOrEqualTo(MustParse("1")),
+ wantOk: true,
+ },
+ {
+ name: "multiple_ceilings_inclusive",
+ cs: []Endless{
+ NewLessThan(MustParse("1")),
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThan(MustParse("2")),
+ NewLessThanOrEqualTo(MustParse("2")),
+ NewGreaterThanOrEqualTo(MustParse("3")),
+ NewGreaterThan(MustParse("3")),
+ NewGreaterThanOrEqualTo(MustParse("4")),
+ NewGreaterThan(MustParse("4")),
+ },
+ want: NewLessThan(MustParse("1")),
+ wantOk: true,
+ },
+ {
+ name: "multiple_ceilings_wildcard",
+ cs: []Endless{
+ NewLessThan(MustParse("1")),
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThan(MustParse("2")),
+ NewLessThanOrEqualTo(MustParse("2")),
+ NewGreaterThanOrEqualTo(MustParse("3")),
+ NewGreaterThan(MustParse("3")),
+ NewGreaterThanOrEqualTo(MustParse("4")),
+ NewGreaterThan(MustParse("4")),
+ NewWildcard(),
+ },
+ want: NewLessThan(MustParse("1")),
+ wantOk: true,
+ },
+ {
+ name: "single_ceiling",
+ cs: []Endless{
+ NewLessThanOrEqualTo(MustParse("2")),
+ NewGreaterThanOrEqualTo(MustParse("3")),
+ NewGreaterThan(MustParse("3")),
+ NewGreaterThanOrEqualTo(MustParse("4")),
+ NewGreaterThan(MustParse("4")),
+ },
+ want: NewLessThanOrEqualTo(MustParse("2")),
+ wantOk: true,
+ },
+ {
+ name: "single_ceiling_wildcard",
+ cs: []Endless{
+ NewLessThanOrEqualTo(MustParse("2")),
+ NewGreaterThanOrEqualTo(MustParse("3")),
+ NewGreaterThan(MustParse("3")),
+ NewGreaterThanOrEqualTo(MustParse("4")),
+ NewGreaterThan(MustParse("4")),
+ NewWildcard(),
+ },
+ want: NewLessThanOrEqualTo(MustParse("2")),
+ wantOk: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ rand.Shuffle(len(tt.cs), func(i, j int) {
+ tt.cs[i], tt.cs[j] = tt.cs[j], tt.cs[i]
+ })
+
+ original := slices.Clone(tt.cs)
+
+ got, gotOk := minBoundedCeiling(tt.cs...)
+
+ if gotOk != tt.wantOk {
+ t.Fatalf("minBoundedCeiling() gotOk = %v, want %v", gotOk, tt.wantOk)
+ }
+
+ if !tt.wantOk {
+ return
+ }
+
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("minBoundedCeiling() got = %v, want %v", got, tt.want)
+ }
+
+ if !reflect.DeepEqual(tt.cs, original) {
+ t.Errorf("minBoundedCeiling() changed the original slice got = %v, want %v", tt.cs, original)
+ }
+ })
+ }
+}
+
+func Test_maxBoundedFloor(t *testing.T) {
+ t.Parallel()
+
+ type testCase struct {
+ name string
+ fs []Endless
+ want Endless
+ wantOk bool
+ }
+ tests := []testCase{
+ {
+ name: "empty",
+ fs: []Endless{},
+ want: NewWildcard(),
+ wantOk: false,
+ },
+ {
+ name: "nil",
+ fs: nil,
+ want: NewWildcard(),
+ wantOk: false,
+ },
+ {
+ name: "single_lessThan",
+ fs: []Endless{NewLessThan(MustParse("1"))},
+ want: NewWildcard(),
+ wantOk: false,
+ },
+ {
+ name: "single_lessThanOrEqualTo",
+ fs: []Endless{NewLessThanOrEqualTo(MustParse("2"))},
+ want: NewWildcard(),
+ wantOk: false,
+ },
+ {
+ name: "single_greaterThan",
+ fs: []Endless{NewGreaterThan(MustParse("3"))},
+ want: NewGreaterThan(MustParse("3")),
+ wantOk: true,
+ },
+ {
+ name: "single_greaterThanOrEqualTo",
+ fs: []Endless{NewGreaterThanOrEqualTo(MustParse("4"))},
+ want: NewGreaterThanOrEqualTo(MustParse("4")),
+ wantOk: true,
+ },
+ {
+ name: "single_wildcard",
+ fs: []Endless{NewWildcard()},
+ want: NewWildcard(),
+ wantOk: false,
+ },
+ {
+ name: "multiple_no_floor",
+ fs: []Endless{
+ NewLessThan(MustParse("1")),
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThan(MustParse("2")),
+ NewLessThanOrEqualTo(MustParse("2")),
+ },
+ want: NewWildcard(),
+ wantOk: false,
+ },
+ {
+ name: "multiple_no_floor_wildcard",
+ fs: []Endless{
+ NewLessThan(MustParse("1")),
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThan(MustParse("2")),
+ NewLessThanOrEqualTo(MustParse("2")),
+ NewWildcard(),
+ },
+ want: NewWildcard(),
+ wantOk: false,
+ },
+ {
+ name: "multiple_floors_non_inclusive",
+ fs: []Endless{
+ NewLessThan(MustParse("1")),
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThan(MustParse("2")),
+ NewLessThanOrEqualTo(MustParse("2")),
+ NewGreaterThanOrEqualTo(MustParse("3")),
+ NewGreaterThan(MustParse("3")),
+ NewGreaterThan(MustParse("4")),
+ },
+ want: NewGreaterThan(MustParse("4")),
+ wantOk: true,
+ },
+ {
+ name: "multiple_floors_inclusive",
+ fs: []Endless{
+ NewLessThan(MustParse("1")),
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThan(MustParse("2")),
+ NewLessThanOrEqualTo(MustParse("2")),
+ NewGreaterThanOrEqualTo(MustParse("3")),
+ NewGreaterThan(MustParse("3")),
+ NewGreaterThanOrEqualTo(MustParse("4")),
+ NewGreaterThan(MustParse("4")),
+ },
+ want: NewGreaterThan(MustParse("4")),
+ wantOk: true,
+ },
+ {
+ name: "multiple_floors_wildcard",
+ fs: []Endless{
+ NewLessThan(MustParse("1")),
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThan(MustParse("2")),
+ NewLessThanOrEqualTo(MustParse("2")),
+ NewGreaterThanOrEqualTo(MustParse("3")),
+ NewGreaterThan(MustParse("3")),
+ NewGreaterThanOrEqualTo(MustParse("4")),
+ NewGreaterThan(MustParse("4")),
+ NewWildcard(),
+ },
+ want: NewGreaterThan(MustParse("4")),
+ wantOk: true,
+ },
+ {
+ name: "single_floor",
+ fs: []Endless{
+ NewLessThan(MustParse("1")),
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThan(MustParse("2")),
+ NewLessThanOrEqualTo(MustParse("2")),
+ NewGreaterThan(MustParse("3")),
+ },
+ want: NewGreaterThan(MustParse("3")),
+ wantOk: true,
+ },
+ {
+ name: "single_floor_wildcard",
+ fs: []Endless{
+ NewLessThan(MustParse("1")),
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThan(MustParse("2")),
+ NewLessThanOrEqualTo(MustParse("2")),
+ NewGreaterThan(MustParse("3")),
+ NewWildcard(),
+ },
+ want: NewGreaterThan(MustParse("3")),
+ wantOk: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ rand.Shuffle(len(tt.fs), func(i, j int) {
+ tt.fs[i], tt.fs[j] = tt.fs[j], tt.fs[i]
+ })
+
+ original := slices.Clone(tt.fs)
+
+ got, gotOk := maxBoundedFloor(tt.fs...)
+
+ if gotOk != tt.wantOk {
+ t.Fatalf("maxBoundedFloor() gotOk = %v, want %v", gotOk, tt.wantOk)
+ }
+
+ if !tt.wantOk {
+ return
+ }
+
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("maxBoundedFloor() got = %v, want %v", got, tt.want)
+ }
+
+ if !reflect.DeepEqual(tt.fs, original) {
+ t.Errorf("maxBoundedFloor() changed the original slice got = %v, want %v", tt.fs, original)
+ }
+ })
+ }
+}
diff --git a/compact.go b/compact.go
new file mode 100644
index 0000000..2936c33
--- /dev/null
+++ b/compact.go
@@ -0,0 +1,249 @@
+package comver
+
+import (
+ "slices"
+)
+
+// Compact returns a new [Constrainter] that is logically equivalent to the input [Or].
+// The returned [Constrainter] may or may be not be an [Or] instance.
+// When it is, Compact tries to return the shortest [Or] possible.
+func Compact(o Or) Constrainter { //nolint:cyclop,ireturn
+ if len(o) == 0 {
+ return Or{}
+ }
+ if len(o) == 1 {
+ return o[0]
+ }
+ if slices.ContainsFunc(o, wildcard) {
+ return NewWildcard()
+ }
+
+ o = slices.Clone(o)
+
+ ceiling, ceilingOk := maxFloorlessCeiling(o...)
+ floor, floorOk := minCeilinglessFloor(o...)
+
+ // short circuit if we have a wildcard
+ if ceilingOk && floorOk && matchAll(ceiling, floor) {
+ return NewWildcard()
+ }
+
+ o = slices.DeleteFunc(o, func(c CeilingFloorConstrainter) bool {
+ return c.ceiling().wildcard() || c.floor().wildcard() ||
+ (ceilingOk && ceiling.compare(c.ceiling()) >= 0) ||
+ (floorOk && floor.compare(c.floor()) <= 0)
+ })
+
+ // important to sort before compacting
+ slices.SortFunc(o, compare)
+ o = slices.CompactFunc(o, func(a, b CeilingFloorConstrainter) bool {
+ return compare(a, b) == 0
+ })
+
+ r := compactMultiple(o)
+
+ if floorOk {
+ r = append(r, floor)
+ }
+ if ceilingOk {
+ r = append(r, ceiling)
+ }
+
+ if len(r) == 1 {
+ return r[0]
+ }
+
+ slices.SortFunc(r, compare)
+
+ return slices.Clip(r)
+}
+
+func wildcard(c CeilingFloorConstrainter) bool {
+ return c.floor().wildcard() && c.ceiling().wildcard()
+}
+
+func minCeilinglessFloor(cs ...CeilingFloorConstrainter) (Endless, bool) {
+ cs = slices.Clone(cs)
+ o := slices.Clone(cs)
+
+ cs = slices.DeleteFunc(cs, func(c CeilingFloorConstrainter) bool {
+ return !c.ceiling().wildcard()
+ })
+
+ if len(cs) == 0 {
+ var nilE Endless
+
+ return nilE, false
+ }
+
+ m := slices.MinFunc(cs, func(a, b CeilingFloorConstrainter) int {
+ return a.floor().compare(b.floor())
+ }).floor()
+
+ o = slices.DeleteFunc(o, func(c CeilingFloorConstrainter) bool {
+ return c.ceiling().wildcard() ||
+ c.floor().wildcard()
+ })
+
+ for i := range o {
+ if o[i].floor().compare(m) < 0 {
+ if o[i].Check(*m.version) || matchAll(o[i].ceiling(), m) {
+ m = o[i].floor()
+ }
+
+ continue
+ }
+
+ if m.versionCompare(o[i].floor().version) == 0 {
+ if o[i].floor().inclusive() {
+ m = o[i].floor()
+ }
+
+ continue
+ }
+ }
+
+ return m.floor(), true
+}
+
+func maxFloorlessCeiling(cs ...CeilingFloorConstrainter) (Endless, bool) {
+ cs = slices.Clone(cs)
+ o := slices.Clone(cs)
+
+ cs = slices.DeleteFunc(cs, func(c CeilingFloorConstrainter) bool {
+ return !c.floor().wildcard()
+ })
+
+ if len(cs) == 0 {
+ var nilE Endless
+
+ return nilE, false
+ }
+
+ m := slices.MaxFunc(cs, func(a, b CeilingFloorConstrainter) int {
+ return a.ceiling().compare(b.ceiling())
+ }).ceiling()
+
+ o = slices.DeleteFunc(o, func(c CeilingFloorConstrainter) bool {
+ return c.ceiling().wildcard() || c.floor().wildcard()
+ })
+
+ for i := range o {
+ if o[i].ceiling().compare(m) > 0 {
+ if o[i].Check(*m.version) || matchAll(o[i].floor(), m) {
+ m = o[i].ceiling()
+ }
+
+ continue
+ }
+
+ if m.versionCompare(o[i].ceiling().version) == 0 {
+ if o[i].ceiling().inclusive() {
+ m = o[i].ceiling()
+ }
+
+ continue
+ }
+ }
+
+ return m, true
+}
+
+func matchAll(e, f Endless) bool {
+ if e.ceilingBounded() && f.ceilingBounded() {
+ return false
+ }
+
+ if e.floorBounded() && f.floorBounded() {
+ return false
+ }
+
+ cmp := e.compare(f)
+
+ if cmp == 0 {
+ return false
+ }
+
+ if cmp > 0 {
+ e, f = f, e
+ }
+
+ if e.versionCompare(f.version) == 0 {
+ return e.inclusive() || f.inclusive()
+ }
+
+ return !e.ceilingBounded() && !f.floorBounded()
+}
+
+func compare(a, b CeilingFloorConstrainter) int {
+ cmp := a.floor().compare(b.floor())
+
+ if cmp != 0 {
+ return cmp
+ }
+
+ return a.ceiling().compare(b.ceiling())
+}
+
+func compactTwo(a, b CeilingFloorConstrainter) (CeilingFloorConstrainter, bool) { //nolint:ireturn
+ cmp := compare(a, b)
+ if cmp == 0 {
+ return a, true
+ }
+
+ if cmp > 0 {
+ a, b = b, a
+ }
+
+ if !overlap(a, b) && !continuous(a, b) {
+ return a, false
+ }
+
+ if a.ceiling().compare(b.ceiling()) > 0 {
+ return a, true
+ }
+
+ return interval{
+ upper: b.ceiling(),
+ lower: a.floor(),
+ }, true
+}
+
+func compactMultiple(o []CeilingFloorConstrainter) Or {
+ r := make(Or, 0, len(o)+2) //nolint:mnd
+
+ if len(o) != 0 {
+ p := o[0]
+ for i := range o {
+ q, ok := compactTwo(p, o[i])
+
+ if ok {
+ p = q
+ } else {
+ r = append(r, p)
+ p = o[i]
+ }
+
+ // always append the last r
+ if i == len(o)-1 {
+ r = append(r, p)
+ }
+ }
+ }
+
+ return r
+}
+
+func overlap(a, b CeilingFloorConstrainter) bool {
+ return a.Check(*b.floor().version) ||
+ b.Check(*a.floor().version)
+}
+
+func continuous(a, b CeilingFloorConstrainter) bool {
+ f := func(a, b CeilingFloorConstrainter) bool {
+ return (a.ceiling().inclusive() || b.floor().inclusive()) &&
+ a.ceiling().version.Compare(*b.floor().version) == 0
+ }
+
+ return f(a, b) || f(b, a)
+}
diff --git a/compact_example_test.go b/compact_example_test.go
new file mode 100644
index 0000000..496d20e
--- /dev/null
+++ b/compact_example_test.go
@@ -0,0 +1,87 @@
+package comver_test
+
+import (
+ "fmt"
+
+ "github.com/typisttech/comver"
+)
+
+func ExampleCompact() {
+ o := comver.Or{
+ comver.MustAnd(
+ comver.NewLessThan(comver.MustParse("2")),
+ comver.NewGreaterThan(comver.MustParse("1")),
+ ),
+ comver.MustAnd(
+ comver.NewLessThan(comver.MustParse("5")),
+ comver.NewGreaterThan(comver.MustParse("3")),
+ ),
+ comver.MustAnd(
+ comver.NewLessThan(comver.MustParse("6")),
+ comver.NewGreaterThan(comver.MustParse("4")),
+ ),
+ }
+
+ c := comver.Compact(o)
+
+ fmt.Println("Before:", o)
+ fmt.Println("After:", c)
+
+ // Output:
+ // Before: >1 <2 || >3 <5 || >4 <6
+ // After: >1 <2 || >3 <6
+}
+
+func ExampleCompact_endless() {
+ o := comver.Or{
+ comver.MustAnd(
+ comver.NewLessThan(comver.MustParse("5")),
+ comver.NewGreaterThan(comver.MustParse("3")),
+ ),
+ comver.NewGreaterThan(comver.MustParse("4")),
+ }
+
+ c := comver.Compact(o)
+
+ fmt.Println("Before:", o)
+ fmt.Println("After:", c)
+
+ // Output:
+ // Before: >3 <5 || >4
+ // After: >3
+}
+
+func ExampleCompact_wildcard() {
+ o := comver.Or{
+ comver.NewLessThan(comver.MustParse("3")),
+ comver.NewGreaterThan(comver.MustParse("2")),
+ }
+
+ c := comver.Compact(o)
+
+ fmt.Println("Before:", o)
+ fmt.Println("After:", c)
+
+ // Output:
+ // Before: <3 || >2
+ // After: *
+}
+
+func ExampleCompact_wildcardTrumps() {
+ o := comver.Or{
+ comver.MustAnd(
+ comver.NewLessThan(comver.MustParse("2")),
+ comver.NewGreaterThan(comver.MustParse("1")),
+ ),
+ comver.NewWildcard(),
+ }
+
+ c := comver.Compact(o)
+
+ fmt.Println("Before:", o)
+ fmt.Println("After:", c)
+
+ // Output:
+ // Before: >1 <2 || *
+ // After: *
+}
diff --git a/compact_test.go b/compact_test.go
new file mode 100644
index 0000000..260d3aa
--- /dev/null
+++ b/compact_test.go
@@ -0,0 +1,1583 @@
+package comver
+
+import (
+ "math/rand/v2"
+ "reflect"
+ "slices"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+)
+
+func TestCompact(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ o Or
+ want Constrainter
+ }{
+ {
+ name: "empty",
+ o: Or{},
+ want: Or{},
+ },
+ {
+ name: "nil",
+ o: nil,
+ want: Or{},
+ },
+ {
+ name: "single_lessThan",
+ o: Or{NewLessThan(MustParse("1"))},
+ want: NewLessThan(MustParse("1")),
+ },
+ {
+ name: "single_lessThanOrEqualTo",
+ o: Or{NewLessThanOrEqualTo(MustParse("2"))},
+ want: NewLessThanOrEqualTo(MustParse("2")),
+ },
+ {
+ name: "single_greaterThan",
+ o: Or{NewGreaterThan(MustParse("3"))},
+ want: NewGreaterThan(MustParse("3")),
+ },
+ {
+ name: "single_greaterThanOrEqualTo",
+ o: Or{NewGreaterThanOrEqualTo(MustParse("4"))},
+ want: NewGreaterThanOrEqualTo(MustParse("4")),
+ },
+ {
+ name: "single_exact",
+ o: Or{NewExactConstraint(MustParse("5"))},
+ want: NewExactConstraint(MustParse("5")),
+ },
+ {
+ name: "single_interval",
+ o: Or{
+ interval{
+ upper: NewLessThan(MustParse("7")),
+ lower: NewGreaterThan(MustParse("6")),
+ },
+ },
+ want: interval{
+ upper: NewLessThan(MustParse("7")),
+ lower: NewGreaterThan(MustParse("6")),
+ },
+ },
+ {
+ name: "single_wildcard",
+ o: Or{NewWildcard()},
+ want: NewWildcard(),
+ },
+ {
+ name: "multiple_wildcards",
+ o: Or{NewWildcard(), NewWildcard()},
+ want: NewWildcard(),
+ },
+ {
+ name: "wildcard_trumps_everything_else",
+ o: Or{
+ NewLessThan(MustParse("1")),
+ NewLessThanOrEqualTo(MustParse("1")),
+ NewLessThan(MustParse("2")),
+ NewLessThanOrEqualTo(MustParse("2")),
+ NewGreaterThanOrEqualTo(MustParse("3")),
+ NewGreaterThan(MustParse("3")),
+ NewGreaterThanOrEqualTo(MustParse("4")),
+ NewGreaterThan(MustParse("4")),
+ NewExactConstraint(MustParse("5")),
+ interval{
+ upper: NewLessThan(MustParse("7")),
+ lower: NewGreaterThan(MustParse("6")),
+ },
+ NewWildcard(),
+ },
+ want: NewWildcard(),
+ },
+ {
+ name: "match_all",
+ o: Or{
+ NewLessThan(MustParse("10")),
+ NewGreaterThan(MustParse("9")),
+ },
+ want: NewWildcard(),
+ },
+ {
+ name: "match_all_same_version_ceiling_inclusive_floor_inclusive",
+ o: Or{
+ NewLessThanOrEqualTo(MustParse("10")),
+ NewGreaterThanOrEqualTo(MustParse("10")),
+ },
+ want: NewWildcard(),
+ },
+ {
+ name: "match_all_same_version_ceiling_non_inclusive_floor_inclusive",
+ o: Or{
+ NewLessThan(MustParse("10")),
+ NewGreaterThanOrEqualTo(MustParse("10")),
+ },
+ want: NewWildcard(),
+ },
+ {
+ name: "match_all_same_version_ceiling_inclusive_floor_non_inclusive",
+ o: Or{
+ NewLessThanOrEqualTo(MustParse("10")),
+ NewGreaterThan(MustParse("10")),
+ },
+ want: NewWildcard(),
+ },
+ {
+ name: "same_version_not_match_all",
+ o: Or{
+ NewGreaterThan(MustParse("10")),
+ NewLessThan(MustParse("10")),
+ },
+ want: Or{
+ NewLessThan(MustParse("10")),
+ NewGreaterThan(MustParse("10")),
+ },
+ },
+ {
+ name: "unrelated_intervals",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("14")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("17")),
+ upper: NewLessThan(MustParse("20")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("23")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("14")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("17")),
+ upper: NewLessThan(MustParse("20")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("23")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ },
+ {
+ name: "overlapping_intervals",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("18")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("17")),
+ upper: NewLessThan(MustParse("20")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("23")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("20")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("23")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ },
+ {
+ name: "overlapping_intervals",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("14")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("13")),
+ upper: NewLessThan(MustParse("20")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("23")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("20")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("23")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ },
+ {
+ name: "overlapping_intervals",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("14")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("17")),
+ upper: NewLessThan(MustParse("24")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("23")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("14")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("17")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ },
+ {
+ name: "overlapping_intervals",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("14")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("17")),
+ upper: NewLessThan(MustParse("20")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("19")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("14")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("17")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ },
+ {
+ name: "overlapping_intervals",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("25")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("17")),
+ upper: NewLessThan(MustParse("20")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("19")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ want: interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ {
+ name: "overlapping_intervals",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("14")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("17")),
+ upper: NewLessThan(MustParse("20")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("12")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ want: interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ {
+ name: "overlapping_intervals",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("20")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("17")),
+ upper: NewLessThan(MustParse("20")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("19")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ want: interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ {
+ name: "continuous_intervals",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThanOrEqualTo(MustParse("17")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("17")),
+ upper: NewLessThan(MustParse("20")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("23")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("20")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("23")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ },
+ {
+ name: "continuous_intervals",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ interval{
+ lower: NewGreaterThanOrEqualTo(MustParse("17")),
+ upper: NewLessThan(MustParse("20")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("23")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("20")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("23")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ },
+ {
+ name: "continuous_intervals",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("14")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("17")),
+ upper: NewLessThanOrEqualTo(MustParse("23")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("23")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("14")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("17")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ },
+ {
+ name: "continuous_intervals",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("14")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("17")),
+ upper: NewLessThanOrEqualTo(MustParse("23")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("23")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("14")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("17")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ },
+ {
+ name: "continuous_intervals",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("14")),
+ },
+ interval{
+ lower: NewGreaterThanOrEqualTo(MustParse("14")),
+ upper: NewLessThanOrEqualTo(MustParse("23")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("23")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ want: interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("26")),
+ },
+ },
+ {
+ name: "tail_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThanOrEqualTo(MustParse("18")),
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThanOrEqualTo(MustParse("18")),
+ },
+ },
+ {
+ name: "tail_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThanOrEqualTo(MustParse("17")),
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ NewGreaterThan(MustParse("15")),
+ },
+ },
+ {
+ name: "tail_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThanOrEqualTo(MustParse("17")),
+ },
+ NewGreaterThanOrEqualTo(MustParse("17")),
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ NewGreaterThan(MustParse("15")),
+ },
+ },
+ {
+ name: "tail_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThanOrEqualTo(MustParse("16")),
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ NewGreaterThan(MustParse("15")),
+ },
+ },
+ {
+ name: "tail_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThanOrEqualTo(MustParse("15")),
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ NewGreaterThanOrEqualTo(MustParse("15")),
+ },
+ },
+ {
+ name: "tail_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThanOrEqualTo(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThanOrEqualTo(MustParse("15")),
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ NewGreaterThanOrEqualTo(MustParse("15")),
+ },
+ },
+ {
+ name: "tail_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThanOrEqualTo(MustParse("14")),
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ NewGreaterThanOrEqualTo(MustParse("14")),
+ },
+ },
+ {
+ name: "tail_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThanOrEqualTo(MustParse("13")),
+ },
+ want: NewGreaterThan(MustParse("11")),
+ },
+ {
+ name: "tail_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThanOrEqualTo(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThanOrEqualTo(MustParse("13")),
+ },
+ want: NewGreaterThan(MustParse("11")),
+ },
+ {
+ name: "tail_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThanOrEqualTo(MustParse("12")),
+ },
+ want: NewGreaterThan(MustParse("11")),
+ },
+ {
+ name: "tail_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThanOrEqualTo(MustParse("11")),
+ },
+ want: NewGreaterThanOrEqualTo(MustParse("11")),
+ },
+ {
+ name: "tail_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThanOrEqualTo(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThanOrEqualTo(MustParse("11")),
+ },
+ want: NewGreaterThanOrEqualTo(MustParse("11")),
+ },
+ {
+ name: "tail_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThanOrEqualTo(MustParse("10")),
+ },
+ want: NewGreaterThanOrEqualTo(MustParse("10")),
+ },
+
+ {
+ name: "tail_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThan(MustParse("18")),
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThan(MustParse("18")),
+ },
+ },
+ {
+ name: "tail_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThan(MustParse("17")),
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThan(MustParse("17")),
+ },
+ },
+ {
+ name: "tail_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThanOrEqualTo(MustParse("17")),
+ },
+ NewGreaterThan(MustParse("17")),
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ NewGreaterThan(MustParse("15")),
+ },
+ },
+ {
+ name: "tail_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThan(MustParse("16")),
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ NewGreaterThan(MustParse("15")),
+ },
+ },
+ {
+ name: "tail_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThan(MustParse("15")),
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ NewGreaterThan(MustParse("15")),
+ },
+ },
+ {
+ name: "tail_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThanOrEqualTo(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThan(MustParse("15")),
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ NewGreaterThanOrEqualTo(MustParse("15")),
+ },
+ },
+ {
+ name: "tail_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThan(MustParse("14")),
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ NewGreaterThan(MustParse("14")),
+ },
+ },
+ {
+ name: "tail_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThan(MustParse("13")),
+ },
+ want: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ NewGreaterThan(MustParse("13")),
+ },
+ },
+ {
+ name: "tail_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThanOrEqualTo(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThan(MustParse("13")),
+ },
+ want: NewGreaterThan(MustParse("11")),
+ },
+ {
+ name: "tail_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThan(MustParse("12")),
+ },
+ want: NewGreaterThan(MustParse("11")),
+ },
+ {
+ name: "tail_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThan(MustParse("11")),
+ },
+ want: NewGreaterThan(MustParse("11")),
+ },
+ {
+ name: "tail_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThanOrEqualTo(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThan(MustParse("11")),
+ },
+ want: NewGreaterThanOrEqualTo(MustParse("11")),
+ },
+ {
+ name: "tail_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewGreaterThan(MustParse("10")),
+ },
+ want: NewGreaterThan(MustParse("10")),
+ },
+ {
+ name: "head_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThan(MustParse("18")),
+ },
+ want: NewLessThan(MustParse("18")),
+ },
+ {
+ name: "head_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThan(MustParse("17")),
+ },
+ want: NewLessThan(MustParse("17")),
+ },
+ {
+ name: "head_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThanOrEqualTo(MustParse("17")),
+ },
+ NewLessThan(MustParse("17")),
+ },
+ want: NewLessThanOrEqualTo(MustParse("17")),
+ },
+ {
+ name: "head_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThan(MustParse("16")),
+ },
+ want: NewLessThan(MustParse("17")),
+ },
+ {
+ name: "head_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThan(MustParse("15")),
+ },
+ want: Or{
+ NewLessThan(MustParse("15")),
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ },
+ },
+ {
+ name: "head_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThanOrEqualTo(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThan(MustParse("15")),
+ },
+ want: NewLessThan(MustParse("17")),
+ },
+ {
+ name: "head_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThan(MustParse("14")),
+ },
+ want: Or{
+ NewLessThan(MustParse("14")),
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ },
+ },
+ {
+ name: "head_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThan(MustParse("13")),
+ },
+ want: Or{
+ NewLessThan(MustParse("13")),
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ },
+ },
+ {
+ name: "head_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThanOrEqualTo(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThan(MustParse("13")),
+ },
+ want: Or{
+ NewLessThanOrEqualTo(MustParse("13")),
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ },
+ },
+ {
+ name: "head_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThan(MustParse("12")),
+ },
+ want: Or{
+ NewLessThan(MustParse("13")),
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ },
+ },
+ {
+ name: "head_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThan(MustParse("11")),
+ },
+ want: Or{
+ NewLessThan(MustParse("11")),
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ },
+ },
+ {
+ name: "head_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThanOrEqualTo(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThan(MustParse("11")),
+ },
+ want: Or{
+ NewLessThan(MustParse("13")),
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ },
+ },
+ {
+ name: "head_non_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThan(MustParse("10")),
+ },
+ want: Or{
+ NewLessThan(MustParse("10")),
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ },
+ },
+ {
+ name: "head_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThanOrEqualTo(MustParse("18")),
+ },
+ want: NewLessThanOrEqualTo(MustParse("18")),
+ },
+ {
+ name: "head_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThanOrEqualTo(MustParse("17")),
+ },
+ want: NewLessThanOrEqualTo(MustParse("17")),
+ },
+ {
+ name: "head_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThanOrEqualTo(MustParse("17")),
+ },
+ NewLessThanOrEqualTo(MustParse("17")),
+ },
+ want: NewLessThanOrEqualTo(MustParse("17")),
+ },
+ {
+ name: "head_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThanOrEqualTo(MustParse("16")),
+ },
+ want: NewLessThan(MustParse("17")),
+ },
+ {
+ name: "head_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThanOrEqualTo(MustParse("15")),
+ },
+ want: NewLessThan(MustParse("17")),
+ },
+ {
+ name: "head_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThanOrEqualTo(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThanOrEqualTo(MustParse("15")),
+ },
+ want: NewLessThan(MustParse("17")),
+ },
+ {
+ name: "head_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThanOrEqualTo(MustParse("14")),
+ },
+ want: Or{
+ NewLessThanOrEqualTo(MustParse("14")),
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ },
+ },
+ {
+ name: "head_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThanOrEqualTo(MustParse("13")),
+ },
+ want: Or{
+ NewLessThanOrEqualTo(MustParse("13")),
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ },
+ },
+ {
+ name: "head_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThanOrEqualTo(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThanOrEqualTo(MustParse("13")),
+ },
+ want: Or{
+ NewLessThanOrEqualTo(MustParse("13")),
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ },
+ },
+ {
+ name: "head_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThanOrEqualTo(MustParse("12")),
+ },
+ want: Or{
+ NewLessThan(MustParse("13")),
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ },
+ },
+ {
+ name: "head_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThanOrEqualTo(MustParse("11")),
+ },
+ want: Or{
+ NewLessThan(MustParse("13")),
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ },
+ },
+ {
+ name: "head_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThanOrEqualTo(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThanOrEqualTo(MustParse("11")),
+ },
+ want: Or{
+ NewLessThan(MustParse("13")),
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ },
+ },
+ {
+ name: "head_inclusive",
+ o: Or{
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ NewLessThanOrEqualTo(MustParse("10")),
+ },
+ want: Or{
+ NewLessThanOrEqualTo(MustParse("10")),
+ interval{
+ lower: NewGreaterThan(MustParse("11")),
+ upper: NewLessThan(MustParse("13")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("15")),
+ upper: NewLessThan(MustParse("17")),
+ },
+ },
+ },
+ {
+ name: "match_all_with_interval",
+ o: Or{
+ NewGreaterThan(MustParse("1")),
+ interval{
+ lower: NewGreaterThan(MustParse("2")),
+ upper: NewLessThan(MustParse("3")),
+ },
+ NewLessThan(MustParse("4")),
+ },
+ want: NewWildcard(),
+ },
+ {
+ name: "match_all_within_interval",
+ o: Or{
+ NewGreaterThan(MustParse("2")),
+ interval{
+ lower: NewGreaterThan(MustParse("1")),
+ upper: NewLessThan(MustParse("4")),
+ },
+ NewLessThan(MustParse("3")),
+ },
+ want: NewWildcard(),
+ },
+ {
+ name: "match_all_within_intervals",
+ o: Or{
+ NewLessThan(MustParse("3")),
+ interval{
+ lower: NewGreaterThan(MustParse("2")),
+ upper: NewLessThan(MustParse("6")),
+ },
+ interval{
+ lower: NewGreaterThan(MustParse("5")),
+ upper: NewLessThan(MustParse("8")),
+ },
+ NewGreaterThan(MustParse("7")),
+ },
+ want: NewWildcard(),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ rand.Shuffle(len(tt.o), func(i, j int) {
+ tt.o[i], tt.o[j] = tt.o[j], tt.o[i]
+ })
+
+ original := slices.Clone(tt.o)
+
+ got := Compact(tt.o)
+
+ opts := cmp.Options{
+ cmp.Comparer(func(a, b Constrainter) bool {
+ return a.String() == b.String()
+ }),
+ }
+
+ if diff := cmp.Diff(tt.want, got, opts); diff != "" {
+ t.Errorf("Compact(%q) mismatch (-want +got):\n%s", original, diff)
+ }
+
+ if !reflect.DeepEqual(tt.o, original) {
+ t.Errorf("Compact() changed the original slice got = %v, want %v", tt.o, original)
+ }
+ })
+ }
+}
+
+func Test_wildcard(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ c CeilingFloorConstrainter
+ want bool
+ }{
+ {
+ name: "lessThan",
+ c: NewLessThan(MustParse("1")),
+ want: false,
+ },
+ {
+ name: "lessThanOrEqualTo",
+ c: NewLessThanOrEqualTo(MustParse("2")),
+ want: false,
+ },
+ {
+ name: "greaterThan",
+ c: NewGreaterThan(MustParse("3")),
+ want: false,
+ },
+ {
+ name: "greaterThanOrEqualTo",
+ c: NewGreaterThanOrEqualTo(MustParse("4")),
+ want: false,
+ },
+ {
+ name: "exact",
+ c: NewExactConstraint(MustParse("5")),
+ want: false,
+ },
+ {
+ name: "interval",
+ c: interval{
+ upper: NewLessThan(MustParse("7")),
+ lower: NewGreaterThan(MustParse("6")),
+ },
+ want: false,
+ },
+ {
+ name: "wildcard",
+ c: NewWildcard(),
+ want: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ if got := wildcard(tt.c); got != tt.want {
+ t.Errorf("wildcard() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/constraint.go b/constraint.go
index 9f3eacb..43228fe 100644
--- a/constraint.go
+++ b/constraint.go
@@ -1,140 +1,14 @@
package comver
-const (
- greaterThanOrEqualTo constraintOp = iota
- greaterThan
- lessThan
- lessThanOrEqualTo
-
- ErrUnexpectedConstraintOp stringError = "unexpected constraintOp"
-)
-
-type constraintOp int8
-
-func (op constraintOp) compare(other constraintOp) int {
- return int(op) - int(other)
-}
-
-func (op constraintOp) ceillingless() bool {
- return op == greaterThan || op == greaterThanOrEqualTo
-}
-
-func (op constraintOp) floorless() bool {
- return op == lessThanOrEqualTo || op == lessThan
-}
-
-func (op constraintOp) String() string {
- switch op {
- case lessThanOrEqualTo:
- return "<="
- case lessThan:
- return "<"
- case greaterThan:
- return ">"
- case greaterThanOrEqualTo:
- return ">="
- default:
- return ErrUnexpectedConstraintOp.Error()
- }
-}
-
-type constraint struct {
- // The Version used in the constraint check, e.g.: the Version representing 1.2.3 in '<=1.2.3'.
- version Version
- op constraintOp
-}
-
-func NewLessThanOrEqualToConstraint(v Version) *constraint {
- return &constraint{
- version: v,
- op: lessThanOrEqualTo,
- }
-}
-
-func NewLessThanConstraint(v Version) *constraint {
- return &constraint{
- version: v,
- op: lessThan,
- }
-}
-
-func NewGreaterThanConstraint(v Version) *constraint {
- return &constraint{
- version: v,
- op: greaterThan,
- }
-}
-
-func NewGreaterThanOrEqualToConstraint(v Version) *constraint {
- return &constraint{
- version: v,
- op: greaterThanOrEqualTo,
- }
-}
-
-func (c *constraint) lowerbounded() bool {
- return c.op == greaterThan || c.op == greaterThanOrEqualTo
-}
-
-func (c *constraint) upperbounded() bool {
- return c.op == lessThanOrEqualTo || c.op == lessThan
-}
-
-// Check reports whether a [Version] satisfies the constraint.
-func (c *constraint) Check(v Version) bool {
- if c == nil {
- // this should never happen
- return true
- }
-
- cmp := v.Compare(c.version)
-
- switch c.op {
- case lessThan:
- return cmp < 0
- case lessThanOrEqualTo:
- return cmp <= 0
- case greaterThanOrEqualTo:
- return cmp >= 0
- case greaterThan:
- return cmp > 0
- default:
- // this should never happen
- return true
- }
-}
-
-func (c *constraint) String() string {
- return c.op.String() + c.version.Short()
+type Constrainter interface {
+ // Check reports whether a [Version] satisfies the constraint.
+ Check(v Version) bool
+ String() string
}
-// compare returns an integer comparing two constraints.
-//
-// The comparison is done by comparing the version first, then the operator.
-// - Versions are compared according to their semantic precedence
-// - Operators are compared in the following order (lowest to highest): >=, >, <, <=
-// - nil is considered to be the higher than upperbounded constraints
-// - nil is considered to be the lower than lowerbounded constraints
-//
-// The result is 0 when c == d, -1 when c < d, or +1 when c > d.
-func (c *constraint) compare(d *constraint) int {
- switch {
- case c == nil && d == nil:
- return 0
- case c == nil:
- if d.lowerbounded() {
- return -1
- }
- return +1
- case d == nil:
- if c.lowerbounded() {
- return +1
- }
- return -1
- }
+type CeilingFloorConstrainter interface {
+ ceiling() Endless
+ floor() Endless
- if cmp := c.version.Compare(d.version); cmp != 0 {
- return cmp
- }
- return c.op.compare(d.op)
+ Constrainter
}
diff --git a/constraint_test.go b/constraint_test.go
deleted file mode 100644
index d2fcc21..0000000
--- a/constraint_test.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package comver
-
-import "testing"
-
-func Test_constraint_Check(t *testing.T) {
- t.Parallel()
-
- tests := []struct {
- name string
- constraint *constraint
- version Version
- want bool
- }{
- {
- name: "lessThan satisfied",
- constraint: &constraint{version: Version{major: 2}, op: lessThan},
- version: Version{major: 1},
- want: true,
- },
- {
- name: "lessThan just satisfied",
- constraint: &constraint{version: Version{major: 2}, op: lessThan},
- version: Version{major: 2, modifier: modifierRC},
- want: true,
- },
- {
- name: "lessThan just not satisfied",
- constraint: &constraint{version: Version{major: 2}, op: lessThan},
- version: Version{major: 2},
- want: false,
- },
- {
- name: "lessThan not satisfied",
- constraint: &constraint{version: Version{major: 2}, op: lessThan},
- version: Version{major: 3},
- want: false,
- },
-
- {
- name: "lessThanOrEqualTo satisfied",
- constraint: &constraint{version: Version{major: 2}, op: lessThanOrEqualTo},
- version: Version{major: 1},
- want: true,
- },
- {
- name: "lessThanOrEqualTo just satisfied",
- constraint: &constraint{version: Version{major: 2}, op: lessThanOrEqualTo},
- version: Version{major: 2},
- want: true,
- },
- {
- name: "lessThanOrEqualTo just not satisfied",
- constraint: &constraint{version: Version{major: 2}, op: lessThanOrEqualTo},
- version: Version{major: 2, modifier: modifierPatch},
- want: false,
- },
- {
- name: "lessThanOrEqualTo not satisfied",
- constraint: &constraint{version: Version{major: 2}, op: lessThanOrEqualTo},
- version: Version{major: 3},
- want: false,
- },
-
- {
- name: "greaterThan satisfied",
- constraint: &constraint{version: Version{major: 2}, op: greaterThan},
- version: Version{major: 3},
- want: true,
- },
- {
- name: "greaterThan just satisfied",
- constraint: &constraint{version: Version{major: 2}, op: greaterThan},
- version: Version{major: 2, modifier: modifierPatch},
- want: true,
- },
- {
- name: "greaterThan just not satisfied",
- constraint: &constraint{version: Version{major: 2}, op: greaterThan},
- version: Version{major: 2},
- want: false,
- },
- {
- name: "greaterThan not satisfied",
- constraint: &constraint{version: Version{major: 2}, op: greaterThan},
- version: Version{major: 1},
- want: false,
- },
-
- {
- name: "greaterThanOrEqualTo satisfied",
- constraint: &constraint{version: Version{major: 2}, op: greaterThanOrEqualTo},
- version: Version{major: 3},
- want: true,
- },
- {
- name: "greaterThanOrEqualTo just satisfied",
- constraint: &constraint{version: Version{major: 2}, op: greaterThanOrEqualTo},
- version: Version{major: 2},
- want: true,
- },
- {
- name: "greaterThanOrEqualTo just not satisfied",
- constraint: &constraint{version: Version{major: 2}, op: greaterThanOrEqualTo},
- version: Version{major: 2, modifier: modifierRC},
- want: false,
- },
- {
- name: "greaterThanOrEqualTo not satisfied",
- constraint: &constraint{version: Version{major: 2}, op: greaterThanOrEqualTo},
- version: Version{major: 1},
- want: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- t.Parallel()
-
- if got := tt.constraint.Check(tt.version); got != tt.want {
- t.Errorf("Check() = %v, want %v", got, tt.want)
- }
- })
- }
-}
diff --git a/doc_examle_test.go b/doc_examle_test.go
deleted file mode 100644
index e265409..0000000
--- a/doc_examle_test.go
+++ /dev/null
@@ -1,115 +0,0 @@
-package comver_test
-
-import (
- "fmt"
-
- "github.com/typisttech/comver"
-)
-
-func Example_version() {
- ss := []string{
- "1.2.3",
- "v1.2.p5+foo",
- "v1.2.3.4.p5+foo",
- "2010-01-02",
- "2010-01-02.5",
- "not a version",
- "1.0.0-meh",
- "20100102.0.3.4",
- "1.0.0-alpha.beta",
- }
-
- for _, s := range ss {
- v, err := comver.NewVersion(s)
- if err != nil {
- fmt.Println(s, " => ", err)
- continue
- }
- fmt.Println(s, " => ", v)
- }
-
- // Output:
- // 1.2.3 => 1.2.3.0
- // v1.2.p5+foo => 1.2.0.0-patch5
- // v1.2.3.4.p5+foo => 1.2.3.4-patch5
- // 2010-01-02 => 2010.1.2.0
- // 2010-01-02.5 => 2010.1.2.5
- // not a version => error parsing version string "not a version"
- // 1.0.0-meh => error parsing version string "1.0.0-meh"
- // 20100102.0.3.4 => error parsing version string "20100102.0.3.4"
- // 1.0.0-alpha.beta => error parsing version string "1.0.0-alpha.beta"
-}
-
-func Example_constraint() {
- v1, _ := comver.NewVersion("1")
- v2, _ := comver.NewVersion("2")
- v3, _ := comver.NewVersion("3")
- v4, _ := comver.NewVersion("4")
-
- cs := []any{
- comver.NewGreaterThanConstraint(v1),
- comver.NewGreaterThanOrEqualToConstraint(v2),
- comver.NewLessThanOrEqualToConstraint(v3),
- comver.NewLessThanConstraint(v4),
- }
-
- for _, c := range cs {
- fmt.Println(c)
- }
-
- // Output:
- // >1
- // >=2
- // <=3
- // <4
-}
-
-func Example_interval() {
- v1, _ := comver.NewVersion("1")
- v2, _ := comver.NewVersion("2")
- v3, _ := comver.NewVersion("3")
-
- g1l3, _ := comver.NewInterval(
- comver.NewGreaterThanConstraint(v1),
- comver.NewLessThanConstraint(v3),
- )
-
- if g1l3.Check(v2) {
- fmt.Println(v2.Short(), "satisfies", g1l3)
- }
-
- if !g1l3.Check(v3) {
- fmt.Println(v2.Short(), "doesn't satisfy", g1l3)
- }
-
- // Output:
- // 2 satisfies >1 <3
- // 2 doesn't satisfy >1 <3
-}
-
-func Example_intervals() {
- v1, _ := comver.NewVersion("1")
- v2, _ := comver.NewVersion("2")
- v3, _ := comver.NewVersion("3")
- v4, _ := comver.NewVersion("4")
-
- g1l3, _ := comver.NewInterval(
- comver.NewGreaterThanConstraint(v1),
- comver.NewLessThanConstraint(v3),
- )
-
- ge2le4, _ := comver.NewInterval(
- comver.NewGreaterThanOrEqualToConstraint(v2),
- comver.NewLessThanOrEqualToConstraint(v4),
- )
-
- is := comver.Intervals{g1l3, ge2le4}
- fmt.Println(is)
-
- is = comver.Compact(is)
- fmt.Println(is)
-
- // Output:
- // >1 <3 || >=2 <=4
- // >1 <=4
-}
diff --git a/doc_example_test.go b/doc_example_test.go
new file mode 100644
index 0000000..6679738
--- /dev/null
+++ b/doc_example_test.go
@@ -0,0 +1,60 @@
+package comver_test
+
+import (
+ "fmt"
+
+ "github.com/typisttech/comver"
+)
+
+func Example_version() {
+ ss := []string{
+ "1.2.3",
+ "1.2",
+ "1",
+
+ " 1.0.0",
+ "00.01.03.04",
+
+ "2010-01-02.5",
+ "2010-01-02",
+
+ "v1.2.3.4-beta.5+foo",
+ "v1.2.3.4.p5+foo",
+ "v1.2.3",
+ "v1.2.p5+foo",
+
+ "not a version",
+ "1.0.0-alpha.beta",
+ "1.0.0-meh",
+ "1.0.0.0.0",
+ "20100102.0.3.4",
+ }
+
+ for _, s := range ss {
+ v, err := comver.Parse(s)
+ if err != nil {
+ fmt.Printf("%-21q => %v\n", s, err)
+
+ continue
+ }
+ fmt.Printf("%-21q => %v\n", s, v)
+ }
+
+ // Output:
+ // "1.2.3" => 1.2.3.0
+ // "1.2" => 1.2.0.0
+ // "1" => 1.0.0.0
+ // " 1.0.0" => 1.0.0.0
+ // "00.01.03.04" => 0.1.3.4
+ // "2010-01-02.5" => 2010.1.2.5
+ // "2010-01-02" => 2010.1.2.0
+ // "v1.2.3.4-beta.5+foo" => 1.2.3.4-beta5
+ // "v1.2.3.4.p5+foo" => 1.2.3.4-patch5
+ // "v1.2.3" => 1.2.3.0
+ // "v1.2.p5+foo" => 1.2.0.0-patch5
+ // "not a version" => error parsing version string "not a version"
+ // "1.0.0-alpha.beta" => error parsing version string "1.0.0-alpha.beta"
+ // "1.0.0-meh" => error parsing version string "1.0.0-meh"
+ // "1.0.0.0.0" => error parsing version string "1.0.0.0.0"
+ // "20100102.0.3.4" => error parsing version string "20100102.0.3.4"
+}
diff --git a/endless.go b/endless.go
new file mode 100644
index 0000000..afcfa9e
--- /dev/null
+++ b/endless.go
@@ -0,0 +1,148 @@
+package comver
+
+// Endless represents a constraint that is either floor bounded, ceiling bounded,
+// or wildcard (satisfied by any version).
+// The zero value for Endless is a wildcard constraint (satisfied by any version).
+type Endless struct {
+ // The version used in the constraint check,
+ // e.g.: the version representing 1.2.3 in '<=1.2.3'.
+ // If nil, the Endless is a wildcard satisfied by any version.
+ version *Version
+ op op
+}
+
+func NewLessThanOrEqualTo(v Version) Endless {
+ return Endless{
+ version: &v,
+ op: lessThanOrEqualTo,
+ }
+}
+
+func NewLessThan(v Version) Endless {
+ return Endless{
+ version: &v,
+ op: lessThan,
+ }
+}
+
+func NewGreaterThan(v Version) Endless {
+ return Endless{
+ version: &v,
+ op: greaterThan,
+ }
+}
+
+func NewGreaterThanOrEqualTo(v Version) Endless {
+ return Endless{
+ version: &v,
+ op: greaterThanOrEqualTo,
+ }
+}
+
+func NewWildcard() Endless {
+ return Endless{ //nolint:exhaustruct
+ version: nil,
+ }
+}
+
+// Check reports whether a [Version] satisfies the constraint.
+func (b Endless) Check(v Version) bool {
+ if b.version == nil {
+ // this is wildcard, match all versions
+ return true
+ }
+
+ cmp := b.version.Compare(v)
+
+ switch b.op {
+ case lessThan:
+ return cmp > 0
+ case lessThanOrEqualTo:
+ return cmp >= 0
+ case greaterThanOrEqualTo:
+ return cmp <= 0
+ case greaterThan:
+ return cmp < 0
+ default:
+ // this should never happen
+ panic("unexpected constraint operator")
+ }
+}
+
+func (b Endless) ceiling() Endless {
+ if !b.ceilingBounded() {
+ return NewWildcard()
+ }
+
+ return b
+}
+
+func (b Endless) floor() Endless {
+ if !b.floorBounded() {
+ return NewWildcard()
+ }
+
+ return b
+}
+
+func (b Endless) String() string {
+ if b.wildcard() {
+ return "*"
+ }
+
+ return b.op.String() + b.version.Short()
+}
+
+func (b Endless) wildcard() bool {
+ return b.version == nil
+}
+
+// compare returns an integer comparing two [Endless] instances.
+//
+// The comparison is done by comparing the version first, then the operator.
+// - Versions are compared according to their semantic precedence
+// - Operators are compared in the following order (lowest to highest): >=, >, <, <=
+// - wildcard [Endless] is considered to be higher than ceiling bounded [Endless] while
+// floor than floor bounded [Endless]
+//
+// The result is 0 when b == d, -1 when b < d, or +1 when b > d.
+func (b Endless) compare(d Endless) int {
+ switch {
+ case b.wildcard() && d.wildcard():
+ return 0
+ case b.wildcard():
+ if d.floorBounded() {
+ return -1
+ }
+
+ return +1
+ case d.wildcard():
+ if b.floorBounded() {
+ return +1
+ }
+
+ return -1
+ }
+
+ if cmp := b.versionCompare(d.version); cmp != 0 {
+ return cmp
+ }
+
+ return b.op.compare(d.op)
+}
+
+func (b Endless) ceilingBounded() bool {
+ return b.op.ceilingBounded()
+}
+
+func (b Endless) floorBounded() bool {
+ return b.op.floorBounded()
+}
+
+func (b Endless) inclusive() bool {
+ return b.op.inclusive()
+}
+
+func (b Endless) versionCompare(v *Version) int {
+ return b.version.Compare(*v)
+}
diff --git a/endless_test.go b/endless_test.go
new file mode 100644
index 0000000..32ff838
--- /dev/null
+++ b/endless_test.go
@@ -0,0 +1,309 @@
+package comver
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestEndless_Check(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ endless Endless
+ version Version
+ want bool
+ }{
+ {
+ name: "lessThan_satisfied",
+ endless: NewLessThan(MustParse("2")),
+ version: MustParse("1"),
+ want: true,
+ },
+ {
+ name: "lessThan_just_satisfied",
+ endless: NewLessThan(MustParse("2")),
+ version: MustParse("2.rc"),
+ want: true,
+ },
+ {
+ name: "lessThan_just_not_satisfied",
+ endless: NewLessThan(MustParse("2")),
+ version: MustParse("2"),
+ want: false,
+ },
+ {
+ name: "lessThan_not_satisfied",
+ endless: NewLessThan(MustParse("2")),
+ version: MustParse("3"),
+ want: false,
+ },
+
+ {
+ name: "lessThanOrEqualTo_satisfied",
+ endless: NewLessThanOrEqualTo(MustParse("2")),
+ version: MustParse("1"),
+ want: true,
+ },
+ {
+ name: "lessThanOrEqualTo_just_satisfied",
+ endless: NewLessThanOrEqualTo(MustParse("2")),
+ version: MustParse("2"),
+ want: true,
+ },
+ {
+ name: "lessThanOrEqualTo_just_not_satisfied",
+ endless: NewLessThanOrEqualTo(MustParse("2")),
+ version: MustParse("2.patch"),
+ want: false,
+ },
+ {
+ name: "lessThanOrEqualTo_not_satisfied",
+ endless: NewLessThanOrEqualTo(MustParse("2")),
+ version: MustParse("3"),
+ want: false,
+ },
+
+ {
+ name: "greaterThan_satisfied",
+ endless: NewGreaterThan(MustParse("2")),
+ version: MustParse("3"),
+ want: true,
+ },
+ {
+ name: "greaterThan_just_satisfied",
+ endless: NewGreaterThan(MustParse("2")),
+ version: MustParse("2.patch"),
+ want: true,
+ },
+ {
+ name: "greaterThan_just_not_satisfied",
+ endless: NewGreaterThan(MustParse("2")),
+ version: MustParse("2"),
+ want: false,
+ },
+ {
+ name: "greaterThan_not_satisfied",
+ endless: NewGreaterThan(MustParse("2")),
+ version: MustParse("1"),
+ want: false,
+ },
+
+ {
+ name: "greaterThanOrEqualTo_satisfied",
+ endless: NewGreaterThanOrEqualTo(MustParse("2")),
+ version: MustParse("3"),
+ want: true,
+ },
+ {
+ name: "greaterThanOrEqualTo_just_satisfied",
+ endless: NewGreaterThanOrEqualTo(MustParse("2")),
+ version: MustParse("2"),
+ want: true,
+ },
+ {
+ name: "greaterThanOrEqualTo_just_not_satisfied",
+ endless: NewGreaterThanOrEqualTo(MustParse("2")),
+ version: MustParse("2.rc"),
+ want: false,
+ },
+ {
+ name: "greaterThanOrEqualTo_not_satisfied",
+ endless: NewGreaterThanOrEqualTo(MustParse("2")),
+ version: MustParse("1"),
+ want: false,
+ },
+ {
+ name: "wildcard",
+ endless: NewWildcard(),
+ version: MustParse("1"),
+ want: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ if got := tt.endless.Check(tt.version); got != tt.want {
+ t.Errorf("Check() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestEndless_Ceiling(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ endless Endless
+ want Endless
+ }{
+ {
+ name: "lessThan",
+ endless: NewLessThan(MustParse("1")),
+ want: NewLessThan(MustParse("1")),
+ },
+ {
+ name: "lessThanOrEqualTo",
+ endless: NewLessThanOrEqualTo(MustParse("2")),
+ want: NewLessThanOrEqualTo(MustParse("2")),
+ },
+ {
+ name: "greaterThanOrEqualTo",
+ endless: NewGreaterThanOrEqualTo(MustParse("3")),
+ want: NewWildcard(),
+ },
+ {
+ name: "greaterThan",
+ endless: NewGreaterThan(MustParse("4")),
+ want: NewWildcard(),
+ },
+ {
+ name: "wildcard",
+ endless: NewWildcard(),
+ want: NewWildcard(),
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ if got := tt.endless.ceiling(); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("ceiling() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestEndless_Floor(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ endless Endless
+ want Endless
+ }{
+ {
+ name: "lessThan",
+ endless: NewLessThan(MustParse("1")),
+ want: NewWildcard(),
+ },
+ {
+ name: "lessThanOrEqualTo",
+ endless: NewLessThanOrEqualTo(MustParse("2")),
+ want: NewWildcard(),
+ },
+ {
+ name: "greaterThanOrEqualTo",
+ endless: NewGreaterThanOrEqualTo(MustParse("3")),
+ want: NewGreaterThanOrEqualTo(MustParse("3")),
+ },
+ {
+ name: "greaterThan",
+ endless: NewGreaterThan(MustParse("4")),
+ want: NewGreaterThan(MustParse("4")),
+ },
+ {
+ name: "wildcard",
+ endless: NewWildcard(),
+ want: NewWildcard(),
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ if got := tt.endless.floor(); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("floor() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestEndless_wildcard(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ endless Endless
+ want bool
+ }{
+ {
+ name: "lessThan",
+ endless: NewLessThan(MustParse("1")),
+ want: false,
+ },
+ {
+ name: "lessThanOrEqualTo",
+ endless: NewLessThanOrEqualTo(MustParse("2")),
+ want: false,
+ },
+ {
+ name: "greaterThan",
+ endless: NewGreaterThan(MustParse("3")),
+ want: false,
+ },
+ {
+ name: "greaterThanOrEqualTo",
+ endless: NewGreaterThanOrEqualTo(MustParse("4")),
+ want: false,
+ },
+ {
+ name: "wildcard",
+ endless: NewWildcard(),
+ want: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ if got := tt.endless.wildcard(); got != tt.want {
+ t.Errorf("wildcard() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestEndless_String(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ endless Endless
+ want string
+ }{
+ {
+ endless: NewLessThanOrEqualTo(MustParse("2")),
+ want: "<=2",
+ },
+ {
+ endless: NewLessThan(MustParse("2")),
+ want: "<2",
+ },
+ {
+ endless: NewGreaterThan(MustParse("2")),
+ want: ">2",
+ },
+ {
+ endless: NewGreaterThanOrEqualTo(MustParse("2")),
+ want: ">=2",
+ },
+ {
+ endless: NewWildcard(),
+ want: "*",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.want, func(t *testing.T) {
+ t.Parallel()
+
+ if got := tt.endless.String(); got != tt.want {
+ t.Errorf("String() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/exact.go b/exact.go
new file mode 100644
index 0000000..a45ed9f
--- /dev/null
+++ b/exact.go
@@ -0,0 +1,28 @@
+package comver
+
+type ExactConstraint struct {
+ version Version
+}
+
+func NewExactConstraint(v Version) ExactConstraint {
+ return ExactConstraint{
+ version: v,
+ }
+}
+
+func (e ExactConstraint) ceiling() Endless {
+ return NewLessThanOrEqualTo(e.version)
+}
+
+func (e ExactConstraint) floor() Endless {
+ return NewGreaterThanOrEqualTo(e.version)
+}
+
+// Check reports whether a [Version] satisfies the constraint.
+func (e ExactConstraint) Check(v Version) bool {
+ return e.version.Compare(v) == 0
+}
+
+func (e ExactConstraint) String() string {
+ return e.version.Short()
+}
diff --git a/go.mod b/go.mod
index 1d73b81..39201ba 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,5 @@
module github.com/typisttech/comver
go 1.23
+
+require github.com/google/go-cmp v0.6.0
diff --git a/go.sum b/go.sum
index e69de29..5a8d551 100644
--- a/go.sum
+++ b/go.sum
@@ -0,0 +1,2 @@
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
diff --git a/internal/wordfacetest/version_test.go b/internal/wordfacetest/version_test.go
index c433c93..fbb986c 100644
--- a/internal/wordfacetest/version_test.go
+++ b/internal/wordfacetest/version_test.go
@@ -72,18 +72,18 @@ var invalidWordFenceVersions = []string{ //nolint:gochecknoglobals
}
//go:generate go run gen_wordfence.go gen.go
-func TestNewVersion_Wordfence(t *testing.T) {
+func TestParse_Wordfence(t *testing.T) {
t.Parallel()
for _, v := range wordFenceVersions {
t.Run(v, func(t *testing.T) {
t.Parallel()
- _, err := comver.NewVersion(v)
+ _, err := comver.Parse(v)
wantErr := slices.Contains(invalidWordFenceVersions, v)
if (err != nil) != wantErr {
- t.Fatalf("NewVersion(%q) error = %v, wantErr %v", v, err, wantErr)
+ t.Fatalf("Parse(%q) error = %v, wantErr %v", v, err, wantErr)
}
})
}
diff --git a/internal/wordfacetest/wordfence_test.go b/internal/wordfacetest/wordfence_test.go
index fea167a..ad7eda8 100644
--- a/internal/wordfacetest/wordfence_test.go
+++ b/internal/wordfacetest/wordfence_test.go
@@ -84,6 +84,7 @@ var wordFenceVersions = []string{
"0.16.7",
"0.17.0",
"0.18.3",
+ "0.18.6.2",
"0.18.9",
"0.2",
"0.2.0",
@@ -196,6 +197,7 @@ var wordFenceVersions = []string{
"0.5.70",
"0.5.71",
"0.5.72",
+ "0.5.76",
"0.5.8.1",
"0.5.8.2",
"0.58",
@@ -327,6 +329,7 @@ var wordFenceVersions = []string{
"0.90",
"0.91",
"0.92",
+ "0.92.1",
"0.94",
"0.95",
"0.97",
@@ -390,6 +393,7 @@ var wordFenceVersions = []string{
"1.0.227",
"1.0.228",
"1.0.23",
+ "1.0.231",
"1.0.239",
"1.0.24",
"1.0.240",
@@ -434,6 +438,7 @@ var wordFenceVersions = []string{
"1.0.47",
"1.0.48",
"1.0.49",
+ "1.0.4b",
"1.0.5",
"1.0.5.2",
"1.0.52",
@@ -465,6 +470,7 @@ var wordFenceVersions = []string{
"1.0.8",
"1.0.8.1",
"1.0.8.2",
+ "1.0.8.3",
"1.0.8.4",
"1.0.8.7",
"1.0.81",
@@ -607,6 +613,7 @@ var wordFenceVersions = []string{
"1.10.25",
"1.10.27",
"1.10.28.2",
+ "1.10.29",
"1.10.3",
"1.10.31",
"1.10.4",
@@ -636,6 +643,7 @@ var wordFenceVersions = []string{
"1.11.8",
"1.11.9",
"1.110",
+ "1.112.0",
"1.12",
"1.12.0",
"1.12.03",
@@ -725,10 +733,12 @@ var wordFenceVersions = []string{
"1.16.1",
"1.16.10",
"1.16.2",
+ "1.16.3",
"1.16.3.5",
"1.16.4",
"1.16.44",
"1.16.56",
+ "1.16.6",
"1.16.66",
"1.16.68",
"1.16.7",
@@ -1000,7 +1010,6 @@ var wordFenceVersions = []string{
"1.3.33",
"1.3.34",
"1.3.35",
- "1.3.36",
"1.3.37",
"1.3.38",
"1.3.39",
@@ -1030,6 +1039,7 @@ var wordFenceVersions = []string{
"1.3.6",
"1.3.6.1",
"1.3.6.2",
+ "1.3.6.3",
"1.3.6.4",
"1.3.6.5",
"1.3.6.7",
@@ -1369,6 +1379,7 @@ var wordFenceVersions = []string{
"1.6.56.1",
"1.6.57",
"1.6.57.4",
+ "1.6.58",
"1.6.58.2",
"1.6.58.3",
"1.6.59",
@@ -1419,6 +1430,7 @@ var wordFenceVersions = []string{
"1.64",
"1.66",
"1.66.0",
+ "1.66.2",
"1.67",
"1.68",
"1.68.232",
@@ -1443,6 +1455,7 @@ var wordFenceVersions = []string{
"1.7.1.4",
"1.7.10",
"1.7.1001",
+ "1.7.1003",
"1.7.11",
"1.7.12",
"1.7.13",
@@ -1587,6 +1600,7 @@ var wordFenceVersions = []string{
"1.9.13",
"1.9.136",
"1.9.14",
+ "1.9.15",
"1.9.15.11",
"1.9.15.2",
"1.9.16",
@@ -1596,6 +1610,7 @@ var wordFenceVersions = []string{
"1.9.18",
"1.9.19",
"1.9.2",
+ "1.9.2.1",
"1.9.20",
"1.9.217",
"1.9.22",
@@ -1690,6 +1705,7 @@ var wordFenceVersions = []string{
"10.5.5",
"10.6",
"10.6.1",
+ "10.6.2",
"10.6.5",
"10.6.6",
"10.7",
@@ -1700,6 +1716,7 @@ var wordFenceVersions = []string{
"10.9",
"10.9.1",
"10.9.2",
+ "10.9.3",
"11.0",
"11.0.1",
"11.0.12",
@@ -1781,6 +1798,7 @@ var wordFenceVersions = []string{
"12.3.15",
"12.3.16",
"12.3.19",
+ "12.3.20",
"12.4",
"12.4.0",
"12.4.4",
@@ -2160,6 +2178,7 @@ var wordFenceVersions = []string{
"2.14.3",
"2.14.4",
"2.14.5",
+ "2.145",
"2.15",
"2.15.0",
"2.15.14",
@@ -2184,6 +2203,7 @@ var wordFenceVersions = []string{
"2.17.1",
"2.17.2",
"2.17.3",
+ "2.18",
"2.18.0",
"2.18.1",
"2.18.16",
@@ -2253,6 +2273,7 @@ var wordFenceVersions = []string{
"2.2.8",
"2.2.8.3",
"2.2.80",
+ "2.2.82",
"2.2.84",
"2.2.85",
"2.2.86",
@@ -2281,6 +2302,7 @@ var wordFenceVersions = []string{
"2.21.2",
"2.21.3",
"2.210",
+ "2.216",
"2.22",
"2.22.1",
"2.22.14",
@@ -2297,6 +2319,7 @@ var wordFenceVersions = []string{
"2.24.13",
"2.24.14",
"2.24.3",
+ "2.243",
"2.25",
"2.25.0",
"2.25.1",
@@ -2369,6 +2392,8 @@ var wordFenceVersions = []string{
"2.35.0",
"2.35.1.2.3",
"2.35.1.3.0",
+ "2.35.17",
+ "2.35.19",
"2.35.7",
"2.36.1",
"2.37",
@@ -2496,6 +2521,7 @@ var wordFenceVersions = []string{
"2.6.11",
"2.6.12",
"2.6.13",
+ "2.6.14",
"2.6.15",
"2.6.16",
"2.6.18",
@@ -2780,7 +2806,7 @@ var wordFenceVersions = []string{
"2024.04.30",
"2024.08.26",
"2024.1",
- "2024.11.10",
+ "2024.11.20",
"2024.2",
"2024.3",
"2024.4",
@@ -2825,11 +2851,12 @@ var wordFenceVersions = []string{
"2311.17.01",
"234",
"24.0.3",
+ "24.0.7",
"24.0128",
"24.0422",
"24.0902",
"240315",
- "2408",
+ "2411",
"25.2.0",
"26.0",
"26.5",
@@ -2908,6 +2935,7 @@ var wordFenceVersions = []string{
"3.05.0",
"3.05.4",
"3.05.8",
+ "3.06",
"3.09",
"3.1",
"3.1.0",
@@ -2923,6 +2951,7 @@ var wordFenceVersions = []string{
"3.1.13",
"3.1.14",
"3.1.15",
+ "3.1.15.1",
"3.1.16",
"3.1.18",
"3.1.19",
@@ -2954,6 +2983,7 @@ var wordFenceVersions = []string{
"3.1.43",
"3.1.44",
"3.1.45",
+ "3.1.46",
"3.1.5",
"3.1.6",
"3.1.60",
@@ -3088,6 +3118,7 @@ var wordFenceVersions = []string{
"3.2.38",
"3.2.39",
"3.2.4",
+ "3.2.4.2",
"3.2.40",
"3.2.41",
"3.2.42",
@@ -3148,6 +3179,7 @@ var wordFenceVersions = []string{
"3.24.5",
"3.25",
"3.25.1",
+ "3.25.7",
"3.26.2",
"3.27.0",
"3.27.8",
@@ -3231,6 +3263,7 @@ var wordFenceVersions = []string{
"3.37.15",
"3.37.18",
"3.38",
+ "3.39.4",
"3.4",
"3.4.0",
"3.4.06",
@@ -3460,6 +3493,7 @@ var wordFenceVersions = []string{
"3.8.29",
"3.8.3",
"3.8.3.2",
+ "3.8.3.3",
"3.8.3.4",
"3.8.30",
"3.8.31",
@@ -3612,6 +3646,7 @@ var wordFenceVersions = []string{
"4.1.13",
"4.1.14",
"4.1.15",
+ "4.1.16",
"4.1.17",
"4.1.18",
"4.1.19",
@@ -3710,11 +3745,13 @@ var wordFenceVersions = []string{
"4.15.1",
"4.15.12",
"4.15.17",
+ "4.15.18",
"4.15.2",
"4.15.23",
"4.15.3",
"4.15.4",
"4.15.5",
+ "4.15.7",
"4.15.8",
"4.16",
"4.16.1",
@@ -4278,6 +4315,7 @@ var wordFenceVersions = []string{
"5.3.15",
"5.3.16",
"5.3.17",
+ "5.3.18",
"5.3.2",
"5.3.2.0",
"5.3.3",
@@ -4363,6 +4401,7 @@ var wordFenceVersions = []string{
"5.6.7",
"5.6.8",
"5.6.9",
+ "5.61.0",
"5.7",
"5.7.0",
"5.7.0.1",
@@ -4439,6 +4478,7 @@ var wordFenceVersions = []string{
"5.9.27",
"5.9.3",
"5.9.3.2",
+ "5.9.3.6",
"5.9.4",
"5.9.5",
"5.9.6",
@@ -4517,6 +4557,7 @@ var wordFenceVersions = []string{
"6.15.13.1",
"6.15.20",
"6.15.21",
+ "6.16.1.2",
"6.17.4",
"6.18",
"6.2",
@@ -4588,6 +4629,7 @@ var wordFenceVersions = []string{
"6.4.6",
"6.4.6.0",
"6.4.6.1",
+ "6.4.6.2",
"6.4.6.4",
"6.4.7",
"6.4.7.1",
@@ -4596,6 +4638,8 @@ var wordFenceVersions = []string{
"6.4.9",
"6.4.9.4",
"6.4.9.6",
+ "6.43.2",
+ "6.44",
"6.45",
"6.46",
"6.5",
@@ -4672,6 +4716,7 @@ var wordFenceVersions = []string{
"6.9.8",
"6.9.9",
"6.930",
+ "67.1.0",
"7.0",
"7.0.0",
"7.0.06",
@@ -4741,6 +4786,7 @@ var wordFenceVersions = []string{
"7.2.8",
"7.2.9",
"7.23",
+ "7.25",
"7.26",
"7.28",
"7.3",
@@ -4817,6 +4863,7 @@ var wordFenceVersions = []string{
"7.6.24",
"7.6.3",
"7.6.4",
+ "7.6.5",
"7.6.6",
"7.6.8",
"7.62",
@@ -4834,6 +4881,7 @@ var wordFenceVersions = []string{
"7.8.2",
"7.8.3",
"7.8.4",
+ "7.8.5",
"7.8.6",
"7.8.7",
"7.8.9",
@@ -4859,7 +4907,6 @@ var wordFenceVersions = []string{
"8.0.18",
"8.0.2",
"8.0.26",
- "8.0.3",
"8.0.3.1",
"8.0.33",
"8.0.4",
@@ -5063,5 +5110,6 @@ var wordFenceVersions = []string{
"9.95.0.1",
"9.96.0.1",
"9.99.1.0",
+ "91.5.0",
"p1.2.5",
}
diff --git a/interval.go b/interval.go
index 36e77d2..865c135 100644
--- a/interval.go
+++ b/interval.go
@@ -1,200 +1,26 @@
package comver
-// interval represents the intersection (logical AND) of two constraints.
-type interval [2]*constraint
-
-const (
- ErrImpossibleInterval stringError = "impossible interval"
-)
-
-// NewInterval creates a new interval representing the intersection (logical AND) of two constraints.
-//
-// If either c1 or c2 is nil, it represents a boundless range.
-// If both c1 and c2 are nil, it matches all versions (wildcard).
-func NewInterval(c1, c2 *constraint) (interval, error) { //nolint:cyclop
- cmp := c1.compare(c2)
- // ensure c1 is the lower than c2
- if cmp > 0 {
- c1, c2 = c2, c1
- }
-
- switch {
- case c1 == nil && c2 == nil:
- return interval{}, nil
- case c1 == nil:
- return interval{c2}, nil
- case c2 == nil:
- return interval{c1}, nil
- case cmp == 0: // exactly the same
- return interval{c1}, nil
- case c1.op.ceillingless() && c2.op.ceillingless():
- // same direction
- return interval{c2}, nil
- case c1.op.floorless() && c2.op.floorless():
- // same direction
- return interval{c1}, nil
- case c1.version.Compare(c2.version) == 0 && c1.Check(c1.version) && c2.Check(c2.version):
- // same version & different directions & overlapping
- return interval{c1, c2}, nil
- case c1.Check(c2.version) && c2.Check(c1.version):
- return interval{c1, c2}, nil
- default:
- // different directions & no overlap
- return interval{}, ErrImpossibleInterval
- }
+// interval represents a constraint that is both floor bounded and ceiling bounded.
+// It must be initialized via [And].
+// The zero value for interval is a constraint could never be satisfied.
+type interval struct {
+ upper Endless
+ lower Endless
}
-// Check reports whether a [Version] satisfies the interval.
+// Check reports whether a [Version] satisfies the constraint.
func (i interval) Check(v Version) bool {
- for _, c := range i {
- if c != nil && !c.Check(v) {
- return false
- }
- }
- return true
-}
-
-func (i interval) String() string {
- switch {
- case i[0] == nil && i[1] == nil:
- return "*"
- case i[0] == nil:
- return i[1].String()
- case i[1] == nil:
- return i[0].String()
- }
-
- if i.exactVersionOnly() {
- return i[0].version.Short()
- }
-
- cmp := i[0].compare(i[1])
- switch {
- case cmp < 0:
- return i[0].String() + " " + i[1].String()
- case cmp > 0:
- return i[1].String() + " " + i[0].String()
- default:
- return i[0].String()
- }
+ return i.ceiling().Check(v) && i.floor().Check(v)
}
-func (i interval) wildcard() bool {
- return i[0] == nil && i[1] == nil
+func (i interval) ceiling() Endless {
+ return i.upper
}
-func (i interval) floorless() bool {
- return i.floor() == nil
-}
-
-func (i interval) floor() *constraint { //nolint:cyclop
- if i.wildcard() {
- return nil
- }
-
- if i[0] != nil && i[1] == nil {
- if i[0].lowerbounded() {
- return i[0]
- }
- return nil
- }
-
- if i[0] == nil && i[1] != nil {
- if i[1].lowerbounded() {
- return i[1]
- }
- return nil
- }
-
- // both i[0] and i[1] are not nil
-
- if !i[0].lowerbounded() && !i[1].lowerbounded() {
- return nil
- }
-
- if i[0].lowerbounded() && i[1].lowerbounded() {
- cmp := i[0].compare(i[1])
- switch {
- case cmp < 0:
- return i[0]
- case cmp > 0:
- return i[1]
- default:
- return i[0]
- }
- }
-
- // exactly one of them is lower bounded
- if i[0].lowerbounded() {
- return i[0]
- }
- return i[1]
+func (i interval) floor() Endless {
+ return i.lower
}
-func (i interval) ceilingless() bool {
- return i.ceiling() == nil
-}
-
-func (i interval) ceiling() *constraint { //nolint:cyclop
- if i.wildcard() {
- return nil
- }
-
- if i[0] != nil && i[1] == nil {
- if i[0].upperbounded() {
- return i[0]
- }
- return nil
- }
-
- if i[0] == nil && i[1] != nil {
- if i[1].upperbounded() {
- return i[1]
- }
- return nil
- }
-
- // both i[0] and i[1] are not nil
-
- if !i[0].upperbounded() && !i[1].upperbounded() {
- return nil
- }
-
- if i[0].upperbounded() && i[1].upperbounded() {
- cmp := i[0].compare(i[1])
- switch {
- case cmp < 0:
- return i[1]
- case cmp > 0:
- return i[0]
- default:
- return i[0]
- }
- }
-
- // exactly one of them is upper bounded
- if i[0].upperbounded() {
- return i[0]
- }
- return i[1]
-}
-
-func (i interval) exactVersionOnly() bool {
- if i[0] == nil || i[1] == nil {
- return false
- }
-
- if i[0].version.Compare(i[1].version) != 0 {
- return false
- }
-
- return (i[0].lowerbounded() && i[1].upperbounded()) || (i[0].upperbounded() && i[1].lowerbounded())
-}
-
-func (i interval) compare(j interval) int {
- cmp := i.floor().compare(j.floor())
- if cmp != 0 {
- return cmp
- }
- return j.ceiling().compare(j.ceiling())
+func (i interval) String() string {
+ return i.floor().String() + " " + i.ceiling().String()
}
diff --git a/interval_example_test.go b/interval_example_test.go
deleted file mode 100644
index ff59c58..0000000
--- a/interval_example_test.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package comver_test
-
-import (
- "fmt"
-
- "github.com/typisttech/comver"
-)
-
-func ExampleNewInterval() {
- v1, _ := comver.NewVersion("1")
- v2, _ := comver.NewVersion("2")
-
- c1 := comver.NewGreaterThanConstraint(v1)
- c2 := comver.NewLessThanConstraint(v2)
-
- i, _ := comver.NewInterval(c1, c2)
-
- fmt.Println(i)
- // Output: >1 <2
-}
-
-func ExampleNewInterval_error() {
- v1, _ := comver.NewVersion("1")
- v2, _ := comver.NewVersion("2")
-
- c1 := comver.NewLessThanConstraint(v1)
- c2 := comver.NewGreaterThanConstraint(v2)
-
- _, err := comver.NewInterval(c1, c2)
-
- fmt.Println(err)
- // Output: impossible interval
-}
-
-func ExampleNewInterval_ceillingless() {
- v1, _ := comver.NewVersion("1")
-
- c1 := comver.NewGreaterThanOrEqualToConstraint(v1)
-
- i, _ := comver.NewInterval(c1, nil)
-
- fmt.Println(i)
- // Output: >=1
-}
-
-func ExampleNewInterval_floorless() {
- v1, _ := comver.NewVersion("1")
-
- c1 := comver.NewLessThanOrEqualToConstraint(v1)
-
- i, _ := comver.NewInterval(c1, nil)
-
- fmt.Println(i)
- // Output: <=1
-}
-
-func ExampleNewInterval_exactVersion() {
- v1, _ := comver.NewVersion("1")
-
- c1 := comver.NewLessThanOrEqualToConstraint(v1)
- c2 := comver.NewGreaterThanOrEqualToConstraint(v1)
-
- i, _ := comver.NewInterval(c1, c2)
-
- fmt.Println(i)
- // Output: 1
-}
-
-func ExampleNewInterval_wildcard() {
- i, _ := comver.NewInterval(nil, nil)
-
- fmt.Println(i)
- // Output: *
-}
-
-func ExampleNewInterval_compact() {
- v1, _ := comver.NewVersion("1")
- v2, _ := comver.NewVersion("2")
-
- c1 := comver.NewLessThanConstraint(v1)
- c2 := comver.NewLessThanConstraint(v2)
-
- i, _ := comver.NewInterval(c1, c2)
-
- fmt.Println(i)
- // Output: <1
-}
-
-func ExampleNewInterval_compactWildcard() {
- v, _ := comver.NewVersion("1")
-
- c1 := comver.NewLessThanConstraint(v)
- c2 := comver.NewGreaterThanOrEqualToConstraint(v)
-
- i, _ := comver.NewInterval(c1, c2)
-
- fmt.Println(i)
- // Output: *
-}
diff --git a/interval_test.go b/interval_test.go
deleted file mode 100644
index 5872fa7..0000000
--- a/interval_test.go
+++ /dev/null
@@ -1,581 +0,0 @@
-package comver
-
-import (
- "fmt"
- "reflect"
- "strconv"
- "testing"
-)
-
-func newIntervalTestCases() []struct {
- c1 *constraint
- c2 *constraint
- want interval
- wantErr bool
-} {
- return []struct {
- c1 *constraint
- c2 *constraint
- want interval
- wantErr bool
- }{
- {
- c1: nil,
- c2: nil,
- want: interval{},
- wantErr: false,
- },
- // With single nil.
- {
- c1: &constraint{Version{major: 9}, lessThanOrEqualTo},
- c2: nil,
- want: interval{&constraint{Version{major: 9}, lessThanOrEqualTo}},
- wantErr: false,
- },
- {
- c1: &constraint{Version{major: 9}, lessThan},
- c2: nil,
- want: interval{&constraint{Version{major: 9}, lessThan}},
- wantErr: false,
- },
- {
- c1: &constraint{Version{major: 9}, greaterThan},
- c2: nil,
- want: interval{&constraint{Version{major: 9}, greaterThan}},
- wantErr: false,
- },
- {
- c1: &constraint{Version{major: 9}, greaterThanOrEqualTo},
- c2: nil,
- want: interval{&constraint{Version{major: 9}, greaterThanOrEqualTo}},
- wantErr: false,
- },
-
- // Same constraint.
- {
- c1: &constraint{Version{major: 8}, lessThanOrEqualTo},
- c2: &constraint{Version{major: 8}, lessThanOrEqualTo},
- want: interval{&constraint{Version{major: 8}, lessThanOrEqualTo}},
- wantErr: false,
- },
- {
- c1: &constraint{Version{major: 8}, lessThan},
- c2: &constraint{Version{major: 8}, lessThan},
- want: interval{&constraint{Version{major: 8}, lessThan}},
- wantErr: false,
- },
- {
- c1: &constraint{Version{major: 8}, greaterThan},
- c2: &constraint{Version{major: 8}, greaterThan},
- want: interval{&constraint{Version{major: 8}, greaterThan}},
- wantErr: false,
- },
- {
- c1: &constraint{Version{major: 8}, greaterThanOrEqualTo},
- c2: &constraint{Version{major: 8}, greaterThanOrEqualTo},
- want: interval{&constraint{Version{major: 8}, greaterThanOrEqualTo}},
- wantErr: false,
- },
-
- // Same direction. Different versions. Same op.
- {
- c1: &constraint{Version{major: 7}, lessThanOrEqualTo},
- c2: &constraint{Version{major: 6}, lessThanOrEqualTo},
- want: interval{&constraint{Version{major: 6}, lessThanOrEqualTo}},
- wantErr: false,
- },
- {
- c1: &constraint{Version{major: 7}, lessThan},
- c2: &constraint{Version{major: 6}, lessThan},
- want: interval{&constraint{Version{major: 6}, lessThan}},
- wantErr: false,
- },
- {
- c1: &constraint{Version{major: 7}, greaterThan},
- c2: &constraint{Version{major: 6}, greaterThan},
- want: interval{&constraint{Version{major: 7}, greaterThan}},
- wantErr: false,
- },
- {
- c1: &constraint{Version{major: 7}, greaterThanOrEqualTo},
- c2: &constraint{Version{major: 6}, greaterThanOrEqualTo},
- want: interval{&constraint{Version{major: 7}, greaterThanOrEqualTo}},
- wantErr: false,
- },
-
- // Same direction. Same version. Different ops.
- {
- c1: &constraint{Version{major: 5}, lessThanOrEqualTo},
- c2: &constraint{Version{major: 5}, lessThan},
- want: interval{&constraint{Version{major: 5}, lessThan}},
- wantErr: false,
- },
- {
- c1: &constraint{Version{major: 5}, greaterThan},
- c2: &constraint{Version{major: 5}, greaterThanOrEqualTo},
- want: interval{&constraint{Version{major: 5}, greaterThan}},
- wantErr: false,
- },
-
- // Different directions. Same version. Different ops.
- {
- c1: &constraint{Version{major: 5}, lessThanOrEqualTo},
- c2: &constraint{Version{major: 5}, greaterThan},
- want: interval{},
- wantErr: true,
- },
- {
- c1: &constraint{Version{major: 5}, lessThan},
- c2: &constraint{Version{major: 5}, greaterThan},
- want: interval{},
- wantErr: true,
- },
- {
- c1: &constraint{Version{major: 5}, lessThan},
- c2: &constraint{Version{major: 5}, greaterThanOrEqualTo},
- want: interval{},
- wantErr: true,
- },
- {
- c1: &constraint{Version{major: 5}, lessThanOrEqualTo},
- c2: &constraint{Version{major: 5}, greaterThanOrEqualTo},
- want: interval{&constraint{Version{major: 5}, greaterThanOrEqualTo}, &constraint{Version{major: 5}, lessThanOrEqualTo}},
- wantErr: false,
- },
-
- // Different directions. Different versions. Different ops.
- {
- c1: &constraint{Version{major: 4}, lessThanOrEqualTo},
- c2: &constraint{Version{major: 3}, greaterThan},
- want: interval{&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- wantErr: false,
- },
- {
- c1: &constraint{Version{major: 4}, lessThanOrEqualTo},
- c2: &constraint{Version{major: 3}, greaterThanOrEqualTo},
- want: interval{&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- wantErr: false,
- },
- {
- c1: &constraint{Version{major: 4}, lessThan},
- c2: &constraint{Version{major: 3}, greaterThan},
- want: interval{&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- wantErr: false,
- },
- {
- c1: &constraint{Version{major: 4}, lessThan},
- c2: &constraint{Version{major: 3}, greaterThanOrEqualTo},
- want: interval{&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- wantErr: false,
- },
- {
- c1: &constraint{Version{major: 1}, lessThanOrEqualTo},
- c2: &constraint{Version{major: 2}, greaterThan},
- want: interval{},
- wantErr: true,
- },
- {
- c1: &constraint{Version{major: 1}, lessThanOrEqualTo},
- c2: &constraint{Version{major: 2}, greaterThanOrEqualTo},
- want: interval{},
- wantErr: true,
- },
- {
- c1: &constraint{Version{major: 1}, lessThan},
- c2: &constraint{Version{major: 2}, greaterThan},
- want: interval{},
- wantErr: true,
- },
- {
- c1: &constraint{Version{major: 1}, lessThan},
- c2: &constraint{Version{major: 2}, greaterThanOrEqualTo},
- want: interval{},
- wantErr: true,
- },
- }
-}
-
-func TestNewInterval(t *testing.T) {
- t.Parallel()
-
- for _, tt := range newIntervalTestCases() {
- t.Run(fmt.Sprintf("%s_%s_reversed", tt.c1, tt.c2), func(t *testing.T) {
- t.Parallel()
-
- got, err := NewInterval(tt.c1, tt.c2)
-
- if (err != nil) != tt.wantErr {
- t.Errorf("NewInterval(%q, %q) error = %v, wantErr %v", tt.c1, tt.c2, err, tt.wantErr)
- return
- }
-
- if !reflect.DeepEqual(got, tt.want) {
- t.Errorf("NewInterval(%q, %q) = %v, want %v", tt.c1, tt.c2, got, tt.want)
- }
- })
- }
-}
-
-func TestNewInterval_reversed(t *testing.T) {
- t.Parallel()
-
- for _, tt := range newIntervalTestCases() {
- t.Run(fmt.Sprintf("%s_%s_reversed", tt.c2, tt.c1), func(t *testing.T) {
- t.Parallel()
-
- got, err := NewInterval(tt.c2, tt.c1)
-
- if (err != nil) != tt.wantErr {
- t.Fatalf("NewInterval(%q, %q) error = %v, wantErr %v", tt.c2, tt.c1, err, tt.wantErr)
- return
- }
-
- if !reflect.DeepEqual(got, tt.want) {
- t.Errorf("NewInterval(%q, %q) = %v, want %v", tt.c2, tt.c1, got, tt.want)
- }
- })
- }
-}
-
-func Test_interval_Check(t *testing.T) { //nolint:maintidx
- t.Parallel()
-
- tests := []struct {
- i interval
- v Version
- want bool
- }{
- {
- i: interval{},
- v: Version{major: 9},
- want: true,
- },
- {
- i: interval{&constraint{Version{major: 9}, lessThanOrEqualTo}},
- v: Version{major: 10},
- want: false,
- },
- {
- i: interval{&constraint{Version{major: 9}, lessThanOrEqualTo}},
- v: Version{major: 9},
- want: true,
- },
- {
- i: interval{&constraint{Version{major: 9}, lessThanOrEqualTo}},
- v: Version{major: 8},
- want: true,
- },
- {
- i: interval{&constraint{Version{major: 9}, lessThan}},
- v: Version{major: 10},
- want: false,
- },
- {
- i: interval{&constraint{Version{major: 9}, lessThan}},
- v: Version{major: 9},
- want: false,
- },
- {
- i: interval{&constraint{Version{major: 9}, lessThan}},
- v: Version{major: 8},
- want: true,
- },
- {
- i: interval{&constraint{Version{major: 9}, greaterThan}},
- v: Version{major: 10},
- want: true,
- },
- {
- i: interval{&constraint{Version{major: 9}, greaterThan}},
- v: Version{major: 9},
- want: false,
- },
- {
- i: interval{&constraint{Version{major: 9}, greaterThan}},
- v: Version{major: 8},
- want: false,
- },
- {
- i: interval{&constraint{Version{major: 9}, greaterThanOrEqualTo}},
- v: Version{major: 10},
- want: true,
- },
- {
- i: interval{&constraint{Version{major: 9}, greaterThanOrEqualTo}},
- v: Version{major: 9},
- want: true,
- },
- {
- i: interval{&constraint{Version{major: 9}, greaterThanOrEqualTo}},
- v: Version{major: 8},
- want: false,
- },
- {
- i: interval{
- &constraint{Version{major: 9}, greaterThanOrEqualTo},
- &constraint{Version{major: 9}, lessThanOrEqualTo},
- },
- v: Version{major: 10},
- want: false,
- },
- {
- i: interval{
- &constraint{Version{major: 9}, greaterThanOrEqualTo},
- &constraint{Version{major: 9}, lessThanOrEqualTo},
- },
- v: Version{major: 9},
- want: true,
- },
- {
- i: interval{
- &constraint{Version{major: 9}, greaterThanOrEqualTo},
- &constraint{Version{major: 9}, lessThanOrEqualTo},
- },
- v: Version{major: 8},
- want: false,
- },
- {
- i: interval{
- &constraint{Version{major: 4}, greaterThanOrEqualTo},
- &constraint{Version{major: 6}, lessThanOrEqualTo},
- },
- v: Version{major: 7},
- want: false,
- },
- {
- i: interval{
- &constraint{Version{major: 4}, greaterThanOrEqualTo},
- &constraint{Version{major: 6}, lessThanOrEqualTo},
- },
- v: Version{major: 6},
- want: true,
- },
- {
- i: interval{
- &constraint{Version{major: 4}, greaterThanOrEqualTo},
- &constraint{Version{major: 6}, lessThanOrEqualTo},
- },
- v: Version{major: 5},
- want: true,
- },
- {
- i: interval{
- &constraint{Version{major: 4}, greaterThanOrEqualTo},
- &constraint{Version{major: 6}, lessThanOrEqualTo},
- },
- v: Version{major: 4},
- want: true,
- },
- {
- i: interval{
- &constraint{Version{major: 4}, greaterThanOrEqualTo},
- &constraint{Version{major: 6}, lessThanOrEqualTo},
- },
- v: Version{major: 3},
- want: false,
- },
- {
- i: interval{
- &constraint{Version{major: 4}, greaterThanOrEqualTo},
- &constraint{Version{major: 6}, lessThan},
- },
- v: Version{major: 7},
- want: false,
- },
- {
- i: interval{
- &constraint{Version{major: 4}, greaterThanOrEqualTo},
- &constraint{Version{major: 6}, lessThan},
- },
- v: Version{major: 6},
- want: false,
- },
- {
- i: interval{
- &constraint{Version{major: 4}, greaterThanOrEqualTo},
- &constraint{Version{major: 6}, lessThan},
- },
- v: Version{major: 5},
- want: true,
- },
- {
- i: interval{
- &constraint{Version{major: 4}, greaterThanOrEqualTo},
- &constraint{Version{major: 6}, lessThan},
- },
- v: Version{major: 4},
- want: true,
- },
- {
- i: interval{
- &constraint{Version{major: 4}, greaterThanOrEqualTo},
- &constraint{Version{major: 6}, lessThan},
- },
- v: Version{major: 3},
- want: false,
- },
- {
- i: interval{
- &constraint{Version{major: 4}, greaterThan},
- &constraint{Version{major: 6}, lessThanOrEqualTo},
- },
- v: Version{major: 7},
- want: false,
- },
- {
- i: interval{
- &constraint{Version{major: 4}, greaterThan},
- &constraint{Version{major: 6}, lessThanOrEqualTo},
- },
- v: Version{major: 6},
- want: true,
- },
- {
- i: interval{
- &constraint{Version{major: 4}, greaterThan},
- &constraint{Version{major: 6}, lessThanOrEqualTo},
- },
- v: Version{major: 5},
- want: true,
- },
- {
- i: interval{
- &constraint{Version{major: 4}, greaterThan},
- &constraint{Version{major: 6}, lessThanOrEqualTo},
- },
- v: Version{major: 4},
- want: false,
- },
- {
- i: interval{
- &constraint{Version{major: 4}, greaterThan},
- &constraint{Version{major: 6}, lessThanOrEqualTo},
- },
- v: Version{major: 3},
- want: false,
- },
- {
- i: interval{
- &constraint{Version{major: 4}, greaterThan},
- &constraint{Version{major: 6}, lessThan},
- },
- v: Version{major: 7},
- want: false,
- },
- {
- i: interval{
- &constraint{Version{major: 4}, greaterThan},
- &constraint{Version{major: 6}, lessThan},
- },
- v: Version{major: 6},
- want: false,
- },
- {
- i: interval{
- &constraint{Version{major: 4}, greaterThan},
- &constraint{Version{major: 6}, lessThan},
- },
- v: Version{major: 5},
- want: true,
- },
- {
- i: interval{
- &constraint{Version{major: 4}, greaterThan},
- &constraint{Version{major: 6}, lessThan},
- },
- v: Version{major: 4},
- want: false,
- },
- {
- i: interval{
- &constraint{Version{major: 4}, greaterThan},
- &constraint{Version{major: 6}, lessThan},
- },
- v: Version{major: 3},
- want: false,
- },
- }
- for i, tt := range tests {
- t.Run(strconv.Itoa(i), func(t *testing.T) {
- t.Parallel()
-
- if got := tt.i.Check(tt.v); got != tt.want {
- t.Errorf("%q.Check(%q) = %v, want %v", tt.i, tt.v.Short(), got, tt.want)
- }
- })
- }
-}
-
-func Test_interval_String(t *testing.T) {
- t.Parallel()
-
- tests := []struct {
- i interval
- want string
- }{
- {
- i: interval{},
- want: "*",
- },
- {
- i: interval{&constraint{Version{major: 9}, lessThanOrEqualTo}},
- want: "<=9",
- },
- {
- i: interval{&constraint{Version{major: 9}, lessThan}},
- want: "<9",
- },
- {
- i: interval{&constraint{Version{major: 9}, greaterThan}},
- want: ">9",
- },
- {
- i: interval{&constraint{Version{major: 9}, greaterThanOrEqualTo}},
- want: ">=9",
- },
- {
- i: interval{
- &constraint{Version{major: 9}, greaterThanOrEqualTo},
- &constraint{Version{major: 9}, lessThanOrEqualTo},
- },
- want: "9",
- },
- {
- i: interval{
- &constraint{Version{major: 7}, greaterThanOrEqualTo},
- &constraint{Version{major: 8}, lessThanOrEqualTo},
- },
- want: ">=7 <=8",
- },
- {
- i: interval{
- &constraint{Version{major: 7}, greaterThanOrEqualTo},
- &constraint{Version{major: 8}, lessThan},
- },
- want: ">=7 <8",
- },
- {
- i: interval{
- &constraint{Version{major: 7}, greaterThan},
- &constraint{Version{major: 8}, lessThanOrEqualTo},
- },
- want: ">7 <=8",
- },
- {
- i: interval{
- &constraint{Version{major: 7}, greaterThan},
- &constraint{Version{major: 8}, lessThan},
- },
- want: ">7 <8",
- },
- }
- for _, tt := range tests {
- t.Run(tt.want, func(t *testing.T) {
- t.Parallel()
-
- if got := tt.i.String(); got != tt.want {
- t.Errorf("String() = %v, want %v", got, tt.want)
- }
- })
- }
-}
diff --git a/intervals.go b/intervals.go
deleted file mode 100644
index c9a074c..0000000
--- a/intervals.go
+++ /dev/null
@@ -1,138 +0,0 @@
-package comver
-
-import (
- "slices"
-)
-
-// Intervals represent the union (logical OR) of multiple intervals.
-type Intervals []interval
-
-func (is Intervals) String() string {
- s := ""
- for _, i := range is {
- if len(s) > 0 {
- s += " || "
- }
- s += i.String()
- }
- return s
-}
-
-// Compact returns a new [Intervals] covering the same version ranges but with the smallest number of intervals.
-func Compact(is Intervals) Intervals {
- if len(is) == 1 {
- return is
- }
- if len(is) == 0 {
- return Intervals{}
- }
-
- is = slices.Clone(is)
-
- // if there is a wildcard, return only the wildcard
- wIndex := slices.IndexFunc(is, func(i interval) bool {
- return i.wildcard()
- })
- if wIndex >= 0 {
- return Intervals{is[wIndex]}
- }
-
- head := lowestCeilingless(is)
- tail := highestFloorless(is)
-
- is = slices.DeleteFunc(is, func(i interval) bool {
- return i.floorless() || i.ceilingless()
- })
-
- is = slices.Clip(slices.Concat(head, is, tail))
-
- // sort the intervals
- slices.SortFunc(is, func(a, b interval) int {
- return a.compare(b)
- })
- // remove duplicates
- is = slices.CompactFunc(is, func(a, b interval) bool {
- return a.compare(b) == 0
- })
-
- vals := make(Intervals, 0, len(is))
- pendingI := is[0]
- for index := range is {
- i, ok := compactTwo(pendingI, is[index])
- if ok {
- pendingI = i
- } else {
- vals = append(vals, pendingI)
- pendingI = is[index]
- }
-
- if index == len(is)-1 {
- vals = append(vals, pendingI)
- }
- }
-
- return slices.Clip(vals)
-}
-
-func compactTwo(a, b interval) (interval, bool) {
- cmp := a.compare(b)
- if cmp > 0 {
- a, b = b, a
- }
-
- if a.compare(b) == 0 {
- return a, true
- }
-
- overlap := a.Check(b.floor().version) ||
- (a.ceiling().version.Compare(b.floor().version) == 0 &&
- (a.ceiling().op == lessThanOrEqualTo || b.floor().op == greaterThanOrEqualTo))
-
- if !overlap {
- return a, false
- }
-
- ccmp := a.ceiling().compare(b.ceiling())
- if ccmp > 0 {
- return a, true
- }
-
- i, err := NewInterval(a.floor(), b.ceiling())
- if err != nil {
- // this should not happen
- return a, false
- }
- return i, true
-}
-
-func lowestCeilingless(is Intervals) Intervals {
- ceilingless := slices.DeleteFunc(slices.Clone(is), func(i interval) bool {
- return !i.ceilingless()
- })
-
- if len(ceilingless) == 0 {
- return Intervals{}
- }
-
- i := slices.MinFunc(ceilingless, func(a, b interval) int {
- return a.floor().compare(b.floor())
- })
-
- return Intervals{i}
-}
-
-func highestFloorless(is Intervals) Intervals {
- floorlesses := slices.DeleteFunc(slices.Clone(is), func(i interval) bool {
- return !i.floorless()
- })
-
- if len(floorlesses) == 0 {
- return Intervals{}
- }
-
- i := slices.MaxFunc(floorlesses, func(a, b interval) int {
- return a.ceiling().compare(b.ceiling())
- })
-
- return Intervals{i}
-}
diff --git a/intervals_example_test.go b/intervals_example_test.go
deleted file mode 100644
index 6182699..0000000
--- a/intervals_example_test.go
+++ /dev/null
@@ -1,149 +0,0 @@
-package comver_test
-
-import (
- "fmt"
-
- "github.com/typisttech/comver"
-)
-
-func buildIntervals(ks ...string) comver.Intervals {
- v1, _ := comver.NewVersion("1")
- v2, _ := comver.NewVersion("2")
- v3, _ := comver.NewVersion("3")
- v4, _ := comver.NewVersion("4")
- v5, _ := comver.NewVersion("5")
- v6, _ := comver.NewVersion("6")
- v7, _ := comver.NewVersion("7")
- v8, _ := comver.NewVersion("8")
- v9, _ := comver.NewVersion("9")
- v10, _ := comver.NewVersion("10")
-
- g1 := comver.NewGreaterThanConstraint(v1)
- g2 := comver.NewGreaterThanConstraint(v2)
- g3 := comver.NewGreaterThanConstraint(v3)
- g4 := comver.NewGreaterThanConstraint(v4)
- g7 := comver.NewGreaterThanConstraint(v7)
- g8 := comver.NewGreaterThanConstraint(v8)
- g10 := comver.NewGreaterThanConstraint(v10)
-
- gt4 := comver.NewGreaterThanOrEqualToConstraint(v4)
-
- l2 := comver.NewLessThanConstraint(v2)
- l3 := comver.NewLessThanConstraint(v3)
- l4 := comver.NewLessThanConstraint(v4)
- l1 := comver.NewLessThanConstraint(v1)
- l5 := comver.NewLessThanConstraint(v5)
- l6 := comver.NewLessThanConstraint(v6)
- l9 := comver.NewLessThanConstraint(v9)
-
- g1l2, _ := comver.NewInterval(g1, l2)
- g1l3, _ := comver.NewInterval(g1, l3)
- g2l4, _ := comver.NewInterval(g2, l4)
- g3l4, _ := comver.NewInterval(g3, l4)
- g4l5, _ := comver.NewInterval(g4, l5)
- g8l9, _ := comver.NewInterval(g8, l9)
- gt4l6, _ := comver.NewInterval(gt4, l6)
-
- ig1, _ := comver.NewInterval(g1, nil)
- ig10, _ := comver.NewInterval(g10, nil)
- ig7, _ := comver.NewInterval(g7, nil)
-
- il1, _ := comver.NewInterval(l1, nil)
- il3, _ := comver.NewInterval(l3, nil)
- il4, _ := comver.NewInterval(l4, nil)
-
- wildcard, _ := comver.NewInterval(nil, nil)
-
- m := map[string]comver.Intervals{
- "*": {wildcard},
- "<1": {il1},
- "<3": {il3},
- "<4": {il4},
- ">=5 <6": {gt4l6},
- ">1 <2": {g1l2},
- ">1 <3": {g1l3},
- ">1": {ig1},
- ">10": {ig10},
- ">2 <4": {g2l4},
- ">3 <4": {g3l4},
- ">4 <5": {g4l5},
- ">7": {ig7},
- ">8 <9": {g8l9},
- }
-
- is := make(comver.Intervals, 0, len(ks))
- for _, k := range ks {
- is = append(is, m[k]...)
- }
-
- return is
-}
-
-func ExampleCompact() {
- is := buildIntervals(">1 <3", ">2 <4")
- fmt.Println("Original:", is)
-
- is = comver.Compact(is)
- fmt.Println("Compacted:", is)
-
- // Output:
- // Original: >1 <3 || >2 <4
- // Compacted: >1 <4
-}
-
-func ExampleCompact_wildcard() {
- is := buildIntervals(">1", "<4")
- fmt.Println("Original:", is)
-
- is = comver.Compact(is)
- fmt.Println("Compacted:", is)
-
- // Output:
- // Original: >1 || <4
- // Compacted: *
-}
-
-func ExampleCompact_wildcard2() {
- is := buildIntervals(">1 <3", "*")
- fmt.Println("Original:", is)
-
- is = comver.Compact(is)
- fmt.Println("Compacted:", is)
-
- // Output:
- // Original: >1 <3 || *
- // Compacted: *
-}
-
-func ExampleCompact_unrelated() {
- is := buildIntervals(">1 <2", ">3 <4")
- fmt.Println("Original:", is)
-
- is = comver.Compact(is)
- fmt.Println("Compacted:", is)
-
- // Output:
- // Original: >1 <2 || >3 <4
- // Compacted: >1 <2 || >3 <4
-}
-
-func ExampleCompact_mega() {
- is := buildIntervals(
- "<1",
- ">1 <2",
- "<3",
- ">4 <5",
- ">=5 <6",
- ">7",
- ">8 <9",
- ">10",
- )
- fmt.Println("Original:", is)
-
- is = comver.Compact(is)
- fmt.Println("Compacted:", is)
-
- // Output:
- // Original: <1 || >1 <2 || <3 || >4 <5 || >=4 <6 || >7 || >8 <9 || >10
- // Compacted: <3 || >=4 <6 || >7
-}
diff --git a/intervals_test.go b/intervals_test.go
deleted file mode 100644
index f49c3b5..0000000
--- a/intervals_test.go
+++ /dev/null
@@ -1,1734 +0,0 @@
-package comver
-
-import (
- "math/rand/v2"
- "reflect"
- "slices"
- "testing"
-)
-
-func compactTestCases() []struct { //nolint:maintidx
- name string
- is Intervals
- want Intervals
-} {
- return []struct {
- name string
- is Intervals
- want Intervals
- }{
- {
- name: "nil",
- is: nil,
- want: Intervals{},
- },
- {
- name: "empty",
- is: Intervals{},
- want: Intervals{},
- },
- {
- name: "single_wildcard",
- is: Intervals{{}},
- want: Intervals{{}},
- },
- {
- name: "single_boundless",
- is: Intervals{{&constraint{Version{major: 9}, lessThanOrEqualTo}}},
- want: Intervals{{&constraint{Version{major: 9}, lessThanOrEqualTo}}},
- },
- {
- name: "single_bounded",
- is: Intervals{
- {&constraint{Version{major: 7}, greaterThanOrEqualTo}, &constraint{Version{major: 8}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 7}, greaterThanOrEqualTo}, &constraint{Version{major: 8}, lessThanOrEqualTo}},
- },
- },
- {
- name: "wildcard_with_<=",
- is: Intervals{{}, {&constraint{Version{major: 9}, lessThanOrEqualTo}}},
- want: Intervals{{}},
- },
- {
- name: "wildcard_with_<",
- is: Intervals{{}, {&constraint{Version{major: 9}, lessThan}}},
- want: Intervals{{}},
- },
- {
- name: "wildcard_with_=",
- is: Intervals{
- {},
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}},
- },
- want: Intervals{{}},
- },
- {
- name: "wildcard_with_>",
- is: Intervals{{}, {&constraint{Version{major: 9}, greaterThan}}},
- want: Intervals{{}},
- },
- {
- name: "wildcard_with_>=",
- is: Intervals{{}, {&constraint{Version{major: 9}, greaterThanOrEqualTo}}},
- want: Intervals{{}},
- },
- {
- name: "wildcard_with_single_bounded",
- is: Intervals{
- {},
- {&constraint{Version{major: 7}, greaterThanOrEqualTo}, &constraint{Version{major: 8}, lessThanOrEqualTo}},
- },
- want: Intervals{{}},
- },
- {
- name: "wildcard_with_wildcard",
- is: Intervals{{}, {}},
- want: Intervals{{}},
- },
- {
- name: "<=_<=_same_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 9}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 9}, lessThanOrEqualTo}}},
- },
- {
- name: "<=_<=_different_versions",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 10}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 10}, lessThanOrEqualTo}}},
- },
- {
- name: "<=_<_same_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 9}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 9}, lessThanOrEqualTo}}},
- },
- {
- name: "<=_<_lower_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 8}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 9}, lessThanOrEqualTo}}},
- },
- {
- name: "<=_<_higher_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 10}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 10}, lessThan}}},
- },
- {
- name: "<=_=_same_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 9}, lessThanOrEqualTo}}},
- },
- {
- name: "<=_=_lower_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 8}, greaterThanOrEqualTo}, &constraint{Version{major: 8}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 9}, lessThanOrEqualTo}}},
- },
- {
- name: "<=_=_higher_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 10}, greaterThanOrEqualTo}, &constraint{Version{major: 10}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 10}, greaterThanOrEqualTo}, &constraint{Version{major: 10}, lessThanOrEqualTo}},
- },
- },
-
- {
- name: "<=_>_same_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 9}, greaterThan}},
- },
- want: Intervals{{}},
- },
- {
- name: "<=_>_lower_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 8}, greaterThan}},
- },
- want: Intervals{{}},
- },
- {
- name: "<=_>_higher_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 10}, greaterThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 10}, greaterThan}},
- },
- },
-
- {
- name: "<=_>=_same_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}},
- },
- want: Intervals{{}},
- },
- {
- name: "<=_>=_lower_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 8}, greaterThanOrEqualTo}},
- },
- want: Intervals{{}},
- },
- {
- name: "<=_>=_higher_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 10}, greaterThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 10}, greaterThanOrEqualTo}},
- },
- },
-
- {
- name: "<_<_same_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThan}},
- {&constraint{Version{major: 9}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 9}, lessThan}}},
- },
- {
- name: "<_<_different_versions",
- is: Intervals{{&constraint{Version{major: 9}, lessThan}}, {&constraint{Version{major: 10}, lessThan}}},
- want: Intervals{{&constraint{Version{major: 10}, lessThan}}},
- },
-
- {
- name: "<_=_same_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThan}},
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 9}, lessThanOrEqualTo}}},
- },
- {
- name: "<_=_lower_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThan}},
- {&constraint{Version{major: 8}, greaterThanOrEqualTo}, &constraint{Version{major: 8}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 9}, lessThan}}},
- },
- {
- name: "<_=_higher_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThan}},
- {&constraint{Version{major: 10}, greaterThanOrEqualTo}, &constraint{Version{major: 10}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 9}, lessThan}},
- {&constraint{Version{major: 10}, greaterThanOrEqualTo}, &constraint{Version{major: 10}, lessThanOrEqualTo}},
- },
- },
-
- {
- name: "<_>_same_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThan}},
- {&constraint{Version{major: 9}, greaterThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 9}, lessThan}},
- {&constraint{Version{major: 9}, greaterThan}},
- },
- },
- {
- name: "<_>_lower_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThan}},
- {&constraint{Version{major: 8}, greaterThan}},
- },
- want: Intervals{{}},
- },
- {
- name: "<_>_higher_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThan}},
- {&constraint{Version{major: 10}, greaterThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 9}, lessThan}},
- {&constraint{Version{major: 10}, greaterThan}},
- },
- },
-
- {
- name: "<_>=_same_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThan}},
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}},
- },
- want: Intervals{{}},
- },
- {
- name: "<_>=_lower_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThan}},
- {&constraint{Version{major: 8}, greaterThanOrEqualTo}},
- },
- want: Intervals{{}},
- },
- {
- name: "<_>=_higher_version",
- is: Intervals{
- {&constraint{Version{major: 9}, lessThan}},
- {&constraint{Version{major: 10}, greaterThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 9}, lessThan}},
- {&constraint{Version{major: 10}, greaterThanOrEqualTo}},
- },
- },
-
- {
- name: "=_=_same_version",
- is: Intervals{
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}},
- },
- },
- {
- name: "=_=_different_versions",
- is: Intervals{
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 10}, greaterThanOrEqualTo}, &constraint{Version{major: 10}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 10}, greaterThanOrEqualTo}, &constraint{Version{major: 10}, lessThanOrEqualTo}},
- },
- },
-
- {
- name: "=_>_same_version",
- is: Intervals{
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 9}, greaterThan}},
- },
- want: Intervals{{&constraint{Version{major: 9}, greaterThanOrEqualTo}}},
- },
- {
- name: "=_>_lower_version",
- is: Intervals{
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 8}, greaterThan}},
- },
- want: Intervals{{&constraint{Version{major: 8}, greaterThan}}},
- },
- {
- name: "=_>_higher_version",
- is: Intervals{
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 10}, greaterThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 10}, greaterThan}},
- },
- },
-
- {
- name: "=_>=_same_version",
- is: Intervals{
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 9}, greaterThanOrEqualTo}}},
- },
- {
- name: "=_>=_lower_version",
- is: Intervals{
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 8}, greaterThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 8}, greaterThanOrEqualTo}}},
- },
- {
- name: "=_>=_higher_version",
- is: Intervals{
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 10}, greaterThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}},
- {&constraint{Version{major: 10}, greaterThanOrEqualTo}},
- },
- },
-
- {
- name: ">_>_same_version",
- is: Intervals{
- {&constraint{Version{major: 9}, greaterThan}},
- {&constraint{Version{major: 9}, greaterThan}},
- },
- want: Intervals{{&constraint{Version{major: 9}, greaterThan}}},
- },
- {
- name: ">_>_different_versions",
- is: Intervals{
- {&constraint{Version{major: 9}, greaterThan}},
- {&constraint{Version{major: 10}, greaterThan}},
- },
- want: Intervals{{&constraint{Version{major: 9}, greaterThan}}},
- },
-
- {
- name: ">_>=_same_version",
- is: Intervals{
- {&constraint{Version{major: 9}, greaterThan}},
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 9}, greaterThanOrEqualTo}}},
- },
- {
- name: ">_>=_lower_version",
- is: Intervals{
- {&constraint{Version{major: 9}, greaterThan}},
- {&constraint{Version{major: 8}, greaterThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 8}, greaterThanOrEqualTo}}},
- },
- {
- name: ">_>=_higher_version",
- is: Intervals{
- {&constraint{Version{major: 9}, greaterThan}},
- {&constraint{Version{major: 10}, greaterThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 9}, greaterThan}},
- },
- },
-
- {
- name: ">=_>=_same_version",
- is: Intervals{
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 9}, greaterThanOrEqualTo}}},
- },
- {
- name: ">=_>=_different_versions",
- is: Intervals{
- {&constraint{Version{major: 9}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 10}, greaterThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 9}, greaterThanOrEqualTo}}},
- },
-
- {
- name: "<=_>=_gap_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<=_>=_gap_<=_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- },
- {
- name: "<=_>=_gap_<_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<=_>=_gap_<_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- },
-
- {
- name: "<=_>_gap_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<=_>_gap_<=_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- },
- {
- name: "<=_>_gap_<_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<=_>_gap_<_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- },
-
- {
- name: "<_>=_gap_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<_>=_gap_<=_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- },
- {
- name: "<_>=_gap_<_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<_>=_gap_<_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- },
-
- {
- name: "<_>_gap_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<_>_gap_<=_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- },
- {
- name: "<_>_gap_<_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<_>_gap_<_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- },
-
- {
- name: "<=_>=_seamless_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<=_>=_seamless_<=_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- },
- {
- name: "<=_>=_seamless_<_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<=_>=_seamless_<_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- },
-
- {
- name: "<=_>_seamless_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<=_>_seamless_<=_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- },
- {
- name: "<=_>_seamless_<_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<=_>_seamless_<_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- },
-
- {
- name: "<_>=_seamless_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<_>=_seamless_<=_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- },
- {
- name: "<_>=_seamless_<_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<_>=_seamless_<_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}},
- {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- },
-
- {
- name: "<_>_seamless_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<_>_seamless_<=_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- },
- {
- name: "<_>_seamless_<_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<_>_seamless_<_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}},
- {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- },
-
- {
- name: "<=_>=_overlap_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 3}, lessThanOrEqualTo}},
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<=_>=_overlap_<=_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 3}, lessThanOrEqualTo}},
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- },
- {
- name: "<=_>=_overlap_<_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 3}, lessThanOrEqualTo}},
- {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<=_>=_overlap_<_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 3}, lessThanOrEqualTo}},
- {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- },
-
- {
- name: "<=_>_overlap_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 3}, lessThan}},
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<=_>_overlap_<=_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 3}, lessThan}},
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- },
- {
- name: "<=_>_overlap_<_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 3}, lessThan}},
- {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<=_>_overlap_<_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 3}, lessThan}},
- {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- },
-
- {
- name: "<_>=_overlap_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 3}, lessThanOrEqualTo}},
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<_>=_overlap_<=_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 3}, lessThanOrEqualTo}},
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- },
- {
- name: "<_>=_overlap_<_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 3}, lessThanOrEqualTo}},
- {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<_>=_overlap_<_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 3}, lessThanOrEqualTo}},
- {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- },
-
- {
- name: "<_>_overlap_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 3}, lessThan}},
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<_>_overlap_<=_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 3}, lessThan}},
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- },
- {
- name: "<_>_overlap_<_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 3}, lessThan}},
- {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: "<_>_overlap_<_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 3}, lessThan}},
- {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- },
-
- {
- name: "<=_gap_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 5}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- {&constraint{Version{major: 5}, greaterThanOrEqualTo}},
- },
- },
- {
- name: "<=_gap_<=_>",
- is: Intervals{
- {&constraint{Version{major: 5}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- {&constraint{Version{major: 5}, greaterThanOrEqualTo}},
- },
- },
- {
- name: "<=_gap_<_>=",
- is: Intervals{
- {&constraint{Version{major: 5}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- {&constraint{Version{major: 5}, greaterThanOrEqualTo}},
- },
- },
- {
- name: "<=_gap_<_>",
- is: Intervals{
- {&constraint{Version{major: 5}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- {&constraint{Version{major: 5}, greaterThanOrEqualTo}},
- },
- },
-
- {
- name: "<_gap_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 5}, greaterThan}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- {&constraint{Version{major: 5}, greaterThan}},
- },
- },
- {
- name: "<_gap_<=_>",
- is: Intervals{
- {&constraint{Version{major: 5}, greaterThan}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- {&constraint{Version{major: 5}, greaterThan}},
- },
- },
- {
- name: "<_gap_<_>=",
- is: Intervals{
- {&constraint{Version{major: 5}, greaterThan}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- {&constraint{Version{major: 5}, greaterThan}},
- },
- },
- {
- name: "<_gap_<_>",
- is: Intervals{
- {&constraint{Version{major: 5}, greaterThan}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- {&constraint{Version{major: 5}, greaterThan}},
- },
- },
-
- {
- name: ">=_gap_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: ">=_gap_<=_>",
- is: Intervals{
- {&constraint{Version{major: 1}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- },
- {
- name: ">=_gap_<_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: ">=_gap_<_>",
- is: Intervals{
- {&constraint{Version{major: 1}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- },
-
- {
- name: ">_gap_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, lessThan}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, lessThan}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: ">_gap_<=_>",
- is: Intervals{
- {&constraint{Version{major: 1}, lessThan}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, lessThan}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- },
- {
- name: ">_gap_<_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, lessThan}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, lessThan}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: ">_gap_<_>",
- is: Intervals{
- {&constraint{Version{major: 1}, lessThan}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, lessThan}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- },
-
- {
- name: "<=_cover_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 2}, greaterThanOrEqualTo}}},
- },
- {
- name: "<=_cover_<=_>",
- is: Intervals{
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 2}, greaterThanOrEqualTo}}},
- },
- {
- name: "<=_cover_<_>=",
- is: Intervals{
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 2}, greaterThanOrEqualTo}}},
- },
- {
- name: "<=_cover_<_>",
- is: Intervals{
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 2}, greaterThanOrEqualTo}}},
- },
-
- {
- name: "<_cover_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 2}, greaterThan}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 2}, greaterThan}}},
- },
- {
- name: "<_cover_<=_>",
- is: Intervals{
- {&constraint{Version{major: 2}, greaterThan}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 2}, greaterThan}}},
- },
- {
- name: "<_cover_<_>=",
- is: Intervals{
- {&constraint{Version{major: 2}, greaterThan}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 2}, greaterThan}}},
- },
- {
- name: "<_cover_<_>",
- is: Intervals{
- {&constraint{Version{major: 2}, greaterThan}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 2}, greaterThan}}},
- },
-
- {
- name: ">=_cover_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 5}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 5}, lessThanOrEqualTo}}},
- },
- {
- name: ">=_cover_<=_>",
- is: Intervals{
- {&constraint{Version{major: 5}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 5}, lessThanOrEqualTo}}},
- },
- {
- name: ">=_cover_<_>=",
- is: Intervals{
- {&constraint{Version{major: 5}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 5}, lessThanOrEqualTo}}},
- },
- {
- name: ">=_cover_<_>",
- is: Intervals{
- {&constraint{Version{major: 5}, lessThanOrEqualTo}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 5}, lessThanOrEqualTo}}},
- },
-
- {
- name: ">_cover_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 5}, lessThan}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 5}, lessThan}}},
- },
- {
- name: ">_cover_<=_>",
- is: Intervals{
- {&constraint{Version{major: 5}, lessThan}},
- {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 5}, lessThan}}},
- },
- {
- name: ">_cover_<_>=",
- is: Intervals{
- {&constraint{Version{major: 5}, lessThan}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 5}, lessThan}}},
- },
- {
- name: ">_cover_<_>",
- is: Intervals{
- {&constraint{Version{major: 5}, lessThan}},
- {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 5}, lessThan}}},
- },
-
- {
- name: "<=_within_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}},
- },
- {
- name: "<=_within_<=_>",
- is: Intervals{
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}},
- },
- {
- name: "<=_within_<_>=",
- is: Intervals{
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThan}}},
- },
- {
- name: "<=_within_<_>",
- is: Intervals{
- {&constraint{Version{major: 2}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThan}}},
- },
-
- {
- name: "<_within_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 2}, greaterThan}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}},
- },
- {
- name: "<_within_<=_>",
- is: Intervals{
- {&constraint{Version{major: 2}, greaterThan}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}},
- },
- {
- name: "<_within_<_>=",
- is: Intervals{
- {&constraint{Version{major: 2}, greaterThan}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThan}}},
- },
- {
- name: "<_within_<_>",
- is: Intervals{
- {&constraint{Version{major: 2}, greaterThan}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThan}}},
- },
-
- {
- name: ">=_within_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 3}, lessThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}},
- },
- {
- name: ">=_within_<=_>",
- is: Intervals{
- {&constraint{Version{major: 3}, lessThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThan}}},
- },
- {
- name: ">=_within_<_>=",
- is: Intervals{
- {&constraint{Version{major: 3}, lessThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}},
- },
- {
- name: ">=_within_<_>",
- is: Intervals{
- {&constraint{Version{major: 3}, lessThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThan}}},
- },
-
- {
- name: ">_within_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 3}, lessThan}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}},
- },
- {
- name: ">_within_<=_>",
- is: Intervals{
- {&constraint{Version{major: 3}, lessThan}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThan}}},
- },
- {
- name: ">_within_<_>=",
- is: Intervals{
- {&constraint{Version{major: 3}, lessThan}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}},
- },
- {
- name: ">_within_<_>",
- is: Intervals{
- {&constraint{Version{major: 3}, lessThan}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThan}}},
- },
-
- {
- name: "<=_seamless_floor_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}},
- },
- {
- name: "<=_seamless_floor_<=_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}},
- },
- {
- name: "<=_seamless_floor_<_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}},
- },
- {
- name: "<=_seamless_floor_<_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}},
- },
-
- {
- name: "<_seamless_floor_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}},
- },
- {
- name: "<_seamless_floor_<=_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}},
- },
- {
- name: "<_seamless_floor_<_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThan}}},
- },
- {
- name: "<_seamless_floor_<_>",
- is: Intervals{
- {&constraint{Version{major: 1}, greaterThan}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThan}}},
- },
-
- {
- name: ">=_seamless_floor_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, lessThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}},
- },
- {
- name: ">=_seamless_floor_<=_>",
- is: Intervals{
- {&constraint{Version{major: 1}, lessThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThan}}},
- },
- {
- name: ">=_seamless_floor_<_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, lessThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}},
- },
- {
- name: ">=_seamless_floor_<_>",
- is: Intervals{
- {&constraint{Version{major: 1}, lessThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThan}}},
- },
-
- {
- name: ">_seamless_floor_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, lessThan}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}},
- },
- {
- name: ">_seamless_floor_<=_>",
- is: Intervals{
- {&constraint{Version{major: 1}, lessThan}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThan}}},
- },
- {
- name: ">_seamless_floor_<_>=",
- is: Intervals{
- {&constraint{Version{major: 1}, lessThan}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, lessThan}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- },
- {
- name: ">_seamless_floor_<_>",
- is: Intervals{
- {&constraint{Version{major: 1}, lessThan}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, lessThan}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- },
-
- {
- name: "<=_seamless_ceiling_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 4}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}},
- },
- {
- name: "<=_seamless_ceiling_<=_>",
- is: Intervals{
- {&constraint{Version{major: 4}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}},
- },
- {
- name: "<=_seamless_ceiling_<_>=",
- is: Intervals{
- {&constraint{Version{major: 4}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThan}}},
- },
- {
- name: "<=_seamless_ceiling_<_>",
- is: Intervals{
- {&constraint{Version{major: 4}, greaterThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThan}}},
- },
-
- {
- name: "<_seamless_ceiling_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 4}, greaterThan}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}},
- },
- {
- name: "<_seamless_ceiling_<=_>",
- is: Intervals{
- {&constraint{Version{major: 4}, greaterThan}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- {&constraint{Version{major: 4}, greaterThan}},
- },
- },
- {
- name: "<_seamless_ceiling_<_>=",
- is: Intervals{
- {&constraint{Version{major: 4}, greaterThan}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 1}, greaterThan}}},
- },
- {
- name: "<_seamless_ceiling_<_>",
- is: Intervals{
- {&constraint{Version{major: 4}, greaterThan}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- {&constraint{Version{major: 4}, greaterThan}},
- },
- },
-
- {
- name: ">=_seamless_ceiling_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 4}, lessThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}},
- },
- {
- name: ">=_seamless_ceiling_<=_>",
- is: Intervals{
- {&constraint{Version{major: 4}, lessThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}},
- },
- {
- name: ">=_seamless_ceiling_<_>=",
- is: Intervals{
- {&constraint{Version{major: 4}, lessThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}},
- },
- {
- name: ">=_seamless_ceiling_<_>",
- is: Intervals{
- {&constraint{Version{major: 4}, lessThanOrEqualTo}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}},
- },
-
- {
- name: ">_seamless_ceiling_<=_>=",
- is: Intervals{
- {&constraint{Version{major: 4}, lessThan}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}},
- },
- {
- name: ">_seamless_ceiling_<=_>",
- is: Intervals{
- {&constraint{Version{major: 4}, lessThan}},
- {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThan}}},
- },
- {
- name: ">_seamless_ceiling_<_>=",
- is: Intervals{
- {&constraint{Version{major: 4}, lessThan}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}},
- },
- {
- name: ">_seamless_ceiling_<_>",
- is: Intervals{
- {&constraint{Version{major: 4}, lessThan}},
- {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}},
- },
- want: Intervals{{&constraint{Version{major: 4}, lessThan}}},
- },
- }
-}
-
-func TestCompact(t *testing.T) {
- t.Parallel()
-
- for _, tt := range compactTestCases() {
- t.Run(tt.name, func(t *testing.T) {
- t.Parallel()
-
- if got := Compact(tt.is); !reflect.DeepEqual(got, tt.want) {
- t.Errorf("Compact(%q) = %q, want %q", tt.is, got, tt.want)
- }
- })
- }
-}
-
-func TestCompact_reverse(t *testing.T) {
- t.Parallel()
-
- for _, tt := range compactTestCases() {
- t.Run(tt.name+"_reverse", func(t *testing.T) {
- t.Parallel()
-
- slices.Reverse(tt.is)
-
- if got := Compact(tt.is); !reflect.DeepEqual(got, tt.want) {
- t.Errorf("Compact(%q) = %q, want %q", tt.is, got, tt.want)
- }
- })
- }
-}
-
-func TestCompact_shuffle(t *testing.T) {
- t.Parallel()
-
- for _, tt := range compactTestCases() {
- t.Run(tt.name+"_shuffle", func(t *testing.T) {
- t.Parallel()
-
- rand.Shuffle(len(tt.is), func(i, j int) {
- tt.is[i], tt.is[j] = tt.is[j], tt.is[i]
- })
-
- if got := Compact(tt.is); !reflect.DeepEqual(got, tt.want) {
- t.Errorf("Compact(%q) = %q, want %q", tt.is, got, tt.want)
- }
- })
- }
-}
diff --git a/modifier.go b/modifier.go
index 2c3a5c3..2d2c803 100644
--- a/modifier.go
+++ b/modifier.go
@@ -8,7 +8,7 @@ const (
modifierRC modifier = -10
modifierBeta modifier = -20
modifierAlpha modifier = -30
- ErrUnexpectedModifier stringError = "unexpected modifier"
+ errUnexpectedModifier stringError = "unexpected modifier"
)
func newModifier(s string) (modifier, error) {
@@ -24,7 +24,8 @@ func newModifier(s string) (modifier, error) {
case "alpha", "a":
return modifierAlpha, nil
}
- return modifierStable, ErrUnexpectedModifier
+
+ return modifierStable, errUnexpectedModifier
}
func (s modifier) String() string {
diff --git a/op.go b/op.go
new file mode 100644
index 0000000..05cc668
--- /dev/null
+++ b/op.go
@@ -0,0 +1,53 @@
+package comver
+
+const (
+ greaterThanOrEqualTo op = iota
+ greaterThan
+ lessThan
+ lessThanOrEqualTo
+
+ errUnexpectedOp stringError = "unexpected op"
+)
+
+type op int8
+
+func (o op) String() string {
+ switch o {
+ case lessThanOrEqualTo:
+ return "<="
+ case lessThan:
+ return "<"
+ case greaterThan:
+ return ">"
+ case greaterThanOrEqualTo:
+ return ">="
+ default:
+ // logic error! This should never happen
+ panic(errUnexpectedOp)
+ }
+}
+
+func (o op) compare(other op) int {
+ i := int(o) - int(other)
+
+ switch {
+ case i < 0:
+ return -1
+ case i > 0:
+ return 1
+ default:
+ return 0
+ }
+}
+
+func (o op) ceilingBounded() bool {
+ return o == lessThan || o == lessThanOrEqualTo
+}
+
+func (o op) floorBounded() bool {
+ return o == greaterThan || o == greaterThanOrEqualTo
+}
+
+func (o op) inclusive() bool {
+ return o == lessThanOrEqualTo || o == greaterThanOrEqualTo
+}
diff --git a/op_test.go b/op_test.go
new file mode 100644
index 0000000..7e72cb7
--- /dev/null
+++ b/op_test.go
@@ -0,0 +1,120 @@
+package comver
+
+import "testing"
+
+func Test_op_compare(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ o op
+ other op
+ want int
+ }{
+ {
+ name: "greaterThanOrEqualTo_compare_greaterThanOrEqualTo",
+ o: greaterThanOrEqualTo,
+ other: greaterThanOrEqualTo,
+ want: 0,
+ },
+ {
+ name: "greaterThanOrEqualTo_compare_greaterThan",
+ o: greaterThanOrEqualTo,
+ other: greaterThan,
+ want: -1,
+ },
+ {
+ name: "greaterThanOrEqualTo_compare_lessThan",
+ o: greaterThanOrEqualTo,
+ other: lessThan,
+ want: -1,
+ },
+ {
+ name: "greaterThanOrEqualTo_compare_lessThanOrEqualTo",
+ o: greaterThanOrEqualTo,
+ other: lessThanOrEqualTo,
+ want: -1,
+ },
+ {
+ name: "greaterThan_compare_greaterThanOrEqualTo",
+ o: greaterThan,
+ other: greaterThanOrEqualTo,
+ want: 1,
+ },
+ {
+ name: "greaterThan_compare_greaterThan",
+ o: greaterThan,
+ other: greaterThan,
+ want: 0,
+ },
+ {
+ name: "greaterThan_compare_lessThan",
+ o: greaterThan,
+ other: lessThan,
+ want: -1,
+ },
+ {
+ name: "greaterThan_compare_lessThanOrEqualTo",
+ o: greaterThan,
+ other: lessThanOrEqualTo,
+ want: -1,
+ },
+ {
+ name: "lessThan_compare_greaterThanOrEqualTo",
+ o: lessThan,
+ other: greaterThanOrEqualTo,
+ want: 1,
+ },
+ {
+ name: "lessThan_compare_greaterThan",
+ o: lessThan,
+ other: greaterThan,
+ want: 1,
+ },
+ {
+ name: "lessThan_compare_lessThan",
+ o: lessThan,
+ other: lessThan,
+ want: 0,
+ },
+ {
+ name: "lessThan_compare_lessThanOrEqualTo",
+ o: lessThan,
+ other: lessThanOrEqualTo,
+ want: -1,
+ },
+ {
+ name: "lessThanOrEqualTo_compare_greaterThanOrEqualTo",
+ o: lessThanOrEqualTo,
+ other: greaterThanOrEqualTo,
+ want: 1,
+ },
+ {
+ name: "lessThanOrEqualTo_compare_greaterThan",
+ o: lessThanOrEqualTo,
+ other: greaterThan,
+ want: 1,
+ },
+ {
+ name: "lessThanOrEqualTo_compare_lessThan",
+ o: lessThanOrEqualTo,
+ other: lessThan,
+ want: 1,
+ },
+ {
+ name: "lessThanOrEqualTo_compare_lessThanOrEqualTo",
+ o: lessThanOrEqualTo,
+ other: lessThanOrEqualTo,
+ want: 0,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ if got := tt.o.compare(tt.other); got != tt.want {
+ t.Errorf("compare() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/or.go b/or.go
new file mode 100644
index 0000000..8b83cef
--- /dev/null
+++ b/or.go
@@ -0,0 +1,32 @@
+package comver
+
+//nolint:godox
+// TODO: Make Or to be []Constrainter so that we can nest Or
+
+// Or represents a logical OR operation between multiple [CeilingFloorConstrainter] instances.
+// The zero value for Or is a constraint could never be satisfied.
+type Or []CeilingFloorConstrainter
+
+// Check reports whether a [Version] satisfies the constraint.
+func (o Or) Check(v Version) bool {
+ for i := range o {
+ if o[i].Check(v) {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (o Or) String() string {
+ s := ""
+
+ for i := range o {
+ if i > 0 {
+ s += " || "
+ }
+ s += o[i].String()
+ }
+
+ return s
+}
diff --git a/version.go b/version.go
index 31d104b..836ddc3 100644
--- a/version.go
+++ b/version.go
@@ -11,10 +11,10 @@ const (
classicalVersioningRegex = `(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?`
dateOnlyVersioningRegex = `(\d{4})(?:[.:-]?(\d{2}))(?:[.:-]?(\d{2}))?(?:\.(\d+))?`
modifierRegex = `[._-]?(?:(stable|beta|b|rc|alpha|a|patch|pl|p)((?:[.-]?\d+)+)?)?`
- ErrEmptyString stringError = "version string is empty"
- ErrInvalidVersionString stringError = "invalid version string"
- ErrNotFixedVersion stringError = "not a fixed version"
- ErrDateVersionWithFourBits stringError = "date versions with 4 bits"
+ errEmptyString stringError = "version string is empty"
+ errInvalidVersionString stringError = "invalid version string"
+ errNotFixedVersion stringError = "not a fixed version"
+ errDateVersionWithFourBits stringError = "date versions with 4 bits"
)
var (
@@ -23,6 +23,7 @@ var (
)
// Version represents a single composer version.
+// The zero value for Version is v0.0.0.0 with an empty original string.
type Version struct {
major, minor, patch, tweak uint64 `exhaustruct:"optional"`
modifier modifier `exhaustruct:"optional"`
@@ -30,10 +31,10 @@ type Version struct {
original string `exhaustruct:"optional"`
}
-// NewVersion parses a given version string, attempts to coerce a version string into
+// Parse parses a given version string, attempts to coerce a version string into
// a [Version] object or return an error if unable to parse the version string.
//
-// If there is a leading v or a version listed without all parts (e.g. v1.2.p5+foo) it will
+// If there is a leading v or a version listed without all parts (e.g. v1.2.p5+foo) it
// attempt to coerce it into a valid composer version (e.g. 1.2.0.0-patch5). In both cases
// a [Version] object is returned that can be sorted, compared, and used in constraints.
//
@@ -42,7 +43,7 @@ type Version struct {
//
// [composer versioning]: https://github.com/composer/semver/
// [version_test.go]: https://github.com/typisttech/comver/blob/main/version_test.go
-func NewVersion(v string) (Version, error) { //nolint:cyclop,funlen
+func Parse(v string) (Version, error) { //nolint:cyclop,funlen
original := v
// normalize to lowercase for easier pattern matching
@@ -50,38 +51,38 @@ func NewVersion(v string) (Version, error) { //nolint:cyclop,funlen
v = strings.TrimSpace(v)
if v == "" {
- return Version{}, &ParseError{original, ErrEmptyString}
+ return Version{}, &ParseError{original, errEmptyString}
}
v = strings.TrimPrefix(v, "v")
if v == "" {
- return Version{}, &ParseError{original, ErrInvalidVersionString}
+ return Version{}, &ParseError{original, errInvalidVersionString}
}
if strings.Contains(v, " as ") {
- return Version{}, &ParseError{original, ErrNotFixedVersion}
+ return Version{}, &ParseError{original, errNotFixedVersion}
}
if hasSuffixAnyOf(v, "@stable", "@rc", "@beta", "@alpha", "@dev") {
- return Version{}, &ParseError{original, ErrNotFixedVersion}
+ return Version{}, &ParseError{original, errNotFixedVersion}
}
if containsAnyOf(v, "master", "trunk", "default") {
- return Version{}, &ParseError{original, ErrNotFixedVersion}
+ return Version{}, &ParseError{original, errNotFixedVersion}
}
if strings.HasPrefix(v, "dev-") {
- return Version{}, &ParseError{original, ErrNotFixedVersion}
+ return Version{}, &ParseError{original, errNotFixedVersion}
}
// strip off build metadata
v, metadata, _ := strings.Cut(v, "+")
if v == "" || strings.Contains(metadata, " ") {
- return Version{}, &ParseError{original, ErrInvalidVersionString}
+ return Version{}, &ParseError{original, errInvalidVersionString}
}
if strings.HasSuffix(v, "dev") {
- return Version{}, &ParseError{original, ErrNotFixedVersion}
+ return Version{}, &ParseError{original, errNotFixedVersion}
}
cv := Version{
@@ -96,7 +97,7 @@ func NewVersion(v string) (Version, error) { //nolint:cyclop,funlen
match = dm
}
if match == nil || len(match) != 7 {
- return Version{}, &ParseError{original, ErrInvalidVersionString}
+ return Version{}, &ParseError{original, errInvalidVersionString}
}
var err error
@@ -106,7 +107,7 @@ func NewVersion(v string) (Version, error) { //nolint:cyclop,funlen
}
// CalVer (as MAJOR) must be in YYYYMMDDhhmm or YYYYMMDD formats
if s := strconv.FormatUint(cv.major, 10); len(s) > 12 || len(s) == 11 || len(s) == 9 || len(s) == 7 {
- return Version{}, &ParseError{original, ErrInvalidVersionString}
+ return Version{}, &ParseError{original, errInvalidVersionString}
}
if cv.minor, err = strconv.ParseUint(match[2], 10, 64); match[2] != "" && err != nil {
@@ -118,7 +119,7 @@ func NewVersion(v string) (Version, error) { //nolint:cyclop,funlen
}
if cv.major >= 1000_00 && match[4] != "" {
- return Version{}, &ParseError{original, ErrDateVersionWithFourBits}
+ return Version{}, &ParseError{original, errDateVersionWithFourBits}
}
if cv.tweak, err = strconv.ParseUint(match[4], 10, 64); match[4] != "" && err != nil {
return Version{}, &ParseError{original, err}
@@ -133,12 +134,23 @@ func NewVersion(v string) (Version, error) { //nolint:cyclop,funlen
return cv, nil
}
+// MustParse is like [Parse] but panics if the version string cannot be parsed.
+func MustParse(v string) Version {
+ cv, err := Parse(v)
+ if err != nil {
+ panic(err)
+ }
+
+ return cv
+}
+
func hasSuffixAnyOf(s string, suffixes ...string) bool {
for _, suffix := range suffixes {
if strings.HasSuffix(s, suffix) {
return true
}
}
+
return false
}
@@ -148,6 +160,7 @@ func containsAnyOf(s string, substrs ...string) bool {
return true
}
}
+
return false
}
@@ -177,15 +190,19 @@ func (v Version) Short() string {
return s
}
+// Original returns the original version string passed into [Parse].
+// Empty string is returned when [Version] is the zero value.
func (v Version) Original() string {
return v.original
}
-// Compare returns an integer comparing two versions.
+// Compare returns an integer comparing two [Version] instances.
//
-// Pre-release versions are compared according to semantic version precedence.
+// Pre-release versions are compared according to [semantic version precedence].
// The result is 0 when v == w, -1 when v < w, or +1 when v > w.
-func (v Version) Compare(w Version) int { ////nolint:cyclop,funlen
+//
+// [semantic version precedence]: https://semver.org/#spec-item-11
+func (v Version) Compare(w Version) int { //nolint:cyclop,funlen
switch {
case v.String() == w.String():
return 0
@@ -218,7 +235,7 @@ func (v Version) Compare(w Version) int { ////nolint:cyclop,funlen
vPres := strings.Split(v.preRelease, ".")
wPres := strings.Split(w.preRelease, ".")
- // comparing each dot separated identifier from left to right
+ // comparing each dot separated identifier from ceiling to floor
for i := range vPres {
// a larger set of pre-release fields has a higher precedence than a smaller set
if i >= len(wPres) {
@@ -241,6 +258,7 @@ func (v Version) Compare(w Version) int { ////nolint:cyclop,funlen
if vii > wii {
return +1
}
+
return -1
}
@@ -252,16 +270,18 @@ func (v Version) Compare(w Version) int { ////nolint:cyclop,funlen
if vi > wi {
return +1
}
+
return -1
}
//nolint:godox
// TODO: Find out whether composer/semver supports this
//
- // numeric identifiers always have lower precedence than non-numeric identifiers
+ // numeric identifiers always have floor precedence than non-numeric identifiers
if !vid && wid {
return +1
}
+
return -1
}
@@ -275,5 +295,6 @@ func isDigits(s string) bool {
return false
}
}
+
return true
}
diff --git a/version_example_test.go b/version_example_test.go
index cdac780..6f2c75c 100644
--- a/version_example_test.go
+++ b/version_example_test.go
@@ -6,93 +6,38 @@ import (
"github.com/typisttech/comver"
)
-func ExampleNewVersion() {
- v, _ := comver.NewVersion("1.2.3")
+func ExampleParse() {
+ v, _ := comver.Parse("1.2.3")
fmt.Println(v)
// Output: 1.2.3.0
}
-func ExampleNewVersion_full() {
- v, _ := comver.NewVersion("1.2.3.4-beta.5+foo")
+func ExampleParse_full() {
+ v, _ := comver.Parse("1.2.3.4-beta.5+foo")
fmt.Println(v)
// Output: 1.2.3.4-beta5
}
-func ExampleNewVersion_withLeadingV() {
- v, _ := comver.NewVersion("v1.2.3.4-beta.5+foo")
-
- fmt.Println(v)
- // Output: 1.2.3.4-beta5
-}
-
-func ExampleNewVersion_error() {
- _, err := comver.NewVersion("not a version")
+func ExampleParse_error() {
+ _, err := comver.Parse("not a version")
fmt.Println(err)
// Output: error parsing version string "not a version"
}
-func ExampleNewVersion_dateCal() {
- v, _ := comver.NewVersion("2010-01-02")
-
- fmt.Println(v)
- // Output: 2010.1.2.0
-}
-
-func ExampleNewVersion_spacePadding() {
- v, _ := comver.NewVersion(" 1.0.0")
-
- fmt.Println(v)
- // Output: 1.0.0.0
-}
-
-func ExampleNewVersion_modifierShorthand() {
- v, _ := comver.NewVersion("1.2.3-b5")
-
- fmt.Println(v)
- // Output: 1.2.3.0-beta5
-}
-
-func ExampleNewVersion_zeroPadding() {
- v, _ := comver.NewVersion("00.01.03.04")
-
- fmt.Println(v)
- // Output: 0.1.3.4
-}
-
-func ExampleNewVersion_dateWithFourBits() {
- _, err := comver.NewVersion("20100102.0.3.4")
-
- fmt.Println(err)
- // Output: error parsing version string "20100102.0.3.4"
-}
-
-func ExampleNewVersion_invalidModifier() {
- _, err := comver.NewVersion("1.0.0-meh")
-
- fmt.Println(err)
- // Output: error parsing version string "1.0.0-meh"
-}
-
-func ExampleNewVersion_tooManyBits() {
- _, err := comver.NewVersion("1.0.0.0.0")
-
- fmt.Println(err)
- // Output: error parsing version string "1.0.0.0.0"
-}
-
func ExampleVersion_Compare() {
- v1, _ := comver.NewVersion("1")
- v2, _ := comver.NewVersion("2")
- v3, _ := comver.NewVersion("3")
+ v1 := comver.MustParse("1")
+ v2 := comver.MustParse("2")
+ w2 := comver.MustParse("2")
+ v3 := comver.MustParse("3")
v2v1 := v2.Compare(v1)
fmt.Println(v2v1)
- v2v2 := v2.Compare(v2) //nolint:gocritic
- fmt.Println(v2v2)
+ v2w2 := v2.Compare(w2)
+ fmt.Println(v2w2)
v2v3 := v2.Compare(v3)
fmt.Println(v2v3)
@@ -104,93 +49,95 @@ func ExampleVersion_Compare() {
}
func ExampleVersion_Compare_patch() {
- v1, _ := comver.NewVersion("1")
- v1p, _ := comver.NewVersion("1.patch")
+ v1 := comver.MustParse("1")
+ v1p := comver.MustParse("1.patch")
- v1v1p := v1.Compare(v1p)
+ got := v1.Compare(v1p)
- fmt.Println(v1v1p)
+ fmt.Println(got)
// Output: -1
}
func ExampleVersion_Compare_preRelease() {
- v1b5, _ := comver.NewVersion("1.0.0-beta.5")
- v1b6, _ := comver.NewVersion("1.0.0-beta.6")
+ v1b5 := comver.MustParse("1.0.0-beta.5")
+ v1b6 := comver.MustParse("1.0.0-beta.6")
- v1b5v1b6 := v1b5.Compare(v1b6)
+ got := v1b5.Compare(v1b6)
- fmt.Println(v1b5v1b6)
+ fmt.Println(got)
// Output: -1
}
-func ExampleVersion_Short_major() {
- v, _ := comver.NewVersion("1")
-
- s := v.Short()
-
- fmt.Println(s)
- // Output: 1
-}
-
-func ExampleVersion_Short_minor() {
- v, _ := comver.NewVersion("1.2")
-
- s := v.Short()
-
- fmt.Println(s)
- // Output: 1.2
-}
-
-func ExampleVersion_Short_patch() {
- v, _ := comver.NewVersion("1.2.3")
-
- s := v.Short()
-
- fmt.Println(s)
- // Output: 1.2.3
-}
-
-func ExampleVersion_Short_tweak() {
- v, _ := comver.NewVersion("1.2.3.4")
+func ExampleVersion_Compare_metadata() {
+ foo := comver.MustParse("1.0.0+foo")
+ bar := comver.MustParse("1.0.0+bar")
- s := v.Short()
+ got := foo.Compare(bar)
- fmt.Println(s)
- // Output: 1.2.3.4
+ fmt.Println(got)
+ // Output: 0
}
-func ExampleVersion_Short_modifier() {
- v, _ := comver.NewVersion("1.2.3.4.beta")
-
- s := v.Short()
-
- fmt.Println(s)
- // Output: 1.2.3.4-beta
-}
-
-func ExampleVersion_Short_preRelease() {
- v, _ := comver.NewVersion("1.2.3.4-beta5")
-
- s := v.Short()
+func ExampleVersion_Short() {
+ ss := []string{
+ "1",
+ "1.2",
+ "1.2.3",
+ "1.2.3+foo",
+ "1.2.3.4",
+ "1.2.3.4.beta",
+ "1.2.3.4-beta5",
+ "1.2.3.4-beta5+foo",
+ "1.b5+foo",
+ }
- fmt.Println(s)
- // Output: 1.2.3.4-beta5
-}
+ for _, s := range ss {
+ v := comver.MustParse(s)
+ got := v.Short()
-func ExampleVersion_Short_metadata() {
- v, _ := comver.NewVersion("1.2.3.4-beta5+foo")
+ fmt.Printf("%-19q => %v\n", s, got)
+ }
- s := v.Short()
-
- fmt.Println(s)
- // Output: 1.2.3.4-beta5
+ // Output:
+ // "1" => 1
+ // "1.2" => 1.2
+ // "1.2.3" => 1.2.3
+ // "1.2.3+foo" => 1.2.3
+ // "1.2.3.4" => 1.2.3.4
+ // "1.2.3.4.beta" => 1.2.3.4-beta
+ // "1.2.3.4-beta5" => 1.2.3.4-beta5
+ // "1.2.3.4-beta5+foo" => 1.2.3.4-beta5
+ // "1.b5+foo" => 1-beta5
}
func ExampleVersion_Original() {
- v, _ := comver.NewVersion("1.b5+foo")
-
- s := v.Original()
+ ss := []string{
+ "1",
+ "1.2",
+ "1.2.3",
+ "1.2.3+foo",
+ "1.2.3.4",
+ "1.2.3.4.beta",
+ "1.2.3.4-beta5",
+ "1.2.3.4-beta5+foo",
+ "1.b5+foo",
+ }
+
+ for _, s := range ss {
+ v := comver.MustParse(s)
+ got := v.Original()
+
+ fmt.Printf("%-19q => %v\n", s, got)
+ }
- fmt.Println(s)
- // Output: 1.b5+foo
+ // Output:
+ // "1" => 1
+ // "1.2" => 1.2
+ // "1.2.3" => 1.2.3
+ // "1.2.3+foo" => 1.2.3+foo
+ // "1.2.3.4" => 1.2.3.4
+ // "1.2.3.4.beta" => 1.2.3.4.beta
+ // "1.2.3.4-beta5" => 1.2.3.4-beta5
+ // "1.2.3.4-beta5+foo" => 1.2.3.4-beta5+foo
+ // "1.b5+foo" => 1.b5+foo
}
diff --git a/version_test.go b/version_test.go
index 08ccbde..6d33d28 100644
--- a/version_test.go
+++ b/version_test.go
@@ -5,10 +5,12 @@ import (
"testing"
)
-func TestNewVersion(t *testing.T) {
- t.Parallel()
-
- tests := []struct {
+func goodVersionTestCases() []struct {
+ name string
+ v string
+ want string
+} {
+ return []struct {
name string
v string
want string
@@ -69,28 +71,54 @@ func TestNewVersion(t *testing.T) {
// additional tests
{"parses dates y-m", "2010-01", "2010.1.0.0"},
}
- for _, tt := range tests {
+}
+
+func TestParse(t *testing.T) {
+ t.Parallel()
+
+ for _, tt := range goodVersionTestCases() {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
- got, err := NewVersion(tt.v)
+ got, err := Parse(tt.v)
if err != nil {
- t.Fatalf("NewVersion() error = %v, wantErr %v", err, nil)
+ t.Fatalf("Parse() error = %v, wantErr %v", err, nil)
}
if gotString := got.String(); gotString != tt.want {
- t.Errorf("NewVersion().String() got = %q, want %v", gotString, tt.want)
+ t.Errorf("Parse().String() got = %q, want %v", gotString, tt.want)
}
- if gotOriginal := got.original; gotOriginal != tt.v {
- t.Errorf("NewVersion().original got = %q, want %v", gotOriginal, tt.v)
+ if gotOriginal := got.Original(); gotOriginal != tt.v {
+ t.Errorf("Parse().Original() got = %q, want %v", gotOriginal, tt.v)
}
})
}
}
-func TestNewVersion_ParseError(t *testing.T) {
+func TestMustParse(t *testing.T) {
t.Parallel()
- tests := []struct {
+ for _, tt := range goodVersionTestCases() {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ got := MustParse(tt.v)
+
+ if gotString := got.String(); gotString != tt.want {
+ t.Errorf("MustParse().String() got = %q, want %v", gotString, tt.want)
+ }
+ if gotOriginal := got.Original(); gotOriginal != tt.v {
+ t.Errorf("MustParse().Original() got = %q, want %v", gotOriginal, tt.v)
+ }
+ })
+ }
+}
+
+func badVersionTestCases() []struct {
+ name string
+ v string
+ wantErr error
+} {
+ return []struct {
name string
v string
wantErr error
@@ -98,99 +126,143 @@ func TestNewVersion_ParseError(t *testing.T) {
// composer/semver supports a lot of different version formats, but we only support a subset of them
// taken from composer/semver VersionParserTest::successfulNormalizedVersions()
// https://github.com/composer/semver/blob/1d09200268e7d1052ded8e5da9c73c96a63d18f5/tests/VersionParserTest.php#L65-L142
- {"parses state", "1.0.0RC1dev", ErrNotFixedVersion},
- {"CI parsing", "1.0.0-rC15-dev", ErrNotFixedVersion},
- {"delimiters", "1.0.0.RC.15-dev", ErrNotFixedVersion},
- {"patch replace", "1.0.0.pl3-dev", ErrNotFixedVersion},
- {"forces w.x.y.z", "1.0-dev", ErrNotFixedVersion},
- {"parses dates w/ - and .", "2010-01-02-10-20-30.0.3", ErrInvalidVersionString},
- {"parses dates w/ - and ./2", "2010-01-02-10-20-30.5", ErrInvalidVersionString},
- {"parses datetime", "20100102-203040", ErrInvalidVersionString},
- {"parses date dev", "20100102.x-dev", ErrNotFixedVersion},
- {"parses datetime dev", "20100102.203040.x-dev", ErrNotFixedVersion},
- {"parses dt+number", "20100102203040-10", ErrInvalidVersionString},
- {"parses dt+patch", "20100102-203040-p1", ErrInvalidVersionString},
- {"parses dt Ym dev", "201903.x-dev", ErrNotFixedVersion},
- {"parses master", "dev-master", ErrNotFixedVersion},
- {"parses master w/o dev", "master", ErrNotFixedVersion},
- {"parses trunk", "dev-trunk", ErrNotFixedVersion},
- {"parses branches", "1.x-dev", ErrNotFixedVersion},
- {"parses arbitrary", "dev-feature-foo", ErrNotFixedVersion},
- {"parses arbitrary/2", "DEV-FOOBAR", ErrNotFixedVersion},
- {"parses arbitrary/3", "dev-feature/foo", ErrNotFixedVersion},
- {"parses arbitrary/4", "dev-feature+issue-1", ErrNotFixedVersion},
- {"ignores aliases", "dev-master as 1.0.0", ErrNotFixedVersion},
- {"ignores aliases/2", "dev-load-varnish-only-when-used as ^2.0", ErrNotFixedVersion},
- {"ignores aliases/3", "dev-load-varnish-only-when-used@dev as ^2.0@dev", ErrNotFixedVersion},
- {"ignores stability", "1.0.0+foo@dev", ErrNotFixedVersion},
- {"ignores stability/2", "dev-load-varnish-only-when-used@stable", ErrNotFixedVersion},
- {"semver metadata/7", "1.0.0-0.3.7", ErrInvalidVersionString}, // composer/semver doesn't support this
- {"semver metadata/8", "1.0.0-x.7.z.92", ErrInvalidVersionString}, // composer/semver doesn't support this
- {"metadata w/ alias", "1.0.0+foo as 2.0", ErrNotFixedVersion},
- {"keep zero-padding/5", "041.x-dev", ErrNotFixedVersion},
- {"keep zero-padding/6", "dev-041.003", ErrNotFixedVersion},
- {"dev with mad name", "dev-1.0.0-dev<1.0.5-dev", ErrNotFixedVersion},
- {"dev prefix with spaces", "dev-foo bar", ErrNotFixedVersion},
+ {"parses state", "1.0.0RC1dev", errNotFixedVersion},
+ {"CI parsing", "1.0.0-rC15-dev", errNotFixedVersion},
+ {"delimiters", "1.0.0.RC.15-dev", errNotFixedVersion},
+ {"patch replace", "1.0.0.pl3-dev", errNotFixedVersion},
+ {"forces w.x.y.z", "1.0-dev", errNotFixedVersion},
+ {"parses dates w/ - and .", "2010-01-02-10-20-30.0.3", errInvalidVersionString},
+ {"parses dates w/ - and ./2", "2010-01-02-10-20-30.5", errInvalidVersionString},
+ {"parses datetime", "20100102-203040", errInvalidVersionString},
+ {"parses date dev", "20100102.x-dev", errNotFixedVersion},
+ {"parses datetime dev", "20100102.203040.x-dev", errNotFixedVersion},
+ {"parses dt+number", "20100102203040-10", errInvalidVersionString},
+ {"parses dt+patch", "20100102-203040-p1", errInvalidVersionString},
+ {"parses dt Ym dev", "201903.x-dev", errNotFixedVersion},
+ {"parses master", "dev-master", errNotFixedVersion},
+ {"parses master w/o dev", "master", errNotFixedVersion},
+ {"parses trunk", "dev-trunk", errNotFixedVersion},
+ {"parses branches", "1.x-dev", errNotFixedVersion},
+ {"parses arbitrary", "dev-feature-foo", errNotFixedVersion},
+ {"parses arbitrary/2", "DEV-FOOBAR", errNotFixedVersion},
+ {"parses arbitrary/3", "dev-feature/foo", errNotFixedVersion},
+ {"parses arbitrary/4", "dev-feature+issue-1", errNotFixedVersion},
+ {"ignores aliases", "dev-master as 1.0.0", errNotFixedVersion},
+ {"ignores aliases/2", "dev-load-varnish-only-when-used as ^2.0", errNotFixedVersion},
+ {"ignores aliases/3", "dev-load-varnish-only-when-used@dev as ^2.0@dev", errNotFixedVersion},
+ {"ignores stability", "1.0.0+foo@dev", errNotFixedVersion},
+ {"ignores stability/2", "dev-load-varnish-only-when-used@stable", errNotFixedVersion},
+ {"semver metadata/7", "1.0.0-0.3.7", errInvalidVersionString}, // composer/semver doesn't support this
+ {"semver metadata/8", "1.0.0-x.7.z.92", errInvalidVersionString}, // composer/semver doesn't support this
+ {"metadata w/ alias", "1.0.0+foo as 2.0", errNotFixedVersion},
+ {"keep zero-padding/5", "041.x-dev", errNotFixedVersion},
+ {"keep zero-padding/6", "dev-041.003", errNotFixedVersion},
+ {"dev with mad name", "dev-1.0.0-dev<1.0.5-dev", errNotFixedVersion},
+ {"dev prefix with spaces", "dev-foo bar", errNotFixedVersion},
// composer/semver doesn't support these
// taken from composer/semver VersionParserTest::failingNormalizedVersions()
// https://github.com/composer/semver/blob/1d09200268e7d1052ded8e5da9c73c96a63d18f5/tests/VersionParserTest.php#L158-L183
- {"empty", "", ErrEmptyString},
- {"invalid chars", "a", ErrInvalidVersionString},
- {"invalid type", "1.0.0-meh", ErrInvalidVersionString},
- {"too many bits", "1.0.0.0.0", ErrInvalidVersionString},
- {"non-dev arbitrary", "feature-foo", ErrInvalidVersionString},
- {"metadata w/ space", "1.0.0+foo bar", ErrInvalidVersionString},
- {"maven style release", "1.0.1-SNAPSHOT", ErrInvalidVersionString},
- {"dev with less than", "1.0.0<1.0.5-dev", ErrNotFixedVersion},
- {"dev with less than/2", "1.0.0-dev<1.0.5-dev", ErrNotFixedVersion},
- {"dev suffix with spaces", "foo bar-dev", ErrNotFixedVersion},
- {"any with spaces", "1.0 .2", ErrInvalidVersionString},
- {"no version, no alias", " as ", ErrInvalidVersionString},
- {"no version, only alias", " as 1.2", ErrInvalidVersionString},
- {"just an operator", "^", ErrInvalidVersionString},
- {"just an operator/2", "^8 || ^", ErrInvalidVersionString},
- {"just an operator/3", "~", ErrInvalidVersionString},
- {"just an operator/4", "~1 ~", ErrInvalidVersionString},
- {"constraint", "~1", ErrInvalidVersionString},
- {"constraint/2", "^1", ErrInvalidVersionString},
- {"constraint/3", "1.*", ErrInvalidVersionString},
- {"date versions with 4 bits", "20100102.0.3.4", ErrDateVersionWithFourBits},
- {"date versions with 4 bits/earliest year", "100000.0.0.0", ErrDateVersionWithFourBits},
- {"invalid CalVer (as MAJOR) versions/YYYYMMD", "2023013.0.0", ErrInvalidVersionString},
- {"invalid CalVer (as MAJOR) versions/YYYYMMDDh", "202301311.0.0", ErrInvalidVersionString},
- {"invalid CalVer (as MAJOR) versions/YYYYMMDDhhm", "20230131000.0.0", ErrInvalidVersionString},
- {"invalid CalVer (as MAJOR) versions/YYYYMMDDhhmmX", "2023013100000.0.0", ErrInvalidVersionString},
+ {"empty", "", errEmptyString},
+ {"invalid chars", "a", errInvalidVersionString},
+ {"invalid type", "1.0.0-meh", errInvalidVersionString},
+ {"too many bits", "1.0.0.0.0", errInvalidVersionString},
+ {"non-dev arbitrary", "feature-foo", errInvalidVersionString},
+ {"metadata w/ space", "1.0.0+foo bar", errInvalidVersionString},
+ {"maven style release", "1.0.1-SNAPSHOT", errInvalidVersionString},
+ {"dev with less than", "1.0.0<1.0.5-dev", errNotFixedVersion},
+ {"dev with less than/2", "1.0.0-dev<1.0.5-dev", errNotFixedVersion},
+ {"dev suffix with spaces", "foo bar-dev", errNotFixedVersion},
+ {"any with spaces", "1.0 .2", errInvalidVersionString},
+ {"no version, no alias", " as ", errInvalidVersionString},
+ {"no version, only alias", " as 1.2", errInvalidVersionString},
+ {"just an operator", "^", errInvalidVersionString},
+ {"just an operator/2", "^8 || ^", errInvalidVersionString},
+ {"just an operator/3", "~", errInvalidVersionString},
+ {"just an operator/4", "~1 ~", errInvalidVersionString},
+ {"constraint", "~1", errInvalidVersionString},
+ {"constraint/2", "^1", errInvalidVersionString},
+ {"constraint/3", "1.*", errInvalidVersionString},
+ {"date versions with 4 bits", "20100102.0.3.4", errDateVersionWithFourBits},
+ {"date versions with 4 bits/earliest year", "100000.0.0.0", errDateVersionWithFourBits},
+ {"invalid CalVer (as MAJOR) versions/YYYYMMD", "2023013.0.0", errInvalidVersionString},
+ {"invalid CalVer (as MAJOR) versions/YYYYMMDDh", "202301311.0.0", errInvalidVersionString},
+ {"invalid CalVer (as MAJOR) versions/YYYYMMDDhhm", "20230131000.0.0", errInvalidVersionString},
+ {"invalid CalVer (as MAJOR) versions/YYYYMMDDhhmmX", "2023013100000.0.0", errInvalidVersionString},
// composer/semver doesn't support these.
// taken from https://semver.org/#spec-item-11
- {"incompatible semver", "1.0.0-alpha.beta", ErrInvalidVersionString},
+ {"incompatible semver", "1.0.0-alpha.beta", errInvalidVersionString},
}
- for _, tt := range tests {
+}
+
+func TestParse_ParseError(t *testing.T) {
+ t.Parallel()
+
+ for _, tt := range badVersionTestCases() {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
- got, err := NewVersion(tt.v)
+ got, err := Parse(tt.v)
if err == nil {
- t.Fatalf("NewVersion() got = %s error = %v, wantErr %v", got, err, tt.wantErr)
+ t.Fatalf("Parse() got = %s error = %v, wantErr %v", got, err, tt.wantErr)
}
if !errors.Is(err, tt.wantErr) {
- t.Errorf("NewVersion() error = %#v, wantErr %#v", err, tt.wantErr)
+ t.Errorf("Parse() error = %#v, wantErr %#v", err, tt.wantErr)
}
var wantParseError *ParseError
if !errors.As(err, &wantParseError) {
- t.Fatalf("NewVersion() error = %#v, wantErr %#v", err, wantParseError)
+ t.Fatalf("Parse() error = %#v, wantErr %#v", err, wantParseError)
}
if wantParseError.original != tt.v {
- t.Errorf("NewVersion() error.original = %v, want %v", wantParseError.original, tt.v)
+ t.Errorf("Parse() error.original = %v, want %v", wantParseError.original, tt.v)
}
})
}
}
+func TestMustParse_ParseError(t *testing.T) {
+ t.Parallel()
+
+ for _, tt := range badVersionTestCases() {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ var got Version
+
+ defer func() {
+ err := recover()
+ if err == nil {
+ t.Fatalf("MustParse() got = %s panic = %v, wantErr %v", got, err, tt.wantErr)
+ }
+
+ e, ok := err.(error)
+ if !ok {
+ t.Fatalf("MustParse() doesn't panic with error got = %s panic = %v, wantErr %v", got, err, tt.wantErr)
+ }
+
+ if !errors.Is(e, tt.wantErr) {
+ t.Fatalf("MustParse() got = %s panic = %v, wantErr %v", got, err, tt.wantErr)
+ }
+
+ var wantParseError *ParseError
+ if !errors.As(e, &wantParseError) {
+ t.Fatalf("MustParse() panic = %#v, wantErr %#v", err, wantParseError)
+ }
+
+ if wantParseError.original != tt.v {
+ t.Errorf("MustParse() error.original = %v, want %v", wantParseError.original, tt.v)
+ }
+ }()
+
+ got = MustParse(tt.v)
+ })
+ }
+}
+
func TestVersion_Compare(t *testing.T) {
t.Parallel()
@@ -236,21 +308,21 @@ func TestVersion_Compare(t *testing.T) {
t.Run(tt.v+"<=>"+tt.w, func(t *testing.T) {
t.Parallel()
- v, err := NewVersion(tt.v)
+ v, err := Parse(tt.v)
if err != nil {
- t.Fatalf("NewVersion(%q) error = %v, wantErr %v", tt.v, err, nil)
+ t.Fatalf("Parse(%q) error = %v, wantErr %v", tt.v, err, nil)
}
- w, err := NewVersion(tt.w)
+ w, err := Parse(tt.w)
if err != nil {
- t.Fatalf("NewVersion(%q) error = %v, wantErr %v", tt.w, err, nil)
+ t.Fatalf("Parse(%q) error = %v, wantErr %v", tt.w, err, nil)
}
if got := v.Compare(w); got != tt.want {
- t.Errorf("%q.Compare(%q) = %v, want %v", tt.v, tt.w, got, tt.want)
+ t.Errorf("%q.compare(%q) = %v, want %v", tt.v, tt.w, got, tt.want)
}
if got := w.Compare(v); got != -1*tt.want {
- t.Errorf("%q.Compare(%q) = %v, want %v", tt.w, tt.v, got, tt.want)
+ t.Errorf("%q.compare(%q) = %v, want %v", tt.w, tt.v, got, tt.want)
}
})
}
@@ -311,9 +383,9 @@ func TestVersion_String(t *testing.T) {
t.Run(tt.v, func(t *testing.T) {
t.Parallel()
- v, err := NewVersion(tt.v)
+ v, err := Parse(tt.v)
if err != nil {
- t.Fatalf("NewVersion(%q) error = %v, wantErr %v", tt.v, err, nil)
+ t.Fatalf("Parse(%q) error = %v, wantErr %v", tt.v, err, nil)
}
if got := v.String(); got != tt.want {
@@ -378,9 +450,9 @@ func TestVersion_Short(t *testing.T) {
t.Run(tt.v, func(t *testing.T) {
t.Parallel()
- v, err := NewVersion(tt.v)
+ v, err := Parse(tt.v)
if err != nil {
- t.Fatalf("NewVersion(%q) error = %v, wantErr %v", tt.v, err, nil)
+ t.Fatalf("Parse(%q) error = %v, wantErr %v", tt.v, err, nil)
}
if got := v.Short(); got != tt.want {
@@ -389,3 +461,21 @@ func TestVersion_Short(t *testing.T) {
})
}
}
+
+func TestVersion_zero(t *testing.T) {
+ t.Parallel()
+
+ v := Version{}
+
+ if got := v.String(); got != "0.0.0.0" {
+ t.Errorf("Version{}.String() = %q, want %q", got, "0.0.0.0")
+ }
+
+ if got := v.Short(); got != "0" {
+ t.Errorf("Version{}.Short() = %q, want %q", got, "0")
+ }
+
+ if got := v.Original(); got != "" {
+ t.Errorf("Version{}.Original() = %q, want %q", got, "")
+ }
+}