Files
BigChef/main.go

222 lines
7.6 KiB
Go

package main
import (
"flag"
"fmt"
"os"
"sort"
"sync"
"modify/utils"
"github.com/go-git/go-git/v5"
"modify/logger"
)
type GlobalStats struct {
TotalMatches int
TotalModifications int
ProcessedFiles int
FailedFiles int
}
var (
repo *git.Repository
worktree *git.Worktree
stats GlobalStats = GlobalStats{}
)
func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [options] <pattern> <lua_expression> <...files_or_globs>\n", os.Args[0])
fmt.Fprintf(os.Stderr, "\nOptions:\n")
fmt.Fprintf(os.Stderr, " -git\n")
fmt.Fprintf(os.Stderr, " Use git to manage files\n")
fmt.Fprintf(os.Stderr, " -reset\n")
fmt.Fprintf(os.Stderr, " Reset files to their original state\n")
fmt.Fprintf(os.Stderr, " -loglevel string\n")
fmt.Fprintf(os.Stderr, " Set logging level: ERROR, WARNING, INFO, DEBUG, TRACE (default \"INFO\")\n")
fmt.Fprintf(os.Stderr, "\nExamples:\n")
fmt.Fprintf(os.Stderr, " Regex mode (default):\n")
fmt.Fprintf(os.Stderr, " %s \"<value>(\\d+)</value>\" \"*1.5\" data.xml\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()
level := logger.ParseLevel(*utils.LogLevel)
logger.Init(level)
logger.Info("Initializing with log level: %s", level.String())
// The plan is:
// Load all commands
commands, err := utils.LoadCommands(args)
if err != nil {
logger.Error("Failed to load commands: %v", err)
flag.Usage()
return
}
// Then aggregate all the globs and deduplicate them
globs := utils.AggregateGlobs(commands)
// Resolve all the files for all the globs
logger.Info("Found %d unique file patterns", len(globs))
files, err := utils.ExpandGLobs(globs)
if err != nil {
logger.Error("Failed to expand file patterns: %v", err)
return
}
logger.Info("Found %d files to process", len(files))
// Somehow connect files to commands via globs..
// For each file check every glob of every command
// Maybe memoize this part
// That way we know what commands affect what files
associations, err := utils.AssociateFilesWithCommands(files, commands)
if err != nil {
logger.Error("Failed to associate files with commands: %v", err)
return
}
// TODO: Utilize parallel workers for this
// Then for each file run all commands associated with the file
workers := make(chan struct{}, *utils.ParallelFiles)
wg := sync.WaitGroup{}
for file, commands := range associations {
workers <- struct{}{}
wg.Add(1)
go func(file string, commands []utils.ModifyCommand) {
defer func() { <-workers }()
defer wg.Done()
fileData, err := os.ReadFile(file)
if err != nil {
logger.Error("Failed to read file %q: %v", file, err)
return
}
logger.Trace("Loaded %d bytes of data for file %q", len(fileData), file)
fileDataStr := string(fileData)
// Aggregate all the modifications and execute them
modifications := []utils.ReplaceCommand{}
for _, command := range commands {
logger.Info("Processing file %q with command %q", file, command.Pattern)
// TODO: Run processor and return modifications
}
// Sort commands in reverse order for safe replacements
sort.Slice(modifications, func(i, j int) bool {
return modifications[i].From > modifications[j].From
})
logger.Trace("Applying %d replacement commands in reverse order", len(modifications))
fileDataStr, count := utils.ExecuteModifications(modifications, fileDataStr)
logger.Info("Executed %d modifications for file %q", count, file)
err = os.WriteFile(file, []byte(fileDataStr), 0644)
if err != nil {
logger.Error("Failed to write file %q: %v", file, err)
return
}
}(file, commands)
}
// This will also relieve processor of some of the file loading
// But we will also have to rework the tests.......
// Also give each command its own logger, maybe prefix it with something... Maybe give commands a name?
// Do that with logger.WithField("loglevel", level.String())
// Since each command also has its own log level
// TODO: Maybe even figure out how to run individual commands...?
// TODO: What to do with git? Figure it out ....
// if *gitFlag {
// logger.Info("Git integration enabled, setting up git repository")
// err := setupGit()
// if err != nil {
// logger.Error("Failed to setup git: %v", err)
// fmt.Fprintf(os.Stderr, "Error setting up git: %v\n", err)
// return
// }
// }
// logger.Debug("Expanding file patterns")
// files, err := expandFilePatterns(filePatterns)
// if err != nil {
// logger.Error("Failed to expand file patterns: %v", err)
// fmt.Fprintf(os.Stderr, "Error expanding file patterns: %v\n", err)
// return
// }
// if len(files) == 0 {
// logger.Warning("No files found matching the specified patterns")
// fmt.Fprintf(os.Stderr, "No files found matching the specified patterns\n")
// return
// }
// if *gitFlag {
// logger.Info("Cleaning up git files before processing")
// err := cleanupGitFiles(files)
// if err != nil {
// logger.Error("Failed to cleanup git files: %v", err)
// fmt.Fprintf(os.Stderr, "Error cleaning up git files: %v\n", err)
// return
// }
// }
// if *resetFlag {
// logger.Info("Files reset to their original state, nothing more to do")
// log.Printf("Files reset to their original state, nothing more to do")
// return
// }
// Process each file
// for _, file := range files {
// wg.Add(1)
// logger.SafeGoWithArgs(func(args ...interface{}) {
// defer wg.Done()
// fileToProcess := args[0].(string)
// logger.Debug("Processing file: %s", fileToProcess)
// // It's a bit fucked, maybe I could do better to call it from proc... But it'll do for now
// modCount, matchCount, err := processor.Process(proc, fileToProcess, pattern, luaExpr)
// if err != nil {
// logger.Error("Failed to process file %s: %v", fileToProcess, err)
// fmt.Fprintf(os.Stderr, "Failed to process file %s: %v\n", fileToProcess, err)
// stats.FailedFiles++
// } else {
// if modCount > 0 {
// logger.Info("Successfully processed file %s: %d modifications from %d matches",
// fileToProcess, modCount, matchCount)
// } else if matchCount > 0 {
// logger.Info("Found %d matches in file %s but made no modifications",
// matchCount, fileToProcess)
// } else {
// logger.Debug("No matches found in file: %s", fileToProcess)
// }
// stats.ProcessedFiles++
// stats.TotalMatches += matchCount
// stats.TotalModifications += modCount
// }
// }, file)
// }
// wg.Wait()
// Print summary
if stats.TotalModifications == 0 {
logger.Warning("No modifications were made in any files")
fmt.Fprintf(os.Stderr, "No modifications were made in any files\n")
} else {
logger.Info("Operation complete! Modified %d values in %d/%d files",
stats.TotalModifications, stats.ProcessedFiles, stats.ProcessedFiles+stats.FailedFiles)
fmt.Printf("Operation complete! Modified %d values in %d/%d files\n",
stats.TotalModifications, stats.ProcessedFiles, stats.ProcessedFiles+stats.FailedFiles)
}
}