// 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: : // Wildcard match: // :* // :* // :** // Nested structs: // .: // Multiple matches: // : :* // 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) } }