From 50973e727223b346fa1113d8a79e9b1539cd4e59 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Sat, 24 Jan 2026 16:51:57 +0100 Subject: [PATCH] Add == and != operators to range predicates Co-authored-by: Claude Opus 4.5 --- common/predicate/predicate.go | 14 ++++++++++++++ common/predicate/predicate_test.go | 21 +++++++++++++++++++++ common/predicate/rangeop_test.go | 8 ++++++++ 3 files changed, 43 insertions(+) diff --git a/common/predicate/predicate.go b/common/predicate/predicate.go index 1168316b6..dde356895 100644 --- a/common/predicate/predicate.go +++ b/common/predicate/predicate.go @@ -159,6 +159,8 @@ const ( rangeOpLTE rangeOpGT rangeOpGTE + rangeOpEQ + rangeOpNE ) func cutRangeOp(s string) (op int, rest string) { @@ -167,6 +169,10 @@ func cutRangeOp(s string) (op int, rest string) { return rangeOpGTE, s[3:] case strings.HasPrefix(s, "<= "): return rangeOpLTE, s[3:] + case strings.HasPrefix(s, "== "): + return rangeOpEQ, s[3:] + case strings.HasPrefix(s, "!= "): + return rangeOpNE, s[3:] case strings.HasPrefix(s, "> "): return rangeOpGT, s[2:] case strings.HasPrefix(s, "< "): @@ -260,6 +266,14 @@ func NewIndexStringPredicateFromGlobsAndRanges(patterns []string, getIndex func( p = p.And(func(s IndexString) Match { return BoolMatch(s.Index >= i) }) + case rangeOpEQ: + p = p.And(func(s IndexString) Match { + return BoolMatch(s.Index == i) + }) + case rangeOpNE: + p = p.And(func(s IndexString) Match { + return BoolMatch(s.Index != i) + }) } } else { g, err := getGlob(pattern) diff --git a/common/predicate/predicate_test.go b/common/predicate/predicate_test.go index cf867dcb6..6802938cb 100644 --- a/common/predicate/predicate_test.go +++ b/common/predicate/predicate_test.go @@ -224,6 +224,27 @@ func TestNewIndexStringPredicateFromGlobsAndRanges(t *testing.T) { m = n(">= v99.0.0") c.Assert(m(is(0)), qt.IsFalse) c.Assert(m(is(3)), qt.IsFalse) + + // Test == v2.0.0: should only match v2.0.0 + m = n("== v2.0.0") + c.Assert(m(is(0)), qt.IsFalse) // v4.0.0 + c.Assert(m(is(1)), qt.IsFalse) // v3.0.0 + c.Assert(m(is(2)), qt.IsTrue) // v2.0.0 + c.Assert(m(is(3)), qt.IsFalse) // v1.0.0 + + // Test != v2.0.0: should match everything except v2.0.0 + m = n("!= v2.0.0") + c.Assert(m(is(0)), qt.IsTrue) // v4.0.0 + c.Assert(m(is(1)), qt.IsTrue) // v3.0.0 + c.Assert(m(is(2)), qt.IsFalse) // v2.0.0 + c.Assert(m(is(3)), qt.IsTrue) // v1.0.0 + + // Test != with range: >= v2.0.0 AND != v3.0.0 + m = n(">= v2.0.0", "!= v3.0.0") + c.Assert(m(is(0)), qt.IsTrue) // v4.0.0 + c.Assert(m(is(1)), qt.IsFalse) // v3.0.0 - excluded by != + c.Assert(m(is(2)), qt.IsTrue) // v2.0.0 + c.Assert(m(is(3)), qt.IsFalse) // v1.0.0 - out of range } func BenchmarkPredicate(b *testing.B) { diff --git a/common/predicate/rangeop_test.go b/common/predicate/rangeop_test.go index 3b282a2c5..1650c2764 100644 --- a/common/predicate/rangeop_test.go +++ b/common/predicate/rangeop_test.go @@ -38,6 +38,14 @@ func TestCutRangeOp(t *testing.T) { c.Assert(op, qt.Equals, rangeOpLT) c.Assert(rest, qt.Equals, "value") + op, rest = cutRangeOp("== value") + c.Assert(op, qt.Equals, rangeOpEQ) + c.Assert(rest, qt.Equals, "value") + + op, rest = cutRangeOp("!= value") + c.Assert(op, qt.Equals, rangeOpNE) + c.Assert(rest, qt.Equals, "value") + op, rest = cutRangeOp("value") c.Assert(op, qt.Equals, rangeOpNone) c.Assert(rest, qt.Equals, "value") -- 2.39.5