// Copyright (C) 2017 Marius Schellenberger // Package filter implements simple struct field filtering package filter import ( "errors" "reflect" "strings" ) // Filter is the filter interface to be implemented for faster non generic filtering // Example implementation: // type f []struct // func (v f) Len() int { return len(v) } // func (v f) Index(i int) interface{} { return v[i] } // func (v *f) Remove(i, j int) { *v = append((*v)[:i], (*v)[j:]...) } type Filter interface { Len() int Index(int) interface{} Remove(int, int) } type match struct { fields []string mf func(string) bool bf func(bool) bool } // Logic is the filter logic type Logic int const ( // AND all of the queries have to match AND Logic = iota // OR only one query has to match OR // NOT none of the queries have to match NOT ) // Query is the filter query type Query struct { m []match l Logic } // NewQuery parses the filter query and match logic // Examples: // Explicit match: : // Wildcard match (use ** to match the '*' character): // :* // :* // :** // Nested structs: // .: // Multiple matches: // : :* // Supported data types are: string, int, float, bool // Bool does not support the wildcard operator func NewQuery(s string, l Logic) (q *Query, err error) { if l > 2 || l < 0 { l = AND } q = &Query{l: l} if s == "" { err = errors.New("query is empty") return } qm := make(map[string]struct{}) fields := strings.Split(s, " ") for _, v := range fields { if v == "" { continue } m := strings.SplitN(v, ":", 2) if len(m) != 2 { err = errors.New("invalid match: " + v) return } if l == AND { if _, ok := qm[m[0]]; ok { err = errors.New("field for AND matching used more than once: " + m[0]) return } qm[m[0]] = struct{}{} } q.m = append(q.m, match{ strings.Split(m[0], "."), matchFunc(m[1]), boolFunc(m[1]), }) } return } func (q *Query) check(oks []bool) (b bool) { b = true switch q.l { case AND: for _, ok := range oks { if !ok { b = false return } } case OR: b = false case NOT: for _, ok := range oks { if ok { b = false return } } } return } func (q *Query) match(v reflect.Value) (ok bool) { if v.Kind() != reflect.Struct { return } ql := len(q.m) if ql == 0 { return } var ( oks []bool match bool ) if q.l != OR { oks = make([]bool, ql) } for i, qv := range q.m { fl := len(qv.fields) if fl == 0 { continue } val := v.FieldByName(qv.fields[0]) for j := 1; j < fl; j++ { val = val.FieldByName(qv.fields[j]) } s, b, err := getValue(val) if err != nil { continue } switch s { case "": match = qv.bf(b) default: match = qv.mf(s) } if q.l == OR { if match { return true } continue } oks[i] = match } return q.check(oks) } // 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() } // Filter takes a pointer to the Filter interface as an argument. // The original slice is modified is the filtering process. 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) } }