From a547738453f0d96275ec1c5f17602fec83a8db66 Mon Sep 17 00:00:00 2001 From: barry Date: Tue, 30 Dec 2025 22:57:55 +0800 Subject: [PATCH] chore: quick update feat/flag-action at 2025-12-31 20:27:57 --- .version/VERSION | 2 +- command.go | 35 +++++++++++++++++++++++++++++------ example/demo/main.go | 5 +++-- option.go | 5 +++++ 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/.version/VERSION b/.version/VERSION index 8ce995b..7df503e 100644 --- a/.version/VERSION +++ b/.version/VERSION @@ -1 +1 @@ -v0.0.3 \ No newline at end of file +v0.0.4 diff --git a/command.go b/command.go index cd789c7..318f46f 100644 --- a/command.go +++ b/command.go @@ -164,6 +164,7 @@ func (c *Command) FullOptions() OptionSet { } // GetGlobalFlags returns the global flags from the root command +// All non-hidden options in the root command are considered global flags func (c *Command) GetGlobalFlags() OptionSet { // Traverse to the root command root := c @@ -171,11 +172,10 @@ func (c *Command) GetGlobalFlags() OptionSet { root = root.parent } - // Find global flags (they have specific names) + // Return all non-hidden options from root command as global flags var globalFlags OptionSet for _, opt := range root.Options { - switch opt.Flag { - case "help", "list-commands", "list-flags": + if opt.Flag != "" && !opt.Hidden { globalFlags = append(globalFlags, opt) } } @@ -620,6 +620,30 @@ func (inv *Invocation) run(state *runState) error { } } + // Execute Action callbacks for options that were set + // Don't execute actions if help was requested + if !isHelpRequested && !errors.Is(state.flagParseErr, pflag.ErrHelp) && inv.Flags != nil { + // Use a map to track which flags we've already processed + // This prevents executing Action multiple times for the same flag + processedFlags := make(map[string]bool) + + // Execute actions for flags, starting from the current command and moving up to the root. + // This ensures that if a flag is defined in multiple commands (e.g., overridden), + // the action of the most specific command (the current one) is executed. + for cmd := inv.Command; cmd != nil; cmd = cmd.parent { + for _, opt := range cmd.Options { + if opt.Action != nil && opt.Flag != "" && !processedFlags[opt.Flag] { + if ff := inv.Flags.Lookup(opt.Flag); ff != nil && ff.Changed { + if err := opt.Action(ff.Value); err != nil { + return fmt.Errorf("action for flag %q failed: %w", opt.Flag, err) + } + processedFlags[opt.Flag] = true + } + } + } + } + } + // Parse and assign arguments if inv.Command.RawArgs { // If we're at the root command, then the name is omitted @@ -670,12 +694,11 @@ func (inv *Invocation) run(state *runState) error { // to get [root, parent, ..., child] order. Chain() will reverse again // to ensure execution order is root -> parent -> ... -> child -> handler var middlewareChain []MiddlewareFunc - cmd := inv.Command - for cmd != nil { + for cmd := inv.Command; cmd != nil; cmd = cmd.parent { if cmd.Middleware != nil { middlewareChain = append(middlewareChain, cmd.Middleware) } - cmd = cmd.parent + } // Reverse to get order from root (parent) to current (child) // This ensures Chain() will execute them in the correct order: root -> parent -> child -> handler diff --git a/example/demo/main.go b/example/demo/main.go index 63dfb72..8f1bcb2 100644 --- a/example/demo/main.go +++ b/example/demo/main.go @@ -11,7 +11,7 @@ import ( ) func main() { - ok := true + ok := "true" // Create root command rootCmd := &redant.Command{ Use: "myapp", @@ -20,7 +20,8 @@ func main() { Options: redant.OptionSet{ { Flag: "upper", - Value: redant.BoolOf(&ok), + Value: redant.StringOf(&ok), + Shorthand: "u", Description: "Prints the text in upper case.", }, }, diff --git a/option.go b/option.go index 50a8aad..5da2d93 100644 --- a/option.go +++ b/option.go @@ -39,6 +39,11 @@ type Option struct { Deprecated string Category string + + // Action is called after the flag is parsed and set. + // It receives the flag value and can perform additional validation or side effects. + // If Action returns an error, command execution will fail. + Action func(val pflag.Value) error `json:"-"` } // OptionSet is a group of options that can be applied to a command.