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)) } else { // For non-numeric values, set v to 0 L.SetGlobal(fmt.Sprintf("v%d", i), lua.LNumber(0)) } } 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) // First check string variables (s1, s2, etc.) as they should have priority if sLuaVal != lua.LNil { if sStr, ok := sLuaVal.(lua.LString); ok { newStrVal := string(sStr) modifications[i] = newStrVal continue } } // Then check numeric variables (v1, v2, etc.) if vLuaVal != lua.LNil { switch v := vLuaVal.(type) { case lua.LNumber: // Convert numeric value to string newNumVal := strconv.FormatFloat(float64(v), 'f', -1, 64) modifications[i] = newNumVal continue case lua.LString: // Use string value directly newStrVal := string(v) modifications[i] = newStrVal continue default: // Convert other types to string newDefaultVal := fmt.Sprintf("%v", v) modifications[i] = newDefaultVal 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) } 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 return match } // Pass the captures to Lua environment if err := p.ToLua(L, captures); err != nil { return match } // Execute the user's Lua code if err := L.DoString(luaExpr); err != nil { return match // Return unchanged on error } // Get modifications from Lua modResult, err := p.FromLua(L) if err != nil { return match } // Apply modifications to the matched text modsMap, ok := modResult.(map[int]string) if !ok || len(modsMap) == 0 { 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 }