diff --git a/v4/range.go b/v4/range.go index 95f7139..09bc3f5 100644 --- a/v4/range.go +++ b/v4/range.go @@ -1,6 +1,8 @@ package semver import ( + "bytes" + "errors" "fmt" "strconv" "strings" @@ -29,28 +31,82 @@ func wildcardTypefromInt(i int) wildcardType { } } -type comparator func(Version, Version) bool +//----------------------------------------------------------------------------------------------------------// +// +//----------------------------------------------------------------------------------------------------------// -var ( - compEQ comparator = func(v1 Version, v2 Version) bool { - return v1.Compare(v2) == 0 +type comparator uint8 + +const ( + compInvalid comparator = iota + compEQ + compNE + compGT + compGE + compLT + compLE +) + +func parseComparator(s string) comparator { + switch s { + case "==", "", "=": + return compEQ + case ">": + return compGT + case ">=": + return compGE + case "<": + return compLT + case "<=": + return compLE + case "!", "!=": + return compNE } - compNE = func(v1 Version, v2 Version) bool { + + return compInvalid +} + +func (c comparator) compare(v1 Version, v2 Version) bool { + switch c { + case compEQ: + return v1.Compare(v2) == 0 + case compNE: return v1.Compare(v2) != 0 - } - compGT = func(v1 Version, v2 Version) bool { + case compGT: return v1.Compare(v2) == 1 - } - compGE = func(v1 Version, v2 Version) bool { + case compGE: return v1.Compare(v2) >= 0 - } - compLT = func(v1 Version, v2 Version) bool { + case compLT: return v1.Compare(v2) == -1 - } - compLE = func(v1 Version, v2 Version) bool { + case compLE: return v1.Compare(v2) <= 0 + default: + panic("invalid comparator") } -) +} + +func (c comparator) String() string { + switch c { + case compEQ: + return "" + case compNE: + return "!" + case compGT: + return ">" + case compGE: + return ">=" + case compLT: + return "<" + case compLE: + return "<=" + default: + panic("invalid comparator") + } +} + +//----------------------------------------------------------------------------------------------------------// +// +//----------------------------------------------------------------------------------------------------------// type versionRange struct { v Version @@ -58,31 +114,73 @@ type versionRange struct { } // rangeFunc creates a Range from the given versionRange. -func (vr *versionRange) rangeFunc() Range { - return Range(func(v Version) bool { - return vr.c(v, vr.v) - }) +func (vr *versionRange) Range(v Version) bool { + return vr.c.compare(v, vr.v) +} + +func (vr *versionRange) String() string { + return vr.c.String() + vr.v.String() } // Range represents a range of versions. -// A Range can be used to check if a Version satisfies it: +// Ranger is slightly different from Range, that it can be converted back to string. +// A Ranger can be used to check if a Version satisfies it: // -// range, err := semver.ParseRange(">1.0.0 <2.0.0") -// range(semver.MustParse("1.1.1") // returns true -type Range func(Version) bool - -// OR combines the existing Range with another Range using logical OR. -func (rf Range) OR(f Range) Range { - return Range(func(v Version) bool { - return rf(v) || f(v) - }) +// r, err := semver.ParseRanger(">1.0.0 <2.0.0") +// r.Range(semver.MustParse("1.1.1") // returns true +// r.String() // returns ">1.0.0 <2.0.0" back +type Ranger interface { + Range(b Version) bool + fmt.Stringer +} + +type andRange []Ranger + +func (r andRange) Range(v Version) bool { + for _, i := range r { + if !i.Range(v) { + return false + } + } + return true } -// AND combines the existing Range with another Range using logical AND. -func (rf Range) AND(f Range) Range { - return Range(func(v Version) bool { - return rf(v) && f(v) - }) +func (r andRange) String() string { + strs := make([]string, len(r)) + for i, item := range r { + strs[i] = item.String() + } + + return strings.Join(strs, " ") +} + +type orRange []Ranger + +func (r orRange) Range(v Version) bool { + for _, i := range r { + if i.Range(v) { + return true + } + } + return false +} + +func (r orRange) String() string { + strs := make([]string, len(r)) + for i, item := range r { + strs[i] = item.String() + } + + return strings.Join(strs, " || ") +} + +// MustParseRange is like ParseRange but panics if the range cannot be parsed. +func MustParseRange(s string) Ranger { + r, err := ParseRange(s) + if err != nil { + panic(`semver: ParseRange(` + s + `): ` + err.Error()) + } + return r } // ParseRange parses a range and returns a Range. @@ -109,7 +207,7 @@ func (rf Range) AND(f Range) Range { // Ranges can be combined by both AND and OR // // - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1` -func ParseRange(s string) (Range, error) { +func ParseRange(s string) (Ranger, error) { parts := splitAndTrim(s) orParts, err := splitORParts(parts) if err != nil { @@ -119,35 +217,38 @@ func ParseRange(s string) (Range, error) { if err != nil { return nil, err } - var orFn Range - for _, p := range expandedParts { - var andFn Range - for _, ap := range p { - opStr, vStr, err := splitComparatorVersion(ap) + var andRanges []Ranger + for _, parts := range expandedParts { + var ranges []Ranger + for _, andParts := range parts { + opStr, vStr, err := splitComparatorVersion(andParts) if err != nil { return nil, err } vr, err := buildVersionRange(opStr, vStr) if err != nil { - return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err) + return nil, fmt.Errorf("Could not parse Range %q: %s", andParts, err) } - rf := vr.rangeFunc() - // Set function - if andFn == nil { - andFn = rf - } else { // Combine with existing function - andFn = andFn.AND(rf) - } + ranges = append(ranges, vr) } - if orFn == nil { - orFn = andFn - } else { - orFn = orFn.OR(andFn) + switch len(ranges) { + case 0: + return nil, errors.New("empty range") + case 1: + andRanges = append(andRanges, ranges[0]) + default: + andRanges = append(andRanges, andRange(ranges)) } - } - return orFn, nil + switch len(andRanges) { + case 0: + return nil, errors.New("empty range") + case 1: + return andRanges[0], nil + default: + return orRange(andRanges), nil + } } // splitORParts splits the already cleaned parts by '||'. @@ -176,7 +277,7 @@ func splitORParts(parts []string) ([][]string, error) { // and builds a versionRange, otherwise an error. func buildVersionRange(opStr, vStr string) (*versionRange, error) { c := parseComparator(opStr) - if c == nil { + if c == compInvalid { return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, "")) } v, err := Parse(vStr) @@ -191,14 +292,8 @@ func buildVersionRange(opStr, vStr string) (*versionRange, error) { } -// inArray checks if a byte is contained in an array of bytes -func inArray(s byte, list []byte) bool { - for _, el := range list { - if el == s { - return true - } - } - return false +func containsByte(b []byte, c byte) bool { + return bytes.IndexByte(b, c) >= 0 } // splitAndTrim splits a range string by spaces and cleans whitespaces @@ -207,7 +302,7 @@ func splitAndTrim(s string) (result []string) { var lastChar byte excludeFromSplit := []byte{'>', '<', '='} for i := 0; i < len(s); i++ { - if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) { + if s[i] == ' ' && !containsByte(excludeFromSplit, lastChar) { if last < i-1 { result = append(result, s[last:i]) } @@ -381,36 +476,9 @@ func expandWildcardVersion(parts [][]string) ([][]string, error) { return expandedParts, nil } -func parseComparator(s string) comparator { - switch s { - case "==": - fallthrough - case "": - fallthrough - case "=": - return compEQ - case ">": - return compGT - case ">=": - return compGE - case "<": - return compLT - case "<=": - return compLE - case "!": - fallthrough - case "!=": - return compNE - } - - return nil -} - -// MustParseRange is like ParseRange but panics if the range cannot be parsed. -func MustParseRange(s string) Range { - r, err := ParseRange(s) - if err != nil { - panic(`semver: ParseRange(` + s + `): ` + err.Error()) - } - return r +type wildcardSemver struct { + Major int + Minor int + Patch int + PR []PRVersion } diff --git a/v4/range_test.go b/v4/range_test.go index 2f44de4..ff4b478 100644 --- a/v4/range_test.go +++ b/v4/range_test.go @@ -34,7 +34,7 @@ func TestParseComparator(t *testing.T) { } for _, tc := range compatorTests { - if c := parseComparator(tc.input); c == nil { + if c := parseComparator(tc.input); c == compInvalid { if tc.comparator != nil { t.Errorf("Comparator nil for case %q\n", tc.input) } @@ -51,27 +51,27 @@ var ( ) func testEQ(f comparator) bool { - return f(v1, v1) && !f(v1, v2) + return f.compare(v1, v1) && !f.compare(v1, v2) } func testNE(f comparator) bool { - return !f(v1, v1) && f(v1, v2) + return !f.compare(v1, v1) && f.compare(v1, v2) } func testGT(f comparator) bool { - return f(v2, v1) && f(v3, v2) && !f(v1, v2) && !f(v1, v1) + return f.compare(v2, v1) && f.compare(v3, v2) && !f.compare(v1, v2) && !f.compare(v1, v1) } func testGE(f comparator) bool { - return f(v2, v1) && f(v3, v2) && !f(v1, v2) + return f.compare(v2, v1) && f.compare(v3, v2) && !f.compare(v1, v2) } func testLT(f comparator) bool { - return f(v1, v2) && f(v2, v3) && !f(v2, v1) && !f(v1, v1) + return f.compare(v1, v2) && f.compare(v2, v3) && !f.compare(v2, v1) && !f.compare(v1, v1) } func testLE(f comparator) bool { - return f(v1, v2) && f(v2, v3) && !f(v2, v1) + return f.compare(v1, v2) && f.compare(v2, v3) && !f.compare(v2, v1) } func TestSplitAndTrim(t *testing.T) { @@ -157,7 +157,7 @@ func TestBuildVersionRange(t *testing.T) { t.Errorf("Invalid for case %q: Expected version %q, got: %q", strings.Join([]string{tc.opStr, tc.vStr}, ""), tv, r.v) } // test comparator - if r.c == nil { + if r.c == compInvalid { t.Errorf("Invalid for case %q: got nil comparator", strings.Join([]string{tc.opStr, tc.vStr}, "")) continue } @@ -299,8 +299,7 @@ func TestVersionRangeToRange(t *testing.T) { v: MustParse("1.2.3"), c: compLT, } - rf := vr.rangeFunc() - if !rf(MustParse("1.2.2")) || rf(MustParse("1.2.3")) { + if !vr.Range((MustParse("1.2.2"))) || vr.Range(MustParse("1.2.3")) { t.Errorf("Invalid conversion to range func") } } @@ -309,20 +308,16 @@ func TestRangeAND(t *testing.T) { v := MustParse("1.2.2") v1 := MustParse("1.2.1") v2 := MustParse("1.2.3") - rf1 := Range(func(v Version) bool { - return v.GT(v1) - }) - rf2 := Range(func(v Version) bool { - return v.LT(v2) - }) - rf := rf1.AND(rf2) - if rf(v1) { + rf1 := Ranger(&versionRange{v: MustParse("1.2.1"), c: compGT}) + rf2 := Ranger(&versionRange{v: MustParse("1.2.3"), c: compLT}) + rf := andRange{rf1, rf2} + if rf.Range(v1) { t.Errorf("Invalid rangefunc, accepted: %s", v1) } - if rf(v2) { + if rf.Range(v2) { t.Errorf("Invalid rangefunc, accepted: %s", v2) } - if !rf(v) { + if !rf.Range(v) { t.Errorf("Invalid rangefunc, did not accept: %s", v) } } @@ -336,17 +331,11 @@ func TestRangeOR(t *testing.T) { {MustParse("1.2.2"), false}, {MustParse("1.2.4"), true}, } - v1 := MustParse("1.2.1") - v2 := MustParse("1.2.3") - rf1 := Range(func(v Version) bool { - return v.LT(v1) - }) - rf2 := Range(func(v Version) bool { - return v.GT(v2) - }) - rf := rf1.OR(rf2) + rf1 := Ranger(&versionRange{v: MustParse("1.2.1"), c: compLT}) + rf2 := Ranger(&versionRange{v: MustParse("1.2.3"), c: compGT}) + rf := orRange{rf1, rf2} for _, tc := range tests { - if r := rf(tc.v); r != tc.b { + if r := rf.Range(tc.v); r != tc.b { t.Errorf("Invalid for case %q: Expected %t, got %t", tc.v, tc.b, r) } } @@ -495,7 +484,7 @@ func TestParseRange(t *testing.T) { } for _, tvc := range tc.t { v := MustParse(tvc.v) - if res := r(v); res != tvc.b { + if res := r.Range(v); res != tvc.b { t.Errorf("Invalid for case %q matching %q: Expected %t, got: %t", tc.i, tvc.v, tvc.b, res) } } @@ -506,7 +495,7 @@ func TestParseRange(t *testing.T) { func TestMustParseRange(t *testing.T) { testCase := ">1.2.2 <1.2.4 || >=2.0.0 <3.0.0" r := MustParseRange(testCase) - if !r(MustParse("1.2.3")) { + if !r.Range(MustParse("1.2.3")) { t.Errorf("Unexpected range behavior on MustParseRange") } } @@ -554,7 +543,7 @@ func BenchmarkRangeMatchSimple(b *testing.B) { b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { - r(v) + r.Range(v) } } @@ -565,7 +554,7 @@ func BenchmarkRangeMatchAverage(b *testing.B) { b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { - r(v) + r.Range(v) } } @@ -576,6 +565,6 @@ func BenchmarkRangeMatchComplex(b *testing.B) { b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { - r(v) + r.Range(v) } }