// Copyright (C) 2018 Marius Schellenberger // Package filter implements simple struct field filtering package filter import ( "reflect" "sort" "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 } type sorter struct { enabled bool only bool desc bool fields []string } const ( desc = "desc" asc = "asc" ) type logic int const ( undefined logic = iota and or not ) func parseLogic(s string) (l logic, ok bool) { ok = true switch s { case "AND": l = and case "OR": l = or case "NOT": l = not default: ok = false l = and } return } // Query is the filter query type Query struct { q string m []match l logic s sorter } // NewQuery parses the filter/sort query and match logic // If the query does not start with a filter logic (AND, OR, NOT), it defaults to AND. // Examples: // Explicit match: : // Wildcard match (use ** to match the '*' character): // :* // :* // :** // Nested structs: // .: // Multiple matches: // Multiple statemenmts are separated by a single whitespace character: ' ' // : :* // Supported data types are: string, int, float, bool // Bool does not support the wildcard operator // Sorting: // The sort statement consists of the literal string 'sort', the field name (nesting like above is supported) and the literal strings 'asc' or 'desc'. // Examples: // sort::asc // sort::desc // sort:.:desc func NewQuery(s string) (q *Query, err error) { q = &Query{q: s} if s == "" { err = errEmpty return } qm := make(map[string]struct{}) fields := clearFields(s) if len(fields) == 0 { err = errEmpty return } for _, v := range fields { if q.l == undefined { l, ok := parseLogic(v) q.l = l if ok { continue } } if !q.s.enabled { s := strings.SplitN(v, ":", 3) if s[0] == "sort" && len(s) >= 2 { q.s.fields = strings.Split(s[1], ".") if err = checkFields(q.s.fields, v); err != nil { return } if len(s) == 3 { switch s[2] { case desc: q.s.desc = true case asc: default: err = newErr("invalid sort: " + v) return } } q.s.enabled = true continue } } m := strings.SplitN(v, ":", 2) if len(m) != 2 { err = newErr("invalid match: " + v) return } if q.l == and { if _, ok := qm[m[0]]; ok { err = newErr("field for AND matching used more than once: " + m[0]) return } qm[m[0]] = struct{}{} } f := strings.Split(m[0], ".") if err = checkFields(f, m[0]); err != nil { return } q.m = append(q.m, match{ f, matchFunc(m[1]), boolFunc(m[1]), }) } if len(q.m) == 0 && q.s.enabled { q.s.only = true } else if len(q.m) == 0 { err = errEmpty } if len(q.m) == 1 && q.l != not { q.l = or } return } func (q *Query) String() string { return q.q } 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 := walkFields(v.FieldByName(qv.fields[0]), qv.fields[1:]) if val.Kind() == reflect.Invalid { continue } 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. // GenericFilter does not support sorting. 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) sort(f Filter) { if f.Len() == 0 { return } l := makeLess(f, q.s.fields) if l == nil { return } if q.s.desc { l = reverse(l) } sort.SliceStable(reflect.Indirect(reflect.ValueOf(f)).Interface(), l) } // Filter takes a pointer to the Filter interface as an argument. // The original slice is modified in the filtering/sorting process. func (q Query) Filter(f Filter) error { v := reflect.ValueOf(f) if v.Kind() != reflect.Ptr { return newErr("non-pointer " + v.Type().String()) } if q.s.enabled && q.s.only { q.sort(f) return nil } 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) } if q.s.enabled { q.sort(f) } return nil }