Repository: urfave/cli
Pull request #1337 (2022-02-15 16:04:53)

Flag-level Action
<!--
  This template provides some ideas of things to include in your PR description.
  To start, try providing a short summary of your changes in the Title above.
  If a section of the PR template does not apply to this PR, then delete that section.
 -->

## What type of PR is this?

- feature

## What this PR does / why we need it:
https://github.com/urfave/cli/issues/1219
I like the idea, but there is no comment from the maintainers, I'd like to have a simple implementation first and then hear your opinions.

### Example
```go
package main

import (
	"fmt"
	"os"

	"github.com/urfave/cli/v2"
)

func main() {
	app := &cli.App{
		Action: func(c *cli.Context) error { return nil },
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name: "f",
				Action: func(c *cli.Context, s string) error {
					fmt.Println(s)
					return nil
				},
			},
		},
	}

	err := app.Run(os.Args)
	if err != nil {
		panic(err)
	}
}
```

```bash
## nothing if no flag
go run main.go
## print flag value
go run main.go -f test
#> test
```

<!--
  What goal is this change working towards?
  Provide a bullet pointed summary of how each file was changed.
  Briefly explain any decisions you made with respect to the changes.
  Include anything here that you didn't include in *Release Notes*
  above, such as changes to CI or changes to internal methods.
-->

## Which issue(s) this PR fixes:

close https://github.com/urfave/cli/issues/1219
<!--
If this PR fixes one of more issues, list them here.
One line each, like so:

Fixes #123
Fixes #39
-->


xwjdsh: @dearchap Thank you for your review, I updated and rebased main branch.
meatballhat: Ack, sorry for the conflicts! I'm happy to attempt to resolve them.
meatballhat: These conflicts are going to be more difficult given the flag generation code introduced in the meantime. Giving it a shot 🤞🏼 
xwjdsh: @meatballhat I'll fix it later, that's OK.
meatballhat: @xwjdsh I'm happy to carry this work forward if you're not able ❤️ 
xwjdsh: Sorry for being late, I updated.

Update `gopkg.in/yaml.v2` to `gopkg.in/yaml.v3` cause v2 seems have an issue that commas cannot be unmarshal correctly, I switched to v3 and the issue resolved.
meatballhat: @xwjdsh I'm sorry about this frustrating feedback loop with having to approve GitHub Actions 😩 
xwjdsh: @meatballhat I'm a little bit confused about the CI error, my local environment is normal...
meatballhat: @xwjdsh I'm sorry about this. There's something up with the way that the `go.mod` is written that seems to be upsetting for go 1.16.x. This isn't the first time this problem has come up in CI. Here's my attempted fix: https://github.com/urfave/cli/pull/1407. Once that's merged, can you incorporate latest `main`?
dearchap: @xwjdsh @meatballhat Is one of you looking at the test failures and conflicts ? 
meatballhat: > @xwjdsh @meatballhat Is one of you looking at the test failures and conflicts ?

Not me!

Patch:
diff --git a/app.go b/app.go
index 2ffacd5..9f72f1b 100644
--- a/app.go
+++ b/app.go
@@ -342,6 +342,10 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
 		}
 	}
 
+	if err = runFlagActions(cCtx, a.Flags); err != nil {
+		return err
+	}
+
 	var c *Command
 	args := cCtx.Args()
 	if args.Present() {
@@ -523,6 +527,10 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
 		}
 	}
 
+	if err = runFlagActions(cCtx, a.Flags); err != nil {
+		return err
+	}
+
 	args := cCtx.Args()
 	if args.Present() {
 		name := args.First()
@@ -646,6 +654,26 @@ func (a *App) argsWithDefaultCommand(oldArgs Args) Args {
 	return oldArgs
 }
 
+func runFlagActions(c *Context, fs []Flag) error {
+	for _, f := range fs {
+		isSet := false
+		for _, name := range f.Names() {
+			if c.IsSet(name) {
+				isSet = true
+				break
+			}
+		}
+		if isSet {
+			if af, ok := f.(ActionableFlag); ok {
+				if err := af.RunAction(c); err != nil {
+					return err
+				}
+			}
+		}
+	}
+	return nil
+}
+
 // Author represents someone who has contributed to a cli project.
 type Author struct {
 	Name  string // The Authors name
diff --git a/cmd/urfave-cli-genflags/go.sum b/cmd/urfave-cli-genflags/go.sum
index 9821127..e59916d 100644
--- a/cmd/urfave-cli-genflags/go.sum
+++ b/cmd/urfave-cli-genflags/go.sum
@@ -1,5 +1,3 @@
-github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
-github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@@ -10,7 +8,6 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRT
 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
 golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/command.go b/command.go
index 13b79de..cd8ea91 100644
--- a/command.go
+++ b/command.go
@@ -165,6 +165,10 @@ func (c *Command) Run(ctx *Context) (err error) {
 		}
 	}
 
+	if err = runFlagActions(cCtx, c.Flags); err != nil {
+		return err
+	}
+
 	if c.Action == nil {
 		c.Action = helpSubcommand.Action
 	}
diff --git a/flag-spec.yaml b/flag-spec.yaml
index 5c480bb..76480ba 100644
--- a/flag-spec.yaml
+++ b/flag-spec.yaml
@@ -7,47 +7,77 @@ flag_types:
       - name: Count
         type: int
         pointer: true
+      - name: Action
+        type: "func(*Context, bool) error"
   float64:
+    struct_fields:
+      - name: Action
+        type: "func(*Context, float64) error"
   Float64Slice:
     value_pointer: true
     skip_interfaces:
       - fmt.Stringer
+    struct_fields:
+      - name: Action
+        type: "func(*Context, []float64) error"
   int:
     struct_fields:
       - name: Base
         type: int
+      - name: Action
+        type: "func(*Context, int) error"
   IntSlice:
     value_pointer: true
     skip_interfaces:
       - fmt.Stringer
+    struct_fields:
+      - name: Action
+        type: "func(*Context, []int) error"
   int64:
     struct_fields:
       - name: Base
         type: int
+      - name: Action
+        type: "func(*Context, int64) error"
   Int64Slice:
     value_pointer: true
     skip_interfaces:
       - fmt.Stringer
+    struct_fields:
+      - name: Action
+        type: "func(*Context, []int64) error"
   uint:
     struct_fields:
       - name: Base
         type: int
+      - name: Action
+        type: "func(*Context, uint) error"
   UintSlice:
     value_pointer: true
     skip_interfaces:
       - fmt.Stringer
+    struct_fields:
+      - name: Action
+        type: "func(*Context, []uint) error"
   uint64:
     struct_fields:
       - name: Base
         type: int
+      - name: Action
+        type: "func(*Context, uint64) error"
   Uint64Slice:
     value_pointer: true
     skip_interfaces:
       - fmt.Stringer
+    struct_fields:
+      - name: Action
+        type: "func(*Context, []uint64) error"
   string:
     struct_fields:
       - name: TakesFile
         type: bool
+      - name: Action
+        type: "func(*Context, string) error"
   StringSlice:
     value_pointer: true
     skip_interfaces:
@@ -55,7 +85,12 @@ flag_types:
     struct_fields:
       - name: TakesFile
         type: bool
+      - name: Action
+        type: "func(*Context, []string) error"
   time.Duration:
+    struct_fields:
+      - name: Action
+        type: "func(*Context, time.Duration) error"
   Timestamp:
     value_pointer: true
     struct_fields:
@@ -63,12 +98,18 @@ flag_types:
         type: string
       - name: Timezone
         type: "*time.Location"
+      - name: Action
+        type: "func(*Context, *time.Time) error"
   Generic:
     no_destination_pointer: true
     struct_fields:
       - name: TakesFile
         type: bool
+      - name: Action
+        type: "func(*Context, interface{}) error"
   Path:
     struct_fields:
       - name: TakesFile
         type: bool
+      - name: Action
+        type: "func(*Context, Path) error"
diff --git a/flag.go b/flag.go
index 050bb4b..7b5ec49 100644
--- a/flag.go
+++ b/flag.go
@@ -83,6 +83,12 @@ func (f FlagsByName) Swap(i, j int) {
 	f[i], f[j] = f[j], f[i]
 }
 
+// ActionableFlag is an interface that wraps Flag interface and RunAction operation.
+type ActionableFlag interface {
+	Flag
+	RunAction(*Context) error
+}
+
 // Flag is a common interface related to parsing flags in cli.
 // For more advanced flag parsing techniques, it is recommended that
 // this interface be implemented.
diff --git a/flag_bool.go b/flag_bool.go
index cb937ae..0be27e3 100644
--- a/flag_bool.go
+++ b/flag_bool.go
@@ -92,6 +92,15 @@ func (f *BoolFlag) GetEnvVars() []string {
 	return f.EnvVars
 }
 
+// RunAction executes flag action if set
+func (f *BoolFlag) RunAction(c *Context) error {
+	if f.Action != nil {
+		return f.Action(c, c.Bool(f.Name))
+	}
+
+	return nil
+}
+
 // Apply populates the flag given the flag set and environment
 func (f *BoolFlag) Apply(set *flag.FlagSet) error {
 	if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
diff --git a/flag_duration.go b/flag_duration.go
index 5178c6a..31db410 100644
--- a/flag_duration.go
+++ b/flag_duration.go
@@ -70,6 +70,15 @@ func (f *DurationFlag) Get(ctx *Context) time.Duration {
 	return ctx.Duration(f.Name)
 }
 
+// RunAction executes flag action if set
+func (f *DurationFlag) RunAction(c *Context) error {
+	if f.Action != nil {
+		return f.Action(c, c.Duration(f.Name))
+	}
+
+	return nil
+}
+
 // Duration looks up the value of a local DurationFlag, returns
 // 0 if not found
 func (cCtx *Context) Duration(name string) time.Duration {
diff --git a/flag_float64.go b/flag_float64.go
index 2d31739..bce26c1 100644
--- a/flag_float64.go
+++ b/flag_float64.go
@@ -70,6 +70,15 @@ func (f *Float64Flag) Get(ctx *Context) float64 {
 	return ctx.Float64(f.Name)
 }
 
+// RunAction executes flag action if set
+func (f *Float64Flag) RunAction(c *Context) error {
+	if f.Action != nil {
+		return f.Action(c, c.Float64(f.Name))
+	}
+
+	return nil
+}
+
 // Float64 looks up the value of a local Float64Flag, returns
 // 0 if not found
 func (cCtx *Context) Float64(name string) float64 {
diff --git a/flag_float64_slice.go b/flag_float64_slice.go
index e4aff73..2cb5e4a 100644
--- a/flag_float64_slice.go
+++ b/flag_float64_slice.go
@@ -181,6 +181,15 @@ func (f *Float64SliceFlag) stringify() string {
 	return stringifySliceFlag(f.Usage, f.Names(), defaultVals)
 }
 
+// RunAction executes flag action if set
+func (f *Float64SliceFlag) RunAction(c *Context) error {
+	if f.Action != nil {
+		return f.Action(c, c.Float64Slice(f.Name))
+	}
+
+	return nil
+}
+
 // Float64Slice looks up the value of a local Float64SliceFlag, returns
 // nil if not found
 func (cCtx *Context) Float64Slice(name string) []float64 {
diff --git a/flag_generic.go b/flag_generic.go
index 6a19aef..5034728 100644
--- a/flag_generic.go
+++ b/flag_generic.go
@@ -73,6 +73,15 @@ func (f *GenericFlag) Get(ctx *Context) interface{} {
 	return ctx.Generic(f.Name)
 }
 
+// RunAction executes flag action if set
+func (f *GenericFlag) RunAction(c *Context) error {
+	if f.Action != nil {
+		return f.Action(c, c.Generic(f.Name))
+	}
+
+	return nil
+}
+
 // Generic looks up the value of a local GenericFlag, returns
 // nil if not found
 func (cCtx *Context) Generic(name string) interface{} {
diff --git a/flag_int.go b/flag_int.go
index 0f5c403..af98e93 100644
--- a/flag_int.go
+++ b/flag_int.go
@@ -71,6 +71,15 @@ func (f *IntFlag) Get(ctx *Context) int {
 	return ctx.Int(f.Name)
 }
 
+// RunAction executes flag action if set
+func (f *IntFlag) RunAction(c *Context) error {
+	if f.Action != nil {
+		return f.Action(c, c.Int(f.Name))
+	}
+
+	return nil
+}
+
 // Int looks up the value of a local IntFlag, returns
 // 0 if not found
 func (cCtx *Context) Int(name string) int {
diff --git a/flag_int64.go b/flag_int64.go
index a392275..ebe46d2 100644
--- a/flag_int64.go
+++ b/flag_int64.go
@@ -70,6 +70,15 @@ func (f *Int64Flag) Get(ctx *Context) int64 {
 	return ctx.Int64(f.Name)
 }
 
+// RunAction executes flag action if set
+func (f *Int64Flag) RunAction(c *Context) error {
+	if f.Action != nil {
+		return f.Action(c, c.Int64(f.Name))
+	}
+
+	return nil
+}
+
 // Int64 looks up the value of a local Int64Flag, returns
 // 0 if not found
 func (cCtx *Context) Int64(name string) int64 {
diff --git a/flag_int64_slice.go b/flag_int64_slice.go
index ead4e77..d4a11b6 100644
--- a/flag_int64_slice.go
+++ b/flag_int64_slice.go
@@ -179,6 +179,15 @@ func (f *Int64SliceFlag) stringify() string {
 	return stringifySliceFlag(f.Usage, f.Names(), defaultVals)
 }
 
+// RunAction executes flag action if set
+func (f *Int64SliceFlag) RunAction(c *Context) error {
+	if f.Action != nil {
+		return f.Action(c, c.Int64Slice(f.Name))
+	}
+
+	return nil
+}
+
 // Int64Slice looks up the value of a local Int64SliceFlag, returns
 // nil if not found
 func (cCtx *Context) Int64Slice(name string) []int64 {
diff --git a/flag_int_slice.go b/flag_int_slice.go
index b40e0d8..2cabe72 100644
--- a/flag_int_slice.go
+++ b/flag_int_slice.go
@@ -179,6 +179,15 @@ func (f *IntSliceFlag) Get(ctx *Context) []int {
 	return ctx.IntSlice(f.Name)
 }
 
+// RunAction executes flag action if set
+func (f *IntSliceFlag) RunAction(c *Context) error {
+	if f.Action != nil {
+		return f.Action(c, c.IntSlice(f.Name))
+	}
+
+	return nil
+}
+
 func (f *IntSliceFlag) stringify() string {
 	var defaultVals []string
 	if f.Value != nil && len(f.Value.Value()) > 0 {
diff --git a/flag_path.go b/flag_path.go
index 7c87a89..911819d 100644
--- a/flag_path.go
+++ b/flag_path.go
@@ -67,6 +67,15 @@ func (f *PathFlag) Get(ctx *Context) string {
 	return ctx.Path(f.Name)
 }
 
+// RunAction executes flag action if set
+func (f *PathFlag) RunAction(c *Context) error {
+	if f.Action != nil {
+		return f.Action(c, c.Path(f.Name))
+	}
+
+	return nil
+}
+
 // Path looks up the value of a local PathFlag, returns
 // "" if not found
 func (cCtx *Context) Path(name string) string {
diff --git a/flag_string.go b/flag_string.go
index c8da38f..b7163ba 100644
--- a/flag_string.go
+++ b/flag_string.go
@@ -65,6 +65,15 @@ func (f *StringFlag) Get(ctx *Context) string {
 	return ctx.String(f.Name)
 }
 
+// RunAction executes flag action if set
+func (f *StringFlag) RunAction(c *Context) error {
+	if f.Action != nil {
+		return f.Action(c, c.String(f.Name))
+	}
+
+	return nil
+}
+
 // String looks up the value of a local StringFlag, returns
 // "" if not found
 func (cCtx *Context) String(name string) string {
diff --git a/flag_string_slice.go b/flag_string_slice.go
index 9d69342..7b46a24 100644
--- a/flag_string_slice.go
+++ b/flag_string_slice.go
@@ -171,6 +171,15 @@ func (f *StringSliceFlag) stringify() string {
 	return stringifySliceFlag(f.Usage, f.Names(), defaultVals)
 }
 
+// RunAction executes flag action if set
+func (f *StringSliceFlag) RunAction(c *Context) error {
+	if f.Action != nil {
+		return f.Action(c, c.StringSlice(f.Name))
+	}
+
+	return nil
+}
+
 // StringSlice looks up the value of a local StringSliceFlag, returns
 // nil if not found
 func (cCtx *Context) StringSlice(name string) []string {
diff --git a/flag_timestamp.go b/flag_timestamp.go
index 16f42dd..17bc8d7 100644
--- a/flag_timestamp.go
+++ b/flag_timestamp.go
@@ -148,6 +148,15 @@ func (f *TimestampFlag) Get(ctx *Context) *time.Time {
 	return ctx.Timestamp(f.Name)
 }
 
+// RunAction executes flag action if set
+func (f *TimestampFlag) RunAction(c *Context) error {
+	if f.Action != nil {
+		return f.Action(c, c.Timestamp(f.Name))
+	}
+
+	return nil
+}
+
 // Timestamp gets the timestamp from a flag name
 func (cCtx *Context) Timestamp(name string) *time.Time {
 	if fs := cCtx.lookupFlagSet(name); fs != nil {
diff --git a/flag_uint.go b/flag_uint.go
index d25ff73..f9acb6d 100644
--- a/flag_uint.go
+++ b/flag_uint.go
@@ -46,6 +46,15 @@ func (f *UintFlag) Apply(set *flag.FlagSet) error {
 	return nil
 }
 
+// RunAction executes flag action if set
+func (f *UintFlag) RunAction(c *Context) error {
+	if f.Action != nil {
+		return f.Action(c, c.Uint(f.Name))
+	}
+
+	return nil
+}
+
 // GetValue returns the flags value as string representation and an empty
 // string if the flag takes no value at all.
 func (f *UintFlag) GetValue() string {
diff --git a/flag_uint64.go b/flag_uint64.go
index 975c733..09590d7 100644
--- a/flag_uint64.go
+++ b/flag_uint64.go
@@ -46,6 +46,15 @@ func (f *Uint64Flag) Apply(set *flag.FlagSet) error {
 	return nil
 }
 
+// RunAction executes flag action if set
+func (f *Uint64Flag) RunAction(c *Context) error {
+	if f.Action != nil {
+		return f.Action(c, c.Uint64(f.Name))
+	}
+
+	return nil
+}
+
 // GetValue returns the flags value as string representation and an empty
 // string if the flag takes no value at all.
 func (f *Uint64Flag) GetValue() string {
diff --git a/godoc-current.txt b/godoc-current.txt
index fbeeb5f..d41dc97 100644
--- a/godoc-current.txt
+++ b/godoc-current.txt
@@ -250,6 +250,13 @@ TYPES
 type ActionFunc func(*Context) error
     ActionFunc is the action to execute when no subcommands are specified
 
+type ActionableFlag interface {
+	Flag
+	RunAction(*Context) error
+}
+    ActionableFlag is an interface that wraps Flag interface and RunAction
+    operation.
+
 type AfterFunc func(*Context) error
     AfterFunc is an action to execute after any subcommands are run, but after
     the subcommand has finished it is run even if Action() panics
@@ -491,6 +498,9 @@ func (f *BoolFlag) IsVisible() bool
 func (f *BoolFlag) Names() []string
     Names returns the names of the flag
 
+func (f *BoolFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *BoolFlag) String() string
     String returns a readable representation of this value (for usage defaults)
 
@@ -798,6 +808,9 @@ func (f *DurationFlag) IsVisible() bool
 func (f *DurationFlag) Names() []string
     Names returns the names of the flag
 
+func (f *DurationFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *DurationFlag) String() string
     String returns a readable representation of this value (for usage defaults)
 
@@ -971,6 +984,9 @@ func (f *Float64Flag) IsVisible() bool
 func (f *Float64Flag) Names() []string
     Names returns the names of the flag
 
+func (f *Float64Flag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *Float64Flag) String() string
     String returns a readable representation of this value (for usage defaults)
 
@@ -1056,6 +1072,9 @@ func (f *Float64SliceFlag) IsVisible() bool
 func (f *Float64SliceFlag) Names() []string
     Names returns the names of the flag
 
+func (f *Float64SliceFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *Float64SliceFlag) SetDestination(slice []float64)
 
 func (f *Float64SliceFlag) SetValue(slice []float64)
@@ -1129,6 +1148,9 @@ func (f *GenericFlag) IsVisible() bool
 func (f *GenericFlag) Names() []string
     Names returns the names of the flag
 
+func (f *GenericFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *GenericFlag) String() string
     String returns a readable representation of this value (for usage defaults)
 
@@ -1191,6 +1213,9 @@ func (f *Int64Flag) IsVisible() bool
 func (f *Int64Flag) Names() []string
     Names returns the names of the flag
 
+func (f *Int64Flag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *Int64Flag) String() string
     String returns a readable representation of this value (for usage defaults)
 
@@ -1276,6 +1301,9 @@ func (f *Int64SliceFlag) IsVisible() bool
 func (f *Int64SliceFlag) Names() []string
     Names returns the names of the flag
 
+func (f *Int64SliceFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *Int64SliceFlag) SetDestination(slice []int64)
 
 func (f *Int64SliceFlag) SetValue(slice []int64)
@@ -1342,6 +1370,9 @@ func (f *IntFlag) IsVisible() bool
 func (f *IntFlag) Names() []string
     Names returns the names of the flag
 
+func (f *IntFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *IntFlag) String() string
     String returns a readable representation of this value (for usage defaults)
 
@@ -1431,6 +1462,9 @@ func (f *IntSliceFlag) IsVisible() bool
 func (f *IntSliceFlag) Names() []string
     Names returns the names of the flag
 
+func (f *IntSliceFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *IntSliceFlag) SetDestination(slice []int)
 
 func (f *IntSliceFlag) SetValue(slice []int)
@@ -1531,6 +1565,9 @@ func (f *PathFlag) IsVisible() bool
 func (f *PathFlag) Names() []string
     Names returns the names of the flag
 
+func (f *PathFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *PathFlag) String() string
     String returns a readable representation of this value (for usage defaults)
 
@@ -1668,6 +1705,9 @@ func (f *StringFlag) IsVisible() bool
 func (f *StringFlag) Names() []string
     Names returns the names of the flag
 
+func (f *StringFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *StringFlag) String() string
     String returns a readable representation of this value (for usage defaults)
 
@@ -1755,6 +1795,9 @@ func (f *StringSliceFlag) IsVisible() bool
 func (f *StringSliceFlag) Names() []string
     Names returns the names of the flag
 
+func (f *StringSliceFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *StringSliceFlag) SetDestination(slice []string)
 
 func (f *StringSliceFlag) SetValue(slice []string)
@@ -1856,6 +1899,9 @@ func (f *TimestampFlag) IsVisible() bool
 func (f *TimestampFlag) Names() []string
     Names returns the names of the flag
 
+func (f *TimestampFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *TimestampFlag) String() string
     String returns a readable representation of this value (for usage defaults)
 
@@ -1918,6 +1964,9 @@ func (f *Uint64Flag) IsVisible() bool
 func (f *Uint64Flag) Names() []string
     Names returns the names of the flag
 
+func (f *Uint64Flag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *Uint64Flag) String() string
     String returns a readable representation of this value (for usage defaults)
 
@@ -2063,6 +2112,9 @@ func (f *UintFlag) IsVisible() bool
 func (f *UintFlag) Names() []string
     Names returns the names of the flag
 
+func (f *UintFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *UintFlag) String() string
     String returns a readable representation of this value (for usage defaults)
 
diff --git a/zz_generated.flags.go b/zz_generated.flags.go
index 034ef35..0309a28 100644
--- a/zz_generated.flags.go
+++ b/zz_generated.flags.go
@@ -22,6 +22,8 @@ type Float64SliceFlag struct {
 
 	Aliases []string
 	EnvVars []string
+
+	Action func(*Context, []float64) error
 }
 
 // IsSet returns whether or not the flag has been set through env or file
@@ -64,6 +66,8 @@ type GenericFlag struct {
 	EnvVars []string
 
 	TakesFile bool
+
+	Action func(*Context, interface{}) error
 }
 
 // String returns a readable representation of this value (for usage defaults)
@@ -109,6 +113,8 @@ type Int64SliceFlag struct {
 
 	Aliases []string
 	EnvVars []string
+
+	Action func(*Context, []int64) error
 }
 
 // IsSet returns whether or not the flag has been set through env or file
@@ -149,6 +155,8 @@ type IntSliceFlag struct {
 
 	Aliases []string
 	EnvVars []string
+
+	Action func(*Context, []int) error
 }
 
 // IsSet returns whether or not the flag has been set through env or file
@@ -191,6 +199,8 @@ type PathFlag struct {
 	EnvVars []string
 
 	TakesFile bool
+
+	Action func(*Context, Path) error
 }
 
 // String returns a readable representation of this value (for usage defaults)
@@ -238,6 +248,8 @@ type StringSliceFlag struct {
 	EnvVars []string
 
 	TakesFile bool
+
+	Action func(*Context, []string) error
 }
 
 // IsSet returns whether or not the flag has been set through env or file
@@ -282,6 +294,8 @@ type TimestampFlag struct {
 	Layout string
 
 	Timezone *time.Location
+
+	Action func(*Context, *time.Time) error
 }
 
 // String returns a readable representation of this value (for usage defaults)
@@ -327,6 +341,8 @@ type Uint64SliceFlag struct {
 
 	Aliases []string
 	EnvVars []string
+
+	Action func(*Context, []uint64) error
 }
 
 // IsSet returns whether or not the flag has been set through env or file
@@ -367,6 +383,8 @@ type UintSliceFlag struct {
 
 	Aliases []string
 	EnvVars []string
+
+	Action func(*Context, []uint) error
 }
 
 // IsSet returns whether or not the flag has been set through env or file
@@ -409,6 +427,8 @@ type BoolFlag struct {
 	EnvVars []string
 
 	Count *int
+
+	Action func(*Context, bool) error
 }
 
 // String returns a readable representation of this value (for usage defaults)
@@ -454,6 +474,8 @@ type Float64Flag struct {
 
 	Aliases []string
 	EnvVars []string
+
+	Action func(*Context, float64) error
 }
 
 // String returns a readable representation of this value (for usage defaults)
@@ -501,6 +523,8 @@ type IntFlag struct {
 	EnvVars []string
 
 	Base int
+
+	Action func(*Context, int) error
 }
 
 // String returns a readable representation of this value (for usage defaults)
@@ -548,6 +572,8 @@ type Int64Flag struct {
 	EnvVars []string
 
 	Base int
+
+	Action func(*Context, int64) error
 }
 
 // String returns a readable representation of this value (for usage defaults)
@@ -595,6 +621,8 @@ type StringFlag struct {
 	EnvVars []string
 
 	TakesFile bool
+
+	Action func(*Context, string) error
 }
 
 // String returns a readable representation of this value (for usage defaults)
@@ -640,6 +668,8 @@ type DurationFlag struct {
 
 	Aliases []string
 	EnvVars []string
+
+	Action func(*Context, time.Duration) error
 }
 
 // String returns a readable representation of this value (for usage defaults)
@@ -687,6 +717,8 @@ type UintFlag struct {
 	EnvVars []string
 
 	Base int
+
+	Action func(*Context, uint) error
 }
 
 // String returns a readable representation of this value (for usage defaults)
@@ -734,6 +766,8 @@ type Uint64Flag struct {
 	EnvVars []string
 
 	Base int
+
+	Action func(*Context, uint64) error
 }
 
 // String returns a readable representation of this value (for usage defaults)


Test patch:
diff --git a/app_test.go b/app_test.go
index 64316fc..f3cbb1e 100644
--- a/app_test.go
+++ b/app_test.go
@@ -9,8 +9,10 @@ import (
 	"io/ioutil"
 	"os"
 	"reflect"
+	"strconv"
 	"strings"
 	"testing"
+	"time"
 )
 
 var (
@@ -2357,6 +2359,10 @@ func (c *customBoolFlag) Apply(set *flag.FlagSet) error {
 	return nil
 }
 
+func (c *customBoolFlag) RunAction(*Context) error {
+	return nil
+}
+
 func (c *customBoolFlag) IsSet() bool {
 	return false
 }
@@ -2576,3 +2582,392 @@ func TestSetupInitializesOnlyNilWriters(t *testing.T) {
 		t.Errorf("expected a.Writer to be os.Stdout")
 	}
 }
+
+type stringGeneric struct {
+	value string
+}
+
+func (s *stringGeneric) Set(value string) error {
+	s.value = value
+	return nil
+}
+
+func (s *stringGeneric) String() string {
+	return s.value
+}
+
+func TestFlagAction(t *testing.T) {
+	stringFlag := &StringFlag{
+		Name: "f_string",
+		Action: func(c *Context, v string) error {
+			if v == "" {
+				return fmt.Errorf("empty string")
+			}
+			c.App.Writer.Write([]byte(v + " "))
+			return nil
+		},
+	}
+	app := &App{
+		Name: "app",
+		Commands: []*Command{
+			{
+				Name:   "c1",
+				Flags:  []Flag{stringFlag},
+				Action: func(ctx *Context) error { return nil },
+				Subcommands: []*Command{
+					{
+						Name:   "sub1",
+						Action: func(ctx *Context) error { return nil },
+						Flags:  []Flag{stringFlag},
+					},
+				},
+			},
+		},
+		Flags: []Flag{
+			stringFlag,
+			&StringFlag{
+				Name: "f_no_action",
+			},
+			&StringSliceFlag{
+				Name: "f_string_slice",
+				Action: func(c *Context, v []string) error {
+					if v[0] == "err" {
+						return fmt.Errorf("error string slice")
+					}
+					c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
+					return nil
+				},
+			},
+			&BoolFlag{
+				Name: "f_bool",
+				Action: func(c *Context, v bool) error {
+					if !v {
+						return fmt.Errorf("value is false")
+					}
+					c.App.Writer.Write([]byte(fmt.Sprintf("%t ", v)))
+					return nil
+				},
+			},
+			&DurationFlag{
+				Name: "f_duration",
+				Action: func(c *Context, v time.Duration) error {
+					if v == 0 {
+						return fmt.Errorf("empty duration")
+					}
+					c.App.Writer.Write([]byte(v.String() + " "))
+					return nil
+				},
+			},
+			&Float64Flag{
+				Name: "f_float64",
+				Action: func(c *Context, v float64) error {
+					if v < 0 {
+						return fmt.Errorf("negative float64")
+					}
+					c.App.Writer.Write([]byte(strconv.FormatFloat(v, 'f', -1, 64) + " "))
+					return nil
+				},
+			},
+			&Float64SliceFlag{
+				Name: "f_float64_slice",
+				Action: func(c *Context, v []float64) error {
+					if len(v) > 0 && v[0] < 0 {
+						return fmt.Errorf("invalid float64 slice")
+					}
+					c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
+					return nil
+				},
+			},
+			&GenericFlag{
+				Name:  "f_generic",
+				Value: new(stringGeneric),
+				Action: func(c *Context, v interface{}) error {
+					fmt.Printf("%T %v\n", v, v)
+					switch vv := v.(type) {
+					case *stringGeneric:
+						if vv.value == "" {
+							return fmt.Errorf("generic value not set")
+						}
+					}
+
+					c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
+					return nil
+				},
+			},
+			&IntFlag{
+				Name: "f_int",
+				Action: func(c *Context, v int) error {
+					if v < 0 {
+						return fmt.Errorf("negative int")
+					}
+					c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
+					return nil
+				},
+			},
+			&IntSliceFlag{
+				Name: "f_int_slice",
+				Action: func(c *Context, v []int) error {
+					if len(v) > 0 && v[0] < 0 {
+						return fmt.Errorf("invalid int slice")
+					}
+					c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
+					return nil
+				},
+			},
+			&Int64Flag{
+				Name: "f_int64",
+				Action: func(c *Context, v int64) error {
+					if v < 0 {
+						return fmt.Errorf("negative int64")
+					}
+					c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
+					return nil
+				},
+			},
+			&Int64SliceFlag{
+				Name: "f_int64_slice",
+				Action: func(c *Context, v []int64) error {
+					if len(v) > 0 && v[0] < 0 {
+						return fmt.Errorf("invalid int64 slice")
+					}
+					c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
+					return nil
+				},
+			},
+			&PathFlag{
+				Name: "f_path",
+				Action: func(c *Context, v string) error {
+					if v == "" {
+						return fmt.Errorf("empty path")
+					}
+					c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
+					return nil
+				},
+			},
+			&TimestampFlag{
+				Name:   "f_timestamp",
+				Layout: "2006-01-02 15:04:05",
+				Action: func(c *Context, v *time.Time) error {
+					if v.IsZero() {
+						return fmt.Errorf("zero timestamp")
+					}
+					c.App.Writer.Write([]byte(v.Format(time.RFC3339) + " "))
+					return nil
+				},
+			},
+			&UintFlag{
+				Name: "f_uint",
+				Action: func(c *Context, v uint) error {
+					if v == 0 {
+						return fmt.Errorf("zero uint")
+					}
+					c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
+					return nil
+				},
+			},
+			&Uint64Flag{
+				Name: "f_uint64",
+				Action: func(c *Context, v uint64) error {
+					if v == 0 {
+						return fmt.Errorf("zero uint64")
+					}
+					c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
+					return nil
+				},
+			},
+		},
+		Action: func(ctx *Context) error { return nil },
+	}
+
+	tests := []struct {
+		name string
+		args []string
+		err  error
+		exp  string
+	}{
+		{
+			name: "flag_string",
+			args: []string{"app", "--f_string=string"},
+			exp:  "string ",
+		},
+		{
+			name: "flag_string_error",
+			args: []string{"app", "--f_string="},
+			err:  fmt.Errorf("empty string"),
+		},
+		{
+			name: "flag_string_slice",
+			args: []string{"app", "--f_string_slice=s1,s2,s3"},
+			exp:  "[s1 s2 s3] ",
+		},
+		{
+			name: "flag_string_slice_error",
+			args: []string{"app", "--f_string_slice=err"},
+			err:  fmt.Errorf("error string slice"),
+		},
+		{
+			name: "flag_bool",
+			args: []string{"app", "--f_bool"},
+			exp:  "true ",
+		},
+		{
+			name: "flag_bool_error",
+			args: []string{"app", "--f_bool=false"},
+			err:  fmt.Errorf("value is false"),
+		},
+		{
+			name: "flag_duration",
+			args: []string{"app", "--f_duration=1h30m20s"},
+			exp:  "1h30m20s ",
+		},
+		{
+			name: "flag_duration_error",
+			args: []string{"app", "--f_duration=0"},
+			err:  fmt.Errorf("empty duration"),
+		},
+		{
+			name: "flag_float64",
+			args: []string{"app", "--f_float64=3.14159"},
+			exp:  "3.14159 ",
+		},
+		{
+			name: "flag_float64_error",
+			args: []string{"app", "--f_float64=-1"},
+			err:  fmt.Errorf("negative float64"),
+		},
+		{
+			name: "flag_float64_slice",
+			args: []string{"app", "--f_float64_slice=1.1,2.2,3.3"},
+			exp:  "[1.1 2.2 3.3] ",
+		},
+		{
+			name: "flag_float64_slice_error",
+			args: []string{"app", "--f_float64_slice=-1"},
+			err:  fmt.Errorf("invalid float64 slice"),
+		},
+		{
+			name: "flag_generic",
+			args: []string{"app", "--f_generic=1"},
+			exp:  "1 ",
+		},
+		{
+			name: "flag_generic_error",
+			args: []string{"app", "--f_generic="},
+			err:  fmt.Errorf("generic value not set"),
+		},
+		{
+			name: "flag_int",
+			args: []string{"app", "--f_int=1"},
+			exp:  "1 ",
+		},
+		{
+			name: "flag_int_error",
+			args: []string{"app", "--f_int=-1"},
+			err:  fmt.Errorf("negative int"),
+		},
+		{
+			name: "flag_int_slice",
+			args: []string{"app", "--f_int_slice=1,2,3"},
+			exp:  "[1 2 3] ",
+		},
+		{
+			name: "flag_int_slice_error",
+			args: []string{"app", "--f_int_slice=-1"},
+			err:  fmt.Errorf("invalid int slice"),
+		},
+		{
+			name: "flag_int64",
+			args: []string{"app", "--f_int64=1"},
+			exp:  "1 ",
+		},
+		{
+			name: "flag_int64_error",
+			args: []string{"app", "--f_int64=-1"},
+			err:  fmt.Errorf("negative int64"),
+		},
+		{
+			name: "flag_int64_slice",
+			args: []string{"app", "--f_int64_slice=1,2,3"},
+			exp:  "[1 2 3] ",
+		},
+		{
+			name: "flag_int64_slice",
+			args: []string{"app", "--f_int64_slice=-1"},
+			err:  fmt.Errorf("invalid int64 slice"),
+		},
+		{
+			name: "flag_path",
+			args: []string{"app", "--f_path=/root"},
+			exp:  "/root ",
+		},
+		{
+			name: "flag_path_error",
+			args: []string{"app", "--f_path="},
+			err:  fmt.Errorf("empty path"),
+		},
+		{
+			name: "flag_timestamp",
+			args: []string{"app", "--f_timestamp", "2022-05-01 02:26:20"},
+			exp:  "2022-05-01T02:26:20Z ",
+		},
+		{
+			name: "flag_timestamp_error",
+			args: []string{"app", "--f_timestamp", "0001-01-01 00:00:00"},
+			err:  fmt.Errorf("zero timestamp"),
+		},
+		{
+			name: "flag_uint",
+			args: []string{"app", "--f_uint=1"},
+			exp:  "1 ",
+		},
+		{
+			name: "flag_uint_error",
+			args: []string{"app", "--f_uint=0"},
+			err:  fmt.Errorf("zero uint"),
+		},
+		{
+			name: "flag_uint64",
+			args: []string{"app", "--f_uint64=1"},
+			exp:  "1 ",
+		},
+		{
+			name: "flag_uint64_error",
+			args: []string{"app", "--f_uint64=0"},
+			err:  fmt.Errorf("zero uint64"),
+		},
+		{
+			name: "flag_no_action",
+			args: []string{"app", "--f_no_action="},
+			exp:  "",
+		},
+		{
+			name: "command_flag",
+			args: []string{"app", "c1", "--f_string=c1"},
+			exp:  "c1 ",
+		},
+		{
+			name: "subCommand_flag",
+			args: []string{"app", "c1", "sub1", "--f_string=sub1"},
+			exp:  "sub1 ",
+		},
+		{
+			name: "mixture",
+			args: []string{"app", "--f_string=app", "--f_uint=1", "--f_int_slice=1,2,3", "--f_duration=1h30m20s", "c1", "--f_string=c1", "sub1", "--f_string=sub1"},
+			exp:  "app 1h30m20s [1 2 3] 1 c1 sub1 ",
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			buf := new(bytes.Buffer)
+			app.Writer = buf
+			err := app.Run(test.args)
+			if test.err != nil {
+				expect(t, err, test.err)
+			} else {
+				expect(t, err, nil)
+				expect(t, buf.String(), test.exp)
+			}
+		})
+	}
+}
diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt
index fbeeb5f..d41dc97 100644
--- a/testdata/godoc-v2.x.txt
+++ b/testdata/godoc-v2.x.txt
@@ -250,6 +250,13 @@ TYPES
 type ActionFunc func(*Context) error
     ActionFunc is the action to execute when no subcommands are specified
 
+type ActionableFlag interface {
+	Flag
+	RunAction(*Context) error
+}
+    ActionableFlag is an interface that wraps Flag interface and RunAction
+    operation.
+
 type AfterFunc func(*Context) error
     AfterFunc is an action to execute after any subcommands are run, but after
     the subcommand has finished it is run even if Action() panics
@@ -491,6 +498,9 @@ func (f *BoolFlag) IsVisible() bool
 func (f *BoolFlag) Names() []string
     Names returns the names of the flag
 
+func (f *BoolFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *BoolFlag) String() string
     String returns a readable representation of this value (for usage defaults)
 
@@ -798,6 +808,9 @@ func (f *DurationFlag) IsVisible() bool
 func (f *DurationFlag) Names() []string
     Names returns the names of the flag
 
+func (f *DurationFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *DurationFlag) String() string
     String returns a readable representation of this value (for usage defaults)
 
@@ -971,6 +984,9 @@ func (f *Float64Flag) IsVisible() bool
 func (f *Float64Flag) Names() []string
     Names returns the names of the flag
 
+func (f *Float64Flag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *Float64Flag) String() string
     String returns a readable representation of this value (for usage defaults)
 
@@ -1056,6 +1072,9 @@ func (f *Float64SliceFlag) IsVisible() bool
 func (f *Float64SliceFlag) Names() []string
     Names returns the names of the flag
 
+func (f *Float64SliceFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *Float64SliceFlag) SetDestination(slice []float64)
 
 func (f *Float64SliceFlag) SetValue(slice []float64)
@@ -1129,6 +1148,9 @@ func (f *GenericFlag) IsVisible() bool
 func (f *GenericFlag) Names() []string
     Names returns the names of the flag
 
+func (f *GenericFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *GenericFlag) String() string
     String returns a readable representation of this value (for usage defaults)
 
@@ -1191,6 +1213,9 @@ func (f *Int64Flag) IsVisible() bool
 func (f *Int64Flag) Names() []string
     Names returns the names of the flag
 
+func (f *Int64Flag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *Int64Flag) String() string
     String returns a readable representation of this value (for usage defaults)
 
@@ -1276,6 +1301,9 @@ func (f *Int64SliceFlag) IsVisible() bool
 func (f *Int64SliceFlag) Names() []string
     Names returns the names of the flag
 
+func (f *Int64SliceFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *Int64SliceFlag) SetDestination(slice []int64)
 
 func (f *Int64SliceFlag) SetValue(slice []int64)
@@ -1342,6 +1370,9 @@ func (f *IntFlag) IsVisible() bool
 func (f *IntFlag) Names() []string
     Names returns the names of the flag
 
+func (f *IntFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *IntFlag) String() string
     String returns a readable representation of this value (for usage defaults)
 
@@ -1431,6 +1462,9 @@ func (f *IntSliceFlag) IsVisible() bool
 func (f *IntSliceFlag) Names() []string
     Names returns the names of the flag
 
+func (f *IntSliceFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *IntSliceFlag) SetDestination(slice []int)
 
 func (f *IntSliceFlag) SetValue(slice []int)
@@ -1531,6 +1565,9 @@ func (f *PathFlag) IsVisible() bool
 func (f *PathFlag) Names() []string
     Names returns the names of the flag
 
+func (f *PathFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *PathFlag) String() string
     String returns a readable representation of this value (for usage defaults)
 
@@ -1668,6 +1705,9 @@ func (f *StringFlag) IsVisible() bool
 func (f *StringFlag) Names() []string
     Names returns the names of the flag
 
+func (f *StringFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *StringFlag) String() string
     String returns a readable representation of this value (for usage defaults)
 
@@ -1755,6 +1795,9 @@ func (f *StringSliceFlag) IsVisible() bool
 func (f *StringSliceFlag) Names() []string
     Names returns the names of the flag
 
+func (f *StringSliceFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *StringSliceFlag) SetDestination(slice []string)
 
 func (f *StringSliceFlag) SetValue(slice []string)
@@ -1856,6 +1899,9 @@ func (f *TimestampFlag) IsVisible() bool
 func (f *TimestampFlag) Names() []string
     Names returns the names of the flag
 
+func (f *TimestampFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *TimestampFlag) String() string
     String returns a readable representation of this value (for usage defaults)
 
@@ -1918,6 +1964,9 @@ func (f *Uint64Flag) IsVisible() bool
 func (f *Uint64Flag) Names() []string
     Names returns the names of the flag
 
+func (f *Uint64Flag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *Uint64Flag) String() string
     String returns a readable representation of this value (for usage defaults)
 
@@ -2063,6 +2112,9 @@ func (f *UintFlag) IsVisible() bool
 func (f *UintFlag) Names() []string
     Names returns the names of the flag
 
+func (f *UintFlag) RunAction(c *Context) error
+    RunAction executes flag action if set
+
 func (f *UintFlag) String() string
     String returns a readable representation of this value (for usage defaults)