From 06aed7b27acffb38ae673d296fc9d27300efc9a0 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Sun, 26 Oct 2025 16:34:44 +0100 Subject: [PATCH] Migrate from flag to cobra --- go.mod | 3 + go.sum | 8 ++ main.go | 205 ++++++++++++++++++++++++++++--------------------- utils/flags.go | 23 ------ 4 files changed, 127 insertions(+), 112 deletions(-) delete mode 100644 utils/flags.go diff --git a/go.mod b/go.mod index 57357b8..497162b 100644 --- a/go.mod +++ b/go.mod @@ -15,12 +15,15 @@ require ( github.com/BurntSushi/toml v1.5.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/hexops/valast v1.5.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/spf13/cobra v1.10.1 // indirect + github.com/spf13/pflag v1.0.9 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect golang.org/x/mod v0.21.0 // indirect diff --git a/go.sum b/go.sum index 23a84ca..3cd9bfc 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,7 @@ github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -17,6 +18,8 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hexops/valast v1.5.0 h1:FBTuvVi0wjTngtXJRZXMbkN/Dn6DgsUsBwch2DUJU8Y= github.com/hexops/valast v1.5.0/go.mod h1:Jcy1pNH7LNraVaAZDLyv21hHg2WBv9Nf9FL6fGxU7o4= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -36,6 +39,11 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= diff --git a/main.go b/main.go index b55fd6a..7a02f37 100644 --- a/main.go +++ b/main.go @@ -3,8 +3,6 @@ package main import ( _ "embed" "errors" - "flag" - "fmt" "os" "sort" "sync" @@ -14,6 +12,7 @@ import ( "cook/processor" "cook/utils" + "github.com/spf13/cobra" logger "git.site.quack-lab.dev/dave/cylogger" ) @@ -37,52 +36,114 @@ var ( } ) +// rootCmd represents the base command when called without any subcommands +var rootCmd *cobra.Command + +func init() { + rootCmd = &cobra.Command{ + Use: "modifier [options] <...files_or_globs>", + Short: "A powerful file modification tool with Lua scripting", + Long: `Modifier is a powerful file processing tool that supports regex patterns, +JSON manipulation, and YAML to TOML conversion with Lua scripting capabilities. + +Features: +- Regex-based pattern matching and replacement +- JSON file processing with query support +- YAML to TOML conversion +- Lua scripting for complex transformations +- Parallel file processing +- Command filtering and organization`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + CreateExampleConfig() + logger.InitFlag() + mainLogger.Info("Initializing with log level: %s", logger.GetLevel().String()) + mainLogger.Trace("Full argv: %v", os.Args) + }, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + cmd.Usage() + return + } + runModifier(args, cmd) + }, + } + + // Global flags + rootCmd.PersistentFlags().StringP("loglevel", "l", "INFO", "Set logging level: ERROR, WARNING, INFO, DEBUG, TRACE") + + // Local flags + rootCmd.Flags().IntP("parallel", "P", 100, "Number of files to process in parallel") + rootCmd.Flags().StringP("filter", "f", "", "Filter commands before running them") + rootCmd.Flags().Bool("json", false, "Enable JSON mode for processing JSON files") + rootCmd.Flags().BoolP("conv", "c", false, "Convert YAML files to TOML format") + + // Set up examples in the help text + rootCmd.SetUsageTemplate(`Usage:{{if .Runnable}} + {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} + {{.CommandPath}} [command]{{end}} {{if gt (len .Aliases) 0}} + +Aliases: + {{.NameAndAliases}}{{end}}{{if .HasExample}} + +Examples: +{{.Example}}{{end}}{{if .HasAvailableSubCommands}} + +Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} + +Flags: +{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} + +Global Flags: +{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} + +Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} + {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} + +Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} +`) + + // Add examples + rootCmd.Example = ` Regex mode (default): + modifier "(\\d+)" "*1.5" data.xml + + JSON mode: + modifier -json data.json + + YAML to TOML conversion: + modifier -conv *.yml + modifier -conv **/*.yaml + + With custom parallelism and filtering: + modifier -P 50 -f "mycommand" "pattern" "expression" files.txt + +Note: v1, v2, etc. are used to refer to capture groups as numbers. + s1, s2, etc. are used to refer to capture groups as strings. + Helper functions: num(str) converts string to number, str(num) converts number to string + is_number(str) checks if a string is numeric + If expression starts with an operator like *, /, +, -, =, etc., v1 is automatically prepended + You can use any valid Lua code, including if statements, loops, etc. + Glob patterns are supported for file selection (*.xml, data/**.xml, etc.) + +` + processor.GetLuaFunctionsHelp() +} + func main() { - flag.Usage = func() { - CreateExampleConfig() - fmt.Fprintf(os.Stderr, "Usage: %s [options] <...files_or_globs>\n", os.Args[0]) - fmt.Fprintf(os.Stderr, "\nOptions:\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, " -json\n") - fmt.Fprintf(os.Stderr, " Enable JSON mode for processing JSON files\n") - fmt.Fprintf(os.Stderr, " -conv\n") - fmt.Fprintf(os.Stderr, " Convert YAML files to TOML format\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, " JSON mode:\n") - fmt.Fprintf(os.Stderr, " %s -json data.json\n", os.Args[0]) - fmt.Fprintf(os.Stderr, " YAML to TOML conversion:\n") - fmt.Fprintf(os.Stderr, " %s -conv *.yml\n", os.Args[0]) - fmt.Fprintf(os.Stderr, " %s -conv **/*.yaml\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") - fmt.Fprintf(os.Stderr, "\nLua Functions Available:\n") - fmt.Fprintf(os.Stderr, "%s\n", processor.GetLuaFunctionsHelp()) + if err := rootCmd.Execute(); err != nil { + mainLogger.Error("Command execution failed: %v", err) + os.Exit(1) } - // TODO: Fix bed shitting when doing *.yml in barotrauma directory - flag.Parse() - args := flag.Args() +} - logger.InitFlag() - mainLogger.Info("Initializing with log level: %s", logger.GetLevel().String()) - mainLogger.Trace("Full argv: %v", os.Args) - - if flag.NArg() == 0 { - flag.Usage() - return - } +func runModifier(args []string, cmd *cobra.Command) { + // Get flag values from Cobra + convertFlag, _ := cmd.Flags().GetBool("conv") + parallelFlag, _ := cmd.Flags().GetInt("parallel") + filterFlag, _ := cmd.Flags().GetString("filter") + jsonFlag, _ := cmd.Flags().GetBool("json") // Handle YAML to TOML conversion if -conv flag is set - if *utils.Convert { + if convertFlag { mainLogger.Info("YAML to TOML conversion mode enabled") conversionCount := 0 for _, arg := range args { @@ -127,7 +188,7 @@ func main() { commands, err := utils.LoadCommands(args) if err != nil || len(commands) == 0 { mainLogger.Error("Failed to load commands: %v", err) - flag.Usage() + cmd.Usage() return } // Collect global modifiers from special entries and filter them out @@ -149,9 +210,9 @@ func main() { commands = filtered mainLogger.Info("Loaded %d commands", len(commands)) - if *utils.Filter != "" { - mainLogger.Info("Filtering commands by name: %s", *utils.Filter) - commands = utils.FilterCommands(commands, *utils.Filter) + if filterFlag != "" { + mainLogger.Info("Filtering commands by name: %s", filterFlag) + commands = utils.FilterCommands(commands, filterFlag) mainLogger.Info("Filtered %d commands", len(commands)) } @@ -221,9 +282,9 @@ func main() { mainLogger.Debug("Files reset where necessary") // Then for each file run all commands associated with the file - workers := make(chan struct{}, *utils.ParallelFiles) + workers := make(chan struct{}, parallelFlag) wg := sync.WaitGroup{} - mainLogger.Debug("Starting file processing with %d parallel workers", *utils.ParallelFiles) + mainLogger.Debug("Starting file processing with %d parallel workers", parallelFlag) // Add performance tracking startTime := time.Now() @@ -273,7 +334,7 @@ func main() { isChanged := false mainLogger.Debug("Running isolate commands for file %q", file) - fileDataStr, err = RunIsolateCommands(association, file, fileDataStr) + fileDataStr, err = RunIsolateCommands(association, file, fileDataStr, jsonFlag) if err != nil && err != NothingToDo { mainLogger.Error("Failed to run isolate commands for file %q: %v", file, err) atomic.AddInt64(&stats.FailedFiles, 1) @@ -284,7 +345,7 @@ func main() { } mainLogger.Debug("Running other commands for file %q", file) - fileDataStr, err = RunOtherCommands(file, fileDataStr, association, commandLoggers) + fileDataStr, err = RunOtherCommands(file, fileDataStr, association, commandLoggers, jsonFlag) if err != nil && err != NothingToDo { mainLogger.Error("Failed to run other commands for file %q: %v", file, err) atomic.AddInt64(&stats.FailedFiles, 1) @@ -333,40 +394,6 @@ func main() { // 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 { - // mainLogger.Info("Git integration enabled, setting up git repository") - // err := setupGit() - // if err != nil { - // mainLogger.Error("Failed to setup git: %v", err) - // fmt.Fprintf(os.Stderr, "Error setting up git: %v\n", err) - // return - // } - // } - - // mainLogger.Debug("Expanding file patterns") - // files, err := expandFilePatterns(filePatterns) - // if err != nil { - // mainLogger.Error("Failed to expand file patterns: %v", err) - // fmt.Fprintf(os.Stderr, "Error expanding file patterns: %v\n", err) - // return - // } - - // if *gitFlag { - // mainLogger.Info("Cleaning up git files before processing") - // err := cleanupGitFiles(files) - // if err != nil { - // mainLogger.Error("Failed to cleanup git files: %v", err) - // fmt.Fprintf(os.Stderr, "Error cleaning up git files: %v\n", err) - // return - // } - // } - // if *resetFlag { - // mainLogger.Info("Files reset to their original state, nothing more to do") - // log.Printf("Files reset to their original state, nothing more to do") - // return - // } // Print summary totalModifications := atomic.LoadInt64(&stats.TotalModifications) @@ -444,7 +471,7 @@ func CreateExampleConfig() { var NothingToDo = errors.New("nothing to do") -func RunOtherCommands(file string, fileDataStr string, association utils.FileCommandAssociation, commandLoggers map[string]*logger.Logger) (string, error) { +func RunOtherCommands(file string, fileDataStr string, association utils.FileCommandAssociation, commandLoggers map[string]*logger.Logger, jsonFlag bool) (string, error) { runOtherCommandsLogger := mainLogger.WithPrefix("RunOtherCommands").WithField("file", file) runOtherCommandsLogger.Debug("Running other commands for file") runOtherCommandsLogger.Trace("File data before modifications: %s", utils.LimitString(fileDataStr, 200)) @@ -454,7 +481,7 @@ func RunOtherCommands(file string, fileDataStr string, association utils.FileCom regexCommands := []utils.ModifyCommand{} for _, command := range association.Commands { - if command.JSON || *utils.JSON { + if command.JSON || jsonFlag { jsonCommands = append(jsonCommands, command) } else { regexCommands = append(regexCommands, command) @@ -548,7 +575,7 @@ func RunOtherCommands(file string, fileDataStr string, association utils.FileCom return fileDataStr, nil } -func RunIsolateCommands(association utils.FileCommandAssociation, file string, fileDataStr string) (string, error) { +func RunIsolateCommands(association utils.FileCommandAssociation, file string, fileDataStr string, jsonFlag bool) (string, error) { runIsolateCommandsLogger := mainLogger.WithPrefix("RunIsolateCommands").WithField("file", file) runIsolateCommandsLogger.Debug("Running isolate commands for file") runIsolateCommandsLogger.Trace("File data before isolate modifications: %s", utils.LimitString(fileDataStr, 200)) @@ -558,7 +585,7 @@ func RunIsolateCommands(association utils.FileCommandAssociation, file string, f for _, isolateCommand := range association.IsolateCommands { // Check if this isolate command should use JSON mode - if isolateCommand.JSON || *utils.JSON { + if isolateCommand.JSON || jsonFlag { runIsolateCommandsLogger.Debug("Begin processing file with JSON isolate command %q", isolateCommand.Name) modifications, err := processor.ProcessJSON(currentFileData, isolateCommand, file) if err != nil { diff --git a/utils/flags.go b/utils/flags.go deleted file mode 100644 index fb47fe0..0000000 --- a/utils/flags.go +++ /dev/null @@ -1,23 +0,0 @@ -package utils - -import ( - "flag" - - logger "git.site.quack-lab.dev/dave/cylogger" -) - -// flagsLogger is a scoped logger for the utils/flags package. -var flagsLogger = logger.Default.WithPrefix("utils/flags") - -var ( - ParallelFiles = flag.Int("P", 100, "Number of files to process in parallel") - Filter = flag.String("f", "", "Filter commands before running them") - JSON = flag.Bool("json", false, "Enable JSON mode for processing JSON files") - Convert = flag.Bool("conv", false, "Convert YAML files to TOML format") -) - -func init() { - flagsLogger.Debug("Initializing command-line flags") - flagsLogger.Trace("Initial flag values - ParallelFiles: %d, Filter: %q, JSON: %t, Convert: %t", *ParallelFiles, *Filter, *JSON, *Convert) - flagsLogger.Debug("Flag definitions: -P (parallel files), -f (filter), -json (JSON mode), -conv (YAML to TOML conversion)") -}