initial commit
This commit is contained in:
commit
e309b478f5
5 changed files with 453 additions and 0 deletions
152
filter.go
Normal file
152
filter.go
Normal 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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue