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 }