From c9aceb53240f1a7f771a76f1892355e159853d4f Mon Sep 17 00:00:00 2001 From: Tobias Gesellchen Date: Sun, 11 Jan 2026 18:07:03 +0100 Subject: [PATCH] feat: implement alphabetical sorting for CLI commands and flags - Add recursive sortCommands function to sort commands and subcommands alphabetically - Add sortFlags function to sort flags alphabetically by name - Add getFlagName helper to extract flag names from different flag types - Sort both top-level commands and all nested subcommands recursively - Sort command-specific flags while preserving auto-generated help flags - Improve CLI usability by making commands and options easier to find Commands and subcommands are now displayed in alphabetical order in help output. All user-defined flags are sorted alphabetically within each command. --- cmd/soundtouch-cli/main.go | 62 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/cmd/soundtouch-cli/main.go b/cmd/soundtouch-cli/main.go index f2d9dd4..1927368 100644 --- a/cmd/soundtouch-cli/main.go +++ b/cmd/soundtouch-cli/main.go @@ -1,9 +1,11 @@ package main import ( + "fmt" "log" "os" "runtime/debug" + "sort" "time" "github.com/urfave/cli/v2" @@ -16,6 +18,58 @@ var ( date = "unknown" ) +// sortCommands recursively sorts commands and their subcommands alphabetically +func sortCommands(commands []*cli.Command) { + sort.Slice(commands, func(i, j int) bool { + return commands[i].Name < commands[j].Name + }) + + // Recursively sort subcommands and flags + for _, cmd := range commands { + // Sort flags for this command + if len(cmd.Flags) > 0 { + sortFlags(cmd.Flags) + } + + // Recursively sort subcommands + if len(cmd.Subcommands) > 0 { + sortCommands(cmd.Subcommands) + } + } +} + +// sortFlags sorts a slice of flags alphabetically by name +func sortFlags(flags []cli.Flag) { + sort.Slice(flags, func(i, j int) bool { + // Get the flag names for comparison + name1 := getFlagName(flags[i]) + name2 := getFlagName(flags[j]) + + return name1 < name2 + }) +} + +// getFlagName extracts the primary name from a flag +func getFlagName(flag cli.Flag) string { + switch f := flag.(type) { + case *cli.StringFlag: + return f.Name + case *cli.IntFlag: + return f.Name + case *cli.BoolFlag: + return f.Name + case *cli.DurationFlag: + return f.Name + case *cli.StringSliceFlag: + return f.Name + default: + // Fallback: try to get name using reflection or string representation + flagStr := fmt.Sprintf("%v", flag) + // This is a simple fallback - in practice, all flags should match the types above + return flagStr + } +} + // updateBuildInfo extracts version information from debug.BuildInfo and updates package variables func updateBuildInfo() { if info, ok := debug.ReadBuildInfo(); ok { @@ -911,6 +965,14 @@ func main() { }, } + // Sort commands alphabetically (including subcommands and flags recursively) + sortCommands(app.Commands) + + // Also sort global flags + if len(app.Flags) > 0 { + sortFlags(app.Flags) + } + if err := app.Run(os.Args); err != nil { log.Fatal(err) }