Author: LakeFox
Email: [email protected]
Date: Sun, 20 Apr 2025 21:14:43 -0600
search/main.go
Delete
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)
	}
}