package main import ( "flag" "fmt" "log" "os" "sync" "github.com/bmatcuk/doublestar/v4" "modify/processor" ) type GlobalStats struct { TotalMatches int TotalModifications int ProcessedFiles int FailedFiles int } var stats GlobalStats var logger *log.Logger var ( jsonFlag = flag.Bool("json", false, "Process JSON files") xmlFlag = flag.Bool("xml", false, "Process XML files") ) func init() { log.SetFlags(log.Lmicroseconds | log.Lshortfile) logger = log.New(os.Stdout, "", log.Lmicroseconds|log.Lshortfile) stats = GlobalStats{} } func main() { flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: %s [options] <...files_or_globs>\n", os.Args[0]) fmt.Fprintf(os.Stderr, "\nOptions:\n") fmt.Fprintf(os.Stderr, " -mode string\n") fmt.Fprintf(os.Stderr, " Processing mode: regex, xml, json (default \"regex\")\n") fmt.Fprintf(os.Stderr, "\nExamples:\n") fmt.Fprintf(os.Stderr, " Regex mode (default):\n") fmt.Fprintf(os.Stderr, " %s \"(\\d+)\" \"*1.5\" data.xml\n", os.Args[0]) fmt.Fprintf(os.Stderr, " XML mode:\n") fmt.Fprintf(os.Stderr, " %s -mode=xml -xpath=\"//value\" \"*1.5\" data.xml\n", os.Args[0]) fmt.Fprintf(os.Stderr, " JSON mode:\n") fmt.Fprintf(os.Stderr, " %s -mode=json -jsonpath=\"$.items[*].value\" \"*1.5\" data.json\n", os.Args[0]) fmt.Fprintf(os.Stderr, "\nNote: v1, v2, etc. are used to refer to capture groups as numbers.\n") fmt.Fprintf(os.Stderr, " s1, s2, etc. are used to refer to capture groups as strings.\n") fmt.Fprintf(os.Stderr, " Helper functions: num(str) converts string to number, str(num) converts number to string\n") fmt.Fprintf(os.Stderr, " is_number(str) checks if a string is numeric\n") fmt.Fprintf(os.Stderr, " If expression starts with an operator like *, /, +, -, =, etc., v1 is automatically prepended\n") fmt.Fprintf(os.Stderr, " You can use any valid Lua code, including if statements, loops, etc.\n") fmt.Fprintf(os.Stderr, " Glob patterns are supported for file selection (*.xml, data/**.xml, etc.)\n") } flag.Parse() args := flag.Args() if len(args) < 3 { log.Printf("At least %d arguments are required", 3) flag.Usage() return } // Get the appropriate pattern and expression based on mode var pattern, luaExpr string var filePatterns []string pattern = args[0] luaExpr = args[1] filePatterns = args[2:] // Prepare the Lua expression originalLuaExpr := luaExpr luaExpr = processor.BuildLuaScript(luaExpr) if originalLuaExpr != luaExpr { logger.Printf("Transformed Lua expression from %q to %q", originalLuaExpr, luaExpr) } // Expand file patterns with glob support files, err := expandFilePatterns(filePatterns) if err != nil { fmt.Fprintf(os.Stderr, "Error expanding file patterns: %v\n", err) return } if len(files) == 0 { fmt.Fprintf(os.Stderr, "No files found matching the specified patterns\n") return } // Create the processor based on mode var proc processor.Processor switch { case *xmlFlag: proc = &processor.XMLProcessor{} logger.Printf("Starting XML modifier with XPath %q, expression %q on %d files", pattern, luaExpr, len(files)) case *jsonFlag: proc = &processor.JSONProcessor{} logger.Printf("Starting JSON modifier with JSONPath %q, expression %q on %d files", pattern, luaExpr, len(files)) default: proc = &processor.RegexProcessor{} logger.Printf("Starting regex modifier with pattern %q, expression %q on %d files", pattern, luaExpr, len(files)) } var wg sync.WaitGroup // Process each file for _, file := range files { wg.Add(1) go func(file string) { defer wg.Done() logger.Printf("Processing file: %s", file) modCount, matchCount, err := proc.Process(file, pattern, luaExpr) if err != nil { fmt.Fprintf(os.Stderr, "Failed to process file %s: %v\n", file, err) stats.FailedFiles++ } else { logger.Printf("Successfully processed file: %s", file) stats.ProcessedFiles++ stats.TotalMatches += matchCount stats.TotalModifications += modCount } }(file) } wg.Wait() // Print summary if stats.TotalModifications == 0 { fmt.Fprintf(os.Stderr, "No modifications were made in any files\n") } else { fmt.Printf("Operation complete! Modified %d values in %d/%d files\n", stats.TotalModifications, stats.ProcessedFiles, stats.ProcessedFiles+stats.FailedFiles) } } func expandFilePatterns(patterns []string) ([]string, error) { var files []string filesMap := make(map[string]bool) for _, pattern := range patterns { matches, _ := doublestar.Glob(os.DirFS("."), pattern) for _, m := range matches { if info, err := os.Stat(m); err == nil && !info.IsDir() && !filesMap[m] { filesMap[m], files = true, append(files, m) } } } if len(files) > 0 { logger.Printf("Found %d files to process", len(files)) } return files, nil }