package search import ( "fmt" "os" "path/filepath" "reflect" "regexp" "strconv" "strings" ) func One(data interface{}, filters, removes, replaces []string, path string) interface{} { filter := [][]string{} for _, v := range filters { target, match := splitFilterString(v) filter = append(filter, []string{target, match}) } // !ISSUE: Load file first replace := [][]string{} compiledRegex := []*regexp.Regexp{} for _, v := range replaces { if strings.HasSuffix(v, ".rf") { target, fp := splitFilePath(v) fb, _ := os.ReadFile(filepath.Join(path, fp)) f := string(fb) lines := strings.Split(f, "\n") for _, l := range lines { // Add the target back to the lines // the .rf file should only contain the /match/replace/ statements if len(strings.TrimSpace(l)) != 0 && strings.TrimSpace(l)[0] != '#' { target, match, replacement := splitReplaceString(target + "=" + l) replace = append(replace, []string{target, match, replacement}) re, err := regexp.Compile(match) // Compile the regex. if err != nil { panic(err) } compiledRegex = append(compiledRegex, re) } } } else { target, match, replacement := splitReplaceString(v) replace = append(replace, []string{target, match, replacement}) re, err := regexp.Compile(match) // Compile the regex. if err != nil { panic(err) } compiledRegex = append(compiledRegex, re) } } remove := []string{} for _, v := range removes { remove = append(remove, v) } val := reflect.ValueOf(data) if val.Kind() == reflect.Ptr && val.Elem().Kind() == reflect.Struct { // Already a pointer to struct removeFields(data, remove) m := filterFields(data, filter) if m { replaceFields(data, replace, compiledRegex) } else { return "" } } else if val.Kind() == reflect.Struct { // It's a struct, we need to work with a pointer // Create a copy we can modify ptrToNewCopy := reflect.New(val.Type()) ptrToNewCopy.Elem().Set(val) // Modify the copy removeFields(ptrToNewCopy.Interface(), remove) m := filterFields(ptrToNewCopy.Interface(), filter) if m { replaceFields(ptrToNewCopy.Interface(), replace, compiledRegex) data = ptrToNewCopy.Elem().Interface() } else { return "" } } else { fmt.Printf("Skipping element not a struct or pointer to struct\n") } return data } func Group(data interface{}, groups []string) map[string]interface{} { grouped := make(map[string]interface{}) for _, v := range groups { val := reflect.ValueOf(data) target, match := splitFilterString(v) filter := [][]string{} filter = append(filter, []string{target, match}) if val.Kind() == reflect.Ptr && val.Elem().Kind() == reflect.Struct { // Already a pointer to struct m := filterFields(data, filter) if m { grouped[v] = data } } else if val.Kind() == reflect.Struct { // It's a struct, we need to work with a pointer // Create a copy we can modify ptrToNewCopy := reflect.New(val.Type()) ptrToNewCopy.Elem().Set(val) // Modify the copy m := filterFields(ptrToNewCopy.Interface(), filter) if m { grouped[v] = data } } else { fmt.Printf("Skipping element not a struct or pointer to struct\n") } } return grouped } func removeFields(data interface{}, query []string) { val := reflect.ValueOf(data) if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct { fmt.Println("removeFields: data must be a pointer to a struct") return } val = val.Elem() // Get the struct value the pointer points to typ := val.Type() for i := 0; i < val.NumField(); i++ { field := val.Field(i) fieldType := typ.Field(i) name := fieldType.Name // Check if this field needs to be zeroed for _, v := range query { if strings.Contains(v, ".") { // Handle nested fields vs := strings.Split(v, ".") if strings.EqualFold(name, strings.TrimSpace(vs[0])) { remainingPath := strings.Join(vs[1:], ".") if field.Kind() == reflect.Ptr && !field.IsNil() && field.Elem().Kind() == reflect.Struct { // Field is a pointer to struct removeFields(field.Interface(), []string{remainingPath}) } else if field.Kind() == reflect.Struct { if field.CanAddr() { // Field is a struct that we can get address of removeFields(field.Addr().Interface(), []string{remainingPath}) } } } } else if strings.EqualFold(name, strings.TrimSpace(v)) { // Zero out this field if field.CanSet() { field.Set(reflect.Zero(field.Type())) } else { fmt.Printf("Cannot set field %s\n", name) } } } // Recursively process nested structs (not already processed with dot notation) if field.Kind() == reflect.Ptr && !field.IsNil() && field.Elem().Kind() == reflect.Struct { removeFields(field.Interface(), query) } else if field.Kind() == reflect.Struct { if field.CanAddr() { removeFields(field.Addr().Interface(), query) } } } } func filterFields(data interface{}, query [][]string) bool { if len(query) == 0 { return true } val := reflect.ValueOf(data) // Ensure we're working with a pointer to a struct if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct { fmt.Println("filterFields: data must be a pointer to a struct") return false } val = val.Elem() typ := val.Type() matches := false for i := 0; i < val.NumField(); i++ { field := val.Field(i) fieldType := typ.Field(i) name := fieldType.Name for _, v := range query { key := v[0] exp := v[1] if strings.Contains(key, ".") { vs := strings.Split(key, ".") if strings.EqualFold(name, strings.TrimSpace(vs[0])) { if field.Kind() == reflect.Struct && field.CanAddr() { if filterFields(field.Addr().Interface(), [][]string{[]string{strings.Join(vs[1:], "."), exp}}) { matches = true break } } } } else if strings.EqualFold(name, strings.TrimSpace(key)) { str := []byte(fmt.Sprintf("%v", field.Interface())) matches, _ = regexp.Match(exp, str) if matches { break } } } if matches { break } } return matches } func replaceFields(data interface{}, query [][]string, compiledRegex []*regexp.Regexp) { val := reflect.ValueOf(data) // Ensure we're working with a pointer to a struct if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct { fmt.Println("replaceFields: data must be a pointer to a struct") } val = val.Elem() // Get the value the pointer points to. typ := val.Type() // Iterate over the fields of the struct. for i := 0; i < val.NumField(); i++ { field := val.Field(i) // The field's value. fieldType := typ.Field(i) // The field's type. name := fieldType.Name // The field's name. // Iterate over the query items. for b, v := range query { key := v[0] exp := v[1] rep := unescape(v[2]) // Handle nested fields (e.g., "Nested.Field"). if strings.Contains(key, ".") { parts := strings.Split(key, ".") if strings.EqualFold(name, strings.TrimSpace(parts[0])) { // Check if the field is a struct and addressable. if field.Kind() == reflect.Struct && field.CanAddr() { // Recursively call replaceFields on the nested struct. nestedQuery := [][]string{ {strings.Join(parts[1:], "."), exp, rep}, // Pass the rest of the path. } replaceFields(field.Addr().Interface(), nestedQuery, compiledRegex) } } } else if strings.EqualFold(name, strings.TrimSpace(key)) && field.CanSet() { re := compiledRegex[b] if field.Kind() == reflect.Slice { sliceVal := reflect.ValueOf(field.Interface()) newSlice := reflect.MakeSlice(field.Type(), sliceVal.Len(), sliceVal.Len()) for j := 0; j < sliceVal.Len(); j++ { sliceElem := sliceVal.Index(j) str := fmt.Sprintf("%v", sliceElem.Interface()) newValueStr := re.ReplaceAllString(str, rep) newValue, err := convertStringToType(newValueStr, sliceElem.Kind()) if err != nil { fmt.Printf("replaceFields: error converting '%s' to %s for slice element of field '%s': %v\n", newValueStr, sliceElem.Type(), name, err) continue // Skip on error, or handle as appropriate } newSlice.Index(j).Set(reflect.ValueOf(newValue).Convert(sliceElem.Type())) } field.Set(newSlice) } else { // Field name matches, and the field is settable. str := fmt.Sprintf("%v", field.Interface()) // Convert field value to string. newValueStr := re.ReplaceAllString(str, rep) // Apply the replacement. // Convert the new string value back to the field's original type. newValue, err := convertStringToType(newValueStr, field.Kind()) if err != nil { fmt.Printf("replaceFields: error converting '%s' to %s for field '%s': %v\n", newValueStr, field.Type(), name, err) continue // Skip if conversion fails. } field.Set(reflect.ValueOf(newValue)) // Set the field's new value. } } } } } func unescape(str string) string { return strings.ReplaceAll(str, `\/`, "/") } func splitFilePath(str string) (string, string) { var path, target string targetDone := false escaped := false for _, v := range str { if !targetDone { if v != 61 || (v == 61 && escaped) { target += string(v) } else if !escaped { targetDone = true } } else if targetDone { path += string(v) } if escaped { escaped = false } if v == 92 { escaped = true } } return target, path } func splitFilterString(str string) (string, string) { var target, match string targetDone, matchDone := false, false escaped := false slashCount := 0 for _, v := range str { if !targetDone { if v != 61 || (v == 61 && escaped) { target += string(v) } else if !escaped { targetDone = true } } else if targetDone && !matchDone && slashCount == 1 { if v != 47 || (v == 47 && escaped) { match += string(v) } else { matchDone = true } } else if slashCount == 2 && targetDone && matchDone { break } if v == 47 && !escaped { slashCount++ } if escaped { escaped = false } if v == 92 { escaped = true } } return target, match } func splitReplaceString(str string) (string, string, string) { var target, match, replace string targetDone, matchDone, replaceDone := false, false, false escaped := false slashCount := 0 for _, v := range str { if !targetDone { if v != 61 || (v == 61 && escaped) { target += string(v) } else if !escaped { targetDone = true } } else if targetDone && !matchDone && slashCount == 1 { if v != 47 || (v == 47 && escaped) { match += string(v) } else { matchDone = true } } else if targetDone && matchDone && !replaceDone && slashCount == 2 { if v != 47 || (v == 47 && escaped) { replace += string(v) } else { replaceDone = true } } else if slashCount == 3 && targetDone && matchDone && replaceDone { break } if v == 47 && !escaped { slashCount++ } if escaped { escaped = false } if v == 92 { escaped = true } } return target, match, replace } func convertStringToType(s string, kind reflect.Kind) (interface{}, error) { switch kind { case reflect.String: return s, nil case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: i, err := strconv.ParseInt(s, 10, 64) if err != nil { return nil, err } // Further convert to the specific integer type if necessary. switch kind { case reflect.Int8: return int8(i), nil case reflect.Int16: return int16(i), nil case reflect.Int32: return int32(i), nil case reflect.Int64: return i, nil case reflect.Int: return int(i), nil } return i, nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: u, err := strconv.ParseUint(s, 10, 64) if err != nil { return nil, err } switch kind { case reflect.Uint8: return uint8(u), nil case reflect.Uint16: return uint16(u), nil case reflect.Uint32: return uint32(u), nil case reflect.Uint64: return u, nil case reflect.Uint: return uint(u), nil } return u, nil case reflect.Float32, reflect.Float64: f, err := strconv.ParseFloat(s, 64) if err != nil { return nil, err } if kind == reflect.Float32 { return float32(f), nil } return f, nil case reflect.Bool: b, err := strconv.ParseBool(s) if err != nil { return nil, err } return b, nil default: return nil, fmt.Errorf("unsupported type: %v", kind) } }