added sort support

This commit is contained in:
ston1th 2017-08-30 22:01:19 +02:00
commit 1674abae73
5 changed files with 377 additions and 66 deletions

View file

@ -6,6 +6,7 @@ package filter
import (
"errors"
"reflect"
"sort"
"strings"
)
@ -27,25 +28,39 @@ type match struct {
bf func(bool) bool
}
type sorter struct {
enabled bool
only bool
desc bool
fields []string
}
const (
desc = "desc"
asc = "asc"
)
// Logic is the filter logic
type Logic int
const (
// AND all of the queries have to match
// AND all statements have to match
AND Logic = iota
// OR only one query has to match
// OR only one statement has to match
OR
// NOT none of the queries have to match
// NOT none of the statements have to match
NOT
)
// Query is the filter query
type Query struct {
q string
m []match
l Logic
s sorter
}
// NewQuery parses the filter query and match logic
// NewQuery parses the filter/sort query and match logic
// Examples:
// Explicit match: <struct field>:<struct value>
// Wildcard match (use ** to match the '*' character):
@ -55,14 +70,21 @@ type Query struct {
// Nested structs:
// <struct field>.<struct field>:<struct value>
// Multiple matches:
// Multiple statemenmts are seperated by a single whitespace character: ' '
// <struct field>:<struct value> <struct field>:<struct value>*
// 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:<struct field>:asc
// sort:<struct field>:desc
// sort:<struct field>.<struct field>:desc
func NewQuery(s string, l Logic) (q *Query, err error) {
if l > 2 || l < 0 {
l = AND
}
q = &Query{l: l}
q = &Query{q: s, l: l}
if s == "" {
err = errors.New("query is empty")
return
@ -73,6 +95,28 @@ func NewQuery(s string, l Logic) (q *Query, err error) {
if v == "" {
continue
}
if !q.s.enabled {
s := strings.SplitN(v, ":", 3)
if len(s) == 3 {
if s[0] == "sort" {
switch s[2] {
case asc, desc:
q.s.fields = strings.Split(s[1], ".")
if len(q.s.fields) > 0 {
if s[2] == desc {
q.s.desc = true
}
q.s.enabled = true
}
continue
default:
err = errors.New("invalid sort: " + v)
return
}
}
}
}
m := strings.SplitN(v, ":", 2)
if len(m) != 2 {
err = errors.New("invalid match: " + v)
@ -91,9 +135,19 @@ func NewQuery(s string, l Logic) (q *Query, err error) {
boolFunc(m[1]),
})
}
if len(q.m) == 0 {
q.s.only = true
}
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 {
@ -164,6 +218,7 @@ func (q *Query) match(v reflect.Value) (ok bool) {
// 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 {
@ -179,9 +234,29 @@ func (q *Query) GenericFilter(i interface{}) interface{} {
return ret.Interface()
}
func (q Query) sort(f Filter) {
if f.Len() == 0 {
return
}
l := makeLess(f, q.s.fields)
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 process.
func (q Query) Filter(f Filter) {
// 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 errors.New("filter: 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++ {
@ -198,4 +273,8 @@ func (q Query) Filter(f Filter) {
if count > 0 {
f.Remove(i-count, i)
}
if q.s.enabled {
q.sort(f)
}
return nil
}