package processor import ( "fmt" "os" "path/filepath" "regexp" "strconv" "strings" lua "github.com/yuin/gopher-lua" ) // RegexProcessor implements the Processor interface using regex patterns type RegexProcessor struct{} // Process implements the Processor interface for RegexProcessor func (p *RegexProcessor) Process(filename string, pattern string, luaExpr string) (int, int, error) { // Read file content fullPath := filepath.Join(".", filename) content, err := os.ReadFile(fullPath) if err != nil { return 0, 0, fmt.Errorf("error reading file: %v", err) } fileContent := string(content) // Process the content modifiedContent, modCount, matchCount, err := p.ProcessContent(fileContent, pattern, luaExpr) if err != nil { return 0, 0, err } // If we made modifications, save the file if modCount > 0 { err = os.WriteFile(fullPath, []byte(modifiedContent), 0644) if err != nil { return 0, 0, fmt.Errorf("error writing file: %v", err) } } return modCount, matchCount, nil } // ToLua sets capture groups as Lua variables (v1, v2, etc. for numeric values and s1, s2, etc. for strings) func (p *RegexProcessor) ToLua(L *lua.LState, data interface{}) error { captures, ok := data.([]string) if !ok { return fmt.Errorf("expected []string for captures, got %T", data) } // Set variables for each capture group, starting from v1/s1 for the first capture for i := 1; i < len(captures); i++ { // Set string version (always available as s1, s2, etc.) L.SetGlobal(fmt.Sprintf("s%d", i), lua.LString(captures[i])) // Try to convert to number and set v1, v2, etc. if val, err := strconv.ParseFloat(captures[i], 64); err == nil { L.SetGlobal(fmt.Sprintf("v%d", i), lua.LNumber(val)) } } return nil } // FromLua implements the Processor interface for RegexProcessor func (p *RegexProcessor) FromLua(L *lua.LState) (interface{}, error) { // Get the modified values after Lua execution modifications := make(map[int]string) // Check for modifications to v1-v12 and s1-s12 for i := 0; i < 12; i++ { // Check both v and s variables to see if any were modified vVarName := fmt.Sprintf("v%d", i+1) sVarName := fmt.Sprintf("s%d", i+1) vLuaVal := L.GetGlobal(vVarName) sLuaVal := L.GetGlobal(sVarName) // If our value is a number then it's very likely we want it to be a number // And not a string // If we do want it to be a string we will cast it into a string in lua // wait that wouldn't work... Casting v to a string would not load it here if vLuaVal.Type() == lua.LTNumber { modifications[i] = vLuaVal.String() continue } if sLuaVal.Type() == lua.LTString { modifications[i] = sLuaVal.String() continue } } return modifications, nil } // ProcessContent applies regex replacement with Lua processing func (p *RegexProcessor) ProcessContent(content string, pattern string, luaExpr string) (string, int, int, error) { // Handle special pattern modifications if !strings.HasPrefix(pattern, "(?s)") { pattern = "(?s)" + pattern } compiledPattern, err := regexp.Compile(pattern) if err != nil { return "", 0, 0, fmt.Errorf("error compiling pattern: %v", err) } previous := luaExpr luaExpr = BuildLuaScript(luaExpr) fmt.Printf("Changing Lua expression from: %s to: %s\n", previous, luaExpr) L := lua.NewState() defer L.Close() // Initialize Lua environment modificationCount := 0 matchCount := 0 // Load math library L.Push(L.GetGlobal("require")) L.Push(lua.LString("math")) if err := L.PCall(1, 1, nil); err != nil { return content, 0, 0, fmt.Errorf("error loading Lua math library: %v", err) } // Initialize helper functions if err := InitLuaHelpers(L); err != nil { return content, 0, 0, err } // Process all regex matches result := compiledPattern.ReplaceAllStringFunc(content, func(match string) string { matchCount++ captures := compiledPattern.FindStringSubmatch(match) if len(captures) <= 1 { // No capture groups, return unchanged fmt.Println("No capture groups for lua to chew on") return match } if err := p.ToLua(L, captures); err != nil { fmt.Println("Error setting Lua variables:", err) return match } // Execute the user's Lua code if err := L.DoString(luaExpr); err != nil { fmt.Println("Error executing Lua code:", err) return match // Return unchanged on error } // Get modifications from Lua modResult, err := p.FromLua(L) if err != nil { fmt.Println("Error getting modifications:", err) return match } // Apply modifications to the matched text modsMap, ok := modResult.(map[int]string) if !ok || len(modsMap) == 0 { fmt.Println("No modifications to apply") return match // No changes } // Apply the modifications to the original match result := match for i, newVal := range modsMap { oldVal := captures[i+1] result = strings.Replace(result, oldVal, newVal, 1) } modificationCount++ return result }) return result, modificationCount, matchCount, nil }