github.com/kyleu/dbaudit@v0.0.2-0.20240321155047-ff2f2c940496/app/lib/search/result/match.go (about)

     1  // Package result - Content managed by Project Forge, see [projectforge.md] for details.
     2  package result
     3  
     4  import (
     5  	"cmp"
     6  	"fmt"
     7  	"reflect"
     8  	"slices"
     9  	"strings"
    10  
    11  	"github.com/samber/lo"
    12  
    13  	"github.com/kyleu/dbaudit/app/util"
    14  )
    15  
    16  type Match struct {
    17  	Key   string `json:"k"`
    18  	Value string `json:"v"`
    19  }
    20  
    21  func (m *Match) ValueSplit(q string) []string {
    22  	ql := strings.ToLower(q)
    23  	vl := strings.ToLower(m.Value)
    24  	cut := m.Value
    25  	idx := strings.Index(vl, ql)
    26  	if idx == -1 {
    27  		return []string{cut}
    28  	}
    29  	ret := &util.StringSlice{}
    30  	for idx > -1 {
    31  		if idx > 0 {
    32  			ret.Push(cut[:idx])
    33  		}
    34  		ret.Push(cut[idx : idx+len(ql)])
    35  
    36  		cut = cut[idx+len(ql):]
    37  		vl = vl[idx+len(ql):]
    38  
    39  		idx = strings.Index(vl, ql)
    40  	}
    41  	if cut != "" {
    42  		ret.Push(cut)
    43  	}
    44  	return ret.Slice
    45  }
    46  
    47  type Matches []*Match
    48  
    49  func (m Matches) Sort() {
    50  	slices.SortFunc(m, func(l *Match, r *Match) int {
    51  		return cmp.Compare(strings.ToLower(l.Key), strings.ToLower(r.Key))
    52  	})
    53  }
    54  
    55  func MatchesFor(key string, x any, q string) Matches {
    56  	q = strings.ToLower(q)
    57  	v := reflect.ValueOf(x)
    58  	if v.Kind() == reflect.Ptr {
    59  		if v.IsNil() {
    60  			return nil
    61  		}
    62  		v = reflect.Indirect(v)
    63  	}
    64  
    65  	appendKey := func(s string) string {
    66  		if key == "" {
    67  			return s
    68  		}
    69  		return key + "." + s
    70  	}
    71  	maybe := func(cond bool, v string) Matches {
    72  		if cond {
    73  			return Matches{{Key: key, Value: v}}
    74  		}
    75  		return nil
    76  	}
    77  
    78  	switch v.Kind() {
    79  	case reflect.Bool:
    80  		return nil
    81  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
    82  		i := fmt.Sprint(v.Int())
    83  		return maybe(strings.Contains(i, q), i)
    84  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
    85  		i := fmt.Sprint(v.Uint())
    86  		return maybe(strings.Contains(i, q), i)
    87  	case reflect.Float32, reflect.Float64:
    88  		f := fmt.Sprint(v.Float())
    89  		return maybe(strings.Contains(f, q), f)
    90  	case reflect.Map:
    91  		var ret Matches
    92  		x := v.MapRange()
    93  		for x.Next() {
    94  			ret = append(ret, MatchesFor(appendKey(x.Key().String()), x.Value().Interface(), q)...)
    95  		}
    96  		return ret
    97  	case reflect.Array, reflect.Slice:
    98  		var ret Matches
    99  		for idx := 0; idx < v.Len(); idx++ {
   100  			ret = append(ret, MatchesFor(appendKey(fmt.Sprint(idx)), v.Index(idx), q)...)
   101  		}
   102  		return ret
   103  	case reflect.String:
   104  		s := v.String()
   105  		return maybe(strings.Contains(strings.ToLower(s), q), s)
   106  	case reflect.Struct:
   107  		var ret Matches
   108  		lo.Times(v.NumField(), func(i int) struct{} {
   109  			if f := v.Field(i); f.CanSet() {
   110  				n := v.Type().Field(i).Name
   111  				if m := MatchesFor(appendKey(n), v.Field(i).Interface(), q); m != nil {
   112  					ret = append(ret, m...)
   113  				}
   114  			}
   115  			return struct{}{}
   116  		})
   117  		return ret
   118  	default:
   119  		return Matches{{Key: key, Value: "error: " + v.Kind().String()}}
   120  	}
   121  }