add filters package (#300)

* add filters package

Filters allow a caller to define some comparator-
ie, a filter- and use that filter to try matching various
inputs.
This commit is contained in:
Keepers 2022-07-11 10:39:22 -06:00 committed by GitHub
parent fa190da682
commit 608d19e821
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 286 additions and 0 deletions

139
src/pkg/filters/filters.go Normal file
View File

@ -0,0 +1,139 @@
package filters
import "strings"
type comparator int
const (
UnknownComparator comparator = iota
// a == b
Equal
// a > b
Greater
// a < b
Less
// a < b < c
Between
// "foo" contains "f"
Contains
// "f" is found in "foo"
In
)
const delimiter = ","
func join(s ...string) string {
return strings.Join(s, delimiter)
}
func split(s string) []string {
return strings.Split(s, delimiter)
}
func norm(s string) string {
return strings.ToLower(s)
}
// Filter contains a comparator func and the target to
// compare values against. Filter.Matches(v) returns
// true if Filter.Comparer(filter.target, v) is true.
type Filter struct {
Comparator comparator `json:"comparator"`
Category any `json:"category"` // a caller-provided identifier. Probably an iota or string const.
Target string `json:"target"` // the value to compare against
Negate bool `json:"negate"` // when true, negate the comparator result
}
// NewEquals creates a filter which Matches(v) is true if
// target == v
func NewEquals(negate bool, category any, target string) Filter {
return Filter{Equal, category, norm(target), negate}
}
// NewGreater creates a filter which Matches(v) is true if
// target > v
func NewGreater(negate bool, category any, target string) Filter {
return Filter{Greater, category, norm(target), negate}
}
// NewLess creates a filter which Matches(v) is true if
// target < v
func NewLess(negate bool, category any, target string) Filter {
return Filter{Less, category, norm(target), negate}
}
// NewBetween creates a filter which Matches(v) is true if
// lesser < v && v < greater
func NewBetween(negate bool, category any, lesser, greater string) Filter {
return Filter{Between, category, norm(join(lesser, greater)), negate}
}
// NewContains creates a filter which Matches(v) is true if
// super.Contains(v)
func NewContains(negate bool, category any, super string) Filter {
return Filter{Contains, category, norm(super), negate}
}
// NewIn creates a filter which Matches(v) is true if
// v.Contains(substr)
func NewIn(negate bool, category any, substr string) Filter {
return Filter{In, category, norm(substr), negate}
}
// Checks whether the filter matches the input
func (f Filter) Matches(input string) bool {
var cmp func(string, string) bool
switch f.Comparator {
case Equal:
cmp = equals
case Greater:
cmp = greater
case Less:
cmp = less
case Between:
cmp = between
case Contains:
cmp = contains
case In:
cmp = in
}
result := cmp(f.Target, norm(input))
if f.Negate {
result = !result
}
return result
}
// true if t == i
func equals(target, input string) bool {
return target == input
}
// true if t > i
func greater(target, input string) bool {
return target > input
}
// true if t < i
func less(target, input string) bool {
return target < input
}
// assumes target is a delimited string.
// true if both:
// - less(target[0], input)
// - greater(target[1], input)
func between(target, input string) bool {
parts := split(target)
return less(parts[0], input) && greater(parts[1], input)
}
// true if target contains input as a substring.
func contains(target, input string) bool {
return strings.Contains(target, input)
}
// true if input contains target as a substring.
func in(target, input string) bool {
return strings.Contains(input, target)
}

View File

@ -0,0 +1,147 @@
package filters_test
import (
"testing"
"github.com/alcionai/corso/pkg/filters"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type FiltersSuite struct {
suite.Suite
}
func TestFiltersSuite(t *testing.T) {
suite.Run(t, new(FiltersSuite))
}
func (suite *FiltersSuite) TestEquals() {
make := filters.NewEquals
f := make(false, "", "foo")
nf := make(true, "", "foo")
table := []struct {
input string
expectF assert.BoolAssertionFunc
expectNF assert.BoolAssertionFunc
}{
{"foo", assert.True, assert.False},
{"bar", assert.False, assert.True},
}
for _, test := range table {
suite.T().Run(test.input, func(t *testing.T) {
test.expectF(t, f.Matches(test.input), "filter")
test.expectNF(t, nf.Matches(test.input), "negated filter")
})
}
}
func (suite *FiltersSuite) TestGreater() {
make := filters.NewGreater
f := make(false, "", "5")
nf := make(true, "", "5")
table := []struct {
input string
expectF assert.BoolAssertionFunc
expectNF assert.BoolAssertionFunc
}{
{"4", assert.True, assert.False},
{"5", assert.False, assert.True},
{"6", assert.False, assert.True},
}
for _, test := range table {
suite.T().Run(test.input, func(t *testing.T) {
test.expectF(t, f.Matches(test.input), "filter")
test.expectNF(t, nf.Matches(test.input), "negated filter")
})
}
}
func (suite *FiltersSuite) TestLess() {
make := filters.NewLess
f := make(false, "", "5")
nf := make(true, "", "5")
table := []struct {
input string
expectF assert.BoolAssertionFunc
expectNF assert.BoolAssertionFunc
}{
{"6", assert.True, assert.False},
{"5", assert.False, assert.True},
{"4", assert.False, assert.True},
}
for _, test := range table {
suite.T().Run(test.input, func(t *testing.T) {
test.expectF(t, f.Matches(test.input), "filter")
test.expectNF(t, nf.Matches(test.input), "negated filter")
})
}
}
func (suite *FiltersSuite) TestBetween() {
make := filters.NewBetween
f := make(false, "", "abc", "def")
nf := make(true, "", "abc", "def")
table := []struct {
input string
expectF assert.BoolAssertionFunc
expectNF assert.BoolAssertionFunc
}{
{"cd", assert.True, assert.False},
{"a", assert.False, assert.True},
{"1", assert.False, assert.True},
{"f", assert.False, assert.True},
}
for _, test := range table {
suite.T().Run(test.input, func(t *testing.T) {
test.expectF(t, f.Matches(test.input), "filter")
test.expectNF(t, nf.Matches(test.input), "negated filter")
})
}
}
func (suite *FiltersSuite) TestContains() {
make := filters.NewContains
f := make(false, "", "smurfs")
nf := make(true, "", "smurfs")
table := []struct {
input string
expectF assert.BoolAssertionFunc
expectNF assert.BoolAssertionFunc
}{
{"murf", assert.True, assert.False},
{"frum", assert.False, assert.True},
}
for _, test := range table {
suite.T().Run(test.input, func(t *testing.T) {
test.expectF(t, f.Matches(test.input), "filter")
test.expectNF(t, nf.Matches(test.input), "negated filter")
})
}
}
func (suite *FiltersSuite) TestIn() {
make := filters.NewIn
f := make(false, "", "murf")
nf := make(true, "", "murf")
table := []struct {
input string
expectF assert.BoolAssertionFunc
expectNF assert.BoolAssertionFunc
}{
{"smurfs", assert.True, assert.False},
{"sfrums", assert.False, assert.True},
}
for _, test := range table {
suite.T().Run(test.input, func(t *testing.T) {
test.expectF(t, f.Matches(test.input), "filter")
test.expectNF(t, nf.Matches(test.input), "negated filter")
})
}
}