Migrate from flag to cobra

This commit is contained in:
2025-10-26 16:34:44 +01:00
parent b001dfe667
commit 06aed7b27a
4 changed files with 127 additions and 112 deletions

205
main.go
View File

@@ -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] <pattern> <lua_expression> <...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 "<value>(\\d+)</value>" "*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] <pattern> <lua_expression> <...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 \"<value>(\\\\d+)</value>\" \"*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 {