package processor import ( "crypto/md5" "fmt" "os" "path/filepath" "strings" "time" "github.com/antchfx/xmlquery" lua "github.com/yuin/gopher-lua" "modify/logger" ) // Processor defines the interface for all file processors type Processor interface { // Process handles processing a file with the given pattern and Lua expression // Now implemented as a base function in processor.go // Process(filename string, pattern string, luaExpr string) (int, int, error) // ProcessContent handles processing a string content directly with the given pattern and Lua expression // Returns the modified content, modification count, match count, and any error ProcessContent(content string, pattern string, luaExpr string) (string, int, int, error) // ToLua converts processor-specific data to Lua variables ToLua(L *lua.LState, data interface{}) error // FromLua retrieves modified data from Lua FromLua(L *lua.LState) (interface{}, error) } // ModificationRecord tracks a single value modification type ModificationRecord struct { File string OldValue string NewValue string Operation string Context string } func NewLuaState() (*lua.LState, error) { L := lua.NewState() // defer L.Close() // Load math library logger.Debug("Loading Lua math library") L.Push(L.GetGlobal("require")) L.Push(lua.LString("math")) if err := L.PCall(1, 1, nil); err != nil { logger.Error("Failed to load Lua math library: %v", err) return nil, fmt.Errorf("error loading Lua math library: %v", err) } // Initialize helper functions logger.Debug("Initializing Lua helper functions") if err := InitLuaHelpers(L); err != nil { logger.Error("Failed to initialize Lua helper functions: %v", err) return nil, err } return L, nil } func Process(p Processor, filename string, pattern string, luaExpr string) (int, int, error) { logger.Debug("Processing file %q with pattern %q", filename, pattern) // Read file content cwd, err := os.Getwd() if err != nil { logger.Error("Failed to get current working directory: %v", err) return 0, 0, fmt.Errorf("error getting current working directory: %v", err) } fullPath := filepath.Join(cwd, filename) logger.Trace("Reading file from: %s", fullPath) stat, err := os.Stat(fullPath) if err != nil { logger.Error("Failed to stat file %s: %v", fullPath, err) return 0, 0, fmt.Errorf("error getting file info: %v", err) } logger.Debug("File size: %d bytes, modified: %s", stat.Size(), stat.ModTime().Format(time.RFC3339)) content, err := os.ReadFile(fullPath) if err != nil { logger.Error("Failed to read file %s: %v", fullPath, err) return 0, 0, fmt.Errorf("error reading file: %v", err) } fileContent := string(content) logger.Trace("File read successfully: %d bytes, hash: %x", len(content), md5sum(content)) // Detect and log file type fileType := detectFileType(filename, fileContent) if fileType != "" { logger.Debug("Detected file type: %s", fileType) } // Process the content logger.Debug("Starting content processing with %s processor", getProcessorType(p)) modifiedContent, modCount, matchCount, err := p.ProcessContent(fileContent, pattern, luaExpr) if err != nil { logger.Error("Processing error: %v", err) return 0, 0, err } logger.Debug("Processing results: %d matches, %d modifications", matchCount, modCount) // If we made modifications, save the file if modCount > 0 { // Calculate changes summary changePercent := float64(len(modifiedContent)) / float64(len(fileContent)) * 100 logger.Info("File size change: %d → %d bytes (%.1f%%)", len(fileContent), len(modifiedContent), changePercent) logger.Debug("Writing modified content to %s", fullPath) err = os.WriteFile(fullPath, []byte(modifiedContent), 0644) if err != nil { logger.Error("Failed to write to file %s: %v", fullPath, err) return 0, 0, fmt.Errorf("error writing file: %v", err) } logger.Debug("File written successfully, new hash: %x", md5sum([]byte(modifiedContent))) } else if matchCount > 0 { logger.Debug("No content modifications needed for %d matches", matchCount) } else { logger.Debug("No matches found in file") } return modCount, matchCount, nil } // Helper function to get a short MD5 hash of content for logging func md5sum(data []byte) []byte { h := md5.New() h.Write(data) return h.Sum(nil)[:4] // Just use first 4 bytes for brevity } // Helper function to detect basic file type from extension and content func detectFileType(filename string, content string) string { ext := strings.ToLower(filepath.Ext(filename)) switch ext { case ".xml": return "XML" case ".json": return "JSON" case ".html", ".htm": return "HTML" case ".txt": return "Text" case ".go": return "Go" case ".js": return "JavaScript" case ".py": return "Python" case ".java": return "Java" case ".c", ".cpp", ".h": return "C/C++" default: // Try content-based detection for common formats if strings.HasPrefix(strings.TrimSpace(content), " b { return a } return b } // Min returns the minimum of two integers func Min(a, b int) int { if a < b { return a } return b }