initial commit

This commit is contained in:
ston1th 2017-07-09 01:05:58 +02:00
commit e309b478f5
5 changed files with 453 additions and 0 deletions

24
LICENSE Normal file
View file

@ -0,0 +1,24 @@
Copyright (C) 2017 Marius Schellenberger
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The names of the authors and/or contributors may not be used to
endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL ston1th BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

86
README.md Normal file
View file

@ -0,0 +1,86 @@
# filter - a simple struct field filter for go
## Usage
```
// Example data structure
type Example struct {
String string
Int int
Bool bool
Struct Struct1
}
type Struct1 struct {
String string
Struct Struct2
}
type Struct2 struct {
Int int
}
// Filter implements the filter.Filter interface
type Filter []Example
func (v Filter) Len() int { return len(v) }
func (v Filter) Index(i int) interface{} { return v[i] }
func (v *Filter) Remove(i, j int) { *v = append((*v)[:i], (*v)[j:]...) }
// Example data
var data = Filter{
{"str1", 42, true, Struct1{"s1", Struct2{1}}},
{"2str", 42, true, Struct1{"s2", Struct2{2}}},
{"str3", 42, false, Struct1{"s3", Struct2{3}}},
}
```
### Explicit match
```
filter.NewQuery("Bool:true").Filter(&data)
data = [{str1 42 true {s1 {1}}} {str2 42 true {s2 {2}}}]
```
### Wildcard match
```
filter.NewQuery("String:s*").Filter(&data)
data = [{str1 42 true {s1 {1}}} {str3 42 false {s3 {3}}}]
```
### Nested struct match
```
filter.NewQuery("Struct.Struct.Int:3").Filter(&data)
data = [{str3 42 false {s3 {3}}}]
```
### Multiple matches
```
filter.NewQuery("Struct.String:*2 Bool:true").Filter(&data)
data = [{2str 42 true {s2 {2}}}]
```
## Benchmarks
```
go test -cpu 1,2,4 --bench .
BenchmarkFilterSimple 2000000 899 ns/op 432 B/op 12 allocs/op
BenchmarkFilterSimple-2 2000000 896 ns/op 432 B/op 12 allocs/op
BenchmarkFilterSimple-4 1000000 1439 ns/op 432 B/op 12 allocs/op
BenchmarkGenericFilterSimple 500000 2438 ns/op 1200 B/op 24 allocs/op
BenchmarkGenericFilterSimple-2 500000 2406 ns/op 1200 B/op 24 allocs/op
BenchmarkGenericFilterSimple-4 500000 2645 ns/op 1200 B/op 24 allocs/op
BenchmarkFilterComplex 500000 2929 ns/op 672 B/op 42 allocs/op
BenchmarkFilterComplex-2 500000 2918 ns/op 672 B/op 42 allocs/op
BenchmarkFilterComplex-4 500000 5574 ns/op 672 B/op 42 allocs/op
BenchmarkGenericFilterComplex 200000 7343 ns/op 1680 B/op 84 allocs/op
BenchmarkGenericFilterComplex-2 200000 7400 ns/op 1680 B/op 84 allocs/op
BenchmarkGenericFilterComplex-4 200000 11979 ns/op 1680 B/op 84 allocs/op
```

52
cmd/main.go Normal file
View file

@ -0,0 +1,52 @@
// Copyright (C) 2017 Marius Schellenberger
package main
import (
"fmt"
"git.giftfish.de/ston1th/filter"
)
// Example data structure
type Example struct {
String string
Int int
Bool bool
Struct Struct1
}
type Struct1 struct {
String string
Struct Struct2
}
type Struct2 struct {
Int int
}
// Filter implements the filter.Filter interface
type Filter []Example
func (v Filter) Len() int { return len(v) }
func (v Filter) Index(i int) interface{} { return v[i] }
func (v *Filter) Remove(i, j int) { *v = append((*v)[:i], (*v)[j:]...) }
// Example data
var data = Filter{
{"str1", 42, true, Struct1{"s1", Struct2{1}}},
{"2str", 42, true, Struct1{"s2", Struct2{2}}},
{"str3", 42, false, Struct1{"s3", Struct2{3}}},
}
func main() {
f := filter.NewQuery("String:s*").GenericFilter(data)
if f != nil {
for _, v := range f.(Filter) {
fmt.Println("Generic:", v)
}
}
filter.NewQuery("String:s*").Filter(&data)
for _, v := range data {
fmt.Println("Interface:", v)
}
}

152
filter.go Normal file
View file

@ -0,0 +1,152 @@
// Copyright (C) 2017 Marius Schellenberger
// Package filter implements simple struct field filtering
package filter
import (
"reflect"
"strconv"
"strings"
)
const any = '*'
type Filter interface {
Len() int
Index(int) interface{}
Remove(int, int)
}
type match struct {
fields []string
value string
}
// Query implements the filter query
type Query []match
// NewQuery parses the filter query.
// Examples:
// Explicit match: <struct field>:<struct value>
// Wildcard match:
// <struct field>:*<part of struct value>
// <struct field>:<part of struct value>*
// <struct field>:*<middl of struct value>*
// Nested structs:
// <struct field>.<struct field>:<struct value>
// Multiple matches:
// <struct field>:<struct value> <struct field>:<struct value>*
// Supported data types are: string, int, bool
// Bool does not support the wildcard operator
func NewQuery(s string) (q Query) {
fields := strings.Split(s, " ")
for _, v := range fields {
m := strings.Split(v, ":")
if len(m) == 2 {
q = append(q, match{strings.Split(m[0], "."), m[1]})
}
}
return
}
func (q Query) match(v reflect.Value) (ok bool) {
if v.Kind() != reflect.Struct {
return
}
if len(q) == 0 {
return
}
ok = true
for _, qv := range q {
s := ""
fl := len(qv.fields)
if fl == 0 {
continue
}
val := v.FieldByName(qv.fields[0])
for i := 1; i < fl; i++ {
val = val.FieldByName(qv.fields[i])
}
switch val.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16,
reflect.Int32, reflect.Int64:
s = strconv.Itoa(int(val.Int()))
case reflect.String:
s = val.String()
case reflect.Bool:
if val.Bool() {
s = "true"
} else {
s = "false"
}
if s != qv.value {
ok = false
}
continue
default:
ok = false
continue
}
vl := len(qv.value)
switch {
case vl >= 3:
if qv.value[0] == any && qv.value[vl-1] == any {
if strings.Contains(s, qv.value[1:vl-1]) {
continue
}
}
fallthrough
case vl >= 2:
if qv.value[0] == any {
if strings.HasSuffix(s, qv.value[1:]) {
continue
}
}
if qv.value[vl-1] == any {
if strings.HasPrefix(s, qv.value[:vl-1]) {
continue
}
}
}
if s != qv.value {
ok = false
}
}
return
}
// GenericFilter takes any slice of structs value and returns the same slice type containing only the filter matches.
// Returns nil if interface{} is not a slice.
func (q Query) GenericFilter(i interface{}) interface{} {
v := reflect.ValueOf(i)
if v.Kind() != reflect.Slice {
return nil
}
ret := reflect.MakeSlice(v.Type(), 0, 0)
for i := 0; i < v.Len(); i++ {
val := v.Index(i)
if q.match(val) {
ret = reflect.Append(ret, val)
}
}
return ret.Interface()
}
func (q Query) Filter(f Filter) {
count := 0
i := 0
for ; i < f.Len(); i++ {
if q.match(reflect.ValueOf(f.Index(i))) {
if count > 0 {
f.Remove(i-count, i)
i -= count
}
count = 0
} else {
count++
}
}
if count > 0 {
f.Remove(i-count, i)
}
}

139
filter_test.go Normal file
View file

@ -0,0 +1,139 @@
// Copyright (C) 2017 Marius Schellenberger
package filter
import "testing"
type Example struct {
String string
Int int
Bool bool
Struct Struct1
}
type Struct1 struct {
String string
Struct Struct2
}
type Struct2 struct {
Int int
}
type filter []Example
func makefilter(v filter) (f filter) {
f = make(filter, len(v))
copy(f, v)
return
}
func (v filter) Len() int { return len(v) }
func (v filter) Index(i int) interface{} { return v[i] }
func (v *filter) Remove(i, j int) { *v = append((*v)[:i], (*v)[j:]...) }
var (
data = filter{
{"str1", 42, true, Struct1{"s1", Struct2{1}}},
{"2str", 42, true, Struct1{"s2", Struct2{2}}},
{"str3", 42, false, Struct1{"s3", Struct2{3}}},
}
benchData = filter{
{"str1", 41, true, Struct1{"s1", Struct2{1}}},
{"2str", 42, true, Struct1{"s2", Struct2{1}}},
{"str3", 43, false, Struct1{"s3", Struct2{1}}},
{"str4", 44, false, Struct1{"s4", Struct2{1}}},
{"str5", 45, false, Struct1{"s5", Struct2{1}}},
{"str6", 46, false, Struct1{"s6", Struct2{1}}},
{"str7", 47, true, Struct1{"s7", Struct2{1}}},
{"str8", 48, true, Struct1{"s8", Struct2{1}}},
{"str9", 49, true, Struct1{"s9", Struct2{1}}},
{"str10", 50, true, Struct1{"s10", Struct2{1}}},
{"str11", 51, false, Struct1{"s11", Struct2{1}}},
{"str12", 52, false, Struct1{"s12", Struct2{1}}},
}
)
func TestFilter(t *testing.T) {
f1 := makefilter(data)
NewQuery("String:*st* Bool:true").Filter(&f1)
if len(f1) != 2 {
t.Error("f1 len is:", len(f1))
}
if f1[0] != data[0] {
t.Error("f1[0] != data[0]")
}
if f1[1] != data[1] {
t.Error("f1[1] != data[1]")
}
f2 := makefilter(data)
NewQuery("Struct.Struct.Int:3").Filter(&f2)
if len(f2) != 1 {
t.Error("f2 len is:", len(f2))
}
if f2[0] != data[2] {
t.Error("f1[0] != data[2]")
}
}
func TestGenericFilter(t *testing.T) {
i1 := NewQuery("String:*st* Bool:true").GenericFilter(data)
if i1 == nil {
t.Error("i1 is nil")
}
f1 := i1.(filter)
if len(f1) != 2 {
t.Error("f1 len is:", len(f1))
}
if f1[0] != data[0] {
t.Error("f1[0] != data[0]")
}
if f1[1] != data[1] {
t.Error("f1[1] != data[1]")
}
i2 := NewQuery("Struct.Struct.Int:3").GenericFilter(data)
if i2 == nil {
t.Error("i2 is nil")
}
f2 := i2.(filter)
if len(f2) != 1 {
t.Error("f2 len is:", len(f2))
}
if f2[0] != data[2] {
t.Error("f1[0] != data[2]")
}
}
func BenchmarkFilterSimple(b *testing.B) {
b.ReportAllocs()
q := NewQuery("Bool:true")
fd := makefilter(benchData)
for i := 0; i < b.N; i++ {
q.Filter(&fd)
}
}
func BenchmarkGenericFilterSimple(b *testing.B) {
b.ReportAllocs()
q := NewQuery("Bool:true")
for i := 0; i < b.N; i++ {
q.GenericFilter(benchData)
}
}
func BenchmarkFilterComplex(b *testing.B) {
b.ReportAllocs()
q := NewQuery("String:*st* Bool:false Struct.Struct.Int:1")
fd := makefilter(benchData)
for i := 0; i < b.N; i++ {
q.Filter(&fd)
}
}
func BenchmarkGenericFilterComplex(b *testing.B) {
b.ReportAllocs()
q := NewQuery("String:*st* Bool:false Struct.Struct.Int:1")
for i := 0; i < b.N; i++ {
q.GenericFilter(benchData)
}
}