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

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)
}
}