Commits
Diff
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 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 {
fmt.Printf("Skipping element not a struct or pointer to struct\n")
}
return data
}
func All(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)
}
grouped := []interface{}{}
for i := range data {
val := reflect.ValueOf(data[i])
if val.Kind() == reflect.Ptr && val.Elem().Kind() == reflect.Struct {
// Already a pointer to struct
removeFields(data[i], remove)
m := filterFields(data[i], filter)
if m {
replaceFields(data[i], replace, compiledRegex)
grouped = append(grouped, data[i])
}
} 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[i] = ptrToNewCopy.Elem().Interface()
grouped = append(grouped, data[i])
}
} else {
fmt.Printf("Skipping element at index %d: not a struct or pointer to struct\n", i)
}
}
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)
}
}