From 887d31ffaf33c48d2cd5ed80c5cf04364b842e80 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 16 Jul 2022 12:10:51 -0400 Subject: [PATCH 001/136] Target minimum go 1.18 in v3-dev-main --- .github/workflows/cli.yml | 2 +- sliceflag.go | 3 --- sliceflag_pre18.go | 10 ---------- sliceflag_test.go | 3 --- 4 files changed, 1 insertion(+), 17 deletions(-) delete mode 100644 sliceflag_pre18.go diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 470a8aa7c5..1e4a51f221 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - go: [1.16.x, 1.17.x, 1.18.x] + go: [1.18.x] name: ${{ matrix.os }} @ Go ${{ matrix.go }} runs-on: ${{ matrix.os }} steps: diff --git a/sliceflag.go b/sliceflag.go index 7dea3576a3..b2ca59041f 100644 --- a/sliceflag.go +++ b/sliceflag.go @@ -1,6 +1,3 @@ -//go:build go1.18 -// +build go1.18 - package cli import ( diff --git a/sliceflag_pre18.go b/sliceflag_pre18.go deleted file mode 100644 index 1173ae7402..0000000000 --- a/sliceflag_pre18.go +++ /dev/null @@ -1,10 +0,0 @@ -//go:build !go1.18 -// +build !go1.18 - -package cli - -import ( - "flag" -) - -func unwrapFlagValue(v flag.Value) flag.Value { return v } diff --git a/sliceflag_test.go b/sliceflag_test.go index 179020b14f..8ff905583b 100644 --- a/sliceflag_test.go +++ b/sliceflag_test.go @@ -1,6 +1,3 @@ -//go:build go1.18 -// +build go1.18 - package cli import ( From ab68d8a69d1a479c8f6c1e3d06aef76e99797015 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sun, 4 Sep 2022 22:18:41 -0400 Subject: [PATCH 002/136] Cleanup: Collapse flag interfaces --- app_test.go | 16 ++ flag.go | 21 +- flag_bool.go | 20 -- flag_duration.go | 20 -- flag_float64.go | 20 -- flag_float64_slice.go | 20 -- flag_generic.go | 20 -- flag_int.go | 20 -- flag_int64.go | 20 -- flag_int64_slice.go | 20 -- flag_int_slice.go | 20 -- flag_path.go | 20 -- flag_string.go | 20 -- flag_string_slice.go | 20 -- flag_timestamp.go | 20 -- flag_uint.go | 20 -- flag_uint64.go | 20 -- godoc-current.txt | 127 +++++----- internal/genflags/generated.gotmpl | 29 ++- internal/genflags/generated_test.gotmpl | 23 +- internal/genflags/spec.go | 17 +- sliceflag.go | 2 - zz_generated.flags.go | 300 ++++++++++++++++++++++++ zz_generated.flags_test.go | 132 +++++------ 24 files changed, 489 insertions(+), 478 deletions(-) diff --git a/app_test.go b/app_test.go index 437af25911..53d6f64f21 100644 --- a/app_test.go +++ b/app_test.go @@ -2252,6 +2252,22 @@ func (c *customBoolFlag) IsSet() bool { return false } +func (c *customBoolFlag) IsRequired() bool { + return false +} + +func (c *customBoolFlag) IsVisible() bool { + return false +} + +func (c *customBoolFlag) GetCategory() string { + return "" +} + +func (c *customBoolFlag) GetEnvVars() []string { + return nil +} + func TestCustomFlagsUnused(t *testing.T) { app := &App{ Flags: []Flag{&customBoolFlag{"custom"}}, diff --git a/flag.go b/flag.go index dbed577cdc..4580a3e74d 100644 --- a/flag.go +++ b/flag.go @@ -93,14 +93,20 @@ type Flag interface { Apply(*flag.FlagSet) error Names() []string IsSet() bool + IsRequired() bool + // IsVisible returns true if the flag is not hidden, otherwise false + IsVisible() bool + GetCategory() string + // GetUsage returns the usage string for the flag + GetUsage() string + // GetEnvVars returns the env vars for this flag + GetEnvVars() []string } // RequiredFlag is an interface that allows us to mark flags as required // it allows flags required flags to be backwards compatible with the Flag interface type RequiredFlag interface { Flag - - IsRequired() bool } // DocGenerationFlag is an interface that allows documentation generation for the flag @@ -110,34 +116,23 @@ type DocGenerationFlag interface { // TakesValue returns true if the flag takes a value, otherwise false TakesValue() bool - // GetUsage returns the usage string for the flag - GetUsage() string - // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. GetValue() string // GetDefaultText returns the default text for this flag GetDefaultText() string - - // GetEnvVars returns the env vars for this flag - GetEnvVars() []string } // VisibleFlag is an interface that allows to check if a flag is visible type VisibleFlag interface { Flag - - // IsVisible returns true if the flag is not hidden, otherwise false - IsVisible() bool } // CategorizableFlag is an interface that allows us to potentially // use a flag in a categorized representation. type CategorizableFlag interface { VisibleFlag - - GetCategory() string } func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { diff --git a/flag_bool.go b/flag_bool.go index b21d5163c9..c8a01bb11e 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -6,21 +6,6 @@ import ( "strconv" ) -// TakesValue returns true of the flag takes a value, otherwise false -func (f *BoolFlag) TakesValue() bool { - return false -} - -// GetUsage returns the usage string for the flag -func (f *BoolFlag) GetUsage() string { - return f.Usage -} - -// GetCategory returns the category for the flag -func (f *BoolFlag) GetCategory() string { - return f.Category -} - // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *BoolFlag) GetValue() string { @@ -35,11 +20,6 @@ func (f *BoolFlag) GetDefaultText() string { return fmt.Sprintf("%v", f.Value) } -// GetEnvVars returns the env vars for this flag -func (f *BoolFlag) GetEnvVars() []string { - return f.EnvVars -} - // 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 5178c6ae12..6f4ece0185 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -6,21 +6,6 @@ import ( "time" ) -// TakesValue returns true of the flag takes a value, otherwise false -func (f *DurationFlag) TakesValue() bool { - return true -} - -// GetUsage returns the usage string for the flag -func (f *DurationFlag) GetUsage() string { - return f.Usage -} - -// GetCategory returns the category for the flag -func (f *DurationFlag) GetCategory() string { - return f.Category -} - // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *DurationFlag) GetValue() string { @@ -35,11 +20,6 @@ func (f *DurationFlag) GetDefaultText() string { return f.GetValue() } -// GetEnvVars returns the env vars for this flag -func (f *DurationFlag) GetEnvVars() []string { - return f.EnvVars -} - // Apply populates the flag given the flag set and environment func (f *DurationFlag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_float64.go b/flag_float64.go index 2d31739bc6..4c8778fc34 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -6,21 +6,6 @@ import ( "strconv" ) -// TakesValue returns true of the flag takes a value, otherwise false -func (f *Float64Flag) TakesValue() bool { - return true -} - -// GetUsage returns the usage string for the flag -func (f *Float64Flag) GetUsage() string { - return f.Usage -} - -// GetCategory returns the category for the flag -func (f *Float64Flag) GetCategory() string { - return f.Category -} - // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *Float64Flag) GetValue() string { @@ -35,11 +20,6 @@ func (f *Float64Flag) GetDefaultText() string { return f.GetValue() } -// GetEnvVars returns the env vars for this flag -func (f *Float64Flag) GetEnvVars() []string { - return f.EnvVars -} - // Apply populates the flag given the flag set and environment func (f *Float64Flag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 031ec1d1aa..911cb84b43 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -86,21 +86,6 @@ func (f *Float64SliceFlag) String() string { return withEnvHint(f.GetEnvVars(), stringifyFloat64SliceFlag(f)) } -// TakesValue returns true if the flag takes a value, otherwise false -func (f *Float64SliceFlag) TakesValue() bool { - return true -} - -// GetUsage returns the usage string for the flag -func (f *Float64SliceFlag) GetUsage() string { - return f.Usage -} - -// GetCategory returns the category for the flag -func (f *Float64SliceFlag) GetCategory() string { - return f.Category -} - // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *Float64SliceFlag) GetValue() string { @@ -118,11 +103,6 @@ func (f *Float64SliceFlag) GetDefaultText() string { return f.GetValue() } -// GetEnvVars returns the env vars for this flag -func (f *Float64SliceFlag) GetEnvVars() []string { - return f.EnvVars -} - // Apply populates the flag given the flag set and environment func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { // apply any default diff --git a/flag_generic.go b/flag_generic.go index 680eeb9d71..256aea66a5 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -11,21 +11,6 @@ type Generic interface { String() string } -// TakesValue returns true of the flag takes a value, otherwise false -func (f *GenericFlag) TakesValue() bool { - return true -} - -// GetUsage returns the usage string for the flag -func (f *GenericFlag) GetUsage() string { - return f.Usage -} - -// GetCategory returns the category for the flag -func (f *GenericFlag) GetCategory() string { - return f.Category -} - // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *GenericFlag) GetValue() string { @@ -43,11 +28,6 @@ func (f *GenericFlag) GetDefaultText() string { return f.GetValue() } -// GetEnvVars returns the env vars for this flag -func (f *GenericFlag) GetEnvVars() []string { - return f.EnvVars -} - // Apply takes the flagset and calls Set on the generic flag with the value // provided by the user for parsing by the flag func (f GenericFlag) Apply(set *flag.FlagSet) error { diff --git a/flag_int.go b/flag_int.go index c70b889858..d4d8959fd9 100644 --- a/flag_int.go +++ b/flag_int.go @@ -6,21 +6,6 @@ import ( "strconv" ) -// TakesValue returns true of the flag takes a value, otherwise false -func (f *IntFlag) TakesValue() bool { - return true -} - -// GetUsage returns the usage string for the flag -func (f *IntFlag) GetUsage() string { - return f.Usage -} - -// GetCategory returns the category for the flag -func (f *IntFlag) GetCategory() string { - return f.Category -} - // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *IntFlag) GetValue() string { @@ -35,11 +20,6 @@ func (f *IntFlag) GetDefaultText() string { return f.GetValue() } -// GetEnvVars returns the env vars for this flag -func (f *IntFlag) GetEnvVars() []string { - return f.EnvVars -} - // Apply populates the flag given the flag set and environment func (f *IntFlag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_int64.go b/flag_int64.go index 5e7038cfb1..fdb65b950a 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -6,21 +6,6 @@ import ( "strconv" ) -// TakesValue returns true of the flag takes a value, otherwise false -func (f *Int64Flag) TakesValue() bool { - return true -} - -// GetUsage returns the usage string for the flag -func (f *Int64Flag) GetUsage() string { - return f.Usage -} - -// GetCategory returns the category for the flag -func (f *Int64Flag) GetCategory() string { - return f.Category -} - // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *Int64Flag) GetValue() string { @@ -35,11 +20,6 @@ func (f *Int64Flag) GetDefaultText() string { return f.GetValue() } -// GetEnvVars returns the env vars for this flag -func (f *Int64Flag) GetEnvVars() []string { - return f.EnvVars -} - // Apply populates the flag given the flag set and environment func (f *Int64Flag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 657aaaaf33..0346663b5e 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -87,21 +87,6 @@ func (f *Int64SliceFlag) String() string { return withEnvHint(f.GetEnvVars(), stringifyInt64SliceFlag(f)) } -// TakesValue returns true of the flag takes a value, otherwise false -func (f *Int64SliceFlag) TakesValue() bool { - return true -} - -// GetUsage returns the usage string for the flag -func (f *Int64SliceFlag) GetUsage() string { - return f.Usage -} - -// GetCategory returns the category for the flag -func (f *Int64SliceFlag) GetCategory() string { - return f.Category -} - // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *Int64SliceFlag) GetValue() string { @@ -119,11 +104,6 @@ func (f *Int64SliceFlag) GetDefaultText() string { return f.GetValue() } -// GetEnvVars returns the env vars for this flag -func (f *Int64SliceFlag) GetEnvVars() []string { - return f.EnvVars -} - // Apply populates the flag given the flag set and environment func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { // apply any default diff --git a/flag_int_slice.go b/flag_int_slice.go index 7c383935ac..a24fc8de0e 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -98,21 +98,6 @@ func (f *IntSliceFlag) String() string { return withEnvHint(f.GetEnvVars(), stringifyIntSliceFlag(f)) } -// TakesValue returns true of the flag takes a value, otherwise false -func (f *IntSliceFlag) TakesValue() bool { - return true -} - -// GetUsage returns the usage string for the flag -func (f *IntSliceFlag) GetUsage() string { - return f.Usage -} - -// GetCategory returns the category for the flag -func (f *IntSliceFlag) GetCategory() string { - return f.Category -} - // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *IntSliceFlag) GetValue() string { @@ -130,11 +115,6 @@ func (f *IntSliceFlag) GetDefaultText() string { return f.GetValue() } -// GetEnvVars returns the env vars for this flag -func (f *IntSliceFlag) GetEnvVars() []string { - return f.EnvVars -} - // Apply populates the flag given the flag set and environment func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { // apply any default diff --git a/flag_path.go b/flag_path.go index 7c87a8900d..e622c40819 100644 --- a/flag_path.go +++ b/flag_path.go @@ -7,21 +7,6 @@ import ( type Path = string -// TakesValue returns true of the flag takes a value, otherwise false -func (f *PathFlag) TakesValue() bool { - return true -} - -// GetUsage returns the usage string for the flag -func (f *PathFlag) GetUsage() string { - return f.Usage -} - -// GetCategory returns the category for the flag -func (f *PathFlag) GetCategory() string { - return f.Category -} - // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *PathFlag) GetValue() string { @@ -39,11 +24,6 @@ func (f *PathFlag) GetDefaultText() string { return fmt.Sprintf("%q", f.Value) } -// GetEnvVars returns the env vars for this flag -func (f *PathFlag) GetEnvVars() []string { - return f.EnvVars -} - // Apply populates the flag given the flag set and environment func (f *PathFlag) Apply(set *flag.FlagSet) error { if val, _, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_string.go b/flag_string.go index c8da38f92d..31cd89dc27 100644 --- a/flag_string.go +++ b/flag_string.go @@ -5,21 +5,6 @@ import ( "fmt" ) -// TakesValue returns true of the flag takes a value, otherwise false -func (f *StringFlag) TakesValue() bool { - return true -} - -// GetUsage returns the usage string for the flag -func (f *StringFlag) GetUsage() string { - return f.Usage -} - -// GetCategory returns the category for the flag -func (f *StringFlag) GetCategory() string { - return f.Category -} - // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *StringFlag) GetValue() string { @@ -37,11 +22,6 @@ func (f *StringFlag) GetDefaultText() string { return fmt.Sprintf("%q", f.Value) } -// GetEnvVars returns the env vars for this flag -func (f *StringFlag) GetEnvVars() []string { - return f.EnvVars -} - // Apply populates the flag given the flag set and environment func (f *StringFlag) Apply(set *flag.FlagSet) error { if val, _, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_string_slice.go b/flag_string_slice.go index bcdfd4c554..d56dce103e 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -76,21 +76,6 @@ func (f *StringSliceFlag) String() string { return withEnvHint(f.GetEnvVars(), stringifyStringSliceFlag(f)) } -// TakesValue returns true of the flag takes a value, otherwise false -func (f *StringSliceFlag) TakesValue() bool { - return true -} - -// GetUsage returns the usage string for the flag -func (f *StringSliceFlag) GetUsage() string { - return f.Usage -} - -// GetCategory returns the category for the flag -func (f *StringSliceFlag) GetCategory() string { - return f.Category -} - // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *StringSliceFlag) GetValue() string { @@ -108,11 +93,6 @@ func (f *StringSliceFlag) GetDefaultText() string { return f.GetValue() } -// GetEnvVars returns the env vars for this flag -func (f *StringSliceFlag) GetEnvVars() []string { - return f.EnvVars -} - // Apply populates the flag given the flag set and environment func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { // apply any default diff --git a/flag_timestamp.go b/flag_timestamp.go index 80e1f470be..275fd1edd1 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -72,21 +72,6 @@ func (t *Timestamp) Get() interface{} { return *t } -// TakesValue returns true of the flag takes a value, otherwise false -func (f *TimestampFlag) TakesValue() bool { - return true -} - -// GetUsage returns the usage string for the flag -func (f *TimestampFlag) GetUsage() string { - return f.Usage -} - -// GetCategory returns the category for the flag -func (f *TimestampFlag) GetCategory() string { - return f.Category -} - // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *TimestampFlag) GetValue() string { @@ -104,11 +89,6 @@ func (f *TimestampFlag) GetDefaultText() string { return f.GetValue() } -// GetEnvVars returns the env vars for this flag -func (f *TimestampFlag) GetEnvVars() []string { - return f.EnvVars -} - // Apply populates the flag given the flag set and environment func (f *TimestampFlag) Apply(set *flag.FlagSet) error { if f.Layout == "" { diff --git a/flag_uint.go b/flag_uint.go index 6092b1ad69..c3e8fcbb61 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -6,21 +6,6 @@ import ( "strconv" ) -// TakesValue returns true of the flag takes a value, otherwise false -func (f *UintFlag) TakesValue() bool { - return true -} - -// GetUsage returns the usage string for the flag -func (f *UintFlag) GetUsage() string { - return f.Usage -} - -// GetCategory returns the category for the flag -func (f *UintFlag) GetCategory() string { - return f.Category -} - // Apply populates the flag given the flag set and environment func (f *UintFlag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { @@ -60,11 +45,6 @@ func (f *UintFlag) GetDefaultText() string { return f.GetValue() } -// GetEnvVars returns the env vars for this flag -func (f *UintFlag) GetEnvVars() []string { - return f.EnvVars -} - // Get returns the flag’s value in the given Context. func (f *UintFlag) Get(ctx *Context) uint { return ctx.Uint(f.Name) diff --git a/flag_uint64.go b/flag_uint64.go index a37f30d9fc..cacdcc6ede 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -6,21 +6,6 @@ import ( "strconv" ) -// TakesValue returns true of the flag takes a value, otherwise false -func (f *Uint64Flag) TakesValue() bool { - return true -} - -// GetUsage returns the usage string for the flag -func (f *Uint64Flag) GetUsage() string { - return f.Usage -} - -// GetCategory returns the category for the flag -func (f *Uint64Flag) GetCategory() string { - return f.Category -} - // Apply populates the flag given the flag set and environment func (f *Uint64Flag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { @@ -60,11 +45,6 @@ func (f *Uint64Flag) GetDefaultText() string { return f.GetValue() } -// GetEnvVars returns the env vars for this flag -func (f *Uint64Flag) GetEnvVars() []string { - return f.EnvVars -} - // Get returns the flag’s value in the given Context. func (f *Uint64Flag) Get(ctx *Context) uint64 { return ctx.Uint64(f.Name) diff --git a/godoc-current.txt b/godoc-current.txt index 1ba48cc659..afdb3d5d77 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -64,8 +64,8 @@ GLOBAL OPTIONS: COPYRIGHT: {{wrap .Copyright 3}}{{end}} ` - AppHelpTemplate is the text template for the Default help topic. cli.go uses - text/template to render templates. You can render custom help text by + AppHelpTemplate is the text template for the Default help topic. cli.go + uses text/template to render templates. You can render custom help text by setting this variable. var CommandHelpTemplate = `NAME: @@ -201,9 +201,9 @@ func DefaultAppComplete(cCtx *Context) func DefaultCompleteWithFlags(cmd *Command) func(cCtx *Context) func FlagNames(name string, aliases []string) []string func HandleAction(action interface{}, cCtx *Context) (err error) - HandleAction attempts to figure out which Action signature was used. If it's - an ActionFunc or a func with the legacy signature for Action, the func is - run! + HandleAction attempts to figure out which Action signature was used. + If it's an ActionFunc or a func with the legacy signature for Action, + the func is run! func HandleExitCoder(err error) HandleExitCoder handles errors implementing ExitCoder by printing their @@ -360,14 +360,14 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) to generate command-specific flags func (a *App) RunContext(ctx context.Context, arguments []string) (err error) - RunContext is like Run except it takes a Context that will be passed to its - commands and sub-commands. Through this, you can propagate timeouts and + RunContext is like Run except it takes a Context that will be passed to + its commands and sub-commands. Through this, you can propagate timeouts and cancellation requests func (a *App) Setup() - Setup runs initialization code to ensure all data structures are ready for - `Run` or inspection prior to `Run`. It is internally called by `Run`, but - will return early if setup has already happened. + Setup runs initialization code to ensure all data structures are ready + for `Run` or inspection prior to `Run`. It is internally called by `Run`, + but will return early if setup has already happened. func (a *App) ToFishCompletion() (string, error) ToFishCompletion creates a fish completion string for the `*App` The @@ -460,7 +460,7 @@ func (f *BoolFlag) Get(ctx *Context) bool Get returns the flag’s value in the given Context. func (f *BoolFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *BoolFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -491,12 +491,10 @@ func (f *BoolFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *BoolFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type CategorizableFlag interface { VisibleFlag - - GetCategory() string } CategorizableFlag is an interface that allows us to potentially use a flag in a categorized representation. @@ -707,18 +705,12 @@ type DocGenerationFlag interface { // TakesValue returns true if the flag takes a value, otherwise false TakesValue() bool - // GetUsage returns the usage string for the flag - GetUsage() string - // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. GetValue() string // GetDefaultText returns the default text for this flag GetDefaultText() string - - // GetEnvVars returns the env vars for this flag - GetEnvVars() []string } DocGenerationFlag is an interface that allows documentation generation for the flag @@ -750,7 +742,7 @@ func (f *DurationFlag) Get(ctx *Context) time.Duration Get returns the flag’s value in the given Context. func (f *DurationFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *DurationFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -781,7 +773,7 @@ func (f *DurationFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *DurationFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type ErrorFormatter interface { Format(s fmt.State, verb rune) @@ -799,9 +791,9 @@ func Exit(message interface{}, exitCode int) ExitCoder Exit wraps a message and exit code into an error, which by default is handled with a call to os.Exit during default error handling. - This is the simplest way to trigger a non-zero exit code for an App without - having to call os.Exit manually. During testing, this behavior can be - avoided by overiding the ExitErrHandler function on an App or the + This is the simplest way to trigger a non-zero exit code for an App + without having to call os.Exit manually. During testing, this behavior + can be avoided by overiding the ExitErrHandler function on an App or the package-global OsExiter function. func NewExitError(message interface{}, exitCode int) ExitCoder @@ -820,6 +812,14 @@ type Flag interface { Apply(*flag.FlagSet) error Names() []string IsSet() bool + IsRequired() bool + // IsVisible returns true if the flag is not hidden, otherwise false + IsVisible() bool + GetCategory() string + // GetUsage returns the usage string for the flag + GetUsage() string + // GetEnvVars returns the env vars for this flag + GetEnvVars() []string } Flag is a common interface related to parsing flags in cli. For more advanced flag parsing techniques, it is recommended that this interface be @@ -923,7 +923,7 @@ func (f *Float64Flag) Get(ctx *Context) float64 Get returns the flag’s value in the given Context. func (f *Float64Flag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *Float64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -954,7 +954,7 @@ func (f *Float64Flag) String() string String returns a readable representation of this value (for usage defaults) func (f *Float64Flag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type Float64Slice struct { // Has unexported fields. @@ -1006,7 +1006,7 @@ func (f *Float64SliceFlag) Get(ctx *Context) []float64 Get returns the flag’s value in the given Context. func (f *Float64SliceFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *Float64SliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1081,7 +1081,7 @@ func (f *GenericFlag) Get(ctx *Context) interface{} Get returns the flag’s value in the given Context. func (f *GenericFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *GenericFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1112,7 +1112,7 @@ func (f *GenericFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *GenericFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type Int64Flag struct { Name string @@ -1141,7 +1141,7 @@ func (f *Int64Flag) Get(ctx *Context) int64 Get returns the flag’s value in the given Context. func (f *Int64Flag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *Int64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1172,7 +1172,7 @@ func (f *Int64Flag) String() string String returns a readable representation of this value (for usage defaults) func (f *Int64Flag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type Int64Slice struct { // Has unexported fields. @@ -1224,7 +1224,7 @@ func (f *Int64SliceFlag) Get(ctx *Context) []int64 Get returns the flag’s value in the given Context. func (f *Int64SliceFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *Int64SliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1261,7 +1261,7 @@ func (f *Int64SliceFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *Int64SliceFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type IntFlag struct { Name string @@ -1290,7 +1290,7 @@ func (f *IntFlag) Get(ctx *Context) int Get returns the flag’s value in the given Context. func (f *IntFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *IntFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1321,7 +1321,7 @@ func (f *IntFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *IntFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type IntSlice struct { // Has unexported fields. @@ -1377,7 +1377,7 @@ func (f *IntSliceFlag) Get(ctx *Context) []int Get returns the flag’s value in the given Context. func (f *IntSliceFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *IntSliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1414,7 +1414,7 @@ func (f *IntSliceFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *IntSliceFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type MultiError interface { error @@ -1431,8 +1431,8 @@ type MultiInt64Flag = SliceFlag[*Int64SliceFlag, []int64, int64] directly, as Value and/or Destination. See also SliceFlag. type MultiIntFlag = SliceFlag[*IntSliceFlag, []int, int] - MultiIntFlag extends IntSliceFlag with support for using slices directly, as - Value and/or Destination. See also SliceFlag. + MultiIntFlag extends IntSliceFlag with support for using slices directly, + as Value and/or Destination. See also SliceFlag. type MultiStringFlag = SliceFlag[*StringSliceFlag, []string, string] MultiStringFlag extends StringSliceFlag with support for using slices @@ -1475,7 +1475,7 @@ func (f *PathFlag) Get(ctx *Context) string Get returns the flag’s value in the given Context. func (f *PathFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *PathFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1506,15 +1506,13 @@ func (f *PathFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *PathFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type RequiredFlag interface { Flag - - IsRequired() bool } - RequiredFlag is an interface that allows us to mark flags as required it - allows flags required flags to be backwards compatible with the Flag + RequiredFlag is an interface that allows us to mark flags as required + it allows flags required flags to be backwards compatible with the Flag interface type Serializer interface { @@ -1527,9 +1525,9 @@ type SliceFlag[T SliceFlagTarget[E], S ~[]E, E any] struct { Value S Destination *S } - SliceFlag extends implementations like StringSliceFlag and IntSliceFlag with - support for using slices directly, as Value and/or Destination. See also - SliceFlagTarget, MultiStringFlag, MultiFloat64Flag, MultiInt64Flag, + SliceFlag extends implementations like StringSliceFlag and IntSliceFlag + with support for using slices directly, as Value and/or Destination. + See also SliceFlagTarget, MultiStringFlag, MultiFloat64Flag, MultiInt64Flag, MultiIntFlag. func (x *SliceFlag[T, S, E]) Apply(set *flag.FlagSet) error @@ -1564,9 +1562,7 @@ func (x *SliceFlag[T, S, E]) TakesValue() bool type SliceFlagTarget[E any] interface { Flag - RequiredFlag DocGenerationFlag - VisibleFlag CategorizableFlag // SetValue should propagate the given slice to the target, ideally as a new value. @@ -1612,7 +1608,7 @@ func (f *StringFlag) Get(ctx *Context) string Get returns the flag’s value in the given Context. func (f *StringFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *StringFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1643,7 +1639,7 @@ func (f *StringFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *StringFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type StringSlice struct { // Has unexported fields. @@ -1697,7 +1693,7 @@ func (f *StringSliceFlag) Get(ctx *Context) []string Get returns the flag’s value in the given Context. func (f *StringSliceFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *StringSliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1734,7 +1730,7 @@ func (f *StringSliceFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *StringSliceFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type SuggestCommandFunc func(commands []*Command, provided string) string @@ -1800,7 +1796,7 @@ func (f *TimestampFlag) Get(ctx *Context) *time.Time Get returns the flag’s value in the given Context. func (f *TimestampFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *TimestampFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1831,7 +1827,7 @@ func (f *TimestampFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *TimestampFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type Uint64Flag struct { Name string @@ -1860,7 +1856,7 @@ func (f *Uint64Flag) Get(ctx *Context) uint64 Get returns the flag’s value in the given Context. func (f *Uint64Flag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *Uint64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1891,7 +1887,7 @@ func (f *Uint64Flag) String() string String returns a readable representation of this value (for usage defaults) func (f *Uint64Flag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type UintFlag struct { Name string @@ -1920,7 +1916,7 @@ func (f *UintFlag) Get(ctx *Context) uint Get returns the flag’s value in the given Context. func (f *UintFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *UintFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1951,13 +1947,10 @@ func (f *UintFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *UintFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type VisibleFlag interface { Flag - - // IsVisible returns true if the flag is not hidden, otherwise false - IsVisible() bool } VisibleFlag is an interface that allows to check if a flag is visible @@ -1986,9 +1979,9 @@ func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceCont that are supported by the input source func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(cCtx *cli.Context) (InputSourceContext, error)) cli.BeforeFunc - InitInputSourceWithContext is used to to setup an InputSourceContext on a - cli.Command Before method. It will create a new input source based on the - func provided with potentially using existing cli.Context values to + InitInputSourceWithContext is used to to setup an InputSourceContext on + a cli.Command Before method. It will create a new input source based on + the func provided with potentially using existing cli.Context values to initialize itself. If there is no error it will then apply the new input source to any flags that are supported by the input source diff --git a/internal/genflags/generated.gotmpl b/internal/genflags/generated.gotmpl index 8b82ccfb1d..d4b1538013 100644 --- a/internal/genflags/generated.gotmpl +++ b/internal/genflags/generated.gotmpl @@ -45,21 +45,38 @@ func (f *{{.TypeName}}) Names() []string { return {{$.UrfaveCLINamespace}}FlagNames(f.Name, f.Aliases) } -{{end}}{{/* /if .GenerateFlagInterface */}} - -{{if .GenerateRequiredFlagInterface}} // IsRequired returns whether or not the flag is required func (f *{{.TypeName}}) IsRequired() bool { return f.Required } -{{end}}{{/* /if .GenerateRequiredFlagInterface */}} -{{if .GenerateVisibleFlagInterface}} // IsVisible returns true if the flag is not hidden, otherwise false func (f *{{.TypeName}}) IsVisible() bool { return !f.Hidden } -{{end}}{{/* /if .GenerateVisibleFlagInterface */}} + +// GetCategory returns the category of the flag +func (f *{{.TypeName}}) GetCategory() string { + return f.Category +} + +// GetUsage returns the usage string for the flag +func (f *{{.TypeName}}) GetUsage() string { + return f.Usage +} + +// GetEnvVars returns the env vars for this flag +func (f *{{.TypeName}}) GetEnvVars() []string { + return f.EnvVars +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *{{.TypeName}}) TakesValue() bool { + return "{{.TypeName }}" != "BoolFlag" +} + +{{end}}{{/* /if .GenerateFlagInterface */}} + {{end}}{{/* /range .SortedFlagTypes */}} // vim{{/* 👻 */}}:ro diff --git a/internal/genflags/generated_test.gotmpl b/internal/genflags/generated_test.gotmpl index 52de4e4bf4..83229b06ba 100644 --- a/internal/genflags/generated_test.gotmpl +++ b/internal/genflags/generated_test.gotmpl @@ -10,31 +10,30 @@ func Test{{.TypeName}}_SatisfiesFlagInterface(t *testing.T) { _ = f.IsSet() _ = f.Names() } -{{end}} - -{{if .GenerateFmtStringerInterface}} -func Test{{.TypeName}}_SatisfiesFmtStringerInterface(t *testing.T) { - var f fmt.Stringer = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} - _ = f.String() -} -{{end}} - -{{if .GenerateRequiredFlagInterface}} func Test{{.TypeName}}_SatisfiesRequiredFlagInterface(t *testing.T) { var f {{$.UrfaveCLITestNamespace}}RequiredFlag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} _ = f.IsRequired() } -{{end}} -{{if .GenerateVisibleFlagInterface}} func Test{{.TypeName}}_SatisfiesVisibleFlagInterface(t *testing.T) { var f {{$.UrfaveCLITestNamespace}}VisibleFlag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} _ = f.IsVisible() } + {{end}} + +{{if .GenerateFmtStringerInterface}} +func Test{{.TypeName}}_SatisfiesFmtStringerInterface(t *testing.T) { + var f fmt.Stringer = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} + + _ = f.String() +} +{{end}} + + {{end}} // vim{{/* 👻 */}}:ro diff --git a/internal/genflags/spec.go b/internal/genflags/spec.go index a7afb22143..de537a55a1 100644 --- a/internal/genflags/spec.go +++ b/internal/genflags/spec.go @@ -39,10 +39,11 @@ func (gfs *Spec) SortedFlagTypes() []*FlagType { } type FlagTypeConfig struct { - SkipInterfaces []string `yaml:"skip_interfaces"` - StructFields []*FlagStructField `yaml:"struct_fields"` - TypeName string `yaml:"type_name"` - ValuePointer bool `yaml:"value_pointer"` + SkipInterfaces []string `yaml:"skip_interfaces"` + StructFields []*FlagStructField `yaml:"struct_fields"` + TypeName string `yaml:"type_name"` + ValuePointer bool `yaml:"value_pointer"` + DoesntNeedValue bool `yaml:"doesnt_need_value"` } type FlagStructField struct { @@ -83,14 +84,6 @@ func (ft *FlagType) GenerateFlagInterface() bool { return ft.skipInterfaceNamed("Flag") } -func (ft *FlagType) GenerateRequiredFlagInterface() bool { - return ft.skipInterfaceNamed("RequiredFlag") -} - -func (ft *FlagType) GenerateVisibleFlagInterface() bool { - return ft.skipInterfaceNamed("VisibleFlag") -} - func (ft *FlagType) skipInterfaceNamed(name string) bool { if ft.Config == nil { return true diff --git a/sliceflag.go b/sliceflag.go index b2ca59041f..6cc5266f15 100644 --- a/sliceflag.go +++ b/sliceflag.go @@ -21,9 +21,7 @@ type ( // update). SliceFlagTarget[E any] interface { Flag - RequiredFlag DocGenerationFlag - VisibleFlag CategorizableFlag // SetValue should propagate the given slice to the target, ideally as a new value. diff --git a/zz_generated.flags.go b/zz_generated.flags.go index b89566f900..1ee72d39e9 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -44,6 +44,26 @@ func (f *Float64SliceFlag) IsVisible() bool { return !f.Hidden } +// GetCategory returns the category of the flag +func (f *Float64SliceFlag) GetCategory() string { + return f.Category +} + +// GetUsage returns the usage string for the flag +func (f *Float64SliceFlag) GetUsage() string { + return f.Usage +} + +// GetEnvVars returns the env vars for this flag +func (f *Float64SliceFlag) GetEnvVars() []string { + return f.EnvVars +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *Float64SliceFlag) TakesValue() bool { + return "Float64SliceFlag" != "BoolFlag" +} + // GenericFlag is a flag with type Generic type GenericFlag struct { Name string @@ -91,6 +111,26 @@ func (f *GenericFlag) IsVisible() bool { return !f.Hidden } +// GetCategory returns the category of the flag +func (f *GenericFlag) GetCategory() string { + return f.Category +} + +// GetUsage returns the usage string for the flag +func (f *GenericFlag) GetUsage() string { + return f.Usage +} + +// GetEnvVars returns the env vars for this flag +func (f *GenericFlag) GetEnvVars() []string { + return f.EnvVars +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *GenericFlag) TakesValue() bool { + return "GenericFlag" != "BoolFlag" +} + // Int64SliceFlag is a flag with type *Int64Slice type Int64SliceFlag struct { Name string @@ -131,6 +171,26 @@ func (f *Int64SliceFlag) IsVisible() bool { return !f.Hidden } +// GetCategory returns the category of the flag +func (f *Int64SliceFlag) GetCategory() string { + return f.Category +} + +// GetUsage returns the usage string for the flag +func (f *Int64SliceFlag) GetUsage() string { + return f.Usage +} + +// GetEnvVars returns the env vars for this flag +func (f *Int64SliceFlag) GetEnvVars() []string { + return f.EnvVars +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *Int64SliceFlag) TakesValue() bool { + return "Int64SliceFlag" != "BoolFlag" +} + // IntSliceFlag is a flag with type *IntSlice type IntSliceFlag struct { Name string @@ -171,6 +231,26 @@ func (f *IntSliceFlag) IsVisible() bool { return !f.Hidden } +// GetCategory returns the category of the flag +func (f *IntSliceFlag) GetCategory() string { + return f.Category +} + +// GetUsage returns the usage string for the flag +func (f *IntSliceFlag) GetUsage() string { + return f.Usage +} + +// GetEnvVars returns the env vars for this flag +func (f *IntSliceFlag) GetEnvVars() []string { + return f.EnvVars +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *IntSliceFlag) TakesValue() bool { + return "IntSliceFlag" != "BoolFlag" +} + // PathFlag is a flag with type Path type PathFlag struct { Name string @@ -218,6 +298,26 @@ func (f *PathFlag) IsVisible() bool { return !f.Hidden } +// GetCategory returns the category of the flag +func (f *PathFlag) GetCategory() string { + return f.Category +} + +// GetUsage returns the usage string for the flag +func (f *PathFlag) GetUsage() string { + return f.Usage +} + +// GetEnvVars returns the env vars for this flag +func (f *PathFlag) GetEnvVars() []string { + return f.EnvVars +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *PathFlag) TakesValue() bool { + return "PathFlag" != "BoolFlag" +} + // StringSliceFlag is a flag with type *StringSlice type StringSliceFlag struct { Name string @@ -260,6 +360,26 @@ func (f *StringSliceFlag) IsVisible() bool { return !f.Hidden } +// GetCategory returns the category of the flag +func (f *StringSliceFlag) GetCategory() string { + return f.Category +} + +// GetUsage returns the usage string for the flag +func (f *StringSliceFlag) GetUsage() string { + return f.Usage +} + +// GetEnvVars returns the env vars for this flag +func (f *StringSliceFlag) GetEnvVars() []string { + return f.EnvVars +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *StringSliceFlag) TakesValue() bool { + return "StringSliceFlag" != "BoolFlag" +} + // TimestampFlag is a flag with type *Timestamp type TimestampFlag struct { Name string @@ -309,6 +429,26 @@ func (f *TimestampFlag) IsVisible() bool { return !f.Hidden } +// GetCategory returns the category of the flag +func (f *TimestampFlag) GetCategory() string { + return f.Category +} + +// GetUsage returns the usage string for the flag +func (f *TimestampFlag) GetUsage() string { + return f.Usage +} + +// GetEnvVars returns the env vars for this flag +func (f *TimestampFlag) GetEnvVars() []string { + return f.EnvVars +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *TimestampFlag) TakesValue() bool { + return "TimestampFlag" != "BoolFlag" +} + // BoolFlag is a flag with type bool type BoolFlag struct { Name string @@ -354,6 +494,26 @@ func (f *BoolFlag) IsVisible() bool { return !f.Hidden } +// GetCategory returns the category of the flag +func (f *BoolFlag) GetCategory() string { + return f.Category +} + +// GetUsage returns the usage string for the flag +func (f *BoolFlag) GetUsage() string { + return f.Usage +} + +// GetEnvVars returns the env vars for this flag +func (f *BoolFlag) GetEnvVars() []string { + return f.EnvVars +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *BoolFlag) TakesValue() bool { + return "BoolFlag" != "BoolFlag" +} + // Float64Flag is a flag with type float64 type Float64Flag struct { Name string @@ -399,6 +559,26 @@ func (f *Float64Flag) IsVisible() bool { return !f.Hidden } +// GetCategory returns the category of the flag +func (f *Float64Flag) GetCategory() string { + return f.Category +} + +// GetUsage returns the usage string for the flag +func (f *Float64Flag) GetUsage() string { + return f.Usage +} + +// GetEnvVars returns the env vars for this flag +func (f *Float64Flag) GetEnvVars() []string { + return f.EnvVars +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *Float64Flag) TakesValue() bool { + return "Float64Flag" != "BoolFlag" +} + // IntFlag is a flag with type int type IntFlag struct { Name string @@ -444,6 +624,26 @@ func (f *IntFlag) IsVisible() bool { return !f.Hidden } +// GetCategory returns the category of the flag +func (f *IntFlag) GetCategory() string { + return f.Category +} + +// GetUsage returns the usage string for the flag +func (f *IntFlag) GetUsage() string { + return f.Usage +} + +// GetEnvVars returns the env vars for this flag +func (f *IntFlag) GetEnvVars() []string { + return f.EnvVars +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *IntFlag) TakesValue() bool { + return "IntFlag" != "BoolFlag" +} + // Int64Flag is a flag with type int64 type Int64Flag struct { Name string @@ -489,6 +689,26 @@ func (f *Int64Flag) IsVisible() bool { return !f.Hidden } +// GetCategory returns the category of the flag +func (f *Int64Flag) GetCategory() string { + return f.Category +} + +// GetUsage returns the usage string for the flag +func (f *Int64Flag) GetUsage() string { + return f.Usage +} + +// GetEnvVars returns the env vars for this flag +func (f *Int64Flag) GetEnvVars() []string { + return f.EnvVars +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *Int64Flag) TakesValue() bool { + return "Int64Flag" != "BoolFlag" +} + // StringFlag is a flag with type string type StringFlag struct { Name string @@ -536,6 +756,26 @@ func (f *StringFlag) IsVisible() bool { return !f.Hidden } +// GetCategory returns the category of the flag +func (f *StringFlag) GetCategory() string { + return f.Category +} + +// GetUsage returns the usage string for the flag +func (f *StringFlag) GetUsage() string { + return f.Usage +} + +// GetEnvVars returns the env vars for this flag +func (f *StringFlag) GetEnvVars() []string { + return f.EnvVars +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *StringFlag) TakesValue() bool { + return "StringFlag" != "BoolFlag" +} + // DurationFlag is a flag with type time.Duration type DurationFlag struct { Name string @@ -581,6 +821,26 @@ func (f *DurationFlag) IsVisible() bool { return !f.Hidden } +// GetCategory returns the category of the flag +func (f *DurationFlag) GetCategory() string { + return f.Category +} + +// GetUsage returns the usage string for the flag +func (f *DurationFlag) GetUsage() string { + return f.Usage +} + +// GetEnvVars returns the env vars for this flag +func (f *DurationFlag) GetEnvVars() []string { + return f.EnvVars +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *DurationFlag) TakesValue() bool { + return "DurationFlag" != "BoolFlag" +} + // UintFlag is a flag with type uint type UintFlag struct { Name string @@ -626,6 +886,26 @@ func (f *UintFlag) IsVisible() bool { return !f.Hidden } +// GetCategory returns the category of the flag +func (f *UintFlag) GetCategory() string { + return f.Category +} + +// GetUsage returns the usage string for the flag +func (f *UintFlag) GetUsage() string { + return f.Usage +} + +// GetEnvVars returns the env vars for this flag +func (f *UintFlag) GetEnvVars() []string { + return f.EnvVars +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *UintFlag) TakesValue() bool { + return "UintFlag" != "BoolFlag" +} + // Uint64Flag is a flag with type uint64 type Uint64Flag struct { Name string @@ -671,4 +951,24 @@ func (f *Uint64Flag) IsVisible() bool { return !f.Hidden } +// GetCategory returns the category of the flag +func (f *Uint64Flag) GetCategory() string { + return f.Category +} + +// GetUsage returns the usage string for the flag +func (f *Uint64Flag) GetUsage() string { + return f.Usage +} + +// GetEnvVars returns the env vars for this flag +func (f *Uint64Flag) GetEnvVars() []string { + return f.EnvVars +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *Uint64Flag) TakesValue() bool { + return "Uint64Flag" != "BoolFlag" +} + // vim:ro diff --git a/zz_generated.flags_test.go b/zz_generated.flags_test.go index 1d9afdaa71..37dd857228 100644 --- a/zz_generated.flags_test.go +++ b/zz_generated.flags_test.go @@ -35,12 +35,6 @@ func TestGenericFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } -func TestGenericFlag_SatisfiesFmtStringerInterface(t *testing.T) { - var f fmt.Stringer = &cli.GenericFlag{} - - _ = f.String() -} - func TestGenericFlag_SatisfiesRequiredFlagInterface(t *testing.T) { var f cli.RequiredFlag = &cli.GenericFlag{} @@ -53,6 +47,12 @@ func TestGenericFlag_SatisfiesVisibleFlagInterface(t *testing.T) { _ = f.IsVisible() } +func TestGenericFlag_SatisfiesFmtStringerInterface(t *testing.T) { + var f fmt.Stringer = &cli.GenericFlag{} + + _ = f.String() +} + func TestInt64SliceFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.Int64SliceFlag{} @@ -98,12 +98,6 @@ func TestPathFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } -func TestPathFlag_SatisfiesFmtStringerInterface(t *testing.T) { - var f fmt.Stringer = &cli.PathFlag{} - - _ = f.String() -} - func TestPathFlag_SatisfiesRequiredFlagInterface(t *testing.T) { var f cli.RequiredFlag = &cli.PathFlag{} @@ -116,6 +110,12 @@ func TestPathFlag_SatisfiesVisibleFlagInterface(t *testing.T) { _ = f.IsVisible() } +func TestPathFlag_SatisfiesFmtStringerInterface(t *testing.T) { + var f fmt.Stringer = &cli.PathFlag{} + + _ = f.String() +} + func TestStringSliceFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.StringSliceFlag{} @@ -142,12 +142,6 @@ func TestTimestampFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } -func TestTimestampFlag_SatisfiesFmtStringerInterface(t *testing.T) { - var f fmt.Stringer = &cli.TimestampFlag{} - - _ = f.String() -} - func TestTimestampFlag_SatisfiesRequiredFlagInterface(t *testing.T) { var f cli.RequiredFlag = &cli.TimestampFlag{} @@ -160,6 +154,12 @@ func TestTimestampFlag_SatisfiesVisibleFlagInterface(t *testing.T) { _ = f.IsVisible() } +func TestTimestampFlag_SatisfiesFmtStringerInterface(t *testing.T) { + var f fmt.Stringer = &cli.TimestampFlag{} + + _ = f.String() +} + func TestBoolFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.BoolFlag{} @@ -167,12 +167,6 @@ func TestBoolFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } -func TestBoolFlag_SatisfiesFmtStringerInterface(t *testing.T) { - var f fmt.Stringer = &cli.BoolFlag{} - - _ = f.String() -} - func TestBoolFlag_SatisfiesRequiredFlagInterface(t *testing.T) { var f cli.RequiredFlag = &cli.BoolFlag{} @@ -185,6 +179,12 @@ func TestBoolFlag_SatisfiesVisibleFlagInterface(t *testing.T) { _ = f.IsVisible() } +func TestBoolFlag_SatisfiesFmtStringerInterface(t *testing.T) { + var f fmt.Stringer = &cli.BoolFlag{} + + _ = f.String() +} + func TestFloat64Flag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.Float64Flag{} @@ -192,12 +192,6 @@ func TestFloat64Flag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } -func TestFloat64Flag_SatisfiesFmtStringerInterface(t *testing.T) { - var f fmt.Stringer = &cli.Float64Flag{} - - _ = f.String() -} - func TestFloat64Flag_SatisfiesRequiredFlagInterface(t *testing.T) { var f cli.RequiredFlag = &cli.Float64Flag{} @@ -210,6 +204,12 @@ func TestFloat64Flag_SatisfiesVisibleFlagInterface(t *testing.T) { _ = f.IsVisible() } +func TestFloat64Flag_SatisfiesFmtStringerInterface(t *testing.T) { + var f fmt.Stringer = &cli.Float64Flag{} + + _ = f.String() +} + func TestIntFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.IntFlag{} @@ -217,12 +217,6 @@ func TestIntFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } -func TestIntFlag_SatisfiesFmtStringerInterface(t *testing.T) { - var f fmt.Stringer = &cli.IntFlag{} - - _ = f.String() -} - func TestIntFlag_SatisfiesRequiredFlagInterface(t *testing.T) { var f cli.RequiredFlag = &cli.IntFlag{} @@ -235,6 +229,12 @@ func TestIntFlag_SatisfiesVisibleFlagInterface(t *testing.T) { _ = f.IsVisible() } +func TestIntFlag_SatisfiesFmtStringerInterface(t *testing.T) { + var f fmt.Stringer = &cli.IntFlag{} + + _ = f.String() +} + func TestInt64Flag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.Int64Flag{} @@ -242,12 +242,6 @@ func TestInt64Flag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } -func TestInt64Flag_SatisfiesFmtStringerInterface(t *testing.T) { - var f fmt.Stringer = &cli.Int64Flag{} - - _ = f.String() -} - func TestInt64Flag_SatisfiesRequiredFlagInterface(t *testing.T) { var f cli.RequiredFlag = &cli.Int64Flag{} @@ -260,6 +254,12 @@ func TestInt64Flag_SatisfiesVisibleFlagInterface(t *testing.T) { _ = f.IsVisible() } +func TestInt64Flag_SatisfiesFmtStringerInterface(t *testing.T) { + var f fmt.Stringer = &cli.Int64Flag{} + + _ = f.String() +} + func TestStringFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.StringFlag{} @@ -267,12 +267,6 @@ func TestStringFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } -func TestStringFlag_SatisfiesFmtStringerInterface(t *testing.T) { - var f fmt.Stringer = &cli.StringFlag{} - - _ = f.String() -} - func TestStringFlag_SatisfiesRequiredFlagInterface(t *testing.T) { var f cli.RequiredFlag = &cli.StringFlag{} @@ -285,6 +279,12 @@ func TestStringFlag_SatisfiesVisibleFlagInterface(t *testing.T) { _ = f.IsVisible() } +func TestStringFlag_SatisfiesFmtStringerInterface(t *testing.T) { + var f fmt.Stringer = &cli.StringFlag{} + + _ = f.String() +} + func TestDurationFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.DurationFlag{} @@ -292,12 +292,6 @@ func TestDurationFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } -func TestDurationFlag_SatisfiesFmtStringerInterface(t *testing.T) { - var f fmt.Stringer = &cli.DurationFlag{} - - _ = f.String() -} - func TestDurationFlag_SatisfiesRequiredFlagInterface(t *testing.T) { var f cli.RequiredFlag = &cli.DurationFlag{} @@ -310,6 +304,12 @@ func TestDurationFlag_SatisfiesVisibleFlagInterface(t *testing.T) { _ = f.IsVisible() } +func TestDurationFlag_SatisfiesFmtStringerInterface(t *testing.T) { + var f fmt.Stringer = &cli.DurationFlag{} + + _ = f.String() +} + func TestUintFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.UintFlag{} @@ -317,12 +317,6 @@ func TestUintFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } -func TestUintFlag_SatisfiesFmtStringerInterface(t *testing.T) { - var f fmt.Stringer = &cli.UintFlag{} - - _ = f.String() -} - func TestUintFlag_SatisfiesRequiredFlagInterface(t *testing.T) { var f cli.RequiredFlag = &cli.UintFlag{} @@ -335,6 +329,12 @@ func TestUintFlag_SatisfiesVisibleFlagInterface(t *testing.T) { _ = f.IsVisible() } +func TestUintFlag_SatisfiesFmtStringerInterface(t *testing.T) { + var f fmt.Stringer = &cli.UintFlag{} + + _ = f.String() +} + func TestUint64Flag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.Uint64Flag{} @@ -342,12 +342,6 @@ func TestUint64Flag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } -func TestUint64Flag_SatisfiesFmtStringerInterface(t *testing.T) { - var f fmt.Stringer = &cli.Uint64Flag{} - - _ = f.String() -} - func TestUint64Flag_SatisfiesRequiredFlagInterface(t *testing.T) { var f cli.RequiredFlag = &cli.Uint64Flag{} @@ -360,4 +354,10 @@ func TestUint64Flag_SatisfiesVisibleFlagInterface(t *testing.T) { _ = f.IsVisible() } +func TestUint64Flag_SatisfiesFmtStringerInterface(t *testing.T) { + var f fmt.Stringer = &cli.Uint64Flag{} + + _ = f.String() +} + // vim:ro From 268cb973f8f7b1f38999f43122fb3e39b90b0522 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sun, 4 Sep 2022 22:51:05 -0400 Subject: [PATCH 003/136] Remove all flag interfaces --- app.go | 4 +- app_test.go | 4 ++ category.go | 18 ++--- context.go | 2 +- docs.go | 8 +-- fish.go | 9 +-- flag-spec.yaml | 5 +- flag.go | 47 +++--------- flag_duration.go | 8 --- flag_float64.go | 8 --- flag_float64_slice.go | 8 --- flag_generic.go | 8 --- flag_int.go | 8 --- flag_int64.go | 8 --- flag_int64_slice.go | 8 --- flag_int_slice.go | 8 --- flag_string_slice.go | 8 --- flag_test.go | 30 +++----- flag_timestamp.go | 8 --- flag_uint.go | 8 --- flag_uint64.go | 8 --- godoc-current.txt | 45 +++--------- internal/genflags/generated.gotmpl | 10 +++ internal/genflags/generated_test.gotmpl | 4 +- internal/genflags/spec.go | 14 ++-- sliceflag.go | 2 - zz_generated.flags.go | 96 +++++++++++++++++++++++++ zz_generated.flags_test.go | 60 ++++++++-------- 28 files changed, 192 insertions(+), 262 deletions(-) diff --git a/app.go b/app.go index 7e64c2d9f8..834873aeb3 100644 --- a/app.go +++ b/app.go @@ -227,9 +227,7 @@ func (a *App) Setup() { a.flagCategories = newFlagCategories() for _, fl := range a.Flags { - if cf, ok := fl.(CategorizableFlag); ok { - a.flagCategories.AddFlag(cf.GetCategory(), cf) - } + a.flagCategories.AddFlag(fl.GetCategory(), fl) } if a.Metadata == nil { diff --git a/app_test.go b/app_test.go index 53d6f64f21..da3981c4fb 100644 --- a/app_test.go +++ b/app_test.go @@ -2268,6 +2268,10 @@ func (c *customBoolFlag) GetEnvVars() []string { return nil } +func (c *customBoolFlag) GetDefaultText() string { + return "" +} + func TestCustomFlagsUnused(t *testing.T) { app := &App{ Flags: []Flag{&customBoolFlag{"custom"}}, diff --git a/category.go b/category.go index 8bf325e203..188937d5bd 100644 --- a/category.go +++ b/category.go @@ -101,9 +101,7 @@ func newFlagCategories() FlagCategories { func newFlagCategoriesFromFlags(fs []Flag) FlagCategories { fc := newFlagCategories() for _, fl := range fs { - if cf, ok := fl.(CategorizableFlag); ok { - fc.AddFlag(cf.GetCategory(), cf) - } + fc.AddFlag(fl.GetCategory(), fl) } return fc @@ -138,7 +136,7 @@ type VisibleFlagCategory interface { // Name returns the category name string Name() string // Flags returns a slice of VisibleFlag sorted by name - Flags() []VisibleFlag + Flags() []Flag } type defaultVisibleFlagCategory struct { @@ -150,21 +148,19 @@ func (fc *defaultVisibleFlagCategory) Name() string { return fc.name } -func (fc *defaultVisibleFlagCategory) Flags() []VisibleFlag { +func (fc *defaultVisibleFlagCategory) Flags() []Flag { vfNames := []string{} for flName, fl := range fc.m { - if vf, ok := fl.(VisibleFlag); ok { - if vf.IsVisible() { - vfNames = append(vfNames, flName) - } + if fl.IsVisible() { + vfNames = append(vfNames, flName) } } sort.Strings(vfNames) - ret := make([]VisibleFlag, len(vfNames)) + ret := make([]Flag, len(vfNames)) for i, flName := range vfNames { - ret[i] = fc.m[flName].(VisibleFlag) + ret[i] = fc.m[flName] } return ret diff --git a/context.go b/context.go index 6b497ed20d..216e95813f 100644 --- a/context.go +++ b/context.go @@ -165,7 +165,7 @@ func (cCtx *Context) lookupFlagSet(name string) *flag.FlagSet { func (cCtx *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr { var missingFlags []string for _, f := range flags { - if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() { + if f.IsRequired() { var flagPresent bool var flagName string diff --git a/docs.go b/docs.go index 8b1c9c8a2c..5a8be0ac60 100644 --- a/docs.go +++ b/docs.go @@ -116,11 +116,7 @@ func prepareFlags( addDetails bool, ) []string { args := []string{} - for _, f := range flags { - flag, ok := f.(DocGenerationFlag) - if !ok { - continue - } + for _, flag := range flags { modifiedArg := opener for _, s := range flag.Names() { @@ -151,7 +147,7 @@ func prepareFlags( } // flagDetails returns a string containing the flags metadata -func flagDetails(flag DocGenerationFlag) string { +func flagDetails(flag Flag) string { description := flag.GetUsage() value := flag.GetValue() if value != "" { diff --git a/fish.go b/fish.go index eec3253cda..70946e1d9e 100644 --- a/fish.go +++ b/fish.go @@ -114,12 +114,7 @@ func (a *App) prepareFishCommands(commands []*Command, allCommands *[]string, pr func (a *App) prepareFishFlags(flags []Flag, previousCommands []string) []string { completions := []string{} - for _, f := range flags { - flag, ok := f.(DocGenerationFlag) - if !ok { - continue - } - + for _, flag := range flags { completion := &strings.Builder{} completion.WriteString(fmt.Sprintf( "complete -c %s -n '%s'", @@ -127,7 +122,7 @@ func (a *App) prepareFishFlags(flags []Flag, previousCommands []string) []string a.fishSubcommandHelper(previousCommands), )) - fishAddFileFlag(f, completion) + fishAddFileFlag(flag, completion) for idx, opt := range flag.Names() { if idx == 0 { diff --git a/flag-spec.yaml b/flag-spec.yaml index 45f054d6e5..3fa2d10367 100644 --- a/flag-spec.yaml +++ b/flag-spec.yaml @@ -3,7 +3,8 @@ # `genflags.Spec` type that maps to this file structure. flag_types: - bool: {} + bool: + no_default_text: true float64: {} int64: {} int: {} @@ -12,12 +13,14 @@ flag_types: uint: {} string: + no_default_text: true struct_fields: - { name: TakesFile, type: bool } Generic: struct_fields: - { name: TakesFile, type: bool } Path: + no_default_text: true struct_fields: - { name: TakesFile, type: bool } diff --git a/flag.go b/flag.go index 4580a3e74d..a5434cbd98 100644 --- a/flag.go +++ b/flag.go @@ -101,38 +101,13 @@ type Flag interface { GetUsage() string // GetEnvVars returns the env vars for this flag GetEnvVars() []string -} - -// RequiredFlag is an interface that allows us to mark flags as required -// it allows flags required flags to be backwards compatible with the Flag interface -type RequiredFlag interface { - Flag -} - -// DocGenerationFlag is an interface that allows documentation generation for the flag -type DocGenerationFlag interface { - Flag - // TakesValue returns true if the flag takes a value, otherwise false TakesValue() bool - + // GetDefaultText returns the default text for this flag + GetDefaultText() string // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. GetValue() string - - // GetDefaultText returns the default text for this flag - GetDefaultText() string -} - -// VisibleFlag is an interface that allows to check if a flag is visible -type VisibleFlag interface { - Flag -} - -// CategorizableFlag is an interface that allows us to potentially -// use a flag in a categorized representation. -type CategorizableFlag interface { - VisibleFlag } func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { @@ -192,7 +167,7 @@ func normalizeFlags(flags []Flag, set *flag.FlagSet) error { func visibleFlags(fl []Flag) []Flag { var visible []Flag for _, f := range fl { - if vf, ok := f.(VisibleFlag); ok && vf.IsVisible() { + if f.IsVisible() { visible = append(visible, f) } } @@ -288,14 +263,8 @@ func formatDefault(format string) string { } func stringifyFlag(f Flag) string { - // enforce DocGeneration interface on flags to avoid reflection - df, ok := f.(DocGenerationFlag) - if !ok { - return "" - } - - placeholder, usage := unquoteUsage(df.GetUsage()) - needsPlaceholder := df.TakesValue() + placeholder, usage := unquoteUsage(f.GetUsage()) + needsPlaceholder := f.TakesValue() if needsPlaceholder && placeholder == "" { placeholder = defaultPlaceholder @@ -303,14 +272,14 @@ func stringifyFlag(f Flag) string { defaultValueString := "" - if s := df.GetDefaultText(); s != "" { + if s := f.GetDefaultText(); s != "" { defaultValueString = fmt.Sprintf(formatDefault("%s"), s) } usageWithDefault := strings.TrimSpace(usage + defaultValueString) - return withEnvHint(df.GetEnvVars(), - fmt.Sprintf("%s\t%s", prefixedNames(df.Names(), placeholder), usageWithDefault)) + return withEnvHint(f.GetEnvVars(), + fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault)) } func stringifyIntSliceFlag(f *IntSliceFlag) string { diff --git a/flag_duration.go b/flag_duration.go index 6f4ece0185..35b376da7f 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -12,14 +12,6 @@ func (f *DurationFlag) GetValue() string { return f.Value.String() } -// GetDefaultText returns the default text for this flag -func (f *DurationFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Apply populates the flag given the flag set and environment func (f *DurationFlag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_float64.go b/flag_float64.go index 4c8778fc34..b7b8044a53 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -12,14 +12,6 @@ func (f *Float64Flag) GetValue() string { return fmt.Sprintf("%v", f.Value) } -// GetDefaultText returns the default text for this flag -func (f *Float64Flag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Apply populates the flag given the flag set and environment func (f *Float64Flag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 911cb84b43..56745c1b59 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -95,14 +95,6 @@ func (f *Float64SliceFlag) GetValue() string { return "" } -// GetDefaultText returns the default text for this flag -func (f *Float64SliceFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Apply populates the flag given the flag set and environment func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { // apply any default diff --git a/flag_generic.go b/flag_generic.go index 256aea66a5..cb20486be8 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -20,14 +20,6 @@ func (f *GenericFlag) GetValue() string { return "" } -// GetDefaultText returns the default text for this flag -func (f *GenericFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Apply takes the flagset and calls Set on the generic flag with the value // provided by the user for parsing by the flag func (f GenericFlag) Apply(set *flag.FlagSet) error { diff --git a/flag_int.go b/flag_int.go index d4d8959fd9..908e076162 100644 --- a/flag_int.go +++ b/flag_int.go @@ -12,14 +12,6 @@ func (f *IntFlag) GetValue() string { return fmt.Sprintf("%d", f.Value) } -// GetDefaultText returns the default text for this flag -func (f *IntFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Apply populates the flag given the flag set and environment func (f *IntFlag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_int64.go b/flag_int64.go index fdb65b950a..9f07d79a8c 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -12,14 +12,6 @@ func (f *Int64Flag) GetValue() string { return fmt.Sprintf("%d", f.Value) } -// GetDefaultText returns the default text for this flag -func (f *Int64Flag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Apply populates the flag given the flag set and environment func (f *Int64Flag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 0346663b5e..83167092b0 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -96,14 +96,6 @@ func (f *Int64SliceFlag) GetValue() string { return "" } -// GetDefaultText returns the default text for this flag -func (f *Int64SliceFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Apply populates the flag given the flag set and environment func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { // apply any default diff --git a/flag_int_slice.go b/flag_int_slice.go index a24fc8de0e..7865dab0e9 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -107,14 +107,6 @@ func (f *IntSliceFlag) GetValue() string { return "" } -// GetDefaultText returns the default text for this flag -func (f *IntSliceFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Apply populates the flag given the flag set and environment func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { // apply any default diff --git a/flag_string_slice.go b/flag_string_slice.go index d56dce103e..f50323b445 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -85,14 +85,6 @@ func (f *StringSliceFlag) GetValue() string { return "" } -// GetDefaultText returns the default text for this flag -func (f *StringSliceFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Apply populates the flag given the flag set and environment func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { // apply any default diff --git a/flag_test.go b/flag_test.go index e46b1eff45..9e50526218 100644 --- a/flag_test.go +++ b/flag_test.go @@ -144,10 +144,7 @@ func TestFlagsFromEnv(t *testing.T) { defer resetEnv(os.Environ()) os.Clearenv() - f, ok := test.flag.(DocGenerationFlag) - if !ok { - t.Errorf("flag %v needs to implement DocGenerationFlag to retrieve env vars", test.flag) - } + f := test.flag envVarSlice := f.GetEnvVars() _ = os.Setenv(envVarSlice[0], test.input) @@ -179,12 +176,6 @@ func TestFlagsFromEnv(t *testing.T) { } } -type nodocFlag struct { - Flag - - Name string -} - func TestFlagStringifying(t *testing.T) { for _, tc := range []struct { name string @@ -341,11 +332,6 @@ func TestFlagStringifying(t *testing.T) { fl: &UintFlag{Name: "tubes", DefaultText: "13"}, expected: "--tubes value\t(default: 13)", }, - { - name: "nodoc-flag", - fl: &nodocFlag{Name: "scarecrow"}, - expected: "", - }, } { t.Run(tc.name, func(ct *testing.T) { s := stringifyFlag(tc.fl) @@ -2398,43 +2384,43 @@ func TestFlagDefaultValue(t *testing.T) { name: "stringSclice", flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")}, toParse: []string{"--flag", "parsed"}, - expect: `--flag value (default: "default1", "default2") (accepts multiple inputs)`, + expect: `--flag value (default: "default1", "default2") (accepts multiple inputs)`, }, { name: "float64Sclice", flag: &Float64SliceFlag{Name: "flag", Value: NewFloat64Slice(1.1, 2.2)}, toParse: []string{"--flag", "13.3"}, - expect: `--flag value (default: 1.1, 2.2) (accepts multiple inputs)`, + expect: `--flag value (default: 1.1, 2.2) (accepts multiple inputs)`, }, { name: "int64Sclice", flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, + expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, }, { name: "intSclice", flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, + expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, }, { name: "string", flag: &StringFlag{Name: "flag", Value: "default"}, toParse: []string{"--flag", "parsed"}, - expect: `--flag value (default: "default")`, + expect: `--flag value (default: "default")`, }, { name: "bool", flag: &BoolFlag{Name: "flag", Value: true}, toParse: []string{"--flag", "false"}, - expect: `--flag (default: true)`, + expect: `--flag (default: true)`, }, { name: "uint64", flag: &Uint64Flag{Name: "flag", Value: 1}, toParse: []string{"--flag", "13"}, - expect: `--flag value (default: 1)`, + expect: `--flag value (default: 1)`, }, } for i, v := range cases { diff --git a/flag_timestamp.go b/flag_timestamp.go index 275fd1edd1..8759b85290 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -81,14 +81,6 @@ func (f *TimestampFlag) GetValue() string { return "" } -// GetDefaultText returns the default text for this flag -func (f *TimestampFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Apply populates the flag given the flag set and environment func (f *TimestampFlag) Apply(set *flag.FlagSet) error { if f.Layout == "" { diff --git a/flag_uint.go b/flag_uint.go index c3e8fcbb61..d03fa7cd5a 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -37,14 +37,6 @@ func (f *UintFlag) GetValue() string { return fmt.Sprintf("%d", f.Value) } -// GetDefaultText returns the default text for this flag -func (f *UintFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Get returns the flag’s value in the given Context. func (f *UintFlag) Get(ctx *Context) uint { return ctx.Uint(f.Name) diff --git a/flag_uint64.go b/flag_uint64.go index cacdcc6ede..5c06ab4f59 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -37,14 +37,6 @@ func (f *Uint64Flag) GetValue() string { return fmt.Sprintf("%d", f.Value) } -// GetDefaultText returns the default text for this flag -func (f *Uint64Flag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Get returns the flag’s value in the given Context. func (f *Uint64Flag) Get(ctx *Context) uint64 { return ctx.Uint64(f.Name) diff --git a/godoc-current.txt b/godoc-current.txt index afdb3d5d77..79b4245570 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -493,12 +493,6 @@ func (f *BoolFlag) String() string func (f *BoolFlag) TakesValue() bool TakesValue returns true if the flag takes a value, otherwise false -type CategorizableFlag interface { - VisibleFlag -} - CategorizableFlag is an interface that allows us to potentially use a flag - in a categorized representation. - type Command struct { // The name of the command Name string @@ -699,22 +693,6 @@ func (cCtx *Context) Uint64(name string) uint64 func (cCtx *Context) Value(name string) interface{} Value returns the value of the flag corresponding to `name` -type DocGenerationFlag interface { - Flag - - // TakesValue returns true if the flag takes a value, otherwise false - TakesValue() bool - - // GetValue returns the flags value as string representation and an empty - // string if the flag takes no value at all. - GetValue() string - - // GetDefaultText returns the default text for this flag - GetDefaultText() string -} - DocGenerationFlag is an interface that allows documentation generation for - the flag - type DurationFlag struct { Name string @@ -820,6 +798,13 @@ type Flag interface { GetUsage() string // GetEnvVars returns the env vars for this flag GetEnvVars() []string + // TakesValue returns true if the flag takes a value, otherwise false + TakesValue() bool + // GetDefaultText returns the default text for this flag + GetDefaultText() string + // GetValue returns the flags value as string representation and an empty + // string if the flag takes no value at all. + GetValue() string } Flag is a common interface related to parsing flags in cli. For more advanced flag parsing techniques, it is recommended that this interface be @@ -1508,13 +1493,6 @@ func (f *PathFlag) String() string func (f *PathFlag) TakesValue() bool TakesValue returns true if the flag takes a value, otherwise false -type RequiredFlag interface { - Flag -} - RequiredFlag is an interface that allows us to mark flags as required - it allows flags required flags to be backwards compatible with the Flag - interface - type Serializer interface { Serialize() string } @@ -1562,8 +1540,6 @@ func (x *SliceFlag[T, S, E]) TakesValue() bool type SliceFlagTarget[E any] interface { Flag - DocGenerationFlag - CategorizableFlag // SetValue should propagate the given slice to the target, ideally as a new value. // Note that a nil slice should nil/clear any existing value (modelled as ~[]E). @@ -1949,16 +1925,11 @@ func (f *UintFlag) String() string func (f *UintFlag) TakesValue() bool TakesValue returns true if the flag takes a value, otherwise false -type VisibleFlag interface { - Flag -} - VisibleFlag is an interface that allows to check if a flag is visible - type VisibleFlagCategory interface { // Name returns the category name string Name() string // Flags returns a slice of VisibleFlag sorted by name - Flags() []VisibleFlag + Flags() []Flag } VisibleFlagCategory is a category containing flags. diff --git a/internal/genflags/generated.gotmpl b/internal/genflags/generated.gotmpl index d4b1538013..964658e388 100644 --- a/internal/genflags/generated.gotmpl +++ b/internal/genflags/generated.gotmpl @@ -75,6 +75,16 @@ func (f *{{.TypeName}}) TakesValue() bool { return "{{.TypeName }}" != "BoolFlag" } +{{if .GenerateDefaultText}} +// GetDefaultText returns the default text for this flag +func (f *{{.TypeName}}) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} +{{end}} + {{end}}{{/* /if .GenerateFlagInterface */}} {{end}}{{/* /range .SortedFlagTypes */}} diff --git a/internal/genflags/generated_test.gotmpl b/internal/genflags/generated_test.gotmpl index 83229b06ba..c91f562ef2 100644 --- a/internal/genflags/generated_test.gotmpl +++ b/internal/genflags/generated_test.gotmpl @@ -12,13 +12,13 @@ func Test{{.TypeName}}_SatisfiesFlagInterface(t *testing.T) { } func Test{{.TypeName}}_SatisfiesRequiredFlagInterface(t *testing.T) { - var f {{$.UrfaveCLITestNamespace}}RequiredFlag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} + var f {{$.UrfaveCLITestNamespace}}Flag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} _ = f.IsRequired() } func Test{{.TypeName}}_SatisfiesVisibleFlagInterface(t *testing.T) { - var f {{$.UrfaveCLITestNamespace}}VisibleFlag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} + var f {{$.UrfaveCLITestNamespace}}Flag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} _ = f.IsVisible() } diff --git a/internal/genflags/spec.go b/internal/genflags/spec.go index de537a55a1..5b1d4d3047 100644 --- a/internal/genflags/spec.go +++ b/internal/genflags/spec.go @@ -39,11 +39,11 @@ func (gfs *Spec) SortedFlagTypes() []*FlagType { } type FlagTypeConfig struct { - SkipInterfaces []string `yaml:"skip_interfaces"` - StructFields []*FlagStructField `yaml:"struct_fields"` - TypeName string `yaml:"type_name"` - ValuePointer bool `yaml:"value_pointer"` - DoesntNeedValue bool `yaml:"doesnt_need_value"` + SkipInterfaces []string `yaml:"skip_interfaces"` + StructFields []*FlagStructField `yaml:"struct_fields"` + TypeName string `yaml:"type_name"` + ValuePointer bool `yaml:"value_pointer"` + NoDefaultText bool `yaml:"no_default_text"` } type FlagStructField struct { @@ -84,6 +84,10 @@ func (ft *FlagType) GenerateFlagInterface() bool { return ft.skipInterfaceNamed("Flag") } +func (ft *FlagType) GenerateDefaultText() bool { + return !ft.Config.NoDefaultText +} + func (ft *FlagType) skipInterfaceNamed(name string) bool { if ft.Config == nil { return true diff --git a/sliceflag.go b/sliceflag.go index 6cc5266f15..89f3e0cc89 100644 --- a/sliceflag.go +++ b/sliceflag.go @@ -21,8 +21,6 @@ type ( // update). SliceFlagTarget[E any] interface { Flag - DocGenerationFlag - CategorizableFlag // SetValue should propagate the given slice to the target, ideally as a new value. // Note that a nil slice should nil/clear any existing value (modelled as ~[]E). diff --git a/zz_generated.flags.go b/zz_generated.flags.go index 1ee72d39e9..685d97d7b2 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -64,6 +64,14 @@ func (f *Float64SliceFlag) TakesValue() bool { return "Float64SliceFlag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *Float64SliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // GenericFlag is a flag with type Generic type GenericFlag struct { Name string @@ -131,6 +139,14 @@ func (f *GenericFlag) TakesValue() bool { return "GenericFlag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *GenericFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Int64SliceFlag is a flag with type *Int64Slice type Int64SliceFlag struct { Name string @@ -191,6 +207,14 @@ func (f *Int64SliceFlag) TakesValue() bool { return "Int64SliceFlag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *Int64SliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // IntSliceFlag is a flag with type *IntSlice type IntSliceFlag struct { Name string @@ -251,6 +275,14 @@ func (f *IntSliceFlag) TakesValue() bool { return "IntSliceFlag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *IntSliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // PathFlag is a flag with type Path type PathFlag struct { Name string @@ -380,6 +412,14 @@ func (f *StringSliceFlag) TakesValue() bool { return "StringSliceFlag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *StringSliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // TimestampFlag is a flag with type *Timestamp type TimestampFlag struct { Name string @@ -449,6 +489,14 @@ func (f *TimestampFlag) TakesValue() bool { return "TimestampFlag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *TimestampFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // BoolFlag is a flag with type bool type BoolFlag struct { Name string @@ -579,6 +627,14 @@ func (f *Float64Flag) TakesValue() bool { return "Float64Flag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *Float64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // IntFlag is a flag with type int type IntFlag struct { Name string @@ -644,6 +700,14 @@ func (f *IntFlag) TakesValue() bool { return "IntFlag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *IntFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Int64Flag is a flag with type int64 type Int64Flag struct { Name string @@ -709,6 +773,14 @@ func (f *Int64Flag) TakesValue() bool { return "Int64Flag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *Int64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // StringFlag is a flag with type string type StringFlag struct { Name string @@ -841,6 +913,14 @@ func (f *DurationFlag) TakesValue() bool { return "DurationFlag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *DurationFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // UintFlag is a flag with type uint type UintFlag struct { Name string @@ -906,6 +986,14 @@ func (f *UintFlag) TakesValue() bool { return "UintFlag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *UintFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Uint64Flag is a flag with type uint64 type Uint64Flag struct { Name string @@ -971,4 +1059,12 @@ func (f *Uint64Flag) TakesValue() bool { return "Uint64Flag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *Uint64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // vim:ro diff --git a/zz_generated.flags_test.go b/zz_generated.flags_test.go index 37dd857228..16519e62aa 100644 --- a/zz_generated.flags_test.go +++ b/zz_generated.flags_test.go @@ -17,13 +17,13 @@ func TestFloat64SliceFlag_SatisfiesFlagInterface(t *testing.T) { } func TestFloat64SliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.RequiredFlag = &cli.Float64SliceFlag{} + var f cli.Flag = &cli.Float64SliceFlag{} _ = f.IsRequired() } func TestFloat64SliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.VisibleFlag = &cli.Float64SliceFlag{} + var f cli.Flag = &cli.Float64SliceFlag{} _ = f.IsVisible() } @@ -36,13 +36,13 @@ func TestGenericFlag_SatisfiesFlagInterface(t *testing.T) { } func TestGenericFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.RequiredFlag = &cli.GenericFlag{} + var f cli.Flag = &cli.GenericFlag{} _ = f.IsRequired() } func TestGenericFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.VisibleFlag = &cli.GenericFlag{} + var f cli.Flag = &cli.GenericFlag{} _ = f.IsVisible() } @@ -61,13 +61,13 @@ func TestInt64SliceFlag_SatisfiesFlagInterface(t *testing.T) { } func TestInt64SliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.RequiredFlag = &cli.Int64SliceFlag{} + var f cli.Flag = &cli.Int64SliceFlag{} _ = f.IsRequired() } func TestInt64SliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.VisibleFlag = &cli.Int64SliceFlag{} + var f cli.Flag = &cli.Int64SliceFlag{} _ = f.IsVisible() } @@ -80,13 +80,13 @@ func TestIntSliceFlag_SatisfiesFlagInterface(t *testing.T) { } func TestIntSliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.RequiredFlag = &cli.IntSliceFlag{} + var f cli.Flag = &cli.IntSliceFlag{} _ = f.IsRequired() } func TestIntSliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.VisibleFlag = &cli.IntSliceFlag{} + var f cli.Flag = &cli.IntSliceFlag{} _ = f.IsVisible() } @@ -99,13 +99,13 @@ func TestPathFlag_SatisfiesFlagInterface(t *testing.T) { } func TestPathFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.RequiredFlag = &cli.PathFlag{} + var f cli.Flag = &cli.PathFlag{} _ = f.IsRequired() } func TestPathFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.VisibleFlag = &cli.PathFlag{} + var f cli.Flag = &cli.PathFlag{} _ = f.IsVisible() } @@ -124,13 +124,13 @@ func TestStringSliceFlag_SatisfiesFlagInterface(t *testing.T) { } func TestStringSliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.RequiredFlag = &cli.StringSliceFlag{} + var f cli.Flag = &cli.StringSliceFlag{} _ = f.IsRequired() } func TestStringSliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.VisibleFlag = &cli.StringSliceFlag{} + var f cli.Flag = &cli.StringSliceFlag{} _ = f.IsVisible() } @@ -143,13 +143,13 @@ func TestTimestampFlag_SatisfiesFlagInterface(t *testing.T) { } func TestTimestampFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.RequiredFlag = &cli.TimestampFlag{} + var f cli.Flag = &cli.TimestampFlag{} _ = f.IsRequired() } func TestTimestampFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.VisibleFlag = &cli.TimestampFlag{} + var f cli.Flag = &cli.TimestampFlag{} _ = f.IsVisible() } @@ -168,13 +168,13 @@ func TestBoolFlag_SatisfiesFlagInterface(t *testing.T) { } func TestBoolFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.RequiredFlag = &cli.BoolFlag{} + var f cli.Flag = &cli.BoolFlag{} _ = f.IsRequired() } func TestBoolFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.VisibleFlag = &cli.BoolFlag{} + var f cli.Flag = &cli.BoolFlag{} _ = f.IsVisible() } @@ -193,13 +193,13 @@ func TestFloat64Flag_SatisfiesFlagInterface(t *testing.T) { } func TestFloat64Flag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.RequiredFlag = &cli.Float64Flag{} + var f cli.Flag = &cli.Float64Flag{} _ = f.IsRequired() } func TestFloat64Flag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.VisibleFlag = &cli.Float64Flag{} + var f cli.Flag = &cli.Float64Flag{} _ = f.IsVisible() } @@ -218,13 +218,13 @@ func TestIntFlag_SatisfiesFlagInterface(t *testing.T) { } func TestIntFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.RequiredFlag = &cli.IntFlag{} + var f cli.Flag = &cli.IntFlag{} _ = f.IsRequired() } func TestIntFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.VisibleFlag = &cli.IntFlag{} + var f cli.Flag = &cli.IntFlag{} _ = f.IsVisible() } @@ -243,13 +243,13 @@ func TestInt64Flag_SatisfiesFlagInterface(t *testing.T) { } func TestInt64Flag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.RequiredFlag = &cli.Int64Flag{} + var f cli.Flag = &cli.Int64Flag{} _ = f.IsRequired() } func TestInt64Flag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.VisibleFlag = &cli.Int64Flag{} + var f cli.Flag = &cli.Int64Flag{} _ = f.IsVisible() } @@ -268,13 +268,13 @@ func TestStringFlag_SatisfiesFlagInterface(t *testing.T) { } func TestStringFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.RequiredFlag = &cli.StringFlag{} + var f cli.Flag = &cli.StringFlag{} _ = f.IsRequired() } func TestStringFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.VisibleFlag = &cli.StringFlag{} + var f cli.Flag = &cli.StringFlag{} _ = f.IsVisible() } @@ -293,13 +293,13 @@ func TestDurationFlag_SatisfiesFlagInterface(t *testing.T) { } func TestDurationFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.RequiredFlag = &cli.DurationFlag{} + var f cli.Flag = &cli.DurationFlag{} _ = f.IsRequired() } func TestDurationFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.VisibleFlag = &cli.DurationFlag{} + var f cli.Flag = &cli.DurationFlag{} _ = f.IsVisible() } @@ -318,13 +318,13 @@ func TestUintFlag_SatisfiesFlagInterface(t *testing.T) { } func TestUintFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.RequiredFlag = &cli.UintFlag{} + var f cli.Flag = &cli.UintFlag{} _ = f.IsRequired() } func TestUintFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.VisibleFlag = &cli.UintFlag{} + var f cli.Flag = &cli.UintFlag{} _ = f.IsVisible() } @@ -343,13 +343,13 @@ func TestUint64Flag_SatisfiesFlagInterface(t *testing.T) { } func TestUint64Flag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.RequiredFlag = &cli.Uint64Flag{} + var f cli.Flag = &cli.Uint64Flag{} _ = f.IsRequired() } func TestUint64Flag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.VisibleFlag = &cli.Uint64Flag{} + var f cli.Flag = &cli.Uint64Flag{} _ = f.IsVisible() } From 069c6bfe4af0c2ecbfbb2916b104c4fbab931c47 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 08:12:38 -0400 Subject: [PATCH 004/136] Run gofmt --- flag.go | 15 +++++++++++++++ flag_test.go | 14 +++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/flag.go b/flag.go index a5434cbd98..a0b9834711 100644 --- a/flag.go +++ b/flag.go @@ -89,22 +89,37 @@ func (f FlagsByName) Swap(i, j int) { // this interface be implemented. type Flag interface { fmt.Stringer + // Apply Flag settings to the given flag set Apply(*flag.FlagSet) error + + // All possible names for this flag Names() []string + + // Whether the flag has been set or not IsSet() bool + + // whether the flag is a required flag or not IsRequired() bool + // IsVisible returns true if the flag is not hidden, otherwise false IsVisible() bool + + // Returns the category of the flag GetCategory() string + // GetUsage returns the usage string for the flag GetUsage() string + // GetEnvVars returns the env vars for this flag GetEnvVars() []string + // TakesValue returns true if the flag takes a value, otherwise false TakesValue() bool + // GetDefaultText returns the default text for this flag GetDefaultText() string + // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. GetValue() string diff --git a/flag_test.go b/flag_test.go index 9e50526218..deac525b49 100644 --- a/flag_test.go +++ b/flag_test.go @@ -2384,43 +2384,43 @@ func TestFlagDefaultValue(t *testing.T) { name: "stringSclice", flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")}, toParse: []string{"--flag", "parsed"}, - expect: `--flag value (default: "default1", "default2") (accepts multiple inputs)`, + expect: `--flag value (default: "default1", "default2") (accepts multiple inputs)`, }, { name: "float64Sclice", flag: &Float64SliceFlag{Name: "flag", Value: NewFloat64Slice(1.1, 2.2)}, toParse: []string{"--flag", "13.3"}, - expect: `--flag value (default: 1.1, 2.2) (accepts multiple inputs)`, + expect: `--flag value (default: 1.1, 2.2) (accepts multiple inputs)`, }, { name: "int64Sclice", flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, + expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, }, { name: "intSclice", flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, + expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, }, { name: "string", flag: &StringFlag{Name: "flag", Value: "default"}, toParse: []string{"--flag", "parsed"}, - expect: `--flag value (default: "default")`, + expect: `--flag value (default: "default")`, }, { name: "bool", flag: &BoolFlag{Name: "flag", Value: true}, toParse: []string{"--flag", "false"}, - expect: `--flag (default: true)`, + expect: `--flag (default: true)`, }, { name: "uint64", flag: &Uint64Flag{Name: "flag", Value: 1}, toParse: []string{"--flag", "13"}, - expect: `--flag value (default: 1)`, + expect: `--flag value (default: 1)`, }, } for i, v := range cases { From 7e80d2099c32ba3024a313e0890ddf85f0b2463f Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 08:17:26 -0400 Subject: [PATCH 005/136] Update godoc --- godoc-current.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/godoc-current.txt b/godoc-current.txt index 79b4245570..c35428ebc1 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -786,22 +786,37 @@ type ExitErrHandlerFunc func(cCtx *Context, err error) type Flag interface { fmt.Stringer + // Apply Flag settings to the given flag set Apply(*flag.FlagSet) error + + // All possible names for this flag Names() []string + + // Whether the flag has been set or not IsSet() bool + + // whether the flag is a required flag or not IsRequired() bool + // IsVisible returns true if the flag is not hidden, otherwise false IsVisible() bool + + // Returns the category of the flag GetCategory() string + // GetUsage returns the usage string for the flag GetUsage() string + // GetEnvVars returns the env vars for this flag GetEnvVars() []string + // TakesValue returns true if the flag takes a value, otherwise false TakesValue() bool + // GetDefaultText returns the default text for this flag GetDefaultText() string + // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. GetValue() string From 93a3190684f72014909da62d8b595088b92cbb6f Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 12 Sep 2022 07:51:51 -0400 Subject: [PATCH 006/136] Bump declared module and internal usage to `/v3/` --- README.md | 2 +- altsrc/flag.go | 2 +- altsrc/flag_generated.go | 2 +- altsrc/flag_test.go | 2 +- altsrc/input_source_context.go | 2 +- altsrc/json_command_test.go | 2 +- altsrc/json_source_context.go | 2 +- altsrc/map_input_source.go | 2 +- altsrc/toml_command_test.go | 2 +- altsrc/toml_file_loader.go | 2 +- altsrc/yaml_command_test.go | 2 +- altsrc/yaml_file_loader.go | 2 +- altsrc/yaml_file_loader_test.go | 4 ++-- go.mod | 4 ++-- go.sum | 4 ---- godoc-current.txt | 4 ++-- internal/build/build.go | 6 +++--- internal/example-cli/example-cli.go | 2 +- internal/genflags/cmd/genflags/main.go | 4 ++-- internal/genflags/package_test.go | 2 +- internal/genflags/spec_test.go | 2 +- zz_generated.flags_test.go | 2 +- 22 files changed, 27 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index eaed356307..3ac693bdb0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # cli -[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://pkg.go.dev/github.com/urfave/cli/v2) +[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://pkg.go.dev/github.com/urfave/cli/v3) [![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli) [![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli) [![codecov](https://codecov.io/gh/urfave/cli/branch/main/graph/badge.svg)](https://codecov.io/gh/urfave/cli) diff --git a/altsrc/flag.go b/altsrc/flag.go index db959493bc..cf85072461 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -6,7 +6,7 @@ import ( "strconv" "syscall" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // FlagInputSourceExtension is an extension interface of cli.Flag that diff --git a/altsrc/flag_generated.go b/altsrc/flag_generated.go index c9ecea82e9..8e5c9c09fe 100644 --- a/altsrc/flag_generated.go +++ b/altsrc/flag_generated.go @@ -5,7 +5,7 @@ package altsrc import ( "flag" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // BoolFlag is the flag type that wraps cli.BoolFlag to allow diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index e3725f7d1c..3c496e5208 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) type testApplyInputSource struct { diff --git a/altsrc/input_source_context.go b/altsrc/input_source_context.go index d743253050..e106f7b9bf 100644 --- a/altsrc/input_source_context.go +++ b/altsrc/input_source_context.go @@ -3,7 +3,7 @@ package altsrc import ( "time" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // InputSourceContext is an interface used to allow diff --git a/altsrc/json_command_test.go b/altsrc/json_command_test.go index bd0022b1cd..22c5958cd9 100644 --- a/altsrc/json_command_test.go +++ b/altsrc/json_command_test.go @@ -6,7 +6,7 @@ import ( "os" "testing" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) const ( diff --git a/altsrc/json_source_context.go b/altsrc/json_source_context.go index 168b6dade0..43eaef8aec 100644 --- a/altsrc/json_source_context.go +++ b/altsrc/json_source_context.go @@ -8,7 +8,7 @@ import ( "strings" "time" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // NewJSONSourceFromFlagFunc returns a func that takes a cli.Context diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index 07de00fcc6..acea2e6747 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -6,7 +6,7 @@ import ( "strings" "time" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // MapInputSource implements InputSourceContext to return diff --git a/altsrc/toml_command_test.go b/altsrc/toml_command_test.go index b9c79826fd..ba3b6141c6 100644 --- a/altsrc/toml_command_test.go +++ b/altsrc/toml_command_test.go @@ -6,7 +6,7 @@ import ( "os" "testing" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func TestCommandTomFileTest(t *testing.T) { diff --git a/altsrc/toml_file_loader.go b/altsrc/toml_file_loader.go index dfc9b7b1c8..0e614e3468 100644 --- a/altsrc/toml_file_loader.go +++ b/altsrc/toml_file_loader.go @@ -5,7 +5,7 @@ import ( "reflect" "github.com/BurntSushi/toml" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) type tomlMap struct { diff --git a/altsrc/yaml_command_test.go b/altsrc/yaml_command_test.go index 298e4d7073..0009332d66 100644 --- a/altsrc/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -6,7 +6,7 @@ import ( "os" "testing" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func TestCommandYamlFileTest(t *testing.T) { diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 315db1885f..66afe588a9 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -9,7 +9,7 @@ import ( "runtime" "strings" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" "gopkg.in/yaml.v3" ) diff --git a/altsrc/yaml_file_loader_test.go b/altsrc/yaml_file_loader_test.go index 814586b917..37455c6c8c 100644 --- a/altsrc/yaml_file_loader_test.go +++ b/altsrc/yaml_file_loader_test.go @@ -6,8 +6,8 @@ import ( "os" "time" - "github.com/urfave/cli/v2" - "github.com/urfave/cli/v2/altsrc" + "github.com/urfave/cli/v3" + "github.com/urfave/cli/v3/altsrc" ) func ExampleApp_Run_yamlFileLoaderDuration() { diff --git a/go.mod b/go.mod index 4e39308336..e144e41b6f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ -module github.com/urfave/cli/v2 +module github.com/urfave/cli/v3 -go 1.18 +go 1.19 require ( github.com/BurntSushi/toml v1.1.0 diff --git a/go.sum b/go.sum index 6beae99a6d..96058c71de 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ 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.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= -github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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,8 +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 h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= -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/godoc-current.txt b/godoc-current.txt index c35428ebc1..697c13034b 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -1,4 +1,4 @@ -package cli // import "github.com/urfave/cli/v2" +package cli // import "github.com/urfave/cli/v3" Package cli provides a minimal framework for creating and organizing command line Go applications. cli is designed to be easy to understand and write, @@ -1948,7 +1948,7 @@ type VisibleFlagCategory interface { } VisibleFlagCategory is a category containing flags. -package altsrc // import "github.com/urfave/cli/v2/altsrc" +package altsrc // import "github.com/urfave/cli/v3/altsrc" FUNCTIONS diff --git a/internal/build/build.go b/internal/build/build.go index d94dae7925..777a6308d6 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -13,7 +13,7 @@ import ( "path/filepath" "strings" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) const ( @@ -132,10 +132,10 @@ func TestActionFunc(c *cli.Context) error { tags := c.String("tags") for _, pkg := range c.StringSlice("packages") { - packageName := "github.com/urfave/cli/v2" + packageName := "github.com/urfave/cli/v3" if pkg != "cli" { - packageName = fmt.Sprintf("github.com/urfave/cli/v2/%s", pkg) + packageName = fmt.Sprintf("github.com/urfave/cli/v3/%s", pkg) } if err := runCmd( diff --git a/internal/example-cli/example-cli.go b/internal/example-cli/example-cli.go index 06cbbfffcb..3e6e4dae4d 100644 --- a/internal/example-cli/example-cli.go +++ b/internal/example-cli/example-cli.go @@ -3,7 +3,7 @@ package main import ( - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func main() { diff --git a/internal/genflags/cmd/genflags/main.go b/internal/genflags/cmd/genflags/main.go index 4212e60da1..54759c0f38 100644 --- a/internal/genflags/cmd/genflags/main.go +++ b/internal/genflags/cmd/genflags/main.go @@ -13,8 +13,8 @@ import ( "syscall" "text/template" - "github.com/urfave/cli/v2" - "github.com/urfave/cli/v2/internal/genflags" + "github.com/urfave/cli/v3" + "github.com/urfave/cli/v3/internal/genflags" "gopkg.in/yaml.v3" ) diff --git a/internal/genflags/package_test.go b/internal/genflags/package_test.go index 3920540778..81759f80d5 100644 --- a/internal/genflags/package_test.go +++ b/internal/genflags/package_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/urfave/cli/v2/internal/genflags" + "github.com/urfave/cli/v3/internal/genflags" ) func TestTypeName(t *testing.T) { diff --git a/internal/genflags/spec_test.go b/internal/genflags/spec_test.go index 25a9c8b36f..4a0cd4f88a 100644 --- a/internal/genflags/spec_test.go +++ b/internal/genflags/spec_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/urfave/cli/v2/internal/genflags" + "github.com/urfave/cli/v3/internal/genflags" ) func TestSpec_SortedFlagTypes(t *testing.T) { diff --git a/zz_generated.flags_test.go b/zz_generated.flags_test.go index 16519e62aa..a0769a2024 100644 --- a/zz_generated.flags_test.go +++ b/zz_generated.flags_test.go @@ -6,7 +6,7 @@ import ( "fmt" "testing" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func TestFloat64SliceFlag_SatisfiesFlagInterface(t *testing.T) { From 22ff9a34c9d1f4aa2a90ac974e20955a713afd8b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 29 Sep 2022 07:59:21 -0400 Subject: [PATCH 007/136] Soft revert go.mod version bump --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e144e41b6f..62910e1dae 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/urfave/cli/v3 -go 1.19 +go 1.18 require ( github.com/BurntSushi/toml v1.1.0 From 40d886b0e61acae65989c2f3c0246d5cc939f6a9 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 30 Sep 2022 08:13:34 -0400 Subject: [PATCH 008/136] Add stub for v3 docs + point gfmrun there instead --- .github/workflows/cli.yml | 2 +- docs/v3/index.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 docs/v3/index.md diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 1e4a51f221..9ea3cdfe7a 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -85,7 +85,7 @@ jobs: chmod +x "${GITHUB_WORKSPACE}/.local/bin/gfmrun" - name: gfmrun - run: go run internal/build/build.go gfmrun docs/v2/manual.md + run: go run internal/build/build.go gfmrun docs/v3/index.md - name: diff check run: | diff --git a/docs/v3/index.md b/docs/v3/index.md new file mode 100644 index 0000000000..9632522e67 --- /dev/null +++ b/docs/v3/index.md @@ -0,0 +1 @@ +# v3 guide From dd9cd61befef88718336a379d1c0f555606f5f5d Mon Sep 17 00:00:00 2001 From: miyado <10195648+hmiyado@users.noreply.github.com> Date: Mon, 18 Jul 2022 16:33:53 +0900 Subject: [PATCH 009/136] Fix for TimestampFlag.GetValue to return empty string without value --- flag_timestamp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flag_timestamp.go b/flag_timestamp.go index 8759b85290..7b525b062d 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -75,7 +75,7 @@ func (t *Timestamp) Get() interface{} { // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *TimestampFlag) GetValue() string { - if f.Value != nil { + if f.Value != nil && f.Value.timestamp != nil { return f.Value.timestamp.String() } return "" From 67f592aadb5cbb6979c5e82a9be4d2b6131ff0ff Mon Sep 17 00:00:00 2001 From: Dokiy Date: Fri, 29 Jul 2022 15:26:06 +0800 Subject: [PATCH 010/136] Fix HideHelp --- app.go | 8 +++++-- app_test.go | 58 ++++++++++++++++++++++++++++++++++++--------- command.go | 8 +++++-- suggestions_test.go | 4 ++-- 4 files changed, 61 insertions(+), 17 deletions(-) diff --git a/app.go b/app.go index 834873aeb3..e9493d3877 100644 --- a/app.go +++ b/app.go @@ -273,7 +273,9 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { cCtx := NewContext(a, set, &Context{Context: ctx}) if nerr != nil { _, _ = fmt.Fprintln(a.Writer, nerr) - _ = ShowAppHelp(cCtx) + if !a.HideHelp { + _ = ShowAppHelp(cCtx) + } return nerr } cCtx.shellComplete = shellComplete @@ -294,7 +296,9 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { fmt.Fprintf(a.Writer, suggestion) } } - _ = ShowAppHelp(cCtx) + if !a.HideHelp { + _ = ShowAppHelp(cCtx) + } return err } diff --git a/app_test.go b/app_test.go index da3981c4fb..e7c759eea7 100644 --- a/app_test.go +++ b/app_test.go @@ -1873,31 +1873,67 @@ func TestApp_Run_CommandSubcommandHelpName(t *testing.T) { } func TestApp_Run_Help(t *testing.T) { - var helpArguments = [][]string{{"boom", "--help"}, {"boom", "-h"}, {"boom", "help"}} - - for _, args := range helpArguments { - t.Run(fmt.Sprintf("checking with arguments %v", args), func(t *testing.T) { + var tests = []struct { + helpArguments []string + hideHelp bool + wantContains string + wantErr error + }{ + { + helpArguments: []string{"boom", "--help"}, + hideHelp: false, + wantContains: "boom - make an explosive entrance", + }, + { + helpArguments: []string{"boom", "-h"}, + hideHelp: false, + wantContains: "boom - make an explosive entrance", + }, + { + helpArguments: []string{"boom", "help"}, + hideHelp: false, + wantContains: "boom - make an explosive entrance", + }, + { + helpArguments: []string{"boom", "--help"}, + hideHelp: true, + wantErr: fmt.Errorf("flag: help requested"), + }, + { + helpArguments: []string{"boom", "-h"}, + hideHelp: true, + wantErr: fmt.Errorf("flag: help requested"), + }, + { + helpArguments: []string{"boom", "help"}, + hideHelp: true, + wantContains: "boom I say!", + }, + } + for _, tt := range tests { + t.Run(fmt.Sprintf("checking with arguments %v", tt.helpArguments), func(t *testing.T) { buf := new(bytes.Buffer) app := &App{ - Name: "boom", - Usage: "make an explosive entrance", - Writer: buf, + Name: "boom", + Usage: "make an explosive entrance", + Writer: buf, + HideHelp: tt.hideHelp, Action: func(c *Context) error { buf.WriteString("boom I say!") return nil }, } - err := app.Run(args) - if err != nil { - t.Error(err) + err := app.Run(tt.helpArguments) + if err != nil && err.Error() != tt.wantErr.Error() { + t.Errorf("want err: %s, did note %s\n", tt.wantErr, err) } output := buf.String() - if !strings.Contains(output, "boom - make an explosive entrance") { + if !strings.Contains(output, tt.wantContains) { t.Errorf("want help to contain %q, did not: \n%q", "boom - make an explosive entrance", output) } }) diff --git a/command.go b/command.go index 2cafd8e0ec..13b79de46d 100644 --- a/command.go +++ b/command.go @@ -125,7 +125,9 @@ func (c *Command) Run(ctx *Context) (err error) { fmt.Fprintf(cCtx.App.Writer, suggestion) } } - _ = ShowCommandHelp(cCtx, c.Name) + if !c.HideHelp { + _ = ShowCommandHelp(cCtx, c.Name) + } return err } @@ -135,7 +137,9 @@ func (c *Command) Run(ctx *Context) (err error) { cerr := cCtx.checkRequiredFlags(c.Flags) if cerr != nil { - _ = ShowCommandHelp(cCtx, c.Name) + if !c.HideHelp { + _ = ShowCommandHelp(cCtx, c.Name) + } return cerr } diff --git a/suggestions_test.go b/suggestions_test.go index 4c0984e4bd..909e29cf07 100644 --- a/suggestions_test.go +++ b/suggestions_test.go @@ -124,7 +124,7 @@ func ExampleApp_Suggest() { app := &App{ Name: "greet", Suggest: true, - HideHelp: true, + HideHelp: false, HideHelpCommand: true, CustomAppHelpTemplate: "(this space intentionally left blank)\n", Flags: []Flag{ @@ -149,7 +149,6 @@ func ExampleApp_Suggest_command() { app := &App{ Name: "greet", Suggest: true, - HideHelp: true, HideHelpCommand: true, CustomAppHelpTemplate: "(this space intentionally left blank)\n", Flags: []Flag{ @@ -162,6 +161,7 @@ func ExampleApp_Suggest_command() { Commands: []*Command{ { Name: "neighbors", + HideHelp: false, CustomHelpTemplate: "(this space intentionally left blank)\n", Flags: []Flag{ &BoolFlag{Name: "smiling"}, From 02eb3929162425186ac25dd21253755b8935c823 Mon Sep 17 00:00:00 2001 From: Dokiy Date: Thu, 28 Jul 2022 19:00:48 +0800 Subject: [PATCH 011/136] Fix After not run --- app.go | 24 ++++++++++++------------ app_test.go | 21 +++++++++++++++++++++ 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/app.go b/app.go index e9493d3877..6b743bf7eb 100644 --- a/app.go +++ b/app.go @@ -302,6 +302,18 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { return err } + if a.After != nil { + defer func() { + if afterErr := a.After(cCtx); afterErr != nil { + if err != nil { + err = newMultiError(err, afterErr) + } else { + err = afterErr + } + } + }() + } + if !a.HideHelp && checkHelp(cCtx) { _ = ShowAppHelp(cCtx) return nil @@ -318,18 +330,6 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { return cerr } - if a.After != nil { - defer func() { - if afterErr := a.After(cCtx); afterErr != nil { - if err != nil { - err = newMultiError(err, afterErr) - } else { - err = afterErr - } - } - }() - } - if a.Before != nil { beforeErr := a.Before(cCtx) if beforeErr != nil { diff --git a/app_test.go b/app_test.go index e7c759eea7..cbc1c8b995 100644 --- a/app_test.go +++ b/app_test.go @@ -1309,6 +1309,27 @@ func TestApp_AfterFunc(t *testing.T) { if counts.SubCommand != 1 { t.Errorf("Subcommand not executed when expected") } + + /* + reset + */ + counts = &opCounts{} + + // run with none args + err = app.Run([]string{"command"}) + + // should be the same error produced by the Before func + if err != nil { + t.Fatalf("Run error: %s", err) + } + + if counts.After != 1 { + t.Errorf("After() not executed when expected") + } + + if counts.SubCommand != 0 { + t.Errorf("Subcommand not executed when expected") + } } func TestAppNoHelpFlag(t *testing.T) { From bc99b5865cc9ae1fb926171c11f05a169a5321d3 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sun, 14 Aug 2022 19:09:47 -0400 Subject: [PATCH 012/136] issue_62: Make slice options more posix like --- flag.go | 55 ++++--------------------------------------- flag_float64_slice.go | 14 ++++++++++- flag_int64_slice.go | 13 +++++++++- flag_int_slice.go | 13 +++++++++- flag_string_slice.go | 16 ++++++++++++- flag_test.go | 42 ++++++++++++++++----------------- 6 files changed, 77 insertions(+), 76 deletions(-) diff --git a/flag.go b/flag.go index a0b9834711..9f4d1ab8ae 100644 --- a/flag.go +++ b/flag.go @@ -7,7 +7,6 @@ import ( "io/ioutil" "regexp" "runtime" - "strconv" "strings" "syscall" "time" @@ -297,53 +296,6 @@ func stringifyFlag(f Flag) string { fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault)) } -func stringifyIntSliceFlag(f *IntSliceFlag) string { - var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, strconv.Itoa(i)) - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - -func stringifyInt64SliceFlag(f *Int64SliceFlag) string { - var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, strconv.FormatInt(i, 10)) - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - -func stringifyFloat64SliceFlag(f *Float64SliceFlag) string { - var defaultVals []string - - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, strings.TrimRight(strings.TrimRight(fmt.Sprintf("%f", i), "0"), ".")) - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - -func stringifyStringSliceFlag(f *StringSliceFlag) string { - var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, s := range f.Value.Value() { - if len(s) > 0 { - defaultVals = append(defaultVals, strconv.Quote(s)) - } - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - func stringifySliceFlag(usage string, names, defaultVals []string) string { placeholder, usage := unquoteUsage(usage) if placeholder == "" { @@ -356,11 +308,12 @@ func stringifySliceFlag(usage string, names, defaultVals []string) string { } usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) - multiInputString := "(accepts multiple inputs)" + /*multiInputString := "(accepts multiple inputs)" if usageWithDefault != "" { multiInputString = "\t" + multiInputString - } - return fmt.Sprintf("%s\t%s%s", prefixedNames(names, placeholder), usageWithDefault, multiInputString) + }*/ + pn := prefixedNames(names, placeholder) + return fmt.Sprintf("%s [ %s ]\t%s%s", pn, pn, usageWithDefault, "") } func hasFlag(flags []Flag, fl Flag) bool { diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 56745c1b59..833e65ce33 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -83,7 +83,7 @@ func (f *Float64Slice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *Float64SliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), stringifyFloat64SliceFlag(f)) + return withEnvHint(f.GetEnvVars(), f.stringify()) } // GetValue returns the flags value as string representation and an empty @@ -141,6 +141,18 @@ func (f *Float64SliceFlag) Get(ctx *Context) []float64 { return ctx.Float64Slice(f.Name) } +func (f *Float64SliceFlag) stringify() string { + var defaultVals []string + + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strings.TrimRight(strings.TrimRight(fmt.Sprintf("%f", i), "0"), ".")) + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + // 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_int64_slice.go b/flag_int64_slice.go index 83167092b0..d848b45c01 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -84,7 +84,7 @@ func (i *Int64Slice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *Int64SliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), stringifyInt64SliceFlag(f)) + return withEnvHint(f.GetEnvVars(), f.stringify()) } // GetValue returns the flags value as string representation and an empty @@ -140,6 +140,17 @@ func (f *Int64SliceFlag) Get(ctx *Context) []int64 { return ctx.Int64Slice(f.Name) } +func (f *Int64SliceFlag) stringify() string { + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.FormatInt(i, 10)) + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + // 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 7865dab0e9..96fb7a3e91 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -95,7 +95,7 @@ func (i *IntSlice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *IntSliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), stringifyIntSliceFlag(f)) + return withEnvHint(f.GetEnvVars(), f.stringify()) } // GetValue returns the flags value as string representation and an empty @@ -151,6 +151,17 @@ func (f *IntSliceFlag) Get(ctx *Context) []int { return ctx.IntSlice(f.Name) } +func (f *IntSliceFlag) stringify() string { + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.Itoa(i)) + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + // IntSlice looks up the value of a local IntSliceFlag, returns // nil if not found func (cCtx *Context) IntSlice(name string) []int { diff --git a/flag_string_slice.go b/flag_string_slice.go index f50323b445..c5c9b7829e 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -4,6 +4,7 @@ import ( "encoding/json" "flag" "fmt" + "strconv" "strings" ) @@ -73,7 +74,7 @@ func (s *StringSlice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *StringSliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), stringifyStringSliceFlag(f)) + return withEnvHint(f.GetEnvVars(), f.stringify()) } // GetValue returns the flags value as string representation and an empty @@ -129,6 +130,19 @@ func (f *StringSliceFlag) Get(ctx *Context) []string { return ctx.StringSlice(f.Name) } +func (f *StringSliceFlag) stringify() string { + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, s := range f.Value.Value() { + if len(s) > 0 { + defaultVals = append(defaultVals, strconv.Quote(s)) + } + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + // 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_test.go b/flag_test.go index deac525b49..faec219e6a 100644 --- a/flag_test.go +++ b/flag_test.go @@ -544,11 +544,11 @@ var stringSliceFlagTests = []struct { value *StringSlice expected string }{ - {"foo", nil, NewStringSlice(""), "--foo value\t(accepts multiple inputs)"}, - {"f", nil, NewStringSlice(""), "-f value\t(accepts multiple inputs)"}, - {"f", nil, NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")\t(accepts multiple inputs)"}, - {"test", nil, NewStringSlice("Something"), "--test value\t(default: \"Something\")\t(accepts multiple inputs)"}, - {"dee", []string{"d"}, NewStringSlice("Inka", "Dinka", "dooo"), "--dee value, -d value\t(default: \"Inka\", \"Dinka\", \"dooo\")\t(accepts multiple inputs)"}, + {"foo", nil, NewStringSlice(""), "--foo value [ --foo value ]\t"}, + {"f", nil, NewStringSlice(""), "-f value [ -f value ]\t"}, + {"f", nil, NewStringSlice("Lipstick"), "-f value [ -f value ]\t(default: \"Lipstick\")"}, + {"test", nil, NewStringSlice("Something"), "--test value [ --test value ]\t(default: \"Something\")"}, + {"dee", []string{"d"}, NewStringSlice("Inka", "Dinka", "dooo"), "--dee value, -d value [ --dee value, -d value ]\t(default: \"Inka\", \"Dinka\", \"dooo\")"}, } func TestStringSliceFlagHelpOutput(t *testing.T) { @@ -897,9 +897,9 @@ var intSliceFlagTests = []struct { value *IntSlice expected string }{ - {"heads", nil, NewIntSlice(), "--heads value\t(accepts multiple inputs)"}, - {"H", nil, NewIntSlice(), "-H value\t(accepts multiple inputs)"}, - {"H", []string{"heads"}, NewIntSlice(9, 3), "-H value, --heads value\t(default: 9, 3)\t(accepts multiple inputs)"}, + {"heads", nil, NewIntSlice(), "--heads value [ --heads value ]\t"}, + {"H", nil, NewIntSlice(), "-H value [ -H value ]\t"}, + {"H", []string{"heads"}, NewIntSlice(9, 3), "-H value, --heads value [ -H value, --heads value ]\t(default: 9, 3)"}, } func TestIntSliceFlagHelpOutput(t *testing.T) { @@ -994,10 +994,10 @@ var int64SliceFlagTests = []struct { value *Int64Slice expected string }{ - {"heads", nil, NewInt64Slice(), "--heads value\t(accepts multiple inputs)"}, - {"H", nil, NewInt64Slice(), "-H value\t(accepts multiple inputs)"}, + {"heads", nil, NewInt64Slice(), "--heads value [ --heads value ]\t"}, + {"H", nil, NewInt64Slice(), "-H value [ -H value ]\t"}, {"heads", []string{"H"}, NewInt64Slice(int64(2), int64(17179869184)), - "--heads value, -H value\t(default: 2, 17179869184)\t(accepts multiple inputs)"}, + "--heads value, -H value [ --heads value, -H value ]\t(default: 2, 17179869184)"}, } func TestInt64SliceFlagHelpOutput(t *testing.T) { @@ -1155,10 +1155,10 @@ var float64SliceFlagTests = []struct { value *Float64Slice expected string }{ - {"heads", nil, NewFloat64Slice(), "--heads value\t(accepts multiple inputs)"}, - {"H", nil, NewFloat64Slice(), "-H value\t(accepts multiple inputs)"}, + {"heads", nil, NewFloat64Slice(), "--heads value [ --heads value ]\t"}, + {"H", nil, NewFloat64Slice(), "-H value [ -H value ]\t"}, {"heads", []string{"H"}, NewFloat64Slice(0.1234, -10.5), - "--heads value, -H value\t(default: 0.1234, -10.5)\t(accepts multiple inputs)"}, + "--heads value, -H value [ --heads value, -H value ]\t(default: 0.1234, -10.5)"}, } func TestFloat64SliceFlagHelpOutput(t *testing.T) { @@ -2384,43 +2384,43 @@ func TestFlagDefaultValue(t *testing.T) { name: "stringSclice", flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")}, toParse: []string{"--flag", "parsed"}, - expect: `--flag value (default: "default1", "default2") (accepts multiple inputs)`, + expect: `--flag value [ --flag value ] (default: "default1", "default2")`, }, { name: "float64Sclice", flag: &Float64SliceFlag{Name: "flag", Value: NewFloat64Slice(1.1, 2.2)}, toParse: []string{"--flag", "13.3"}, - expect: `--flag value (default: 1.1, 2.2) (accepts multiple inputs)`, + expect: `--flag value [ --flag value ] (default: 1.1, 2.2)`, }, { name: "int64Sclice", flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "intSclice", flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "string", flag: &StringFlag{Name: "flag", Value: "default"}, toParse: []string{"--flag", "parsed"}, - expect: `--flag value (default: "default")`, + expect: `--flag value (default: "default")`, }, { name: "bool", flag: &BoolFlag{Name: "flag", Value: true}, toParse: []string{"--flag", "false"}, - expect: `--flag (default: true)`, + expect: `--flag (default: true)`, }, { name: "uint64", flag: &Uint64Flag{Name: "flag", Value: 1}, toParse: []string{"--flag", "13"}, - expect: `--flag value (default: 1)`, + expect: `--flag value (default: 1)`, }, } for i, v := range cases { From 45a1375078a57c3faad152809d0ca0e677bfbfea Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sun, 14 Aug 2022 20:32:10 -0400 Subject: [PATCH 013/136] Changes from code review --- flag.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/flag.go b/flag.go index 9f4d1ab8ae..4f0871d332 100644 --- a/flag.go +++ b/flag.go @@ -308,12 +308,8 @@ func stringifySliceFlag(usage string, names, defaultVals []string) string { } usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) - /*multiInputString := "(accepts multiple inputs)" - if usageWithDefault != "" { - multiInputString = "\t" + multiInputString - }*/ pn := prefixedNames(names, placeholder) - return fmt.Sprintf("%s [ %s ]\t%s%s", pn, pn, usageWithDefault, "") + return fmt.Sprintf("%s [ %s ]\t%s", pn, pn, usageWithDefault) } func hasFlag(flags []Flag, fl Flag) bool { From 2ca91434a85af894c974fb9b4051e7821988618b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 14 Aug 2022 10:02:07 -0400 Subject: [PATCH 014/136] Move genflags tool to cmd/ and pin to previous release to alleviate problems caused by the circular dependency of using the same code as a library that is potentially being generated to adhere to a different API. --- .github/workflows/cli.yml | 3 + .gitignore | 1 + cli.go | 32 ++-- cmd/urfave-cli-genflags/Makefile | 21 +++ cmd/urfave-cli-genflags/README.md | 15 ++ .../urfave-cli-genflags}/generated.gotmpl | 0 .../generated_test.gotmpl | 0 cmd/urfave-cli-genflags/go.mod | 15 ++ cmd/urfave-cli-genflags/go.sum | 14 ++ .../urfave-cli-genflags}/main.go | 137 +++++++++++++++++- .../urfave-cli-genflags/main_test.go | 56 +++++-- docs/CONTRIBUTING.md | 2 +- flag-spec.yaml | 4 +- internal/build/build.go | 2 +- internal/genflags/package.go | 34 ----- internal/genflags/package_test.go | 41 ------ internal/genflags/spec.go | 105 -------------- 17 files changed, 268 insertions(+), 214 deletions(-) create mode 100644 cmd/urfave-cli-genflags/Makefile create mode 100644 cmd/urfave-cli-genflags/README.md rename {internal/genflags => cmd/urfave-cli-genflags}/generated.gotmpl (100%) rename {internal/genflags => cmd/urfave-cli-genflags}/generated_test.gotmpl (100%) create mode 100644 cmd/urfave-cli-genflags/go.mod create mode 100644 cmd/urfave-cli-genflags/go.sum rename {internal/genflags/cmd/genflags => cmd/urfave-cli-genflags}/main.go (53%) rename internal/genflags/spec_test.go => cmd/urfave-cli-genflags/main_test.go (52%) delete mode 100644 internal/genflags/package.go delete mode 100644 internal/genflags/package_test.go delete mode 100644 internal/genflags/spec.go diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 1e4a51f221..0a3e7c3685 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -46,6 +46,9 @@ jobs: - name: test run: go run internal/build/build.go test + - name: test urfave-cli-genflags + run: make -C cmd/urfave-cli-genflags + - name: check-binary-size run: go run internal/build/build.go check-binary-size diff --git a/.gitignore b/.gitignore index c04fcc5389..4296f6c7a1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,6 @@ internal/*/built-example coverage.txt /.local/ /site/ +/cmd/urfave-cli-genflags/urfave-cli-genflags *.exe diff --git a/cli.go b/cli.go index 2a11c5ad49..c0c5d9a1b6 100644 --- a/cli.go +++ b/cli.go @@ -1,23 +1,25 @@ // Package cli provides a minimal framework for creating and organizing command line // Go applications. cli is designed to be easy to understand and write, the most simple // cli application can be written as follows: -// func main() { -// (&cli.App{}).Run(os.Args) -// } +// +// func main() { +// (&cli.App{}).Run(os.Args) +// } // // Of course this application does not do much, so let's make this an actual application: -// func main() { -// app := &cli.App{ -// Name: "greet", -// Usage: "say a greeting", -// Action: func(c *cli.Context) error { -// fmt.Println("Greetings") -// return nil -// }, -// } // -// app.Run(os.Args) -// } +// func main() { +// app := &cli.App{ +// Name: "greet", +// Usage: "say a greeting", +// Action: func(c *cli.Context) error { +// fmt.Println("Greetings") +// return nil +// }, +// } +// +// app.Run(os.Args) +// } package cli -//go:generate go run internal/genflags/cmd/genflags/main.go +//go:generate go run cmd/urfave-cli-genflags/main.go diff --git a/cmd/urfave-cli-genflags/Makefile b/cmd/urfave-cli-genflags/Makefile new file mode 100644 index 0000000000..3b11415d62 --- /dev/null +++ b/cmd/urfave-cli-genflags/Makefile @@ -0,0 +1,21 @@ +GOTEST_FLAGS ?= -v --coverprofile main.coverprofile --covermode count --cover github.com/urfave/cli/v2/cmd/urfave-cli-genflags +GOBUILD_FLAGS ?= -x + +.PHONY: all +all: test build smoke-test + +.PHONY: test +test: + go test $(GOTEST_FLAGS) ./... + +.PHONY: build +build: + go build $(GOBUILD_FLAGS) ./... + +.PHONY: smoke-test +smoke-test: build + ./urfave-cli-genflags --help + +.PHONY: show-cover +show-cover: + go tool cover -func main.coverprofile diff --git a/cmd/urfave-cli-genflags/README.md b/cmd/urfave-cli-genflags/README.md new file mode 100644 index 0000000000..9424234e77 --- /dev/null +++ b/cmd/urfave-cli-genflags/README.md @@ -0,0 +1,15 @@ +# urfave-cli-genflags + +This is a tool that is used internally by [urfave/cli] to generate +flag types and methods from a YAML input. It intentionally pins +usage of `github.com/urfave/cli/v2` to a *release* rather than +using the adjacent code so that changes don't result in *this* tool +refusing to compile. It's almost like dogfooding? + +## support warning + +This tool is maintained as a sub-project and is not covered by the +API and backward compatibility guaranteed by releases of +[urfave/cli]. + +[urfave/cli]: https://github.com/urfave/cli diff --git a/internal/genflags/generated.gotmpl b/cmd/urfave-cli-genflags/generated.gotmpl similarity index 100% rename from internal/genflags/generated.gotmpl rename to cmd/urfave-cli-genflags/generated.gotmpl diff --git a/internal/genflags/generated_test.gotmpl b/cmd/urfave-cli-genflags/generated_test.gotmpl similarity index 100% rename from internal/genflags/generated_test.gotmpl rename to cmd/urfave-cli-genflags/generated_test.gotmpl diff --git a/cmd/urfave-cli-genflags/go.mod b/cmd/urfave-cli-genflags/go.mod new file mode 100644 index 0000000000..af40aaa1bf --- /dev/null +++ b/cmd/urfave-cli-genflags/go.mod @@ -0,0 +1,15 @@ +module github.com/urfave/cli/v2/cmd/urfave-cli-genflags + +go 1.18 + +require ( + github.com/urfave/cli/v2 v2.11.2 + golang.org/x/text v0.3.7 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect +) diff --git a/cmd/urfave-cli-genflags/go.sum b/cmd/urfave-cli-genflags/go.sum new file mode 100644 index 0000000000..e59916dc6f --- /dev/null +++ b/cmd/urfave-cli-genflags/go.sum @@ -0,0 +1,14 @@ +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= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/urfave/cli/v2 v2.11.2 h1:FVfNg4m3vbjbBpLYxW//WjxUoHvJ9TlppXcqY9Q9ZfA= +github.com/urfave/cli/v2 v2.11.2/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +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= +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= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/genflags/cmd/genflags/main.go b/cmd/urfave-cli-genflags/main.go similarity index 53% rename from internal/genflags/cmd/genflags/main.go rename to cmd/urfave-cli-genflags/main.go index 4212e60da1..835216a98a 100644 --- a/internal/genflags/cmd/genflags/main.go +++ b/cmd/urfave-cli-genflags/main.go @@ -9,12 +9,14 @@ import ( "os/exec" "os/signal" "path/filepath" + "sort" "strings" "syscall" "text/template" "github.com/urfave/cli/v2" - "github.com/urfave/cli/v2/internal/genflags" + "golang.org/x/text/cases" + "golang.org/x/text/language" "gopkg.in/yaml.v3" ) @@ -22,6 +24,16 @@ const ( defaultPackageName = "cli" ) +var ( + //go:embed generated.gotmpl + TemplateString string + + //go:embed generated_test.gotmpl + TestTemplateString string + + titler = cases.Title(language.Und, cases.NoLower) +) + func sh(ctx context.Context, exe string, args ...string) (string, error) { cmd := exec.CommandContext(ctx, exe, args...) cmd.Stderr = os.Stderr @@ -92,7 +104,7 @@ func runGenFlags(cCtx *cli.Context) error { return err } - spec := &genflags.Spec{} + spec := &Spec{} if err := yaml.Unmarshal(specBytes, spec); err != nil { return err } @@ -123,12 +135,12 @@ func runGenFlags(cCtx *cli.Context) error { spec.UrfaveCLITestNamespace = "cli." } - genTmpl, err := template.New("gen").Parse(genflags.TemplateString) + genTmpl, err := template.New("gen").Parse(TemplateString) if err != nil { return err } - genTestTmpl, err := template.New("gen_test").Parse(genflags.TestTemplateString) + genTestTmpl, err := template.New("gen_test").Parse(TestTemplateString) if err != nil { return err } @@ -161,3 +173,120 @@ func runGenFlags(cCtx *cli.Context) error { return nil } + +func TypeName(goType string, fc *FlagTypeConfig) string { + if fc != nil && strings.TrimSpace(fc.TypeName) != "" { + return strings.TrimSpace(fc.TypeName) + } + + dotSplit := strings.Split(goType, ".") + goType = dotSplit[len(dotSplit)-1] + + if strings.HasPrefix(goType, "[]") { + return titler.String(strings.TrimPrefix(goType, "[]")) + "SliceFlag" + } + + return titler.String(goType) + "Flag" +} + +type Spec struct { + FlagTypes map[string]*FlagTypeConfig `yaml:"flag_types"` + PackageName string `yaml:"package_name"` + TestPackageName string `yaml:"test_package_name"` + UrfaveCLINamespace string `yaml:"urfave_cli_namespace"` + UrfaveCLITestNamespace string `yaml:"urfave_cli_test_namespace"` +} + +func (gfs *Spec) SortedFlagTypes() []*FlagType { + typeNames := []string{} + + for name := range gfs.FlagTypes { + if strings.HasPrefix(name, "[]") { + name = strings.TrimPrefix(name, "[]") + "Slice" + } + + typeNames = append(typeNames, name) + } + + sort.Strings(typeNames) + + ret := make([]*FlagType, len(typeNames)) + + for i, typeName := range typeNames { + ret[i] = &FlagType{ + GoType: typeName, + Config: gfs.FlagTypes[typeName], + } + } + + return ret +} + +type FlagTypeConfig struct { + SkipInterfaces []string `yaml:"skip_interfaces"` + StructFields []*FlagStructField `yaml:"struct_fields"` + TypeName string `yaml:"type_name"` + ValuePointer bool `yaml:"value_pointer"` +} + +type FlagStructField struct { + Name string + Type string +} + +type FlagType struct { + GoType string + Config *FlagTypeConfig +} + +func (ft *FlagType) StructFields() []*FlagStructField { + if ft.Config == nil || ft.Config.StructFields == nil { + return []*FlagStructField{} + } + + return ft.Config.StructFields +} + +func (ft *FlagType) ValuePointer() bool { + if ft.Config == nil { + return false + } + + return ft.Config.ValuePointer +} + +func (ft *FlagType) TypeName() string { + return TypeName(ft.GoType, ft.Config) +} + +func (ft *FlagType) GenerateFmtStringerInterface() bool { + return ft.skipInterfaceNamed("fmt.Stringer") +} + +func (ft *FlagType) GenerateFlagInterface() bool { + return ft.skipInterfaceNamed("Flag") +} + +func (ft *FlagType) GenerateRequiredFlagInterface() bool { + return ft.skipInterfaceNamed("RequiredFlag") +} + +func (ft *FlagType) GenerateVisibleFlagInterface() bool { + return ft.skipInterfaceNamed("VisibleFlag") +} + +func (ft *FlagType) skipInterfaceNamed(name string) bool { + if ft.Config == nil { + return true + } + + lowName := strings.ToLower(name) + + for _, interfaceName := range ft.Config.SkipInterfaces { + if strings.ToLower(interfaceName) == lowName { + return false + } + } + + return true +} diff --git a/internal/genflags/spec_test.go b/cmd/urfave-cli-genflags/main_test.go similarity index 52% rename from internal/genflags/spec_test.go rename to cmd/urfave-cli-genflags/main_test.go index 25a9c8b36f..b5c9fee8ca 100644 --- a/internal/genflags/spec_test.go +++ b/cmd/urfave-cli-genflags/main_test.go @@ -1,29 +1,63 @@ -package genflags_test +package main_test import ( + "fmt" "reflect" "testing" - "github.com/urfave/cli/v2/internal/genflags" + main "github.com/urfave/cli/v2/cmd/urfave-cli-genflags" ) +func TestTypeName(t *testing.T) { + for _, tc := range []struct { + gt string + fc *main.FlagTypeConfig + expected string + }{ + {gt: "int", fc: nil, expected: "IntFlag"}, + {gt: "int", fc: &main.FlagTypeConfig{}, expected: "IntFlag"}, + {gt: "int", fc: &main.FlagTypeConfig{TypeName: "VeryIntyFlag"}, expected: "VeryIntyFlag"}, + {gt: "[]bool", fc: nil, expected: "BoolSliceFlag"}, + {gt: "[]bool", fc: &main.FlagTypeConfig{}, expected: "BoolSliceFlag"}, + {gt: "[]bool", fc: &main.FlagTypeConfig{TypeName: "ManyTruthsFlag"}, expected: "ManyTruthsFlag"}, + {gt: "time.Rumination", fc: nil, expected: "RuminationFlag"}, + {gt: "time.Rumination", fc: &main.FlagTypeConfig{}, expected: "RuminationFlag"}, + {gt: "time.Rumination", fc: &main.FlagTypeConfig{TypeName: "PonderFlag"}, expected: "PonderFlag"}, + } { + t.Run( + fmt.Sprintf("type=%s,cfg=%v", tc.gt, func() string { + if tc.fc != nil { + return tc.fc.TypeName + } + return "nil" + }()), + func(ct *testing.T) { + actual := main.TypeName(tc.gt, tc.fc) + if tc.expected != actual { + ct.Errorf("expected %q, got %q", tc.expected, actual) + } + }, + ) + } +} + func TestSpec_SortedFlagTypes(t *testing.T) { - spec := &genflags.Spec{ - FlagTypes: map[string]*genflags.FlagTypeConfig{ - "nerf": &genflags.FlagTypeConfig{}, + spec := &main.Spec{ + FlagTypes: map[string]*main.FlagTypeConfig{ + "nerf": &main.FlagTypeConfig{}, "gerf": nil, }, } actual := spec.SortedFlagTypes() - expected := []*genflags.FlagType{ + expected := []*main.FlagType{ { GoType: "gerf", Config: nil, }, { GoType: "nerf", - Config: &genflags.FlagTypeConfig{}, + Config: &main.FlagTypeConfig{}, }, } if !reflect.DeepEqual(expected, actual) { @@ -31,12 +65,12 @@ func TestSpec_SortedFlagTypes(t *testing.T) { } } -func genFlagType() *genflags.FlagType { - return &genflags.FlagType{ +func genFlagType() *main.FlagType { + return &main.FlagType{ GoType: "blerf", - Config: &genflags.FlagTypeConfig{ + Config: &main.FlagTypeConfig{ SkipInterfaces: []string{"fmt.Stringer"}, - StructFields: []*genflags.FlagStructField{ + StructFields: []*main.FlagStructField{ { Name: "Foibles", Type: "int", diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index b0d9bfc82b..4462899fdd 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -95,7 +95,7 @@ The built-in `go generate` command is used to run the commands specified in line help system which may be consulted for further information, e.g.: ```sh -go run internal/genflags/cmd/genflags/main.go --help +go run cmd/urfave-cli-genflags/main.go --help ``` #### docs output diff --git a/flag-spec.yaml b/flag-spec.yaml index 3fa2d10367..7199c198c8 100644 --- a/flag-spec.yaml +++ b/flag-spec.yaml @@ -1,6 +1,6 @@ # NOTE: this file is used by the tool defined in -# ./internal/genflags/cmd/genflags/main.go which uses the -# `genflags.Spec` type that maps to this file structure. +# ./cmd/urfave-cli-genflags/main.go which uses the +# `Spec` type that maps to this file structure. flag_types: bool: diff --git a/internal/build/build.go b/internal/build/build.go index d94dae7925..51b560ca40 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -94,7 +94,7 @@ func main() { }, &cli.StringSliceFlag{ Name: "packages", - Value: cli.NewStringSlice("cli", "altsrc", "internal/build", "internal/genflags"), + Value: cli.NewStringSlice("cli", "altsrc", "internal/build"), }, } diff --git a/internal/genflags/package.go b/internal/genflags/package.go deleted file mode 100644 index 4e5de41a77..0000000000 --- a/internal/genflags/package.go +++ /dev/null @@ -1,34 +0,0 @@ -package genflags - -import ( - _ "embed" - "strings" - - "golang.org/x/text/cases" - "golang.org/x/text/language" -) - -var ( - //go:embed generated.gotmpl - TemplateString string - - //go:embed generated_test.gotmpl - TestTemplateString string - - titler = cases.Title(language.Und, cases.NoLower) -) - -func TypeName(goType string, fc *FlagTypeConfig) string { - if fc != nil && strings.TrimSpace(fc.TypeName) != "" { - return strings.TrimSpace(fc.TypeName) - } - - dotSplit := strings.Split(goType, ".") - goType = dotSplit[len(dotSplit)-1] - - if strings.HasPrefix(goType, "[]") { - return titler.String(strings.TrimPrefix(goType, "[]")) + "SliceFlag" - } - - return titler.String(goType) + "Flag" -} diff --git a/internal/genflags/package_test.go b/internal/genflags/package_test.go deleted file mode 100644 index 3920540778..0000000000 --- a/internal/genflags/package_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package genflags_test - -import ( - "fmt" - "testing" - - "github.com/urfave/cli/v2/internal/genflags" -) - -func TestTypeName(t *testing.T) { - for _, tc := range []struct { - gt string - fc *genflags.FlagTypeConfig - expected string - }{ - {gt: "int", fc: nil, expected: "IntFlag"}, - {gt: "int", fc: &genflags.FlagTypeConfig{}, expected: "IntFlag"}, - {gt: "int", fc: &genflags.FlagTypeConfig{TypeName: "VeryIntyFlag"}, expected: "VeryIntyFlag"}, - {gt: "[]bool", fc: nil, expected: "BoolSliceFlag"}, - {gt: "[]bool", fc: &genflags.FlagTypeConfig{}, expected: "BoolSliceFlag"}, - {gt: "[]bool", fc: &genflags.FlagTypeConfig{TypeName: "ManyTruthsFlag"}, expected: "ManyTruthsFlag"}, - {gt: "time.Rumination", fc: nil, expected: "RuminationFlag"}, - {gt: "time.Rumination", fc: &genflags.FlagTypeConfig{}, expected: "RuminationFlag"}, - {gt: "time.Rumination", fc: &genflags.FlagTypeConfig{TypeName: "PonderFlag"}, expected: "PonderFlag"}, - } { - t.Run( - fmt.Sprintf("type=%s,cfg=%v", tc.gt, func() string { - if tc.fc != nil { - return tc.fc.TypeName - } - return "nil" - }()), - func(ct *testing.T) { - actual := genflags.TypeName(tc.gt, tc.fc) - if tc.expected != actual { - ct.Errorf("expected %q, got %q", tc.expected, actual) - } - }, - ) - } -} diff --git a/internal/genflags/spec.go b/internal/genflags/spec.go deleted file mode 100644 index 5b1d4d3047..0000000000 --- a/internal/genflags/spec.go +++ /dev/null @@ -1,105 +0,0 @@ -package genflags - -import ( - "sort" - "strings" -) - -type Spec struct { - FlagTypes map[string]*FlagTypeConfig `yaml:"flag_types"` - PackageName string `yaml:"package_name"` - TestPackageName string `yaml:"test_package_name"` - UrfaveCLINamespace string `yaml:"urfave_cli_namespace"` - UrfaveCLITestNamespace string `yaml:"urfave_cli_test_namespace"` -} - -func (gfs *Spec) SortedFlagTypes() []*FlagType { - typeNames := []string{} - - for name := range gfs.FlagTypes { - if strings.HasPrefix(name, "[]") { - name = strings.TrimPrefix(name, "[]") + "Slice" - } - - typeNames = append(typeNames, name) - } - - sort.Strings(typeNames) - - ret := make([]*FlagType, len(typeNames)) - - for i, typeName := range typeNames { - ret[i] = &FlagType{ - GoType: typeName, - Config: gfs.FlagTypes[typeName], - } - } - - return ret -} - -type FlagTypeConfig struct { - SkipInterfaces []string `yaml:"skip_interfaces"` - StructFields []*FlagStructField `yaml:"struct_fields"` - TypeName string `yaml:"type_name"` - ValuePointer bool `yaml:"value_pointer"` - NoDefaultText bool `yaml:"no_default_text"` -} - -type FlagStructField struct { - Name string - Type string -} - -type FlagType struct { - GoType string - Config *FlagTypeConfig -} - -func (ft *FlagType) StructFields() []*FlagStructField { - if ft.Config == nil || ft.Config.StructFields == nil { - return []*FlagStructField{} - } - - return ft.Config.StructFields -} - -func (ft *FlagType) ValuePointer() bool { - if ft.Config == nil { - return false - } - - return ft.Config.ValuePointer -} - -func (ft *FlagType) TypeName() string { - return TypeName(ft.GoType, ft.Config) -} - -func (ft *FlagType) GenerateFmtStringerInterface() bool { - return ft.skipInterfaceNamed("fmt.Stringer") -} - -func (ft *FlagType) GenerateFlagInterface() bool { - return ft.skipInterfaceNamed("Flag") -} - -func (ft *FlagType) GenerateDefaultText() bool { - return !ft.Config.NoDefaultText -} - -func (ft *FlagType) skipInterfaceNamed(name string) bool { - if ft.Config == nil { - return true - } - - lowName := strings.ToLower(name) - - for _, interfaceName := range ft.Config.SkipInterfaces { - if strings.ToLower(interfaceName) == lowName { - return false - } - } - - return true -} From d8de3b5483649a3f75f524c84099f593f7c8a5d3 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 14 Aug 2022 10:06:26 -0400 Subject: [PATCH 015/136] Tidy up top-level go.mod --- go.mod | 1 - go.sum | 6 ------ 2 files changed, 7 deletions(-) diff --git a/go.mod b/go.mod index 4e39308336..7fa4542cbc 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/BurntSushi/toml v1.1.0 github.com/cpuguy83/go-md2man/v2 v2.0.2 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 - golang.org/x/text v0.3.7 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 6beae99a6d..0756e415ce 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,11 @@ 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.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= -github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= 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 h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= -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= From a5313eb2d5ec33c691b471cb3caa644d4d594fdc Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 14 Aug 2022 10:22:18 -0400 Subject: [PATCH 016/136] Add missing go.sum entries for go 1.16.x --- cmd/urfave-cli-genflags/go.sum | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/urfave-cli-genflags/go.sum b/cmd/urfave-cli-genflags/go.sum index e59916dc6f..98211271c8 100644 --- a/cmd/urfave-cli-genflags/go.sum +++ b/cmd/urfave-cli-genflags/go.sum @@ -1,3 +1,5 @@ +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= @@ -8,6 +10,7 @@ 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= From 67b3de028550d0b87ad733949b0c5525f853c165 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 2 Oct 2022 09:19:29 -0400 Subject: [PATCH 017/136] Turn off docs publishing from v3 series --- .github/workflows/cli.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 1e4a51f221..1d98e234e5 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -93,7 +93,10 @@ jobs: git diff --cached --exit-code publish: - if: startswith(github.ref, 'refs/tags/') + # TODO: switch once v3 is released {{ + # if: startswith(github.ref, 'refs/tags/') + if: 'false' + # }} name: publish needs: [test-docs] runs-on: ubuntu-latest From cbc7f1ad1dd5aae71e00ac16807e989a53363a9f Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 14 Aug 2022 10:47:42 -0400 Subject: [PATCH 018/136] Use goimports as formatting standard given some disagreement with gofmt that seems to have shown up in 1.19 --- .github/workflows/cli.yml | 10 +++++++--- cli.go | 24 ++++++++++++------------ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 0a3e7c3685..3bd66d6539 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -30,12 +30,16 @@ jobs: - name: Set PATH run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}" + - name: install goimports + if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' + run: GOBIN=${PWD}/.local/bin go install golang.org/x/tools/cmd/goimports@latest + - name: Checkout Code uses: actions/checkout@v3 - - name: GOFMT Check - if: matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest' - run: test -z $(gofmt -l .) + - name: goimports check + if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' + run: test -z $(goimports -l .) - name: vet run: go run internal/build/build.go vet diff --git a/cli.go b/cli.go index c0c5d9a1b6..b3b864cf5a 100644 --- a/cli.go +++ b/cli.go @@ -3,23 +3,23 @@ // cli application can be written as follows: // // func main() { -// (&cli.App{}).Run(os.Args) +// (&cli.App{}).Run(os.Args) // } // // Of course this application does not do much, so let's make this an actual application: // -// func main() { -// app := &cli.App{ -// Name: "greet", -// Usage: "say a greeting", -// Action: func(c *cli.Context) error { -// fmt.Println("Greetings") -// return nil -// }, -// } +// func main() { +// app := &cli.App{ +// Name: "greet", +// Usage: "say a greeting", +// Action: func(c *cli.Context) error { +// fmt.Println("Greetings") +// return nil +// }, +// } // -// app.Run(os.Args) -// } +// app.Run(os.Args) +// } package cli //go:generate go run cmd/urfave-cli-genflags/main.go From 76bb9f100bc512938165bcbef9002caa4376926f Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 14 Aug 2022 10:31:53 -0400 Subject: [PATCH 019/136] Shift supported go versions and approve word-wrapping changes to godoc --- .github/workflows/cli.yml | 6 +++--- .gitignore | 13 +++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 3bd66d6539..5364ca0c7a 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - go: [1.18.x] + go: [1.18.x, 1.19.x] name: ${{ matrix.os }} @ Go ${{ matrix.go }} runs-on: ${{ matrix.os }} steps: @@ -60,7 +60,7 @@ jobs: run: go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size - name: Upload coverage to Codecov - if: success() && matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest' + if: success() && matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v2 with: fail_ci_if_error: true @@ -72,7 +72,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.18.x + go-version: 1.19.x - name: Use Node.js 16 uses: actions/setup-node@v3 diff --git a/.gitignore b/.gitignore index 4296f6c7a1..3c6768f8c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,12 @@ *.coverprofile +*.exe *.orig -vendor +.*envrc +.envrc .idea -internal/*/built-example -coverage.txt /.local/ -/site/ /cmd/urfave-cli-genflags/urfave-cli-genflags - -*.exe +/site/ +coverage.txt +internal/*/built-example +vendor From 1ae70fcaddd091dac0023829b99da5af6cc2fcf3 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sun, 14 Aug 2022 14:49:05 -0800 Subject: [PATCH 020/136] split v1 docs into individual pages --- docs/index.md | 2 +- docs/v1/examples/arguments.md | 32 + docs/v1/examples/bash-completions.md | 114 ++ docs/v1/examples/combining-short-options.md | 67 + docs/v1/examples/exit-codes.md | 38 + docs/v1/examples/flags.md | 457 ++++++ docs/v1/examples/generated-help-text.md | 101 ++ docs/v1/examples/greet.md | 69 + docs/v1/examples/subcommands-categories.md | 50 + docs/v1/examples/subcommands.md | 70 + docs/v1/examples/version-flag.md | 360 +++++ docs/v1/getting-started.md | 60 + docs/v1/index.md | 1 - docs/v1/manual.md | 1455 ------------------- docs/v1/migrating-to-v2.md | 6 + mkdocs.yml | 24 +- 16 files changed, 1448 insertions(+), 1458 deletions(-) create mode 100644 docs/v1/examples/arguments.md create mode 100644 docs/v1/examples/bash-completions.md create mode 100644 docs/v1/examples/combining-short-options.md create mode 100644 docs/v1/examples/exit-codes.md create mode 100644 docs/v1/examples/flags.md create mode 100644 docs/v1/examples/generated-help-text.md create mode 100644 docs/v1/examples/greet.md create mode 100644 docs/v1/examples/subcommands-categories.md create mode 100644 docs/v1/examples/subcommands.md create mode 100644 docs/v1/examples/version-flag.md create mode 100644 docs/v1/getting-started.md delete mode 120000 docs/v1/index.md delete mode 100644 docs/v1/manual.md create mode 100644 docs/v1/migrating-to-v2.md diff --git a/docs/index.md b/docs/index.md index 5f02fed5ea..251679d2f1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,7 +12,7 @@ an expressive way. These are the guides for each major supported version: - [`v2`](./v2/) -- [`v1`](./v1/) +- [`v1`](./v1/getting-started) In addition to the version-specific guides, these other documents are available: diff --git a/docs/v1/examples/arguments.md b/docs/v1/examples/arguments.md new file mode 100644 index 0000000000..ba451b6e46 --- /dev/null +++ b/docs/v1/examples/arguments.md @@ -0,0 +1,32 @@ +### Arguments + +You can lookup arguments by calling the `Args` function on `cli.Context`, e.g.: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Action = func(c *cli.Context) error { + fmt.Printf("Hello %q", c.Args().Get(0)) + return nil + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` diff --git a/docs/v1/examples/bash-completions.md b/docs/v1/examples/bash-completions.md new file mode 100644 index 0000000000..5648477fb3 --- /dev/null +++ b/docs/v1/examples/bash-completions.md @@ -0,0 +1,114 @@ +You can enable completion commands by setting the `EnableBashCompletion` +flag on the `App` object. By default, this setting will only auto-complete to +show an app's subcommands, but you can write your own completion methods for +the App or its subcommands. + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} + + app := cli.NewApp() + app.EnableBashCompletion = true + app.Commands = []cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + fmt.Println("completed task: ", c.Args().First()) + return nil + }, + BashComplete: func(c *cli.Context) { + // This will complete if no args are passed + if c.NArg() > 0 { + return + } + for _, t := range tasks { + fmt.Println(t) + } + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +#### Enabling + +Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while +setting the `PROG` variable to the name of your program: + +`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` + +#### Distribution + +Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename +it to the name of the program you wish to add autocomplete support for (or +automatically install it there if you are distributing a package). Don't forget +to source the file to make it active in the current shell. + +``` +sudo cp src/bash_autocomplete /etc/bash_completion.d/ +source /etc/bash_completion.d/ +``` + +Alternatively, you can just document that users should source the generic +`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set +to the name of their program (as above). + +#### Customization + +The default bash completion flag (`--generate-bash-completion`) is defined as +`cli.BashCompletionFlag`, and may be redefined if desired, e.g.: + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + cli.BashCompletionFlag = cli.BoolFlag{ + Name: "compgen", + Hidden: true, + } + + app := cli.NewApp() + app.EnableBashCompletion = true + app.Commands = []cli.Command{ + { + Name: "wat", + }, + } + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` diff --git a/docs/v1/examples/combining-short-options.md b/docs/v1/examples/combining-short-options.md new file mode 100644 index 0000000000..3ab6ebdd12 --- /dev/null +++ b/docs/v1/examples/combining-short-options.md @@ -0,0 +1,67 @@ +Traditional use of options using their shortnames look like this: + +``` +$ cmd -s -o -m "Some message" +``` + +Suppose you want users to be able to combine options with their shortnames. This +can be done using the `UseShortOptionHandling` bool in your app configuration, +or for individual commands by attaching it to the command configuration. For +example: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.UseShortOptionHandling = true + app.Commands = []cli.Command{ + { + Name: "short", + Usage: "complete a task on the list", + Flags: []cli.Flag{ + cli.BoolFlag{Name: "serve, s"}, + cli.BoolFlag{Name: "option, o"}, + cli.StringFlag{Name: "message, m"}, + }, + Action: func(c *cli.Context) error { + fmt.Println("serve:", c.Bool("serve")) + fmt.Println("option:", c.Bool("option")) + fmt.Println("message:", c.String("message")) + return nil + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +If your program has any number of bool flags such as `serve` and `option`, and +optionally one non-bool flag `message`, with the short options of `-s`, `-o`, +and `-m` respectively, setting `UseShortOptionHandling` will also support the +following syntax: + +``` +$ cmd -som "Some message" +``` + +If you enable `UseShortOptionHandling`, then you must not use any flags that +have a single leading `-` or this will result in failures. For example, +`-option` can no longer be used. Flags with two leading dashes (such as +`--options`) are still valid. diff --git a/docs/v1/examples/exit-codes.md b/docs/v1/examples/exit-codes.md new file mode 100644 index 0000000000..39c06424e5 --- /dev/null +++ b/docs/v1/examples/exit-codes.md @@ -0,0 +1,38 @@ +Calling `App.Run` will not automatically call `os.Exit`, which means that by +default the exit code will "fall through" to being `0`. An explicit exit code +may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a +`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.: + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Flags = []cli.Flag{ + cli.BoolFlag{ + Name: "ginger-crouton", + Usage: "Add ginger croutons to the soup", + }, + } + app.Action = func(ctx *cli.Context) error { + if !ctx.Bool("ginger-crouton") { + return cli.NewExitError("Ginger croutons are not in the soup", 86) + } + return nil + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` diff --git a/docs/v1/examples/flags.md b/docs/v1/examples/flags.md new file mode 100644 index 0000000000..f6ae7be566 --- /dev/null +++ b/docs/v1/examples/flags.md @@ -0,0 +1,457 @@ +Setting and querying flags is simple. + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, + } + + app.Action = func(c *cli.Context) error { + name := "Nefertiti" + if c.NArg() > 0 { + name = c.Args().Get(0) + } + if c.String("lang") == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +You can also set a destination variable for a flag, to which the content will be +scanned. + + +``` go +package main + +import ( + "log" + "os" + "fmt" + + "github.com/urfave/cli" +) + +func main() { + var language string + + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + Destination: &language, + }, + } + + app.Action = func(c *cli.Context) error { + name := "someone" + if c.NArg() > 0 { + name = c.Args()[0] + } + if language == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +See full list of flags at http://godoc.org/github.com/urfave/cli + +#### Placeholder Values + +Sometimes it's useful to specify a flag's value within the usage string itself. +Such placeholders are indicated with back quotes. + +For example this: + + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "config, c", + Usage: "Load configuration from `FILE`", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Will result in help output like: + +``` +--config FILE, -c FILE Load configuration from FILE +``` + +Note that only the first placeholder is used. Subsequent back-quoted words will +be left as-is. + +#### Alternate Names + +You can set alternate (or short) names for flags by providing a comma-delimited +list for the `Name`. e.g. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +That flag can then be set with `--lang spanish` or `-l spanish`. Note that +giving two different forms of the same flag in the same command invocation is an +error. + +#### Ordering + +Flags for the application and commands are shown in the order they are defined. +However, it's possible to sort them from outside this library by using `FlagsByName` +or `CommandsByName` with `sort`. + +For example this: + + +``` go +package main + +import ( + "log" + "os" + "sort" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "Language for the greeting", + }, + cli.StringFlag{ + Name: "config, c", + Usage: "Load configuration from `FILE`", + }, + } + + app.Commands = []cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + return nil + }, + }, + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *cli.Context) error { + return nil + }, + }, + } + + sort.Sort(cli.FlagsByName(app.Flags)) + sort.Sort(cli.CommandsByName(app.Commands)) + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Will result in help output like: + +``` +--config FILE, -c FILE Load configuration from FILE +--lang value, -l value Language for the greeting (default: "english") +``` + +#### Values from the Environment + +You can also have the default value set from the environment via `EnvVar`. e.g. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + EnvVar: "APP_LANG", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +The `EnvVar` may also be given as a comma-delimited "cascade", where the first +environment variable that resolves is used as the default. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +#### Values from files + +You can also have the default value set from file via `FilePath`. e.g. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "password, p", + Usage: "password for the mysql database", + FilePath: "/etc/mysql/password", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Note that default values set from file (e.g. `FilePath`) take precedence over +default values set from the environment (e.g. `EnvVar`). + +#### Values from alternate input sources (YAML, TOML, and others) + +There is a separate package altsrc that adds support for getting flag values +from other file input sources. + +Currently supported input source formats: +* YAML +* JSON +* TOML + +In order to get values for a flag from an alternate input source the following +code would be added to wrap an existing cli.Flag like below: + +``` go + altsrc.NewIntFlag(cli.IntFlag{Name: "test"}) +``` + +Initialization must also occur for these flags. Below is an example initializing +getting data from a yaml file below. + +``` go + command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) +``` + +The code above will use the "load" string as a flag name to get the file name of +a yaml file from the cli.Context. It will then use that file name to initialize +the yaml input source for any flags that are defined on that command. As a note +the "load" flag used would also have to be defined on the command flags in order +for this code snippet to work. + +Currently only YAML, JSON, and TOML files are supported but developers can add support +for other input sources by implementing the altsrc.InputSourceContext for their +given sources. + +Here is a more complete sample of a command using YAML support: + + +``` go +package notmain + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" + "github.com/urfave/cli/altsrc" +) + +func main() { + app := cli.NewApp() + + flags := []cli.Flag{ + altsrc.NewIntFlag(cli.IntFlag{Name: "test"}), + cli.StringFlag{Name: "load"}, + } + + app.Action = func(c *cli.Context) error { + fmt.Println("yaml ist rad") + return nil + } + + app.Before = altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")) + app.Flags = flags + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +#### Precedence + +The precedence for flag value sources is as follows (highest to lowest): + +0. Command line flag value from user +0. Environment variable (if specified) +0. Configuration file (if specified) +0. Default defined on the flag diff --git a/docs/v1/examples/generated-help-text.md b/docs/v1/examples/generated-help-text.md new file mode 100644 index 0000000000..0c7f110455 --- /dev/null +++ b/docs/v1/examples/generated-help-text.md @@ -0,0 +1,101 @@ +The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked +by the cli internals in order to print generated help text for the app, command, +or subcommand, and break execution. + +#### Customization + +All of the help text generation may be customized, and at multiple levels. The +templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and +`SubcommandHelpTemplate` which may be reassigned or augmented, and full override +is possible by assigning a compatible func to the `cli.HelpPrinter` variable, +e.g.: + + +``` go +package main + +import ( + "fmt" + "log" + "io" + "os" + + "github.com/urfave/cli" +) + +func main() { + // EXAMPLE: Append to an existing template + cli.AppHelpTemplate = fmt.Sprintf(`%s + +WEBSITE: http://awesometown.example.com + +SUPPORT: support@awesometown.example.com + +`, cli.AppHelpTemplate) + + // EXAMPLE: Override a template + cli.AppHelpTemplate = `NAME: + {{.Name}} - {{.Usage}} +USAGE: + {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} + {{if len .Authors}} +AUTHOR: + {{range .Authors}}{{ . }}{{end}} + {{end}}{{if .Commands}} +COMMANDS: +{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} +GLOBAL OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}}{{if .Copyright }} +COPYRIGHT: + {{.Copyright}} + {{end}}{{if .Version}} +VERSION: + {{.Version}} + {{end}} +` + + // EXAMPLE: Replace the `HelpPrinter` func + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + fmt.Println("Ha HA. I pwnd the help!!1") + } + + err := cli.NewApp().Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +The default flag may be customized to something other than `-h/--help` by +setting `cli.HelpFlag`, e.g.: + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + cli.HelpFlag = cli.BoolFlag{ + Name: "halp, haaaaalp", + Usage: "HALP", + EnvVar: "SHOW_HALP,HALPPLZ", + } + + err := cli.NewApp().Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` diff --git a/docs/v1/examples/greet.md b/docs/v1/examples/greet.md new file mode 100644 index 0000000000..7b47cf6108 --- /dev/null +++ b/docs/v1/examples/greet.md @@ -0,0 +1,69 @@ +Being a programmer can be a lonely job. Thankfully by the power of automation +that is not the case! Let's create a greeter app to fend off our demons of +loneliness! + +Start by creating a directory named `greet`, and within it, add a file, +`greet.go` with the following code in it: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "greet" + app.Usage = "fight the loneliness!" + app.Action = func(c *cli.Context) error { + fmt.Println("Hello friend!") + return nil + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Install our command to the `$GOPATH/bin` directory: + +``` +$ go install +``` + +Finally run our new command: + +``` +$ greet +Hello friend! +``` + +cli also generates neat help text: + +``` +$ greet help +NAME: + greet - fight the loneliness! + +USAGE: + greet [global options] command [command options] [arguments...] + +VERSION: + 0.0.0 + +COMMANDS: + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS + --version Shows version information +``` diff --git a/docs/v1/examples/subcommands-categories.md b/docs/v1/examples/subcommands-categories.md new file mode 100644 index 0000000000..b10c1f97b8 --- /dev/null +++ b/docs/v1/examples/subcommands-categories.md @@ -0,0 +1,50 @@ +For additional organization in apps that have many subcommands, you can +associate a category for each command to group them together in the help +output. + +E.g. + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Commands = []cli.Command{ + { + Name: "noop", + }, + { + Name: "add", + Category: "Template actions", + }, + { + Name: "remove", + Category: "Template actions", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Will include: + +``` +COMMANDS: + noop + + Template actions: + add + remove +``` diff --git a/docs/v1/examples/subcommands.md b/docs/v1/examples/subcommands.md new file mode 100644 index 0000000000..f062ba7e03 --- /dev/null +++ b/docs/v1/examples/subcommands.md @@ -0,0 +1,70 @@ +Subcommands can be defined for a more git-like command line app. + + +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Commands = []cli.Command{ + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *cli.Context) error { + fmt.Println("added task: ", c.Args().First()) + return nil + }, + }, + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + fmt.Println("completed task: ", c.Args().First()) + return nil + }, + }, + { + Name: "template", + Aliases: []string{"t"}, + Usage: "options for task templates", + Subcommands: []cli.Command{ + { + Name: "add", + Usage: "add a new template", + Action: func(c *cli.Context) error { + fmt.Println("new task template: ", c.Args().First()) + return nil + }, + }, + { + Name: "remove", + Usage: "remove an existing template", + Action: func(c *cli.Context) error { + fmt.Println("removed task template: ", c.Args().First()) + return nil + }, + }, + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` diff --git a/docs/v1/examples/version-flag.md b/docs/v1/examples/version-flag.md new file mode 100644 index 0000000000..482eea46b2 --- /dev/null +++ b/docs/v1/examples/version-flag.md @@ -0,0 +1,360 @@ +### Version Flag + +The default version flag (`-v/--version`) is defined as `cli.VersionFlag`, which +is checked by the cli internals in order to print the `App.Version` via +`cli.VersionPrinter` and break execution. + +#### Customization + +The default flag may be customized to something other than `-v/--version` by +setting `cli.VersionFlag`, e.g.: + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + cli.VersionFlag = cli.BoolFlag{ + Name: "print-version, V", + Usage: "print only the version", + } + + app := cli.NewApp() + app.Name = "partay" + app.Version = "19.99.0" + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e.g.: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +var ( + Revision = "fafafaf" +) + +func main() { + cli.VersionPrinter = func(c *cli.Context) { + fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision) + } + + app := cli.NewApp() + app.Name = "partay" + app.Version = "19.99.0" + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +#### Full API Example + +**Notice**: This is a contrived (functioning) example meant strictly for API +demonstration purposes. Use of one's imagination is encouraged. + + +``` go +package main + +import ( + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "time" + + "github.com/urfave/cli" +) + +func init() { + cli.AppHelpTemplate += "\nCUSTOMIZED: you bet ur muffins\n" + cli.CommandHelpTemplate += "\nYMMV\n" + cli.SubcommandHelpTemplate += "\nor something\n" + + cli.HelpFlag = cli.BoolFlag{Name: "halp"} + cli.BashCompletionFlag = cli.BoolFlag{Name: "compgen", Hidden: true} + cli.VersionFlag = cli.BoolFlag{Name: "print-version, V"} + + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + fmt.Fprintf(w, "best of luck to you\n") + } + cli.VersionPrinter = func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "version=%s\n", c.App.Version) + } + cli.OsExiter = func(c int) { + fmt.Fprintf(cli.ErrWriter, "refusing to exit %d\n", c) + } + cli.ErrWriter = ioutil.Discard + cli.FlagStringer = func(fl cli.Flag) string { + return fmt.Sprintf("\t\t%s", fl.GetName()) + } +} + +type hexWriter struct{} + +func (w *hexWriter) Write(p []byte) (int, error) { + for _, b := range p { + fmt.Printf("%x", b) + } + fmt.Printf("\n") + + return len(p), nil +} + +type genericType struct{ + s string +} + +func (g *genericType) Set(value string) error { + g.s = value + return nil +} + +func (g *genericType) String() string { + return g.s +} + +func main() { + app := cli.NewApp() + app.Name = "kənˈtrīv" + app.Version = "19.99.0" + app.Compiled = time.Now() + app.Authors = []cli.Author{ + cli.Author{ + Name: "Example Human", + Email: "human@example.com", + }, + } + app.Copyright = "(c) 1999 Serious Enterprise" + app.HelpName = "contrive" + app.Usage = "demonstrate available API" + app.UsageText = "contrive - demonstrating the available API" + app.ArgsUsage = "[args and such]" + app.Commands = []cli.Command{ + cli.Command{ + Name: "doo", + Aliases: []string{"do"}, + Category: "motion", + Usage: "do the doo", + UsageText: "doo - does the dooing", + Description: "no really, there is a lot of dooing to be done", + ArgsUsage: "[arrgh]", + Flags: []cli.Flag{ + cli.BoolFlag{Name: "forever, forevvarr"}, + }, + Subcommands: cli.Commands{ + cli.Command{ + Name: "wop", + Action: wopAction, + }, + }, + SkipFlagParsing: false, + HideHelp: false, + Hidden: false, + HelpName: "doo!", + BashComplete: func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "--better\n") + }, + Before: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "brace for impact\n") + return nil + }, + After: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "did we lose anyone?\n") + return nil + }, + Action: func(c *cli.Context) error { + c.Command.FullName() + c.Command.HasName("wop") + c.Command.Names() + c.Command.VisibleFlags() + fmt.Fprintf(c.App.Writer, "dodododododoodododddooooododododooo\n") + if c.Bool("forever") { + c.Command.Run(c) + } + return nil + }, + OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { + fmt.Fprintf(c.App.Writer, "for shame\n") + return err + }, + }, + } + app.Flags = []cli.Flag{ + cli.BoolFlag{Name: "fancy"}, + cli.BoolTFlag{Name: "fancier"}, + cli.DurationFlag{Name: "howlong, H", Value: time.Second * 3}, + cli.Float64Flag{Name: "howmuch"}, + cli.GenericFlag{Name: "wat", Value: &genericType{}}, + cli.Int64Flag{Name: "longdistance"}, + cli.Int64SliceFlag{Name: "intervals"}, + cli.IntFlag{Name: "distance"}, + cli.IntSliceFlag{Name: "times"}, + cli.StringFlag{Name: "dance-move, d"}, + cli.StringSliceFlag{Name: "names, N"}, + cli.UintFlag{Name: "age"}, + cli.Uint64Flag{Name: "bigage"}, + } + app.EnableBashCompletion = true + app.UseShortOptionHandling = true + app.HideHelp = false + app.HideVersion = false + app.BashComplete = func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") + } + app.Before = func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n") + return nil + } + app.After = func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "Phew!\n") + return nil + } + app.CommandNotFound = func(c *cli.Context, command string) { + fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command) + } + app.OnUsageError = func(c *cli.Context, err error, isSubcommand bool) error { + if isSubcommand { + return err + } + + fmt.Fprintf(c.App.Writer, "WRONG: %#v\n", err) + return nil + } + app.Action = func(c *cli.Context) error { + cli.DefaultAppComplete(c) + cli.HandleExitCoder(errors.New("not an exit coder, though")) + cli.ShowAppHelp(c) + cli.ShowCommandCompletions(c, "nope") + cli.ShowCommandHelp(c, "also-nope") + cli.ShowCompletions(c) + cli.ShowSubcommandHelp(c) + cli.ShowVersion(c) + + categories := c.App.Categories() + categories.AddCommand("sounds", cli.Command{ + Name: "bloop", + }) + + for _, category := range c.App.Categories() { + fmt.Fprintf(c.App.Writer, "%s\n", category.Name) + fmt.Fprintf(c.App.Writer, "%#v\n", category.Commands) + fmt.Fprintf(c.App.Writer, "%#v\n", category.VisibleCommands()) + } + + fmt.Printf("%#v\n", c.App.Command("doo")) + if c.Bool("infinite") { + c.App.Run([]string{"app", "doo", "wop"}) + } + + if c.Bool("forevar") { + c.App.RunAsSubcommand(c) + } + c.App.Setup() + fmt.Printf("%#v\n", c.App.VisibleCategories()) + fmt.Printf("%#v\n", c.App.VisibleCommands()) + fmt.Printf("%#v\n", c.App.VisibleFlags()) + + fmt.Printf("%#v\n", c.Args().First()) + if len(c.Args()) > 0 { + fmt.Printf("%#v\n", c.Args()[1]) + } + fmt.Printf("%#v\n", c.Args().Present()) + fmt.Printf("%#v\n", c.Args().Tail()) + + set := flag.NewFlagSet("contrive", 0) + nc := cli.NewContext(c.App, set, c) + + fmt.Printf("%#v\n", nc.Args()) + fmt.Printf("%#v\n", nc.Bool("nope")) + fmt.Printf("%#v\n", nc.BoolT("nerp")) + fmt.Printf("%#v\n", nc.Duration("howlong")) + fmt.Printf("%#v\n", nc.Float64("hay")) + fmt.Printf("%#v\n", nc.Generic("bloop")) + fmt.Printf("%#v\n", nc.Int64("bonk")) + fmt.Printf("%#v\n", nc.Int64Slice("burnks")) + fmt.Printf("%#v\n", nc.Int("bips")) + fmt.Printf("%#v\n", nc.IntSlice("blups")) + fmt.Printf("%#v\n", nc.String("snurt")) + fmt.Printf("%#v\n", nc.StringSlice("snurkles")) + fmt.Printf("%#v\n", nc.Uint("flub")) + fmt.Printf("%#v\n", nc.Uint64("florb")) + fmt.Printf("%#v\n", nc.GlobalBool("global-nope")) + fmt.Printf("%#v\n", nc.GlobalBoolT("global-nerp")) + fmt.Printf("%#v\n", nc.GlobalDuration("global-howlong")) + fmt.Printf("%#v\n", nc.GlobalFloat64("global-hay")) + fmt.Printf("%#v\n", nc.GlobalGeneric("global-bloop")) + fmt.Printf("%#v\n", nc.GlobalInt("global-bips")) + fmt.Printf("%#v\n", nc.GlobalIntSlice("global-blups")) + fmt.Printf("%#v\n", nc.GlobalString("global-snurt")) + fmt.Printf("%#v\n", nc.GlobalStringSlice("global-snurkles")) + + fmt.Printf("%#v\n", nc.FlagNames()) + fmt.Printf("%#v\n", nc.GlobalFlagNames()) + fmt.Printf("%#v\n", nc.GlobalIsSet("wat")) + fmt.Printf("%#v\n", nc.GlobalSet("wat", "nope")) + fmt.Printf("%#v\n", nc.NArg()) + fmt.Printf("%#v\n", nc.NumFlags()) + fmt.Printf("%#v\n", nc.Parent()) + + nc.Set("wat", "also-nope") + + ec := cli.NewExitError("ohwell", 86) + fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) + fmt.Printf("made it!\n") + return nil + } + + if os.Getenv("HEXY") != "" { + app.Writer = &hexWriter{} + app.ErrWriter = &hexWriter{} + } + + app.Metadata = map[string]interface{}{ + "layers": "many", + "explicable": false, + "whatever-values": 19.99, + } + + + // ignore error so we don't exit non-zero and break gfmrun README example tests + _ = app.Run(os.Args) +} + +func wopAction(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, ":wave: over here, eh\n") + return nil +} +``` diff --git a/docs/v1/getting-started.md b/docs/v1/getting-started.md new file mode 100644 index 0000000000..b144483e37 --- /dev/null +++ b/docs/v1/getting-started.md @@ -0,0 +1,60 @@ +One of the philosophies behind cli is that an API should be playful and full of +discovery. So a cli app can be as little as one line of code in `main()`. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + err := cli.NewApp().Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +This app will run and show help text, but is not very useful. Let's give an +action to execute and some help documentation: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "boom" + app.Usage = "make an explosive entrance" + app.Action = func(c *cli.Context) error { + fmt.Println("boom! I say!") + return nil + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Running this already gives you a ton of functionality, plus support for things +like subcommands and flags, which are covered below. diff --git a/docs/v1/index.md b/docs/v1/index.md deleted file mode 120000 index 9d0493a7f2..0000000000 --- a/docs/v1/index.md +++ /dev/null @@ -1 +0,0 @@ -manual.md \ No newline at end of file diff --git a/docs/v1/manual.md b/docs/v1/manual.md deleted file mode 100644 index 6f568b73f1..0000000000 --- a/docs/v1/manual.md +++ /dev/null @@ -1,1455 +0,0 @@ -# v1 guide - -## Getting Started - -One of the philosophies behind cli is that an API should be playful and full of -discovery. So a cli app can be as little as one line of code in `main()`. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - err := cli.NewApp().Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -This app will run and show help text, but is not very useful. Let's give an -action to execute and some help documentation: - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - app.Name = "boom" - app.Usage = "make an explosive entrance" - app.Action = func(c *cli.Context) error { - fmt.Println("boom! I say!") - return nil - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Running this already gives you a ton of functionality, plus support for things -like subcommands and flags, which are covered below. - -## Examples - -Being a programmer can be a lonely job. Thankfully by the power of automation -that is not the case! Let's create a greeter app to fend off our demons of -loneliness! - -Start by creating a directory named `greet`, and within it, add a file, -`greet.go` with the following code in it: - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - app.Name = "greet" - app.Usage = "fight the loneliness!" - app.Action = func(c *cli.Context) error { - fmt.Println("Hello friend!") - return nil - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Install our command to the `$GOPATH/bin` directory: - -``` -$ go install -``` - -Finally run our new command: - -``` -$ greet -Hello friend! -``` - -cli also generates neat help text: - -``` -$ greet help -NAME: - greet - fight the loneliness! - -USAGE: - greet [global options] command [command options] [arguments...] - -VERSION: - 0.0.0 - -COMMANDS: - help, h Shows a list of commands or help for one command - -GLOBAL OPTIONS - --version Shows version information -``` - -### Arguments - -You can lookup arguments by calling the `Args` function on `cli.Context`, e.g.: - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Action = func(c *cli.Context) error { - fmt.Printf("Hello %q", c.Args().Get(0)) - return nil - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -### Flags - -Setting and querying flags is simple. - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - }, - } - - app.Action = func(c *cli.Context) error { - name := "Nefertiti" - if c.NArg() > 0 { - name = c.Args().Get(0) - } - if c.String("lang") == "spanish" { - fmt.Println("Hola", name) - } else { - fmt.Println("Hello", name) - } - return nil - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -You can also set a destination variable for a flag, to which the content will be -scanned. - - -``` go -package main - -import ( - "log" - "os" - "fmt" - - "github.com/urfave/cli" -) - -func main() { - var language string - - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - Destination: &language, - }, - } - - app.Action = func(c *cli.Context) error { - name := "someone" - if c.NArg() > 0 { - name = c.Args()[0] - } - if language == "spanish" { - fmt.Println("Hola", name) - } else { - fmt.Println("Hello", name) - } - return nil - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -See full list of flags at http://godoc.org/github.com/urfave/cli - -#### Placeholder Values - -Sometimes it's useful to specify a flag's value within the usage string itself. -Such placeholders are indicated with back quotes. - -For example this: - - -```go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag{ - cli.StringFlag{ - Name: "config, c", - Usage: "Load configuration from `FILE`", - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Will result in help output like: - -``` ---config FILE, -c FILE Load configuration from FILE -``` - -Note that only the first placeholder is used. Subsequent back-quoted words will -be left as-is. - -#### Alternate Names - -You can set alternate (or short) names for flags by providing a comma-delimited -list for the `Name`. e.g. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -That flag can then be set with `--lang spanish` or `-l spanish`. Note that -giving two different forms of the same flag in the same command invocation is an -error. - -#### Ordering - -Flags for the application and commands are shown in the order they are defined. -However, it's possible to sort them from outside this library by using `FlagsByName` -or `CommandsByName` with `sort`. - -For example this: - - -``` go -package main - -import ( - "log" - "os" - "sort" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "Language for the greeting", - }, - cli.StringFlag{ - Name: "config, c", - Usage: "Load configuration from `FILE`", - }, - } - - app.Commands = []cli.Command{ - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - return nil - }, - }, - { - Name: "add", - Aliases: []string{"a"}, - Usage: "add a task to the list", - Action: func(c *cli.Context) error { - return nil - }, - }, - } - - sort.Sort(cli.FlagsByName(app.Flags)) - sort.Sort(cli.CommandsByName(app.Commands)) - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Will result in help output like: - -``` ---config FILE, -c FILE Load configuration from FILE ---lang value, -l value Language for the greeting (default: "english") -``` - -#### Values from the Environment - -You can also have the default value set from the environment via `EnvVar`. e.g. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", - EnvVar: "APP_LANG", - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -The `EnvVar` may also be given as a comma-delimited "cascade", where the first -environment variable that resolves is used as the default. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", - EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG", - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -#### Values from files - -You can also have the default value set from file via `FilePath`. e.g. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "password, p", - Usage: "password for the mysql database", - FilePath: "/etc/mysql/password", - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Note that default values set from file (e.g. `FilePath`) take precedence over -default values set from the environment (e.g. `EnvVar`). - -#### Values from alternate input sources (YAML, TOML, and others) - -There is a separate package altsrc that adds support for getting flag values -from other file input sources. - -Currently supported input source formats: -* YAML -* JSON -* TOML - -In order to get values for a flag from an alternate input source the following -code would be added to wrap an existing cli.Flag like below: - -``` go - altsrc.NewIntFlag(cli.IntFlag{Name: "test"}) -``` - -Initialization must also occur for these flags. Below is an example initializing -getting data from a yaml file below. - -``` go - command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) -``` - -The code above will use the "load" string as a flag name to get the file name of -a yaml file from the cli.Context. It will then use that file name to initialize -the yaml input source for any flags that are defined on that command. As a note -the "load" flag used would also have to be defined on the command flags in order -for this code snippet to work. - -Currently only YAML, JSON, and TOML files are supported but developers can add support -for other input sources by implementing the altsrc.InputSourceContext for their -given sources. - -Here is a more complete sample of a command using YAML support: - - -``` go -package notmain - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" - "github.com/urfave/cli/altsrc" -) - -func main() { - app := cli.NewApp() - - flags := []cli.Flag{ - altsrc.NewIntFlag(cli.IntFlag{Name: "test"}), - cli.StringFlag{Name: "load"}, - } - - app.Action = func(c *cli.Context) error { - fmt.Println("yaml ist rad") - return nil - } - - app.Before = altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")) - app.Flags = flags - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -#### Precedence - -The precedence for flag value sources is as follows (highest to lowest): - -0. Command line flag value from user -0. Environment variable (if specified) -0. Configuration file (if specified) -0. Default defined on the flag - -### Subcommands - -Subcommands can be defined for a more git-like command line app. - - -```go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Commands = []cli.Command{ - { - Name: "add", - Aliases: []string{"a"}, - Usage: "add a task to the list", - Action: func(c *cli.Context) error { - fmt.Println("added task: ", c.Args().First()) - return nil - }, - }, - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - fmt.Println("completed task: ", c.Args().First()) - return nil - }, - }, - { - Name: "template", - Aliases: []string{"t"}, - Usage: "options for task templates", - Subcommands: []cli.Command{ - { - Name: "add", - Usage: "add a new template", - Action: func(c *cli.Context) error { - fmt.Println("new task template: ", c.Args().First()) - return nil - }, - }, - { - Name: "remove", - Usage: "remove an existing template", - Action: func(c *cli.Context) error { - fmt.Println("removed task template: ", c.Args().First()) - return nil - }, - }, - }, - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -### Subcommands categories - -For additional organization in apps that have many subcommands, you can -associate a category for each command to group them together in the help -output. - -E.g. - -```go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Commands = []cli.Command{ - { - Name: "noop", - }, - { - Name: "add", - Category: "Template actions", - }, - { - Name: "remove", - Category: "Template actions", - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Will include: - -``` -COMMANDS: - noop - - Template actions: - add - remove -``` - -### Exit code - -Calling `App.Run` will not automatically call `os.Exit`, which means that by -default the exit code will "fall through" to being `0`. An explicit exit code -may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a -`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.: - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - app.Flags = []cli.Flag{ - cli.BoolFlag{ - Name: "ginger-crouton", - Usage: "Add ginger croutons to the soup", - }, - } - app.Action = func(ctx *cli.Context) error { - if !ctx.Bool("ginger-crouton") { - return cli.NewExitError("Ginger croutons are not in the soup", 86) - } - return nil - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -### Combining short options - -Traditional use of options using their shortnames look like this: - -``` -$ cmd -s -o -m "Some message" -``` - -Suppose you want users to be able to combine options with their shortnames. This -can be done using the `UseShortOptionHandling` bool in your app configuration, -or for individual commands by attaching it to the command configuration. For -example: - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - app.UseShortOptionHandling = true - app.Commands = []cli.Command{ - { - Name: "short", - Usage: "complete a task on the list", - Flags: []cli.Flag{ - cli.BoolFlag{Name: "serve, s"}, - cli.BoolFlag{Name: "option, o"}, - cli.StringFlag{Name: "message, m"}, - }, - Action: func(c *cli.Context) error { - fmt.Println("serve:", c.Bool("serve")) - fmt.Println("option:", c.Bool("option")) - fmt.Println("message:", c.String("message")) - return nil - }, - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -If your program has any number of bool flags such as `serve` and `option`, and -optionally one non-bool flag `message`, with the short options of `-s`, `-o`, -and `-m` respectively, setting `UseShortOptionHandling` will also support the -following syntax: - -``` -$ cmd -som "Some message" -``` - -If you enable `UseShortOptionHandling`, then you must not use any flags that -have a single leading `-` or this will result in failures. For example, -`-option` can no longer be used. Flags with two leading dashes (such as -`--options`) are still valid. - -### Bash Completion - -You can enable completion commands by setting the `EnableBashCompletion` -flag on the `App` object. By default, this setting will only auto-complete to -show an app's subcommands, but you can write your own completion methods for -the App or its subcommands. - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} - - app := cli.NewApp() - app.EnableBashCompletion = true - app.Commands = []cli.Command{ - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - fmt.Println("completed task: ", c.Args().First()) - return nil - }, - BashComplete: func(c *cli.Context) { - // This will complete if no args are passed - if c.NArg() > 0 { - return - } - for _, t := range tasks { - fmt.Println(t) - } - }, - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -#### Enabling - -Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while -setting the `PROG` variable to the name of your program: - -`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` - -#### Distribution - -Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename -it to the name of the program you wish to add autocomplete support for (or -automatically install it there if you are distributing a package). Don't forget -to source the file to make it active in the current shell. - -``` -sudo cp src/bash_autocomplete /etc/bash_completion.d/ -source /etc/bash_completion.d/ -``` - -Alternatively, you can just document that users should source the generic -`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set -to the name of their program (as above). - -#### Customization - -The default bash completion flag (`--generate-bash-completion`) is defined as -`cli.BashCompletionFlag`, and may be redefined if desired, e.g.: - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - cli.BashCompletionFlag = cli.BoolFlag{ - Name: "compgen", - Hidden: true, - } - - app := cli.NewApp() - app.EnableBashCompletion = true - app.Commands = []cli.Command{ - { - Name: "wat", - }, - } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -### Generated Help Text - -The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked -by the cli internals in order to print generated help text for the app, command, -or subcommand, and break execution. - -#### Customization - -All of the help text generation may be customized, and at multiple levels. The -templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and -`SubcommandHelpTemplate` which may be reassigned or augmented, and full override -is possible by assigning a compatible func to the `cli.HelpPrinter` variable, -e.g.: - - -``` go -package main - -import ( - "fmt" - "log" - "io" - "os" - - "github.com/urfave/cli" -) - -func main() { - // EXAMPLE: Append to an existing template - cli.AppHelpTemplate = fmt.Sprintf(`%s - -WEBSITE: http://awesometown.example.com - -SUPPORT: support@awesometown.example.com - -`, cli.AppHelpTemplate) - - // EXAMPLE: Override a template - cli.AppHelpTemplate = `NAME: - {{.Name}} - {{.Usage}} -USAGE: - {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} - {{if len .Authors}} -AUTHOR: - {{range .Authors}}{{ . }}{{end}} - {{end}}{{if .Commands}} -COMMANDS: -{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} -GLOBAL OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}}{{if .Copyright }} -COPYRIGHT: - {{.Copyright}} - {{end}}{{if .Version}} -VERSION: - {{.Version}} - {{end}} -` - - // EXAMPLE: Replace the `HelpPrinter` func - cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { - fmt.Println("Ha HA. I pwnd the help!!1") - } - - err := cli.NewApp().Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -The default flag may be customized to something other than `-h/--help` by -setting `cli.HelpFlag`, e.g.: - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - cli.HelpFlag = cli.BoolFlag{ - Name: "halp, haaaaalp", - Usage: "HALP", - EnvVar: "SHOW_HALP,HALPPLZ", - } - - err := cli.NewApp().Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -### Version Flag - -The default version flag (`-v/--version`) is defined as `cli.VersionFlag`, which -is checked by the cli internals in order to print the `App.Version` via -`cli.VersionPrinter` and break execution. - -#### Customization - -The default flag may be customized to something other than `-v/--version` by -setting `cli.VersionFlag`, e.g.: - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - cli.VersionFlag = cli.BoolFlag{ - Name: "print-version, V", - Usage: "print only the version", - } - - app := cli.NewApp() - app.Name = "partay" - app.Version = "19.99.0" - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e.g.: - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -var ( - Revision = "fafafaf" -) - -func main() { - cli.VersionPrinter = func(c *cli.Context) { - fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision) - } - - app := cli.NewApp() - app.Name = "partay" - app.Version = "19.99.0" - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -#### Full API Example - -**Notice**: This is a contrived (functioning) example meant strictly for API -demonstration purposes. Use of one's imagination is encouraged. - - -``` go -package main - -import ( - "errors" - "flag" - "fmt" - "io" - "io/ioutil" - "os" - "time" - - "github.com/urfave/cli" -) - -func init() { - cli.AppHelpTemplate += "\nCUSTOMIZED: you bet ur muffins\n" - cli.CommandHelpTemplate += "\nYMMV\n" - cli.SubcommandHelpTemplate += "\nor something\n" - - cli.HelpFlag = cli.BoolFlag{Name: "halp"} - cli.BashCompletionFlag = cli.BoolFlag{Name: "compgen", Hidden: true} - cli.VersionFlag = cli.BoolFlag{Name: "print-version, V"} - - cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { - fmt.Fprintf(w, "best of luck to you\n") - } - cli.VersionPrinter = func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "version=%s\n", c.App.Version) - } - cli.OsExiter = func(c int) { - fmt.Fprintf(cli.ErrWriter, "refusing to exit %d\n", c) - } - cli.ErrWriter = ioutil.Discard - cli.FlagStringer = func(fl cli.Flag) string { - return fmt.Sprintf("\t\t%s", fl.GetName()) - } -} - -type hexWriter struct{} - -func (w *hexWriter) Write(p []byte) (int, error) { - for _, b := range p { - fmt.Printf("%x", b) - } - fmt.Printf("\n") - - return len(p), nil -} - -type genericType struct{ - s string -} - -func (g *genericType) Set(value string) error { - g.s = value - return nil -} - -func (g *genericType) String() string { - return g.s -} - -func main() { - app := cli.NewApp() - app.Name = "kənˈtrīv" - app.Version = "19.99.0" - app.Compiled = time.Now() - app.Authors = []cli.Author{ - cli.Author{ - Name: "Example Human", - Email: "human@example.com", - }, - } - app.Copyright = "(c) 1999 Serious Enterprise" - app.HelpName = "contrive" - app.Usage = "demonstrate available API" - app.UsageText = "contrive - demonstrating the available API" - app.ArgsUsage = "[args and such]" - app.Commands = []cli.Command{ - cli.Command{ - Name: "doo", - Aliases: []string{"do"}, - Category: "motion", - Usage: "do the doo", - UsageText: "doo - does the dooing", - Description: "no really, there is a lot of dooing to be done", - ArgsUsage: "[arrgh]", - Flags: []cli.Flag{ - cli.BoolFlag{Name: "forever, forevvarr"}, - }, - Subcommands: cli.Commands{ - cli.Command{ - Name: "wop", - Action: wopAction, - }, - }, - SkipFlagParsing: false, - HideHelp: false, - Hidden: false, - HelpName: "doo!", - BashComplete: func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "--better\n") - }, - Before: func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "brace for impact\n") - return nil - }, - After: func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "did we lose anyone?\n") - return nil - }, - Action: func(c *cli.Context) error { - c.Command.FullName() - c.Command.HasName("wop") - c.Command.Names() - c.Command.VisibleFlags() - fmt.Fprintf(c.App.Writer, "dodododododoodododddooooododododooo\n") - if c.Bool("forever") { - c.Command.Run(c) - } - return nil - }, - OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { - fmt.Fprintf(c.App.Writer, "for shame\n") - return err - }, - }, - } - app.Flags = []cli.Flag{ - cli.BoolFlag{Name: "fancy"}, - cli.BoolTFlag{Name: "fancier"}, - cli.DurationFlag{Name: "howlong, H", Value: time.Second * 3}, - cli.Float64Flag{Name: "howmuch"}, - cli.GenericFlag{Name: "wat", Value: &genericType{}}, - cli.Int64Flag{Name: "longdistance"}, - cli.Int64SliceFlag{Name: "intervals"}, - cli.IntFlag{Name: "distance"}, - cli.IntSliceFlag{Name: "times"}, - cli.StringFlag{Name: "dance-move, d"}, - cli.StringSliceFlag{Name: "names, N"}, - cli.UintFlag{Name: "age"}, - cli.Uint64Flag{Name: "bigage"}, - } - app.EnableBashCompletion = true - app.UseShortOptionHandling = true - app.HideHelp = false - app.HideVersion = false - app.BashComplete = func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") - } - app.Before = func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n") - return nil - } - app.After = func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "Phew!\n") - return nil - } - app.CommandNotFound = func(c *cli.Context, command string) { - fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command) - } - app.OnUsageError = func(c *cli.Context, err error, isSubcommand bool) error { - if isSubcommand { - return err - } - - fmt.Fprintf(c.App.Writer, "WRONG: %#v\n", err) - return nil - } - app.Action = func(c *cli.Context) error { - cli.DefaultAppComplete(c) - cli.HandleExitCoder(errors.New("not an exit coder, though")) - cli.ShowAppHelp(c) - cli.ShowCommandCompletions(c, "nope") - cli.ShowCommandHelp(c, "also-nope") - cli.ShowCompletions(c) - cli.ShowSubcommandHelp(c) - cli.ShowVersion(c) - - categories := c.App.Categories() - categories.AddCommand("sounds", cli.Command{ - Name: "bloop", - }) - - for _, category := range c.App.Categories() { - fmt.Fprintf(c.App.Writer, "%s\n", category.Name) - fmt.Fprintf(c.App.Writer, "%#v\n", category.Commands) - fmt.Fprintf(c.App.Writer, "%#v\n", category.VisibleCommands()) - } - - fmt.Printf("%#v\n", c.App.Command("doo")) - if c.Bool("infinite") { - c.App.Run([]string{"app", "doo", "wop"}) - } - - if c.Bool("forevar") { - c.App.RunAsSubcommand(c) - } - c.App.Setup() - fmt.Printf("%#v\n", c.App.VisibleCategories()) - fmt.Printf("%#v\n", c.App.VisibleCommands()) - fmt.Printf("%#v\n", c.App.VisibleFlags()) - - fmt.Printf("%#v\n", c.Args().First()) - if len(c.Args()) > 0 { - fmt.Printf("%#v\n", c.Args()[1]) - } - fmt.Printf("%#v\n", c.Args().Present()) - fmt.Printf("%#v\n", c.Args().Tail()) - - set := flag.NewFlagSet("contrive", 0) - nc := cli.NewContext(c.App, set, c) - - fmt.Printf("%#v\n", nc.Args()) - fmt.Printf("%#v\n", nc.Bool("nope")) - fmt.Printf("%#v\n", nc.BoolT("nerp")) - fmt.Printf("%#v\n", nc.Duration("howlong")) - fmt.Printf("%#v\n", nc.Float64("hay")) - fmt.Printf("%#v\n", nc.Generic("bloop")) - fmt.Printf("%#v\n", nc.Int64("bonk")) - fmt.Printf("%#v\n", nc.Int64Slice("burnks")) - fmt.Printf("%#v\n", nc.Int("bips")) - fmt.Printf("%#v\n", nc.IntSlice("blups")) - fmt.Printf("%#v\n", nc.String("snurt")) - fmt.Printf("%#v\n", nc.StringSlice("snurkles")) - fmt.Printf("%#v\n", nc.Uint("flub")) - fmt.Printf("%#v\n", nc.Uint64("florb")) - fmt.Printf("%#v\n", nc.GlobalBool("global-nope")) - fmt.Printf("%#v\n", nc.GlobalBoolT("global-nerp")) - fmt.Printf("%#v\n", nc.GlobalDuration("global-howlong")) - fmt.Printf("%#v\n", nc.GlobalFloat64("global-hay")) - fmt.Printf("%#v\n", nc.GlobalGeneric("global-bloop")) - fmt.Printf("%#v\n", nc.GlobalInt("global-bips")) - fmt.Printf("%#v\n", nc.GlobalIntSlice("global-blups")) - fmt.Printf("%#v\n", nc.GlobalString("global-snurt")) - fmt.Printf("%#v\n", nc.GlobalStringSlice("global-snurkles")) - - fmt.Printf("%#v\n", nc.FlagNames()) - fmt.Printf("%#v\n", nc.GlobalFlagNames()) - fmt.Printf("%#v\n", nc.GlobalIsSet("wat")) - fmt.Printf("%#v\n", nc.GlobalSet("wat", "nope")) - fmt.Printf("%#v\n", nc.NArg()) - fmt.Printf("%#v\n", nc.NumFlags()) - fmt.Printf("%#v\n", nc.Parent()) - - nc.Set("wat", "also-nope") - - ec := cli.NewExitError("ohwell", 86) - fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) - fmt.Printf("made it!\n") - return nil - } - - if os.Getenv("HEXY") != "" { - app.Writer = &hexWriter{} - app.ErrWriter = &hexWriter{} - } - - app.Metadata = map[string]interface{}{ - "layers": "many", - "explicable": false, - "whatever-values": 19.99, - } - - - // ignore error so we don't exit non-zero and break gfmrun README example tests - _ = app.Run(os.Args) -} - -func wopAction(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, ":wave: over here, eh\n") - return nil -} -``` - -## Migrating to V2 - -There are a small set of breaking changes between v1 and v2. -Converting is relatively straightforward and typically takes less than -an hour. Specific steps are included in -[Migration Guide: v1 to v2](../migrate-v1-to-v2.md). diff --git a/docs/v1/migrating-to-v2.md b/docs/v1/migrating-to-v2.md new file mode 100644 index 0000000000..ec186efdb6 --- /dev/null +++ b/docs/v1/migrating-to-v2.md @@ -0,0 +1,6 @@ +## Migrating to V2 + +There are a small set of breaking changes between v1 and v2. +Converting is relatively straightforward and typically takes less than +an hour. Specific steps are included in +[Migration Guide: v1 to v2](../migrate-v1-to-v2.md). diff --git a/mkdocs.yml b/mkdocs.yml index 73b88c5093..16d67482c2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -11,7 +11,21 @@ edit_uri: edit/main/docs/ nav: - Home: index.md - v2 Manual: v2/index.md - - v1 Manual: v1/index.md + - v1 Manual: + - Getting Started: v1/getting-started.md + - Migrating to v2: v1/migrating-to-v2.md + - Examples: + - Greet: v1/examples/greet.md + - Arguments: v1/examples/arguments.md + - Flags: v1/examples/flags.md + - Subcommands: v1/examples/subcommands.md + - Subcommands (Categories): v1/examples/subcommands-categories.md + - Exit Codes: v1/examples/exit-codes.md + - Combining Short Options: v1/examples/combining-short-options.md + - Bash Completions: v1/examples/bash-completions.md + - Generated Help Text: v1/examples/generated-help-text.md + - Version Flag: v1/examples/version-flag.md + theme: name: material palette: @@ -25,6 +39,14 @@ theme: toggle: icon: material/brightness-7 name: light mode + features: + - content.code.annotate + - navigation.top + - navigation.instant + - navigation.expand + - navigation.sections + - navigation.tabs + - navigation.tabs.sticky plugins: - git-revision-date-localized - search From edaf885773813e3f086a53cd83413e444a53663c Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sun, 14 Aug 2022 15:55:31 -0800 Subject: [PATCH 021/136] breakup v2 documentation --- docs/v2/examples/arguments.md | 29 + docs/v2/examples/bash-completions.md | 250 +++ docs/v2/examples/combining-short-options.md | 67 + docs/v2/examples/exit-codes.md | 38 + docs/v2/examples/flags.md | 550 ++++++ docs/v2/examples/full-api-example.md | 255 +++ docs/v2/examples/generated-help-text.md | 94 + docs/v2/examples/greet.md | 66 + docs/v2/examples/subcommands-categories.md | 47 + docs/v2/examples/subcommands.md | 69 + docs/v2/examples/suggestions.md | 4 + docs/v2/examples/timestamp-flag.md | 57 + docs/v2/examples/version-flag.md | 70 + docs/v2/getting-started.md | 56 + docs/v2/index.md | 1 - docs/v2/manual.md | 1703 ------------------- docs/v2/migrating-from-older-releases.md | 5 + 17 files changed, 1657 insertions(+), 1704 deletions(-) create mode 100644 docs/v2/examples/arguments.md create mode 100644 docs/v2/examples/bash-completions.md create mode 100644 docs/v2/examples/combining-short-options.md create mode 100644 docs/v2/examples/exit-codes.md create mode 100644 docs/v2/examples/flags.md create mode 100644 docs/v2/examples/full-api-example.md create mode 100644 docs/v2/examples/generated-help-text.md create mode 100644 docs/v2/examples/greet.md create mode 100644 docs/v2/examples/subcommands-categories.md create mode 100644 docs/v2/examples/subcommands.md create mode 100644 docs/v2/examples/suggestions.md create mode 100644 docs/v2/examples/timestamp-flag.md create mode 100644 docs/v2/examples/version-flag.md create mode 100644 docs/v2/getting-started.md delete mode 120000 docs/v2/index.md delete mode 100644 docs/v2/manual.md create mode 100644 docs/v2/migrating-from-older-releases.md diff --git a/docs/v2/examples/arguments.md b/docs/v2/examples/arguments.md new file mode 100644 index 0000000000..ccfd4c0dd3 --- /dev/null +++ b/docs/v2/examples/arguments.md @@ -0,0 +1,29 @@ +You can lookup arguments by calling the `Args` function on `cli.Context`, e.g.: + + +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Action: func(cCtx *cli.Context) error { + fmt.Printf("Hello %q", cCtx.Args().Get(0)) + return nil + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` diff --git a/docs/v2/examples/bash-completions.md b/docs/v2/examples/bash-completions.md new file mode 100644 index 0000000000..0dde02fc0a --- /dev/null +++ b/docs/v2/examples/bash-completions.md @@ -0,0 +1,250 @@ +You can enable completion commands by setting the `EnableBashCompletion` flag on +the `App` object to `true`. By default, this setting will allow auto-completion +for an app's subcommands, but you can write your own completion methods for the +App or its subcommands as well. + +#### Default auto-completion + +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + EnableBashCompletion: true, + Commands: []*cli.Command{ + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(cCtx *cli.Context) error { + fmt.Println("added task: ", cCtx.Args().First()) + return nil + }, + }, + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(cCtx *cli.Context) error { + fmt.Println("completed task: ", cCtx.Args().First()) + return nil + }, + }, + { + Name: "template", + Aliases: []string{"t"}, + Usage: "options for task templates", + Subcommands: []*cli.Command{ + { + Name: "add", + Usage: "add a new template", + Action: func(cCtx *cli.Context) error { + fmt.Println("new task template: ", cCtx.Args().First()) + return nil + }, + }, + { + Name: "remove", + Usage: "remove an existing template", + Action: func(cCtx *cli.Context) error { + fmt.Println("removed task template: ", cCtx.Args().First()) + return nil + }, + }, + }, + }, + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` +![](/docs/v2/images/default-bash-autocomplete.gif) + +#### Custom auto-completion + +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} + + app := &cli.App{ + EnableBashCompletion: true, + Commands: []*cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(cCtx *cli.Context) error { + fmt.Println("completed task: ", cCtx.Args().First()) + return nil + }, + BashComplete: func(cCtx *cli.Context) { + // This will complete if no args are passed + if cCtx.NArg() > 0 { + return + } + for _, t := range tasks { + fmt.Println(t) + } + }, + }, + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` +![](/docs/v2/images/custom-bash-autocomplete.gif) + +#### Enabling + +To enable auto-completion for the current shell session, a bash script, +`autocomplete/bash_autocomplete` is included in this repo. + +To use `autocomplete/bash_autocomplete` set an environment variable named `PROG` +to the name of your program and then `source` the +`autocomplete/bash_autocomplete` file. + +For example, if your cli program is called `myprogram`: + +```sh-session +$ PROG=myprogram source path/to/cli/autocomplete/bash_autocomplete +``` + +Auto-completion is now enabled for the current shell, but will not persist into +a new shell. + +#### Distribution and Persistent Autocompletion + +Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename +it to the name of the program you wish to add autocomplete support for (or +automatically install it there if you are distributing a package). Don't forget +to source the file or restart your shell to activate the auto-completion. + +```sh-session +$ sudo cp path/to/autocomplete/bash_autocomplete /etc/bash_completion.d/ +$ source /etc/bash_completion.d/ +``` + +Alternatively, you can just document that users should `source` the generic +`autocomplete/bash_autocomplete` and set `$PROG` within their bash configuration +file, adding these lines: + +```sh-session +$ PROG= +$ source path/to/cli/autocomplete/bash_autocomplete +``` + +Keep in mind that if they are enabling auto-completion for more than one +program, they will need to set `PROG` and source +`autocomplete/bash_autocomplete` for each program, like so: + +```sh-session +$ PROG= +$ source path/to/cli/autocomplete/bash_autocomplete + +$ PROG= +$ source path/to/cli/autocomplete/bash_autocomplete +``` + +#### Customization + +The default shell completion flag (`--generate-bash-completion`) is defined as +`cli.EnableBashCompletion`, and may be redefined if desired, e.g.: + + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + EnableBashCompletion: true, + Commands: []*cli.Command{ + { + Name: "wat", + }, + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + +#### ZSH Support + +Auto-completion for ZSH is also supported using the +`autocomplete/zsh_autocomplete` file included in this repo. One environment +variable is used, `PROG`. Set `PROG` to the program name as before, and then +`source path/to/autocomplete/zsh_autocomplete`. Adding the following lines to +your ZSH configuration file (usually `.zshrc`) will allow the auto-completion to +persist across new shells: + +```sh-session +$ PROG= +$ source path/to/autocomplete/zsh_autocomplete +``` + +#### ZSH default auto-complete example +![](/docs/v2/images/default-zsh-autocomplete.gif) + +#### ZSH custom auto-complete example +![](/docs/v2/images/custom-zsh-autocomplete.gif) + +#### PowerShell Support + +Auto-completion for PowerShell is also supported using the +`autocomplete/powershell_autocomplete.ps1` file included in this repo. + +Rename the script to `.ps1` and move it anywhere in your file +system. The location of script does not matter, only the file name of the +script has to match the your program's binary name. + +To activate it, enter: + +```powershell +& path/to/autocomplete/.ps1 +``` + +To persist across new shells, open the PowerShell profile (with `code $profile` +or `notepad $profile`) and add the line: + +```powershell +& path/to/autocomplete/.ps1 +``` diff --git a/docs/v2/examples/combining-short-options.md b/docs/v2/examples/combining-short-options.md new file mode 100644 index 0000000000..b0a590ac40 --- /dev/null +++ b/docs/v2/examples/combining-short-options.md @@ -0,0 +1,67 @@ +Traditional use of options using their shortnames look like this: + +```sh-session +$ cmd -s -o -m "Some message" +``` + +Suppose you want users to be able to combine options with their shortnames. This +can be done using the `UseShortOptionHandling` bool in your app configuration, +or for individual commands by attaching it to the command configuration. For +example: + + +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + UseShortOptionHandling: true, + Commands: []*cli.Command{ + { + Name: "short", + Usage: "complete a task on the list", + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "serve", Aliases: []string{"s"}}, + &cli.BoolFlag{Name: "option", Aliases: []string{"o"}}, + &cli.StringFlag{Name: "message", Aliases: []string{"m"}}, + }, + Action: func(cCtx *cli.Context) error { + fmt.Println("serve:", cCtx.Bool("serve")) + fmt.Println("option:", cCtx.Bool("option")) + fmt.Println("message:", cCtx.String("message")) + return nil + }, + }, + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + +If your program has any number of bool flags such as `serve` and `option`, and +optionally one non-bool flag `message`, with the short options of `-s`, `-o`, +and `-m` respectively, setting `UseShortOptionHandling` will also support the +following syntax: + +```sh-session +$ cmd -som "Some message" +``` + +If you enable `UseShortOptionHandling`, then you must not use any flags that +have a single leading `-` or this will result in failures. For example, +`-option` can no longer be used. Flags with two leading dashes (such as +`--options`) are still valid. diff --git a/docs/v2/examples/exit-codes.md b/docs/v2/examples/exit-codes.md new file mode 100644 index 0000000000..90e1384be0 --- /dev/null +++ b/docs/v2/examples/exit-codes.md @@ -0,0 +1,38 @@ +Calling `App.Run` will not automatically call `os.Exit`, which means that by +default the exit code will "fall through" to being `0`. An explicit exit code +may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a +`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.: + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "ginger-crouton", + Usage: "is it in the soup?", + }, + }, + Action: func(ctx *cli.Context) error { + if !ctx.Bool("ginger-crouton") { + return cli.Exit("Ginger croutons are not in the soup", 86) + } + return nil + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` diff --git a/docs/v2/examples/flags.md b/docs/v2/examples/flags.md new file mode 100644 index 0000000000..a3c5e760c4 --- /dev/null +++ b/docs/v2/examples/flags.md @@ -0,0 +1,550 @@ +Setting and querying flags is simple. + + +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, + }, + Action: func(cCtx *cli.Context) error { + name := "Nefertiti" + if cCtx.NArg() > 0 { + name = cCtx.Args().Get(0) + } + if cCtx.String("lang") == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + +You can also set a destination variable for a flag, to which the content will be +scanned. + + +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + var language string + + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + Destination: &language, + }, + }, + Action: func(cCtx *cli.Context) error { + name := "someone" + if cCtx.NArg() > 0 { + name = cCtx.Args().Get(0) + } + if language == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + +See full list of flags at https://pkg.go.dev/github.com/urfave/cli/v2 + +#### Placeholder Values + +Sometimes it's useful to specify a flag's value within the usage string itself. +Such placeholders are indicated with back quotes. + +For example this: + + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "Load configuration from `FILE`", + }, + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + +Will result in help output like: + +``` +--config FILE, -c FILE Load configuration from FILE +``` + +Note that only the first placeholder is used. Subsequent back-quoted words will +be left as-is. + +#### Alternate Names + +You can set alternate (or short) names for flags by providing a comma-delimited +list for the `Name`. e.g. + + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + }, + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + +That flag can then be set with `--lang spanish` or `-l spanish`. Note that +giving two different forms of the same flag in the same command invocation is an +error. + +#### Ordering + +Flags for the application and commands are shown in the order they are defined. +However, it's possible to sort them from outside this library by using `FlagsByName` +or `CommandsByName` with `sort`. + +For example this: + + +```go +package main + +import ( + "log" + "os" + "sort" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "Language for the greeting", + }, + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "Load configuration from `FILE`", + }, + }, + Commands: []*cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(*cli.Context) error { + return nil + }, + }, + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(*cli.Context) error { + return nil + }, + }, + }, + } + + sort.Sort(cli.FlagsByName(app.Flags)) + sort.Sort(cli.CommandsByName(app.Commands)) + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + +Will result in help output like: + +``` +--config FILE, -c FILE Load configuration from FILE +--lang value, -l value Language for the greeting (default: "english") +``` + +#### Values from the Environment + +You can also have the default value set from the environment via `EnvVars`. e.g. + + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + EnvVars: []string{"APP_LANG"}, + }, + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + +If `EnvVars` contains more than one string, the first environment variable that +resolves is used. + + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + EnvVars: []string{"LEGACY_COMPAT_LANG", "APP_LANG", "LANG"}, + }, + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + +#### Values from files + +You can also have the default value set from file via `FilePath`. e.g. + + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "password", + Aliases: []string{"p"}, + Usage: "password for the mysql database", + FilePath: "/etc/mysql/password", + }, + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + +Note that default values set from file (e.g. `FilePath`) take precedence over +default values set from the environment (e.g. `EnvVar`). + +#### Values from alternate input sources (YAML, TOML, and others) + +There is a separate package altsrc that adds support for getting flag values +from other file input sources. + +Currently supported input source formats: + +- YAML +- JSON +- TOML + +In order to get values for a flag from an alternate input source the following +code would be added to wrap an existing cli.Flag like below: + +```go + // --- >8 --- + altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}) +``` + +Initialization must also occur for these flags. Below is an example initializing +getting data from a yaml file below. + +```go + // --- >8 --- + command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) +``` + +The code above will use the "load" string as a flag name to get the file name of +a yaml file from the cli.Context. It will then use that file name to initialize +the yaml input source for any flags that are defined on that command. As a note +the "load" flag used would also have to be defined on the command flags in order +for this code snippet to work. + +Currently only YAML, JSON, and TOML files are supported but developers can add +support for other input sources by implementing the altsrc.InputSourceContext +for their given sources. + +Here is a more complete sample of a command using YAML support: + + +```go +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2/altsrc" +) + +func main() { + flags := []cli.Flag{ + altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}), + &cli.StringFlag{Name: "load"}, + } + + app := &cli.App{ + Action: func(*cli.Context) error { + fmt.Println("--test value.*default: 0") + return nil + }, + Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")), + Flags: flags, + } + + app.Run(os.Args) +} +``` + +#### Required Flags + +You can make a flag required by setting the `Required` field to `true`. If a user +does not provide a required flag, they will be shown an error message. + +Take for example this app that requires the `lang` flag: + + +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + Required: true, + }, + }, + Action: func(cCtx *cli.Context) error { + output := "Hello" + if cCtx.String("lang") == "spanish" { + output = "Hola" + } + fmt.Println(output) + return nil + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + +If the app is run without the `lang` flag, the user will see the following message + +``` +Required flag "lang" not set +``` + +#### Default Values for help output + +Sometimes it's useful to specify a flag's default help-text value within the +flag declaration. This can be useful if the default value for a flag is a +computed value. The default value can be set via the `DefaultText` struct field. + +For example this: + + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "port", + Usage: "Use a randomized port", + Value: 0, + DefaultText: "random", + }, + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + +Will result in help output like: + +``` +--port value Use a randomized port (default: random) +``` + +#### Precedence + +The precedence for flag value sources is as follows (highest to lowest): + +0. Command line flag value from user +0. Environment variable (if specified) +0. Configuration file (if specified) +0. Default defined on the flag diff --git a/docs/v2/examples/full-api-example.md b/docs/v2/examples/full-api-example.md new file mode 100644 index 0000000000..53c76ea131 --- /dev/null +++ b/docs/v2/examples/full-api-example.md @@ -0,0 +1,255 @@ +**Notice**: This is a contrived (functioning) example meant strictly for API +demonstration purposes. Use of one's imagination is encouraged. + + +```go +package main + +import ( + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "time" + + "github.com/urfave/cli/v2" +) + +func init() { + cli.AppHelpTemplate += "\nCUSTOMIZED: you bet ur muffins\n" + cli.CommandHelpTemplate += "\nYMMV\n" + cli.SubcommandHelpTemplate += "\nor something\n" + + cli.HelpFlag = &cli.BoolFlag{Name: "halp"} + cli.VersionFlag = &cli.BoolFlag{Name: "print-version", Aliases: []string{"V"}} + + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + fmt.Fprintf(w, "best of luck to you\n") + } + cli.VersionPrinter = func(cCtx *cli.Context) { + fmt.Fprintf(cCtx.App.Writer, "version=%s\n", cCtx.App.Version) + } + cli.OsExiter = func(cCtx int) { + fmt.Fprintf(cli.ErrWriter, "refusing to exit %d\n", cCtx) + } + cli.ErrWriter = ioutil.Discard + cli.FlagStringer = func(fl cli.Flag) string { + return fmt.Sprintf("\t\t%s", fl.Names()[0]) + } +} + +type hexWriter struct{} + +func (w *hexWriter) Write(p []byte) (int, error) { + for _, b := range p { + fmt.Printf("%x", b) + } + fmt.Printf("\n") + + return len(p), nil +} + +type genericType struct { + s string +} + +func (g *genericType) Set(value string) error { + g.s = value + return nil +} + +func (g *genericType) String() string { + return g.s +} + +func main() { + app := &cli.App{ + Name: "kənˈtrīv", + Version: "v19.99.0", + Compiled: time.Now(), + Authors: []*cli.Author{ + &cli.Author{ + Name: "Example Human", + Email: "human@example.com", + }, + }, + Copyright: "(c) 1999 Serious Enterprise", + HelpName: "contrive", + Usage: "demonstrate available API", + UsageText: "contrive - demonstrating the available API", + ArgsUsage: "[args and such]", + Commands: []*cli.Command{ + &cli.Command{ + Name: "doo", + Aliases: []string{"do"}, + Category: "motion", + Usage: "do the doo", + UsageText: "doo - does the dooing", + Description: "no really, there is a lot of dooing to be done", + ArgsUsage: "[arrgh]", + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "forever", Aliases: []string{"forevvarr"}}, + }, + Subcommands: []*cli.Command{ + &cli.Command{ + Name: "wop", + Action: wopAction, + }, + }, + SkipFlagParsing: false, + HideHelp: false, + Hidden: false, + HelpName: "doo!", + BashComplete: func(cCtx *cli.Context) { + fmt.Fprintf(cCtx.App.Writer, "--better\n") + }, + Before: func(cCtx *cli.Context) error { + fmt.Fprintf(cCtx.App.Writer, "brace for impact\n") + return nil + }, + After: func(cCtx *cli.Context) error { + fmt.Fprintf(cCtx.App.Writer, "did we lose anyone?\n") + return nil + }, + Action: func(cCtx *cli.Context) error { + cCtx.Command.FullName() + cCtx.Command.HasName("wop") + cCtx.Command.Names() + cCtx.Command.VisibleFlags() + fmt.Fprintf(cCtx.App.Writer, "dodododododoodododddooooododododooo\n") + if cCtx.Bool("forever") { + cCtx.Command.Run(cCtx) + } + return nil + }, + OnUsageError: func(cCtx *cli.Context, err error, isSubcommand bool) error { + fmt.Fprintf(cCtx.App.Writer, "for shame\n") + return err + }, + }, + }, + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "fancy"}, + &cli.BoolFlag{Value: true, Name: "fancier"}, + &cli.DurationFlag{Name: "howlong", Aliases: []string{"H"}, Value: time.Second * 3}, + &cli.Float64Flag{Name: "howmuch"}, + &cli.GenericFlag{Name: "wat", Value: &genericType{}}, + &cli.Int64Flag{Name: "longdistance"}, + &cli.Int64SliceFlag{Name: "intervals"}, + &cli.IntFlag{Name: "distance"}, + &cli.IntSliceFlag{Name: "times"}, + &cli.StringFlag{Name: "dance-move", Aliases: []string{"d"}}, + &cli.StringSliceFlag{Name: "names", Aliases: []string{"N"}}, + &cli.UintFlag{Name: "age"}, + &cli.Uint64Flag{Name: "bigage"}, + }, + EnableBashCompletion: true, + HideHelp: false, + HideVersion: false, + BashComplete: func(cCtx *cli.Context) { + fmt.Fprintf(cCtx.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") + }, + Before: func(cCtx *cli.Context) error { + fmt.Fprintf(cCtx.App.Writer, "HEEEERE GOES\n") + return nil + }, + After: func(cCtx *cli.Context) error { + fmt.Fprintf(cCtx.App.Writer, "Phew!\n") + return nil + }, + CommandNotFound: func(cCtx *cli.Context, command string) { + fmt.Fprintf(cCtx.App.Writer, "Thar be no %q here.\n", command) + }, + OnUsageError: func(cCtx *cli.Context, err error, isSubcommand bool) error { + if isSubcommand { + return err + } + + fmt.Fprintf(cCtx.App.Writer, "WRONG: %#v\n", err) + return nil + }, + Action: func(cCtx *cli.Context) error { + cli.DefaultAppComplete(cCtx) + cli.HandleExitCoder(errors.New("not an exit coder, though")) + cli.ShowAppHelp(cCtx) + cli.ShowCommandCompletions(cCtx, "nope") + cli.ShowCommandHelp(cCtx, "also-nope") + cli.ShowCompletions(cCtx) + cli.ShowSubcommandHelp(cCtx) + cli.ShowVersion(cCtx) + + fmt.Printf("%#v\n", cCtx.App.Command("doo")) + if cCtx.Bool("infinite") { + cCtx.App.Run([]string{"app", "doo", "wop"}) + } + + if cCtx.Bool("forevar") { + cCtx.App.RunAsSubcommand(cCtx) + } + cCtx.App.Setup() + fmt.Printf("%#v\n", cCtx.App.VisibleCategories()) + fmt.Printf("%#v\n", cCtx.App.VisibleCommands()) + fmt.Printf("%#v\n", cCtx.App.VisibleFlags()) + + fmt.Printf("%#v\n", cCtx.Args().First()) + if cCtx.Args().Len() > 0 { + fmt.Printf("%#v\n", cCtx.Args().Get(1)) + } + fmt.Printf("%#v\n", cCtx.Args().Present()) + fmt.Printf("%#v\n", cCtx.Args().Tail()) + + set := flag.NewFlagSet("contrive", 0) + nc := cli.NewContext(cCtx.App, set, cCtx) + + fmt.Printf("%#v\n", nc.Args()) + fmt.Printf("%#v\n", nc.Bool("nope")) + fmt.Printf("%#v\n", !nc.Bool("nerp")) + fmt.Printf("%#v\n", nc.Duration("howlong")) + fmt.Printf("%#v\n", nc.Float64("hay")) + fmt.Printf("%#v\n", nc.Generic("bloop")) + fmt.Printf("%#v\n", nc.Int64("bonk")) + fmt.Printf("%#v\n", nc.Int64Slice("burnks")) + fmt.Printf("%#v\n", nc.Int("bips")) + fmt.Printf("%#v\n", nc.IntSlice("blups")) + fmt.Printf("%#v\n", nc.String("snurt")) + fmt.Printf("%#v\n", nc.StringSlice("snurkles")) + fmt.Printf("%#v\n", nc.Uint("flub")) + fmt.Printf("%#v\n", nc.Uint64("florb")) + + fmt.Printf("%#v\n", nc.FlagNames()) + fmt.Printf("%#v\n", nc.IsSet("wat")) + fmt.Printf("%#v\n", nc.Set("wat", "nope")) + fmt.Printf("%#v\n", nc.NArg()) + fmt.Printf("%#v\n", nc.NumFlags()) + fmt.Printf("%#v\n", nc.Lineage()[1]) + nc.Set("wat", "also-nope") + + ec := cli.Exit("ohwell", 86) + fmt.Fprintf(cCtx.App.Writer, "%d", ec.ExitCode()) + fmt.Printf("made it!\n") + return ec + }, + Metadata: map[string]interface{}{ + "layers": "many", + "explicable": false, + "whatever-values": 19.99, + }, + } + + if os.Getenv("HEXY") != "" { + app.Writer = &hexWriter{} + app.ErrWriter = &hexWriter{} + } + + app.Run(os.Args) +} + +func wopAction(cCtx *cli.Context) error { + fmt.Fprintf(cCtx.App.Writer, ":wave: over here, eh\n") + return nil +} +``` diff --git a/docs/v2/examples/generated-help-text.md b/docs/v2/examples/generated-help-text.md new file mode 100644 index 0000000000..caa3a38e6f --- /dev/null +++ b/docs/v2/examples/generated-help-text.md @@ -0,0 +1,94 @@ +The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked +by the cli internals in order to print generated help text for the app, command, +or subcommand, and break execution. + +#### Customization + +All of the help text generation may be customized, and at multiple levels. The +templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and +`SubcommandHelpTemplate` which may be reassigned or augmented, and full override +is possible by assigning a compatible func to the `cli.HelpPrinter` variable, +e.g.: + + +```go +package main + +import ( + "fmt" + "io" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + // EXAMPLE: Append to an existing template + cli.AppHelpTemplate = fmt.Sprintf(`%s + +WEBSITE: http://awesometown.example.com + +SUPPORT: support@awesometown.example.com + +`, cli.AppHelpTemplate) + + // EXAMPLE: Override a template + cli.AppHelpTemplate = `NAME: + {{.Name}} - {{.Usage}} +USAGE: + {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} + {{if len .Authors}} +AUTHOR: + {{range .Authors}}{{ . }}{{end}} + {{end}}{{if .Commands}} +COMMANDS: +{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} +GLOBAL OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}}{{if .Copyright }} +COPYRIGHT: + {{.Copyright}} + {{end}}{{if .Version}} +VERSION: + {{.Version}} + {{end}} +` + + // EXAMPLE: Replace the `HelpPrinter` func + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + fmt.Println("Ha HA. I pwnd the help!!1") + } + + (&cli.App{}).Run(os.Args) +} +``` + +The default flag may be customized to something other than `-h/--help` by +setting `cli.HelpFlag`, e.g.: + + +```go +package main + +import ( + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + cli.HelpFlag = &cli.BoolFlag{ + Name: "haaaaalp", + Aliases: []string{"halp"}, + Usage: "HALP", + EnvVars: []string{"SHOW_HALP", "HALPPLZ"}, + } + + (&cli.App{}).Run(os.Args) +} +``` diff --git a/docs/v2/examples/greet.md b/docs/v2/examples/greet.md new file mode 100644 index 0000000000..cedee7d749 --- /dev/null +++ b/docs/v2/examples/greet.md @@ -0,0 +1,66 @@ +Being a programmer can be a lonely job. Thankfully by the power of automation +that is not the case! Let's create a greeter app to fend off our demons of +loneliness! + +Start by creating a directory named `greet`, and within it, add a file, +`greet.go` with the following code in it: + + +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Name: "greet", + Usage: "fight the loneliness!", + Action: func(*cli.Context) error { + fmt.Println("Hello friend!") + return nil + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + +Install our command to the `$GOPATH/bin` directory: + +```sh-session +$ go install +``` + +Finally run our new command: + +```sh-session +$ greet +Hello friend! +``` + +cli also generates neat help text: + +```sh-session +$ greet help +NAME: + greet - fight the loneliness! + +USAGE: + greet [global options] command [command options] [arguments...] + +COMMANDS: + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS + --help, -h show help (default: false) +``` diff --git a/docs/v2/examples/subcommands-categories.md b/docs/v2/examples/subcommands-categories.md new file mode 100644 index 0000000000..e8ae185e5e --- /dev/null +++ b/docs/v2/examples/subcommands-categories.md @@ -0,0 +1,47 @@ +For additional organization in apps that have many subcommands, you can +associate a category for each command to group them together in the help +output, e.g.: + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Commands: []*cli.Command{ + { + Name: "noop", + }, + { + Name: "add", + Category: "template", + }, + { + Name: "remove", + Category: "template", + }, + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + +Will include: + +``` +COMMANDS: + noop + + Template actions: + add + remove +``` diff --git a/docs/v2/examples/subcommands.md b/docs/v2/examples/subcommands.md new file mode 100644 index 0000000000..b7048b943d --- /dev/null +++ b/docs/v2/examples/subcommands.md @@ -0,0 +1,69 @@ +Subcommands can be defined for a more git-like command line app. + + +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Commands: []*cli.Command{ + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(cCtx *cli.Context) error { + fmt.Println("added task: ", cCtx.Args().First()) + return nil + }, + }, + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(cCtx *cli.Context) error { + fmt.Println("completed task: ", cCtx.Args().First()) + return nil + }, + }, + { + Name: "template", + Aliases: []string{"t"}, + Usage: "options for task templates", + Subcommands: []*cli.Command{ + { + Name: "add", + Usage: "add a new template", + Action: func(cCtx *cli.Context) error { + fmt.Println("new task template: ", cCtx.Args().First()) + return nil + }, + }, + { + Name: "remove", + Usage: "remove an existing template", + Action: func(cCtx *cli.Context) error { + fmt.Println("removed task template: ", cCtx.Args().First()) + return nil + }, + }, + }, + }, + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` diff --git a/docs/v2/examples/suggestions.md b/docs/v2/examples/suggestions.md new file mode 100644 index 0000000000..3e60828a28 --- /dev/null +++ b/docs/v2/examples/suggestions.md @@ -0,0 +1,4 @@ +To enable flag and command suggestions, set `app.Suggest = true`. If the suggest +feature is enabled, then the help output of the corresponding command will +provide an appropriate suggestion for the provided flag or subcommand if +available. diff --git a/docs/v2/examples/timestamp-flag.md b/docs/v2/examples/timestamp-flag.md new file mode 100644 index 0000000000..bcf0262810 --- /dev/null +++ b/docs/v2/examples/timestamp-flag.md @@ -0,0 +1,57 @@ +Using the timestamp flag is simple. Please refer to +[`time.Parse`](https://golang.org/pkg/time/#example_Parse) to get possible +formats. + + +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.TimestampFlag{Name: "meeting", Layout: "2006-01-02T15:04:05"}, + }, + Action: func(cCtx *cli.Context) error { + fmt.Printf("%s", cCtx.Timestamp("meeting").String()) + return nil + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + +In this example the flag could be used like this: + +```sh-session +$ myapp --meeting 2019-08-12T15:04:05 +``` + +When the layout doesn't contain timezones, timestamp will render with UTC. To +change behavior, a default timezone can be provided with flag definition: + +```go +app := &cli.App{ + Flags: []cli.Flag{ + &cli.TimestampFlag{Name: "meeting", Layout: "2006-01-02T15:04:05", Timezone: time.Local}, + }, +} +``` + +(time.Local contains the system's local time zone.) + +Side note: quotes may be necessary around the date depending on your layout (if +you have spaces for instance) diff --git a/docs/v2/examples/version-flag.md b/docs/v2/examples/version-flag.md new file mode 100644 index 0000000000..a014c29652 --- /dev/null +++ b/docs/v2/examples/version-flag.md @@ -0,0 +1,70 @@ +The default version flag (`-v/--version`) is defined as `cli.VersionFlag`, which +is checked by the cli internals in order to print the `App.Version` via +`cli.VersionPrinter` and break execution. + +#### Customization + +The default flag may be customized to something other than `-v/--version` by +setting `cli.VersionFlag`, e.g.: + + +```go +package main + +import ( + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + cli.VersionFlag = &cli.BoolFlag{ + Name: "print-version", + Aliases: []string{"V"}, + Usage: "print only the version", + } + + app := &cli.App{ + Name: "partay", + Version: "v19.99.0", + } + app.Run(os.Args) +} +``` + +Alternatively, the version printer at `cli.VersionPrinter` may be overridden, +e.g.: + + +```go +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli/v2" +) + +var ( + Revision = "fafafaf" +) + +func main() { + cli.VersionPrinter = func(cCtx *cli.Context) { + fmt.Printf("version=%s revision=%s\n", cCtx.App.Version, Revision) + } + + app := &cli.App{ + Name: "partay", + Version: "v19.99.0", + } + app.Run(os.Args) +} +``` diff --git a/docs/v2/getting-started.md b/docs/v2/getting-started.md new file mode 100644 index 0000000000..c2e07501ba --- /dev/null +++ b/docs/v2/getting-started.md @@ -0,0 +1,56 @@ +One of the philosophies behind cli is that an API should be playful and full of +discovery. So a cli app can be as little as one line of code in `main()`. + + +```go +package main + +import ( + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + (&cli.App{}).Run(os.Args) +} +``` + +This app will run and show help text, but is not very useful. Let's give an +action to execute and some help documentation: + + +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Name: "boom", + Usage: "make an explosive entrance", + Action: func(*cli.Context) error { + fmt.Println("boom! I say!") + return nil + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + +Running this already gives you a ton of functionality, plus support for things +like subcommands and flags, which are covered below. diff --git a/docs/v2/index.md b/docs/v2/index.md deleted file mode 120000 index 9d0493a7f2..0000000000 --- a/docs/v2/index.md +++ /dev/null @@ -1 +0,0 @@ -manual.md \ No newline at end of file diff --git a/docs/v2/manual.md b/docs/v2/manual.md deleted file mode 100644 index 475e047fdd..0000000000 --- a/docs/v2/manual.md +++ /dev/null @@ -1,1703 +0,0 @@ -# v2 guide - -## Getting Started - -One of the philosophies behind cli is that an API should be playful and full of -discovery. So a cli app can be as little as one line of code in `main()`. - - -```go -package main - -import ( - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - (&cli.App{}).Run(os.Args) -} -``` - -This app will run and show help text, but is not very useful. Let's give an -action to execute and some help documentation: - - -```go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Name: "boom", - Usage: "make an explosive entrance", - Action: func(*cli.Context) error { - fmt.Println("boom! I say!") - return nil - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -Running this already gives you a ton of functionality, plus support for things -like subcommands and flags, which are covered below. - -## Examples - -Being a programmer can be a lonely job. Thankfully by the power of automation -that is not the case! Let's create a greeter app to fend off our demons of -loneliness! - -Start by creating a directory named `greet`, and within it, add a file, -`greet.go` with the following code in it: - - -```go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Name: "greet", - Usage: "fight the loneliness!", - Action: func(*cli.Context) error { - fmt.Println("Hello friend!") - return nil - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -Install our command to the `$GOPATH/bin` directory: - -```sh-session -$ go install -``` - -Finally run our new command: - -```sh-session -$ greet -Hello friend! -``` - -cli also generates neat help text: - -```sh-session -$ greet help -NAME: - greet - fight the loneliness! - -USAGE: - greet [global options] command [command options] [arguments...] - -COMMANDS: - help, h Shows a list of commands or help for one command - -GLOBAL OPTIONS - --help, -h show help (default: false) -``` - -### Arguments - -You can lookup arguments by calling the `Args` function on `cli.Context`, e.g.: - - -```go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Action: func(cCtx *cli.Context) error { - fmt.Printf("Hello %q", cCtx.Args().Get(0)) - return nil - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -### Flags - -Setting and querying flags is simple. - - -```go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - }, - }, - Action: func(cCtx *cli.Context) error { - name := "Nefertiti" - if cCtx.NArg() > 0 { - name = cCtx.Args().Get(0) - } - if cCtx.String("lang") == "spanish" { - fmt.Println("Hola", name) - } else { - fmt.Println("Hello", name) - } - return nil - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -You can also set a destination variable for a flag, to which the content will be -scanned. - - -```go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - var language string - - app := &cli.App{ - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - Destination: &language, - }, - }, - Action: func(cCtx *cli.Context) error { - name := "someone" - if cCtx.NArg() > 0 { - name = cCtx.Args().Get(0) - } - if language == "spanish" { - fmt.Println("Hola", name) - } else { - fmt.Println("Hello", name) - } - return nil - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -See full list of flags at https://pkg.go.dev/github.com/urfave/cli/v2 - -#### Placeholder Values - -Sometimes it's useful to specify a flag's value within the usage string itself. -Such placeholders are indicated with back quotes. - -For example this: - - -```go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "config", - Aliases: []string{"c"}, - Usage: "Load configuration from `FILE`", - }, - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -Will result in help output like: - -``` ---config FILE, -c FILE Load configuration from FILE -``` - -Note that only the first placeholder is used. Subsequent back-quoted words will -be left as-is. - -#### Alternate Names - -You can set alternate (or short) names for flags by providing a comma-delimited -list for the `Name`. e.g. - - -```go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "lang", - Aliases: []string{"l"}, - Value: "english", - Usage: "language for the greeting", - }, - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -That flag can then be set with `--lang spanish` or `-l spanish`. Note that -giving two different forms of the same flag in the same command invocation is an -error. - -#### Ordering - -Flags for the application and commands are shown in the order they are defined. -However, it's possible to sort them from outside this library by using `FlagsByName` -or `CommandsByName` with `sort`. - -For example this: - - -```go -package main - -import ( - "log" - "os" - "sort" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "lang", - Aliases: []string{"l"}, - Value: "english", - Usage: "Language for the greeting", - }, - &cli.StringFlag{ - Name: "config", - Aliases: []string{"c"}, - Usage: "Load configuration from `FILE`", - }, - }, - Commands: []*cli.Command{ - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(*cli.Context) error { - return nil - }, - }, - { - Name: "add", - Aliases: []string{"a"}, - Usage: "add a task to the list", - Action: func(*cli.Context) error { - return nil - }, - }, - }, - } - - sort.Sort(cli.FlagsByName(app.Flags)) - sort.Sort(cli.CommandsByName(app.Commands)) - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -Will result in help output like: - -``` ---config FILE, -c FILE Load configuration from FILE ---lang value, -l value Language for the greeting (default: "english") -``` - -#### Values from the Environment - -You can also have the default value set from the environment via `EnvVars`. e.g. - - -```go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "lang", - Aliases: []string{"l"}, - Value: "english", - Usage: "language for the greeting", - EnvVars: []string{"APP_LANG"}, - }, - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -If `EnvVars` contains more than one string, the first environment variable that -resolves is used. - - -```go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "lang", - Aliases: []string{"l"}, - Value: "english", - Usage: "language for the greeting", - EnvVars: []string{"LEGACY_COMPAT_LANG", "APP_LANG", "LANG"}, - }, - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -#### Values from files - -You can also have the default value set from file via `FilePath`. e.g. - - -```go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "password", - Aliases: []string{"p"}, - Usage: "password for the mysql database", - FilePath: "/etc/mysql/password", - }, - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -Note that default values set from file (e.g. `FilePath`) take precedence over -default values set from the environment (e.g. `EnvVar`). - -#### Values from alternate input sources (YAML, TOML, and others) - -There is a separate package altsrc that adds support for getting flag values -from other file input sources. - -Currently supported input source formats: - -- YAML -- JSON -- TOML - -In order to get values for a flag from an alternate input source the following -code would be added to wrap an existing cli.Flag like below: - -```go - // --- >8 --- - altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}) -``` - -Initialization must also occur for these flags. Below is an example initializing -getting data from a yaml file below. - -```go - // --- >8 --- - command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) -``` - -The code above will use the "load" string as a flag name to get the file name of -a yaml file from the cli.Context. It will then use that file name to initialize -the yaml input source for any flags that are defined on that command. As a note -the "load" flag used would also have to be defined on the command flags in order -for this code snippet to work. - -Currently only YAML, JSON, and TOML files are supported but developers can add -support for other input sources by implementing the altsrc.InputSourceContext -for their given sources. - -Here is a more complete sample of a command using YAML support: - - -```go -package main - -import ( - "fmt" - "os" - - "github.com/urfave/cli/v2" - "github.com/urfave/cli/v2/altsrc" -) - -func main() { - flags := []cli.Flag{ - altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}), - &cli.StringFlag{Name: "load"}, - } - - app := &cli.App{ - Action: func(*cli.Context) error { - fmt.Println("--test value.*default: 0") - return nil - }, - Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")), - Flags: flags, - } - - app.Run(os.Args) -} -``` - -#### Required Flags - -You can make a flag required by setting the `Required` field to `true`. If a user -does not provide a required flag, they will be shown an error message. - -Take for example this app that requires the `lang` flag: - - -```go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - Required: true, - }, - }, - Action: func(cCtx *cli.Context) error { - output := "Hello" - if cCtx.String("lang") == "spanish" { - output = "Hola" - } - fmt.Println(output) - return nil - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -If the app is run without the `lang` flag, the user will see the following message - -``` -Required flag "lang" not set -``` - -#### Default Values for help output - -Sometimes it's useful to specify a flag's default help-text value within the -flag declaration. This can be useful if the default value for a flag is a -computed value. The default value can be set via the `DefaultText` struct field. - -For example this: - - -```go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.IntFlag{ - Name: "port", - Usage: "Use a randomized port", - Value: 0, - DefaultText: "random", - }, - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -Will result in help output like: - -``` ---port value Use a randomized port (default: random) -``` - -#### Precedence - -The precedence for flag value sources is as follows (highest to lowest): - -0. Command line flag value from user -0. Environment variable (if specified) -0. Configuration file (if specified) -0. Default defined on the flag - -### Subcommands - -Subcommands can be defined for a more git-like command line app. - - -```go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Commands: []*cli.Command{ - { - Name: "add", - Aliases: []string{"a"}, - Usage: "add a task to the list", - Action: func(cCtx *cli.Context) error { - fmt.Println("added task: ", cCtx.Args().First()) - return nil - }, - }, - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(cCtx *cli.Context) error { - fmt.Println("completed task: ", cCtx.Args().First()) - return nil - }, - }, - { - Name: "template", - Aliases: []string{"t"}, - Usage: "options for task templates", - Subcommands: []*cli.Command{ - { - Name: "add", - Usage: "add a new template", - Action: func(cCtx *cli.Context) error { - fmt.Println("new task template: ", cCtx.Args().First()) - return nil - }, - }, - { - Name: "remove", - Usage: "remove an existing template", - Action: func(cCtx *cli.Context) error { - fmt.Println("removed task template: ", cCtx.Args().First()) - return nil - }, - }, - }, - }, - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -### Subcommands categories - -For additional organization in apps that have many subcommands, you can -associate a category for each command to group them together in the help -output, e.g.: - -```go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Commands: []*cli.Command{ - { - Name: "noop", - }, - { - Name: "add", - Category: "template", - }, - { - Name: "remove", - Category: "template", - }, - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -Will include: - -``` -COMMANDS: - noop - - Template actions: - add - remove -``` - -### Exit code - -Calling `App.Run` will not automatically call `os.Exit`, which means that by -default the exit code will "fall through" to being `0`. An explicit exit code -may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a -`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.: - -```go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "ginger-crouton", - Usage: "is it in the soup?", - }, - }, - Action: func(ctx *cli.Context) error { - if !ctx.Bool("ginger-crouton") { - return cli.Exit("Ginger croutons are not in the soup", 86) - } - return nil - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -### Combining short options - -Traditional use of options using their shortnames look like this: - -```sh-session -$ cmd -s -o -m "Some message" -``` - -Suppose you want users to be able to combine options with their shortnames. This -can be done using the `UseShortOptionHandling` bool in your app configuration, -or for individual commands by attaching it to the command configuration. For -example: - - -```go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - UseShortOptionHandling: true, - Commands: []*cli.Command{ - { - Name: "short", - Usage: "complete a task on the list", - Flags: []cli.Flag{ - &cli.BoolFlag{Name: "serve", Aliases: []string{"s"}}, - &cli.BoolFlag{Name: "option", Aliases: []string{"o"}}, - &cli.StringFlag{Name: "message", Aliases: []string{"m"}}, - }, - Action: func(cCtx *cli.Context) error { - fmt.Println("serve:", cCtx.Bool("serve")) - fmt.Println("option:", cCtx.Bool("option")) - fmt.Println("message:", cCtx.String("message")) - return nil - }, - }, - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -If your program has any number of bool flags such as `serve` and `option`, and -optionally one non-bool flag `message`, with the short options of `-s`, `-o`, -and `-m` respectively, setting `UseShortOptionHandling` will also support the -following syntax: - -```sh-session -$ cmd -som "Some message" -``` - -If you enable `UseShortOptionHandling`, then you must not use any flags that -have a single leading `-` or this will result in failures. For example, -`-option` can no longer be used. Flags with two leading dashes (such as -`--options`) are still valid. - -### Bash Completion - -You can enable completion commands by setting the `EnableBashCompletion` flag on -the `App` object to `true`. By default, this setting will allow auto-completion -for an app's subcommands, but you can write your own completion methods for the -App or its subcommands as well. - -#### Default auto-completion - -```go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - EnableBashCompletion: true, - Commands: []*cli.Command{ - { - Name: "add", - Aliases: []string{"a"}, - Usage: "add a task to the list", - Action: func(cCtx *cli.Context) error { - fmt.Println("added task: ", cCtx.Args().First()) - return nil - }, - }, - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(cCtx *cli.Context) error { - fmt.Println("completed task: ", cCtx.Args().First()) - return nil - }, - }, - { - Name: "template", - Aliases: []string{"t"}, - Usage: "options for task templates", - Subcommands: []*cli.Command{ - { - Name: "add", - Usage: "add a new template", - Action: func(cCtx *cli.Context) error { - fmt.Println("new task template: ", cCtx.Args().First()) - return nil - }, - }, - { - Name: "remove", - Usage: "remove an existing template", - Action: func(cCtx *cli.Context) error { - fmt.Println("removed task template: ", cCtx.Args().First()) - return nil - }, - }, - }, - }, - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` -![](/docs/v2/images/default-bash-autocomplete.gif) - -#### Custom auto-completion - -```go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} - - app := &cli.App{ - EnableBashCompletion: true, - Commands: []*cli.Command{ - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(cCtx *cli.Context) error { - fmt.Println("completed task: ", cCtx.Args().First()) - return nil - }, - BashComplete: func(cCtx *cli.Context) { - // This will complete if no args are passed - if cCtx.NArg() > 0 { - return - } - for _, t := range tasks { - fmt.Println(t) - } - }, - }, - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` -![](/docs/v2/images/custom-bash-autocomplete.gif) - -#### Enabling - -To enable auto-completion for the current shell session, a bash script, -`autocomplete/bash_autocomplete` is included in this repo. - -To use `autocomplete/bash_autocomplete` set an environment variable named `PROG` -to the name of your program and then `source` the -`autocomplete/bash_autocomplete` file. - -For example, if your cli program is called `myprogram`: - -```sh-session -$ PROG=myprogram source path/to/cli/autocomplete/bash_autocomplete -``` - -Auto-completion is now enabled for the current shell, but will not persist into -a new shell. - -#### Distribution and Persistent Autocompletion - -Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename -it to the name of the program you wish to add autocomplete support for (or -automatically install it there if you are distributing a package). Don't forget -to source the file or restart your shell to activate the auto-completion. - -```sh-session -$ sudo cp path/to/autocomplete/bash_autocomplete /etc/bash_completion.d/ -$ source /etc/bash_completion.d/ -``` - -Alternatively, you can just document that users should `source` the generic -`autocomplete/bash_autocomplete` and set `$PROG` within their bash configuration -file, adding these lines: - -```sh-session -$ PROG= -$ source path/to/cli/autocomplete/bash_autocomplete -``` - -Keep in mind that if they are enabling auto-completion for more than one -program, they will need to set `PROG` and source -`autocomplete/bash_autocomplete` for each program, like so: - -```sh-session -$ PROG= -$ source path/to/cli/autocomplete/bash_autocomplete - -$ PROG= -$ source path/to/cli/autocomplete/bash_autocomplete -``` - -#### Customization - -The default shell completion flag (`--generate-bash-completion`) is defined as -`cli.EnableBashCompletion`, and may be redefined if desired, e.g.: - - -```go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - EnableBashCompletion: true, - Commands: []*cli.Command{ - { - Name: "wat", - }, - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -#### ZSH Support - -Auto-completion for ZSH is also supported using the -`autocomplete/zsh_autocomplete` file included in this repo. One environment -variable is used, `PROG`. Set `PROG` to the program name as before, and then -`source path/to/autocomplete/zsh_autocomplete`. Adding the following lines to -your ZSH configuration file (usually `.zshrc`) will allow the auto-completion to -persist across new shells: - -```sh-session -$ PROG= -$ source path/to/autocomplete/zsh_autocomplete -``` - -#### ZSH default auto-complete example -![](/docs/v2/images/default-zsh-autocomplete.gif) - -#### ZSH custom auto-complete example -![](/docs/v2/images/custom-zsh-autocomplete.gif) - -#### PowerShell Support - -Auto-completion for PowerShell is also supported using the -`autocomplete/powershell_autocomplete.ps1` file included in this repo. - -Rename the script to `.ps1` and move it anywhere in your file -system. The location of script does not matter, only the file name of the -script has to match the your program's binary name. - -To activate it, enter: - -```powershell -& path/to/autocomplete/.ps1 -``` - -To persist across new shells, open the PowerShell profile (with `code $profile` -or `notepad $profile`) and add the line: - -```powershell -& path/to/autocomplete/.ps1 -``` - -### Generated Help Text - -The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked -by the cli internals in order to print generated help text for the app, command, -or subcommand, and break execution. - -#### Customization - -All of the help text generation may be customized, and at multiple levels. The -templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and -`SubcommandHelpTemplate` which may be reassigned or augmented, and full override -is possible by assigning a compatible func to the `cli.HelpPrinter` variable, -e.g.: - - -```go -package main - -import ( - "fmt" - "io" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - // EXAMPLE: Append to an existing template - cli.AppHelpTemplate = fmt.Sprintf(`%s - -WEBSITE: http://awesometown.example.com - -SUPPORT: support@awesometown.example.com - -`, cli.AppHelpTemplate) - - // EXAMPLE: Override a template - cli.AppHelpTemplate = `NAME: - {{.Name}} - {{.Usage}} -USAGE: - {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} - {{if len .Authors}} -AUTHOR: - {{range .Authors}}{{ . }}{{end}} - {{end}}{{if .Commands}} -COMMANDS: -{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} -GLOBAL OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}}{{if .Copyright }} -COPYRIGHT: - {{.Copyright}} - {{end}}{{if .Version}} -VERSION: - {{.Version}} - {{end}} -` - - // EXAMPLE: Replace the `HelpPrinter` func - cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { - fmt.Println("Ha HA. I pwnd the help!!1") - } - - (&cli.App{}).Run(os.Args) -} -``` - -The default flag may be customized to something other than `-h/--help` by -setting `cli.HelpFlag`, e.g.: - - -```go -package main - -import ( - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - cli.HelpFlag = &cli.BoolFlag{ - Name: "haaaaalp", - Aliases: []string{"halp"}, - Usage: "HALP", - EnvVars: []string{"SHOW_HALP", "HALPPLZ"}, - } - - (&cli.App{}).Run(os.Args) -} -``` - -### Version Flag - -The default version flag (`-v/--version`) is defined as `cli.VersionFlag`, which -is checked by the cli internals in order to print the `App.Version` via -`cli.VersionPrinter` and break execution. - -#### Customization - -The default flag may be customized to something other than `-v/--version` by -setting `cli.VersionFlag`, e.g.: - - -```go -package main - -import ( - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - cli.VersionFlag = &cli.BoolFlag{ - Name: "print-version", - Aliases: []string{"V"}, - Usage: "print only the version", - } - - app := &cli.App{ - Name: "partay", - Version: "v19.99.0", - } - app.Run(os.Args) -} -``` - -Alternatively, the version printer at `cli.VersionPrinter` may be overridden, -e.g.: - - -```go -package main - -import ( - "fmt" - "os" - - "github.com/urfave/cli/v2" -) - -var ( - Revision = "fafafaf" -) - -func main() { - cli.VersionPrinter = func(cCtx *cli.Context) { - fmt.Printf("version=%s revision=%s\n", cCtx.App.Version, Revision) - } - - app := &cli.App{ - Name: "partay", - Version: "v19.99.0", - } - app.Run(os.Args) -} -``` - -### Timestamp Flag - -Using the timestamp flag is simple. Please refer to -[`time.Parse`](https://golang.org/pkg/time/#example_Parse) to get possible -formats. - - -```go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.TimestampFlag{Name: "meeting", Layout: "2006-01-02T15:04:05"}, - }, - Action: func(cCtx *cli.Context) error { - fmt.Printf("%s", cCtx.Timestamp("meeting").String()) - return nil - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -In this example the flag could be used like this: - -```sh-session -$ myapp --meeting 2019-08-12T15:04:05 -``` - -When the layout doesn't contain timezones, timestamp will render with UTC. To -change behavior, a default timezone can be provided with flag definition: - -```go -app := &cli.App{ - Flags: []cli.Flag{ - &cli.TimestampFlag{Name: "meeting", Layout: "2006-01-02T15:04:05", Timezone: time.Local}, - }, -} -``` - -(time.Local contains the system's local time zone.) - -Side note: quotes may be necessary around the date depending on your layout (if -you have spaces for instance) - -### Suggestions - -To enable flag and command suggestions, set `app.Suggest = true`. If the suggest -feature is enabled, then the help output of the corresponding command will -provide an appropriate suggestion for the provided flag or subcommand if -available. - -### Full API Example - -**Notice**: This is a contrived (functioning) example meant strictly for API -demonstration purposes. Use of one's imagination is encouraged. - - -```go -package main - -import ( - "errors" - "flag" - "fmt" - "io" - "io/ioutil" - "os" - "time" - - "github.com/urfave/cli/v2" -) - -func init() { - cli.AppHelpTemplate += "\nCUSTOMIZED: you bet ur muffins\n" - cli.CommandHelpTemplate += "\nYMMV\n" - cli.SubcommandHelpTemplate += "\nor something\n" - - cli.HelpFlag = &cli.BoolFlag{Name: "halp"} - cli.VersionFlag = &cli.BoolFlag{Name: "print-version", Aliases: []string{"V"}} - - cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { - fmt.Fprintf(w, "best of luck to you\n") - } - cli.VersionPrinter = func(cCtx *cli.Context) { - fmt.Fprintf(cCtx.App.Writer, "version=%s\n", cCtx.App.Version) - } - cli.OsExiter = func(cCtx int) { - fmt.Fprintf(cli.ErrWriter, "refusing to exit %d\n", cCtx) - } - cli.ErrWriter = ioutil.Discard - cli.FlagStringer = func(fl cli.Flag) string { - return fmt.Sprintf("\t\t%s", fl.Names()[0]) - } -} - -type hexWriter struct{} - -func (w *hexWriter) Write(p []byte) (int, error) { - for _, b := range p { - fmt.Printf("%x", b) - } - fmt.Printf("\n") - - return len(p), nil -} - -type genericType struct { - s string -} - -func (g *genericType) Set(value string) error { - g.s = value - return nil -} - -func (g *genericType) String() string { - return g.s -} - -func main() { - app := &cli.App{ - Name: "kənˈtrīv", - Version: "v19.99.0", - Compiled: time.Now(), - Authors: []*cli.Author{ - &cli.Author{ - Name: "Example Human", - Email: "human@example.com", - }, - }, - Copyright: "(c) 1999 Serious Enterprise", - HelpName: "contrive", - Usage: "demonstrate available API", - UsageText: "contrive - demonstrating the available API", - ArgsUsage: "[args and such]", - Commands: []*cli.Command{ - &cli.Command{ - Name: "doo", - Aliases: []string{"do"}, - Category: "motion", - Usage: "do the doo", - UsageText: "doo - does the dooing", - Description: "no really, there is a lot of dooing to be done", - ArgsUsage: "[arrgh]", - Flags: []cli.Flag{ - &cli.BoolFlag{Name: "forever", Aliases: []string{"forevvarr"}}, - }, - Subcommands: []*cli.Command{ - &cli.Command{ - Name: "wop", - Action: wopAction, - }, - }, - SkipFlagParsing: false, - HideHelp: false, - Hidden: false, - HelpName: "doo!", - BashComplete: func(cCtx *cli.Context) { - fmt.Fprintf(cCtx.App.Writer, "--better\n") - }, - Before: func(cCtx *cli.Context) error { - fmt.Fprintf(cCtx.App.Writer, "brace for impact\n") - return nil - }, - After: func(cCtx *cli.Context) error { - fmt.Fprintf(cCtx.App.Writer, "did we lose anyone?\n") - return nil - }, - Action: func(cCtx *cli.Context) error { - cCtx.Command.FullName() - cCtx.Command.HasName("wop") - cCtx.Command.Names() - cCtx.Command.VisibleFlags() - fmt.Fprintf(cCtx.App.Writer, "dodododododoodododddooooododododooo\n") - if cCtx.Bool("forever") { - cCtx.Command.Run(cCtx) - } - return nil - }, - OnUsageError: func(cCtx *cli.Context, err error, isSubcommand bool) error { - fmt.Fprintf(cCtx.App.Writer, "for shame\n") - return err - }, - }, - }, - Flags: []cli.Flag{ - &cli.BoolFlag{Name: "fancy"}, - &cli.BoolFlag{Value: true, Name: "fancier"}, - &cli.DurationFlag{Name: "howlong", Aliases: []string{"H"}, Value: time.Second * 3}, - &cli.Float64Flag{Name: "howmuch"}, - &cli.GenericFlag{Name: "wat", Value: &genericType{}}, - &cli.Int64Flag{Name: "longdistance"}, - &cli.Int64SliceFlag{Name: "intervals"}, - &cli.IntFlag{Name: "distance"}, - &cli.IntSliceFlag{Name: "times"}, - &cli.StringFlag{Name: "dance-move", Aliases: []string{"d"}}, - &cli.StringSliceFlag{Name: "names", Aliases: []string{"N"}}, - &cli.UintFlag{Name: "age"}, - &cli.Uint64Flag{Name: "bigage"}, - }, - EnableBashCompletion: true, - HideHelp: false, - HideVersion: false, - BashComplete: func(cCtx *cli.Context) { - fmt.Fprintf(cCtx.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") - }, - Before: func(cCtx *cli.Context) error { - fmt.Fprintf(cCtx.App.Writer, "HEEEERE GOES\n") - return nil - }, - After: func(cCtx *cli.Context) error { - fmt.Fprintf(cCtx.App.Writer, "Phew!\n") - return nil - }, - CommandNotFound: func(cCtx *cli.Context, command string) { - fmt.Fprintf(cCtx.App.Writer, "Thar be no %q here.\n", command) - }, - OnUsageError: func(cCtx *cli.Context, err error, isSubcommand bool) error { - if isSubcommand { - return err - } - - fmt.Fprintf(cCtx.App.Writer, "WRONG: %#v\n", err) - return nil - }, - Action: func(cCtx *cli.Context) error { - cli.DefaultAppComplete(cCtx) - cli.HandleExitCoder(errors.New("not an exit coder, though")) - cli.ShowAppHelp(cCtx) - cli.ShowCommandCompletions(cCtx, "nope") - cli.ShowCommandHelp(cCtx, "also-nope") - cli.ShowCompletions(cCtx) - cli.ShowSubcommandHelp(cCtx) - cli.ShowVersion(cCtx) - - fmt.Printf("%#v\n", cCtx.App.Command("doo")) - if cCtx.Bool("infinite") { - cCtx.App.Run([]string{"app", "doo", "wop"}) - } - - if cCtx.Bool("forevar") { - cCtx.App.RunAsSubcommand(cCtx) - } - cCtx.App.Setup() - fmt.Printf("%#v\n", cCtx.App.VisibleCategories()) - fmt.Printf("%#v\n", cCtx.App.VisibleCommands()) - fmt.Printf("%#v\n", cCtx.App.VisibleFlags()) - - fmt.Printf("%#v\n", cCtx.Args().First()) - if cCtx.Args().Len() > 0 { - fmt.Printf("%#v\n", cCtx.Args().Get(1)) - } - fmt.Printf("%#v\n", cCtx.Args().Present()) - fmt.Printf("%#v\n", cCtx.Args().Tail()) - - set := flag.NewFlagSet("contrive", 0) - nc := cli.NewContext(cCtx.App, set, cCtx) - - fmt.Printf("%#v\n", nc.Args()) - fmt.Printf("%#v\n", nc.Bool("nope")) - fmt.Printf("%#v\n", !nc.Bool("nerp")) - fmt.Printf("%#v\n", nc.Duration("howlong")) - fmt.Printf("%#v\n", nc.Float64("hay")) - fmt.Printf("%#v\n", nc.Generic("bloop")) - fmt.Printf("%#v\n", nc.Int64("bonk")) - fmt.Printf("%#v\n", nc.Int64Slice("burnks")) - fmt.Printf("%#v\n", nc.Int("bips")) - fmt.Printf("%#v\n", nc.IntSlice("blups")) - fmt.Printf("%#v\n", nc.String("snurt")) - fmt.Printf("%#v\n", nc.StringSlice("snurkles")) - fmt.Printf("%#v\n", nc.Uint("flub")) - fmt.Printf("%#v\n", nc.Uint64("florb")) - - fmt.Printf("%#v\n", nc.FlagNames()) - fmt.Printf("%#v\n", nc.IsSet("wat")) - fmt.Printf("%#v\n", nc.Set("wat", "nope")) - fmt.Printf("%#v\n", nc.NArg()) - fmt.Printf("%#v\n", nc.NumFlags()) - fmt.Printf("%#v\n", nc.Lineage()[1]) - nc.Set("wat", "also-nope") - - ec := cli.Exit("ohwell", 86) - fmt.Fprintf(cCtx.App.Writer, "%d", ec.ExitCode()) - fmt.Printf("made it!\n") - return ec - }, - Metadata: map[string]interface{}{ - "layers": "many", - "explicable": false, - "whatever-values": 19.99, - }, - } - - if os.Getenv("HEXY") != "" { - app.Writer = &hexWriter{} - app.ErrWriter = &hexWriter{} - } - - app.Run(os.Args) -} - -func wopAction(cCtx *cli.Context) error { - fmt.Fprintf(cCtx.App.Writer, ":wave: over here, eh\n") - return nil -} -``` - -## Migrating From Older Releases - -There are a small set of breaking changes between v1 and v2. Converting is -relatively straightforward and typically takes less than an hour. Specific steps -are included in [Migration Guide: v1 to v2](../migrate-v1-to-v2.md). Also see -the [pkg.go.dev docs](https://pkg.go.dev/github.com/urfave/cli/v2) for v2 API -documentation. diff --git a/docs/v2/migrating-from-older-releases.md b/docs/v2/migrating-from-older-releases.md new file mode 100644 index 0000000000..564cd20912 --- /dev/null +++ b/docs/v2/migrating-from-older-releases.md @@ -0,0 +1,5 @@ +There are a small set of breaking changes between v1 and v2. Converting is +relatively straightforward and typically takes less than an hour. Specific steps +are included in [Migration Guide: v1 to v2](../migrate-v1-to-v2.md). Also see +the [pkg.go.dev docs](https://pkg.go.dev/github.com/urfave/cli/v2) for v2 API +documentation. From 18f8e6df0b33bb67d3da3fffd6e71092411fb861 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sun, 14 Aug 2022 15:55:40 -0800 Subject: [PATCH 022/136] update sidebar configuration --- mkdocs.yml | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 16d67482c2..84c2fa0d2a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -9,8 +9,30 @@ site_url: https://cli.urfave.org/ repo_url: https://github.com/urfave/cli edit_uri: edit/main/docs/ nav: - - Home: index.md - - v2 Manual: v2/index.md + - Home: + - Welcome: index.md + - Contributing: CONTRIBUTING.md + - Code of Conduct: CODE_OF_CONDUCT.md + - Releasing: RELEASING.md + - Security: SECURITY.md + - Migrate v1 to v2: migrate-v1-to-v2.md + - v2 Manual: + - Getting Started: v2/getting-started.md + - Migrating From Older Releases: v2/migrating-from-older-releases.md + - Examples: + - Greet: v2/examples/greet.md + - Arguments: v2/examples/arguments.md + - Flags: v2/examples/flags.md + - Subcommands: v2/examples/subcommands.md + - Subcommands Categories: v2/examples/subcommands-categories.md + - Exit Codes: v2/examples/exit-codes.md + - Combining Short Options: v2/examples/combining-short-options.md + - Bash Completions: v2/examples/bash-completions.md + - Generated Help Text: v2/examples/generated-help-text.md + - Version Flag: v2/examples/version-flag.md + - Timestamp Flag: v2/examples/timestamp-flag.md + - Suggestions: v2/examples/suggestions.md + - Full API Example: v2/examples/full-api-example.md - v1 Manual: - Getting Started: v1/getting-started.md - Migrating to v2: v1/migrating-to-v2.md From 7cfafe8f806da4554f913a49ac09b562db505f9c Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sun, 14 Aug 2022 16:10:39 -0800 Subject: [PATCH 023/136] add tags plugin --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index 84c2fa0d2a..02657b9edd 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -72,6 +72,7 @@ theme: plugins: - git-revision-date-localized - search + - tags # NOTE: this is the recommended configuration from # https://squidfunk.github.io/mkdocs-material/setup/extensions/#recommended-configuration markdown_extensions: From 8036eac8dc7b23ef30af5597c2a8b4de2bd56409 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sun, 14 Aug 2022 16:11:56 -0800 Subject: [PATCH 024/136] add tags and boost for pages --- docs/v1/examples/arguments.md | 5 ++++- docs/v1/examples/bash-completions.md | 5 +++++ docs/v1/examples/combining-short-options.md | 5 +++++ docs/v1/examples/exit-codes.md | 5 +++++ docs/v1/examples/flags.md | 5 +++++ docs/v1/examples/generated-help-text.md | 5 +++++ docs/v1/examples/greet.md | 5 +++++ docs/v1/examples/subcommands-categories.md | 5 +++++ docs/v1/examples/subcommands.md | 5 +++++ docs/v1/examples/version-flag.md | 5 ++++- docs/v1/getting-started.md | 5 +++++ docs/v1/migrating-to-v2.md | 5 ++++- docs/v2/examples/arguments.md | 7 +++++++ docs/v2/examples/bash-completions.md | 7 +++++++ docs/v2/examples/combining-short-options.md | 7 +++++++ docs/v2/examples/exit-codes.md | 7 +++++++ docs/v2/examples/flags.md | 7 +++++++ docs/v2/examples/full-api-example.md | 7 +++++++ docs/v2/examples/generated-help-text.md | 7 +++++++ docs/v2/examples/greet.md | 7 +++++++ docs/v2/examples/subcommands-categories.md | 7 +++++++ docs/v2/examples/subcommands.md | 7 +++++++ docs/v2/examples/suggestions.md | 7 +++++++ docs/v2/examples/timestamp-flag.md | 7 +++++++ docs/v2/examples/version-flag.md | 7 +++++++ docs/v2/getting-started.md | 7 +++++++ docs/v2/migrating-from-older-releases.md | 7 +++++++ 27 files changed, 162 insertions(+), 3 deletions(-) diff --git a/docs/v1/examples/arguments.md b/docs/v1/examples/arguments.md index ba451b6e46..4416d32358 100644 --- a/docs/v1/examples/arguments.md +++ b/docs/v1/examples/arguments.md @@ -1,4 +1,7 @@ -### Arguments +--- +tags: + - v1 +--- You can lookup arguments by calling the `Args` function on `cli.Context`, e.g.: diff --git a/docs/v1/examples/bash-completions.md b/docs/v1/examples/bash-completions.md index 5648477fb3..73af176d8f 100644 --- a/docs/v1/examples/bash-completions.md +++ b/docs/v1/examples/bash-completions.md @@ -1,3 +1,8 @@ +--- +tags: + - v1 +--- + You can enable completion commands by setting the `EnableBashCompletion` flag on the `App` object. By default, this setting will only auto-complete to show an app's subcommands, but you can write your own completion methods for diff --git a/docs/v1/examples/combining-short-options.md b/docs/v1/examples/combining-short-options.md index 3ab6ebdd12..0496300b8a 100644 --- a/docs/v1/examples/combining-short-options.md +++ b/docs/v1/examples/combining-short-options.md @@ -1,3 +1,8 @@ +--- +tags: + - v1 +--- + Traditional use of options using their shortnames look like this: ``` diff --git a/docs/v1/examples/exit-codes.md b/docs/v1/examples/exit-codes.md index 39c06424e5..1d6627dab8 100644 --- a/docs/v1/examples/exit-codes.md +++ b/docs/v1/examples/exit-codes.md @@ -1,3 +1,8 @@ +--- +tags: + - v1 +--- + Calling `App.Run` will not automatically call `os.Exit`, which means that by default the exit code will "fall through" to being `0`. An explicit exit code may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a diff --git a/docs/v1/examples/flags.md b/docs/v1/examples/flags.md index f6ae7be566..2f9300be69 100644 --- a/docs/v1/examples/flags.md +++ b/docs/v1/examples/flags.md @@ -1,3 +1,8 @@ +--- +tags: + - v1 +--- + Setting and querying flags is simple. +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + var count int + + app := &cli.App{ + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "foo", + Usage: "foo greeting", + Count: &count, + }, + }, + Action: func(cCtx *cli.Context) error { + fmt.Println("Count %d", count) + return nil + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + #### Placeholder Values Sometimes it's useful to specify a flag's value within the usage string itself. @@ -550,6 +589,7 @@ Will result in help output like: #### Precedence The precedence for flag value sources is as follows (highest to lowest): +eikdcclkkujudfjnhknkgruvlncbgvuckugignuhturk 0. Command line flag value from user 0. Environment variable (if specified) From b694a25730c7974d5917a30d43314189168cd6ef Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 11:36:55 -0400 Subject: [PATCH 066/136] Update docs --- docs/v2/examples/flags.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/v2/examples/flags.md b/docs/v2/examples/flags.md index ed7dab29f5..805fb82137 100644 --- a/docs/v2/examples/flags.md +++ b/docs/v2/examples/flags.md @@ -104,7 +104,7 @@ See full list of flags at https://pkg.go.dev/github.com/urfave/cli/v2 For bool flags you can specify the flag multiple times to get a count(e.g -v -v -v or -vvv) ```go package main @@ -129,7 +129,7 @@ func main() { }, }, Action: func(cCtx *cli.Context) error { - fmt.Println("Count %d", count) + fmt.Println("Count ", count) return nil }, } From cbd62ef958bcfbf2f8d8b664b945b094de77d9bd Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 11:43:09 -0400 Subject: [PATCH 067/136] Update godocs --- docs/v2/examples/flags.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/v2/examples/flags.md b/docs/v2/examples/flags.md index 805fb82137..527e43c6ae 100644 --- a/docs/v2/examples/flags.md +++ b/docs/v2/examples/flags.md @@ -129,7 +129,7 @@ func main() { }, }, Action: func(cCtx *cli.Context) error { - fmt.Println("Count ", count) + fmt.Println("count", count) return nil }, } From 3adf8fa48dac4815a15aeacebb3282ac4f53f143 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 14:28:33 -0400 Subject: [PATCH 068/136] Add args --- docs/v2/examples/flags.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/v2/examples/flags.md b/docs/v2/examples/flags.md index 527e43c6ae..4307b0104e 100644 --- a/docs/v2/examples/flags.md +++ b/docs/v2/examples/flags.md @@ -104,7 +104,8 @@ See full list of flags at https://pkg.go.dev/github.com/urfave/cli/v2 For bool flags you can specify the flag multiple times to get a count(e.g -v -v -v or -vvv) ```go package main From fa3bbf91ed2b8024941ee819a379477e64c8334b Mon Sep 17 00:00:00 2001 From: dearchap Date: Mon, 5 Sep 2022 16:45:44 -0400 Subject: [PATCH 069/136] Update docs/v2/examples/flags.md Co-authored-by: Dan Buch --- docs/v2/examples/flags.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/v2/examples/flags.md b/docs/v2/examples/flags.md index 4307b0104e..990a928548 100644 --- a/docs/v2/examples/flags.md +++ b/docs/v2/examples/flags.md @@ -104,7 +104,7 @@ See full list of flags at https://pkg.go.dev/github.com/urfave/cli/v2 For bool flags you can specify the flag multiple times to get a count(e.g -v -v -v or -vvv) ```go From c0a8506cea4bd5485e65f72d6ede6a9fd34ea1fa Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 17:35:30 -0400 Subject: [PATCH 070/136] Add context.Count --- context.go | 12 ++++++++++++ flag_bool.go | 18 +++++++++++------- flag_test.go | 29 +++++++++++++++++++++++++++-- godoc-current.txt | 3 +++ 4 files changed, 53 insertions(+), 9 deletions(-) diff --git a/context.go b/context.go index 9bdd4535b1..5d43de016b 100644 --- a/context.go +++ b/context.go @@ -105,6 +105,18 @@ func (cCtx *Context) Lineage() []*Context { return lineage } +// NumOccurrences returns the num of occurences of this flag +func (cCtx *Context) Count(name string) int { + if fs := cCtx.lookupFlagSet(name); fs != nil { + if bf, ok := fs.Lookup(name).Value.(*boolValue); ok { + if bf.count != nil { + return *bf.count + } + } + } + return 0 +} + // Value returns the value of the flag corresponding to `name` func (cCtx *Context) Value(name string) interface{} { if fs := cCtx.lookupFlagSet(name); fs != nil { diff --git a/flag_bool.go b/flag_bool.go index c727fbc1fe..08705766f4 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -85,14 +85,18 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error { f.HasBeenSet = true } + count := f.Count + dest := f.Destination + + if count == nil { + count = new(int) + } + if dest == nil { + dest = new(bool) + } + for _, name := range f.Names() { - var value flag.Value - if f.Destination != nil { - value = newBoolValue(f.Value, f.Destination, f.Count) - } else { - t := new(bool) - value = newBoolValue(f.Value, t, f.Count) - } + value := newBoolValue(f.Value, dest, count) set.Var(value, name, f.Usage) } diff --git a/flag_test.go b/flag_test.go index f1d162947b..5b1eb2c94f 100644 --- a/flag_test.go +++ b/flag_test.go @@ -67,14 +67,39 @@ func TestBoolFlagApply_SetsCount(t *testing.T) { count := 0 fl := BoolFlag{Name: "wat", Aliases: []string{"W", "huh"}, Destination: &v, Count: &count} set := flag.NewFlagSet("test", 0) - _ = fl.Apply(set) + err := fl.Apply(set) + expect(t, err, nil) - err := set.Parse([]string{"--wat", "-W", "--huh"}) + err = set.Parse([]string{"--wat", "-W", "--huh"}) expect(t, err, nil) expect(t, v, true) expect(t, count, 3) } +func TestBoolFlagCountFromContext(t *testing.T) { + set := flag.NewFlagSet("test", 0) + ctx := NewContext(nil, set, nil) + tf := &BoolFlag{Name: "tf", Aliases: []string{"w", "huh"}} + err := tf.Apply(set) + expect(t, err, nil) + + err = set.Parse([]string{"-tf", "-w", "-huh"}) + expect(t, err, nil) + expect(t, tf.Get(ctx), true) + expect(t, ctx.Count("tf"), 3) + + set1 := flag.NewFlagSet("test", 0) + ctx1 := NewContext(nil, set1, nil) + tf1 := &BoolFlag{Name: "tf", Aliases: []string{"w", "huh"}} + err = tf1.Apply(set1) + expect(t, err, nil) + + err = set1.Parse([]string{}) + expect(t, err, nil) + expect(t, tf1.Get(ctx1), false) + expect(t, ctx1.Count("tf"), 0) +} + func TestFlagsFromEnv(t *testing.T) { newSetFloat64Slice := func(defaults ...float64) Float64Slice { s := NewFloat64Slice(defaults...) diff --git a/godoc-current.txt b/godoc-current.txt index 697c13034b..0e09211a70 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -621,6 +621,9 @@ func (cCtx *Context) Args() Args func (cCtx *Context) Bool(name string) bool Bool looks up the value of a local BoolFlag, returns false if not found +func (cCtx *Context) Count(name string) int + NumOccurrences returns the num of occurences of this flag + func (cCtx *Context) Duration(name string) time.Duration Duration looks up the value of a local DurationFlag, returns 0 if not found From 6b0a3e80b52c82af51bf0485efcfd99e213432bd Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 17:40:42 -0400 Subject: [PATCH 071/136] Add parametrize tests --- flag_test.go | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/flag_test.go b/flag_test.go index 5b1eb2c94f..380a130eca 100644 --- a/flag_test.go +++ b/flag_test.go @@ -77,27 +77,36 @@ func TestBoolFlagApply_SetsCount(t *testing.T) { } func TestBoolFlagCountFromContext(t *testing.T) { - set := flag.NewFlagSet("test", 0) - ctx := NewContext(nil, set, nil) - tf := &BoolFlag{Name: "tf", Aliases: []string{"w", "huh"}} - err := tf.Apply(set) - expect(t, err, nil) - err = set.Parse([]string{"-tf", "-w", "-huh"}) - expect(t, err, nil) - expect(t, tf.Get(ctx), true) - expect(t, ctx.Count("tf"), 3) + boolCountTests := []struct { + input []string + expectedVal bool + expectedCount int + }{ + { + input: []string{"-tf", "-w", "-huh"}, + expectedVal: true, + expectedCount: 3, + }, + { + input: []string{}, + expectedVal: false, + expectedCount: 0, + }, + } - set1 := flag.NewFlagSet("test", 0) - ctx1 := NewContext(nil, set1, nil) - tf1 := &BoolFlag{Name: "tf", Aliases: []string{"w", "huh"}} - err = tf1.Apply(set1) - expect(t, err, nil) + for _, bct := range boolCountTests { + set := flag.NewFlagSet("test", 0) + ctx := NewContext(nil, set, nil) + tf := &BoolFlag{Name: "tf", Aliases: []string{"w", "huh"}} + err := tf.Apply(set) + expect(t, err, nil) - err = set1.Parse([]string{}) - expect(t, err, nil) - expect(t, tf1.Get(ctx1), false) - expect(t, ctx1.Count("tf"), 0) + err = set.Parse(bct.input) + expect(t, err, nil) + expect(t, tf.Get(ctx), bct.expectedVal) + expect(t, ctx.Count("tf"), bct.expectedCount) + } } func TestFlagsFromEnv(t *testing.T) { From a509290e7546f865d8fce8f318d763c1caf1cdf9 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 19:13:26 -0400 Subject: [PATCH 072/136] Add countable interface --- context.go | 8 +++----- flag.go | 6 ++++++ flag_bool.go | 7 +++++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/context.go b/context.go index 5d43de016b..312efb5b38 100644 --- a/context.go +++ b/context.go @@ -105,13 +105,11 @@ func (cCtx *Context) Lineage() []*Context { return lineage } -// NumOccurrences returns the num of occurences of this flag +// Count returns the num of occurences of this flag func (cCtx *Context) Count(name string) int { if fs := cCtx.lookupFlagSet(name); fs != nil { - if bf, ok := fs.Lookup(name).Value.(*boolValue); ok { - if bf.count != nil { - return *bf.count - } + if cf, ok := fs.Lookup(name).Value.(Countable); ok { + return cf.Count() } } return 0 diff --git a/flag.go b/flag.go index 4f0871d332..1618b4da86 100644 --- a/flag.go +++ b/flag.go @@ -124,6 +124,12 @@ type Flag interface { GetValue() string } +// Countable is an interface to enable detection of flag values which support +// repetitive flags +type Countable interface { + Count() int +} + func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { set := flag.NewFlagSet(name, flag.ContinueOnError) diff --git a/flag_bool.go b/flag_bool.go index 08705766f4..e5fa700e1a 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -51,6 +51,13 @@ func (b *boolValue) String() string { func (b *boolValue) IsBoolFlag() bool { return true } +func (b *boolValue) Count() int { + if b.count != nil { + return *b.count + } + return 0 +} + // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *BoolFlag) GetValue() string { From d58b3c70de7ea29021ae57b2d4b363dd38ea82f1 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 20:04:11 -0400 Subject: [PATCH 073/136] Remove keystroke errors --- docs/v2/examples/flags.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/v2/examples/flags.md b/docs/v2/examples/flags.md index 990a928548..dbb41ea936 100644 --- a/docs/v2/examples/flags.md +++ b/docs/v2/examples/flags.md @@ -590,7 +590,6 @@ Will result in help output like: #### Precedence The precedence for flag value sources is as follows (highest to lowest): -eikdcclkkujudfjnhknkgruvlncbgvuckugignuhturk 0. Command line flag value from user 0. Environment variable (if specified) From fdcbf28476e361a9e29d723e6986da10b6a27476 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Fri, 12 Aug 2022 21:57:15 -0400 Subject: [PATCH 074/136] Merge changes from main --- flag-spec.yaml | 13 ++- flag.go | 23 ++++ flag_test.go | 272 +++++++++++++++++++++++++++++++++++++++++-- flag_uint64_slice.go | 192 ++++++++++++++++++++++++++++++ flag_uint_slice.go | 203 ++++++++++++++++++++++++++++++++ 5 files changed, 691 insertions(+), 12 deletions(-) create mode 100644 flag_uint64_slice.go create mode 100644 flag_uint_slice.go diff --git a/flag-spec.yaml b/flag-spec.yaml index 7e1a9e7b14..51601f0fd1 100644 --- a/flag-spec.yaml +++ b/flag-spec.yaml @@ -58,7 +58,12 @@ flag_types: - { name: Layout, type: string } - { name: Timezone, type: "*time.Location" } - # TODO: enable UintSlice - # UintSlice: {} - # TODO: enable Uint64Slice once #1334 lands - # Uint64Slice: {} + UintSlice: + value_pointer: true + skip_interfaces: + - fmt.Stringer + Uint64Slice: + value_pointer: true + skip_interfaces: + - fmt.Stringer + diff --git a/flag.go b/flag.go index 1618b4da86..9616cd3670 100644 --- a/flag.go +++ b/flag.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "regexp" "runtime" + "strconv" "strings" "syscall" "time" @@ -302,6 +303,28 @@ func stringifyFlag(f Flag) string { fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault)) } +func stringifyUintSliceFlag(f *UintSliceFlag) string { + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.FormatUint(uint64(i), 10)) + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + +func stringifyUint64SliceFlag(f *Uint64SliceFlag) string { + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.FormatUint(i, 10)) + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + func stringifySliceFlag(usage string, names, defaultVals []string) string { placeholder, usage := unquoteUsage(usage) if placeholder == "" { diff --git a/flag_test.go b/flag_test.go index 380a130eca..87bf3601b5 100644 --- a/flag_test.go +++ b/flag_test.go @@ -122,12 +122,24 @@ func TestFlagsFromEnv(t *testing.T) { return *s } + newSetUintSlice := func(defaults ...uint) UintSlice { + s := NewUintSlice(defaults...) + s.hasBeenSet = false + return *s + } + newSetInt64Slice := func(defaults ...int64) Int64Slice { s := NewInt64Slice(defaults...) s.hasBeenSet = false return *s } + newSetUint64Slice := func(defaults ...uint64) Uint64Slice { + s := NewUint64Slice(defaults...) + s.hasBeenSet = false + return *s + } + newSetStringSlice := func(defaults ...string) StringSlice { s := NewStringSlice(defaults...) s.hasBeenSet = false @@ -171,10 +183,18 @@ func TestFlagsFromEnv(t *testing.T) { {"1.2,2", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int slice value from environment variable "SECONDS" for flag seconds: .*`}, {"foobar", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int slice value from environment variable "SECONDS" for flag seconds: .*`}, + {"1,2", newSetUintSlice(1, 2), &UintSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1.2,2", newSetUintSlice(), &UintSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as uint slice value from environment variable "SECONDS" for flag seconds: .*`}, + {"foobar", newSetUintSlice(), &UintSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint slice value from environment variable "SECONDS" for flag seconds: .*`}, + {"1,2", newSetInt64Slice(1, 2), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, {"1.2,2", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int64 slice value from environment variable "SECONDS" for flag seconds: .*`}, {"foobar", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int64 slice value from environment variable "SECONDS" for flag seconds: .*`}, + {"1,2", newSetUint64Slice(1, 2), &Uint64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1.2,2", newSetUint64Slice(), &Uint64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as uint64 slice value from environment variable "SECONDS" for flag seconds: .*`}, + {"foobar", newSetUint64Slice(), &Uint64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint64 slice value from environment variable "SECONDS" for flag seconds: .*`}, + {"foo", "foo", &StringFlag{Name: "name", EnvVars: []string{"NAME"}}, ""}, {"path", "path", &PathFlag{Name: "path", EnvVars: []string{"PATH"}}, ""}, @@ -315,6 +335,16 @@ func TestFlagStringifying(t *testing.T) { fl: &IntFlag{Name: "pens", DefaultText: "-19"}, expected: "--pens value\t(default: -19)", }, + { + name: "uint-slice-flag", + fl: &UintSliceFlag{Name: "pencils"}, + expected: "--pencils value\t", + }, + { + name: "uint-slice-flag-with-default-text", + fl: &UintFlag{Name: "pens", DefaultText: "29"}, + expected: "--pens value\t(default: 29)", + }, { name: "int64-flag", fl: &Int64Flag{Name: "flume"}, @@ -335,6 +365,16 @@ func TestFlagStringifying(t *testing.T) { fl: &Int64SliceFlag{Name: "handles", DefaultText: "-2"}, expected: "--handles value\t(default: -2)", }, + { + name: "uint64-slice-flag", + fl: &Uint64SliceFlag{Name: "drawers"}, + expected: "--drawers value\t", + }, + { + name: "uint64-slice-flag-with-default-text", + fl: &Uint64SliceFlag{Name: "handles", DefaultText: "-2"}, + expected: "--handles value\t(default: -2)", + }, { name: "path-flag", fl: &PathFlag{Name: "soup"}, @@ -1155,6 +1195,198 @@ func TestInt64SliceFlagValueFromContext(t *testing.T) { expect(t, f.Get(ctx), []int64{1, 2, 3}) } +var uintSliceFlagTests = []struct { + name string + aliases []string + value *UintSlice + expected string +}{ + {"heads", nil, NewUintSlice(), "--heads value\t(accepts multiple inputs)"}, + {"H", nil, NewUintSlice(), "-H value\t(accepts multiple inputs)"}, + {"heads", []string{"H"}, NewUintSlice(uint(2), uint(17179869184)), + "--heads value, -H value\t(default: 2, 17179869184)\t(accepts multiple inputs)"}, +} + +func TestUintSliceFlagHelpOutput(t *testing.T) { + for _, test := range uintSliceFlagTests { + fl := UintSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} + output := fl.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestUintSliceFlagWithEnvVarHelpOutput(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("APP_SMURF", "42,17179869184") + + for _, test := range uintSliceFlagTests { + fl := UintSliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} + output := fl.String() + + expectedSuffix := " [$APP_SMURF]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_SMURF%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%q does not end with"+expectedSuffix, output) + } + } +} + +func TestUintSliceFlagApply_ParentContext(t *testing.T) { + _ = (&App{ + Flags: []Flag{ + &UintSliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewUintSlice(1, 2, 3)}, + }, + Commands: []*Command{ + { + Name: "child", + Action: func(ctx *Context) error { + expected := []uint{1, 2, 3} + if !reflect.DeepEqual(ctx.UintSlice("numbers"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.UintSlice("numbers")) + } + if !reflect.DeepEqual(ctx.UintSlice("n"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.UintSlice("n")) + } + return nil + }, + }, + }, + }).Run([]string{"run", "child"}) +} + +func TestUintSliceFlag_SetFromParentContext(t *testing.T) { + fl := &UintSliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewUintSlice(1, 2, 3, 4)} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + ctx := &Context{ + parentContext: &Context{ + flagSet: set, + }, + flagSet: flag.NewFlagSet("empty", 0), + } + expected := []uint{1, 2, 3, 4} + if !reflect.DeepEqual(ctx.UintSlice("numbers"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.UintSlice("numbers")) + } +} +func TestUintSliceFlag_ReturnNil(t *testing.T) { + fl := &UintSliceFlag{} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + ctx := &Context{ + parentContext: &Context{ + flagSet: set, + }, + flagSet: flag.NewFlagSet("empty", 0), + } + expected := []uint(nil) + if !reflect.DeepEqual(ctx.UintSlice("numbers"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.UintSlice("numbers")) + } +} + +var uint64SliceFlagTests = []struct { + name string + aliases []string + value *Uint64Slice + expected string +}{ + {"heads", nil, NewUint64Slice(), "--heads value\t(accepts multiple inputs)"}, + {"H", nil, NewUint64Slice(), "-H value\t(accepts multiple inputs)"}, + {"heads", []string{"H"}, NewUint64Slice(uint64(2), uint64(17179869184)), + "--heads value, -H value\t(default: 2, 17179869184)\t(accepts multiple inputs)"}, +} + +func TestUint64SliceFlagHelpOutput(t *testing.T) { + for _, test := range uint64SliceFlagTests { + fl := Uint64SliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} + output := fl.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestUint64SliceFlagWithEnvVarHelpOutput(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("APP_SMURF", "42,17179869184") + + for _, test := range uint64SliceFlagTests { + fl := Uint64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} + output := fl.String() + + expectedSuffix := " [$APP_SMURF]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_SMURF%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%q does not end with"+expectedSuffix, output) + } + } +} + +func TestUint64SliceFlagApply_ParentContext(t *testing.T) { + _ = (&App{ + Flags: []Flag{ + &Uint64SliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewUint64Slice(1, 2, 3)}, + }, + Commands: []*Command{ + { + Name: "child", + Action: func(ctx *Context) error { + expected := []uint64{1, 2, 3} + if !reflect.DeepEqual(ctx.Uint64Slice("numbers"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Uint64Slice("numbers")) + } + if !reflect.DeepEqual(ctx.Uint64Slice("n"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Uint64Slice("n")) + } + return nil + }, + }, + }, + }).Run([]string{"run", "child"}) +} + +func TestUint64SliceFlag_SetFromParentContext(t *testing.T) { + fl := &Uint64SliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewUint64Slice(1, 2, 3, 4)} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + ctx := &Context{ + parentContext: &Context{ + flagSet: set, + }, + flagSet: flag.NewFlagSet("empty", 0), + } + expected := []uint64{1, 2, 3, 4} + if !reflect.DeepEqual(ctx.Uint64Slice("numbers"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Uint64Slice("numbers")) + } +} +func TestUint64SliceFlag_ReturnNil(t *testing.T) { + fl := &Uint64SliceFlag{} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + ctx := &Context{ + parentContext: &Context{ + flagSet: set, + }, + flagSet: flag.NewFlagSet("empty", 0), + } + expected := []uint64(nil) + if !reflect.DeepEqual(ctx.Uint64Slice("numbers"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Uint64Slice("numbers")) + } +} + var float64FlagTests = []struct { name string expected string @@ -2444,29 +2676,41 @@ type flagDefaultTestCase struct { func TestFlagDefaultValue(t *testing.T) { cases := []*flagDefaultTestCase{ { - name: "stringSclice", + name: "stringSlice", flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")}, toParse: []string{"--flag", "parsed"}, expect: `--flag value [ --flag value ] (default: "default1", "default2")`, }, { - name: "float64Sclice", + name: "float64Slice", flag: &Float64SliceFlag{Name: "flag", Value: NewFloat64Slice(1.1, 2.2)}, toParse: []string{"--flag", "13.3"}, expect: `--flag value [ --flag value ] (default: 1.1, 2.2)`, }, { - name: "int64Sclice", + name: "int64Slice", flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)}, toParse: []string{"--flag", "13"}, expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { - name: "intSclice", + name: "intSlice", flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)}, toParse: []string{"--flag", "13"}, expect: `--flag value [ --flag value ] (default: 1, 2)`, }, + { + name: "uint64Slice", + flag: &Uint64SliceFlag{Name: "flag", Value: NewUint64Slice(1, 2)}, + toParse: []string{"--flag", "13"}, + expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, + }, + { + name: "uintSlice", + flag: &UintSliceFlag{Name: "flag", Value: NewUintSlice(1, 2)}, + toParse: []string{"--flag", "13"}, + expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, + }, { name: "string", flag: &StringFlag{Name: "flag", Value: "default"}, @@ -2509,29 +2753,41 @@ type flagValueTestCase struct { func TestFlagValue(t *testing.T) { cases := []*flagValueTestCase{ &flagValueTestCase{ - name: "stringSclice", + name: "stringSlice", flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")}, toParse: []string{"--flag", "parsed,parsed2", "--flag", "parsed3,parsed4"}, expect: `[parsed parsed2 parsed3 parsed4]`, }, &flagValueTestCase{ - name: "float64Sclice", + name: "float64Slice", flag: &Float64SliceFlag{Name: "flag", Value: NewFloat64Slice(1.1, 2.2)}, toParse: []string{"--flag", "13.3,14.4", "--flag", "15.5,16.6"}, expect: `[]float64{13.3, 14.4, 15.5, 16.6}`, }, &flagValueTestCase{ - name: "int64Sclice", + name: "int64Slice", flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)}, toParse: []string{"--flag", "13,14", "--flag", "15,16"}, expect: `[]int64{13, 14, 15, 16}`, }, &flagValueTestCase{ - name: "intSclice", + name: "intSlice", flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)}, toParse: []string{"--flag", "13,14", "--flag", "15,16"}, expect: `[]int{13, 14, 15, 16}`, }, + &flagValueTestCase{ + name: "uint64Slice", + flag: &Uint64SliceFlag{Name: "flag", Value: NewUint64Slice(1, 2)}, + toParse: []string{"--flag", "13,14", "--flag", "15,16"}, + expect: `[]uint64{13, 14, 15, 16}`, + }, + &flagValueTestCase{ + name: "uintSlice", + flag: &UintSliceFlag{Name: "flag", Value: NewUintSlice(1, 2)}, + toParse: []string{"--flag", "13,14", "--flag", "15,16"}, + expect: `[]uint{13, 14, 15, 16}`, + }, } for i, v := range cases { set := flag.NewFlagSet("test", 0) diff --git a/flag_uint64_slice.go b/flag_uint64_slice.go new file mode 100644 index 0000000000..da01eadd25 --- /dev/null +++ b/flag_uint64_slice.go @@ -0,0 +1,192 @@ +package cli + +import ( + "encoding/json" + "flag" + "fmt" + "strconv" + "strings" +) + +// Uint64Slice wraps []int64 to satisfy flag.Value +type Uint64Slice struct { + slice []uint64 + hasBeenSet bool +} + +// NewUint64Slice makes an *Uint64Slice with default values +func NewUint64Slice(defaults ...uint64) *Uint64Slice { + return &Uint64Slice{slice: append([]uint64{}, defaults...)} +} + +// clone allocate a copy of self object +func (i *Uint64Slice) clone() *Uint64Slice { + n := &Uint64Slice{ + slice: make([]uint64, len(i.slice)), + hasBeenSet: i.hasBeenSet, + } + copy(n.slice, i.slice) + return n +} + +// Set parses the value into an integer and appends it to the list of values +func (i *Uint64Slice) Set(value string) error { + if !i.hasBeenSet { + i.slice = []uint64{} + i.hasBeenSet = true + } + + if strings.HasPrefix(value, slPfx) { + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.slice) + i.hasBeenSet = true + return nil + } + + for _, s := range flagSplitMultiValues(value) { + tmp, err := strconv.ParseUint(strings.TrimSpace(s), 0, 64) + if err != nil { + return err + } + + i.slice = append(i.slice, tmp) + } + + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (i *Uint64Slice) String() string { + v := i.slice + if v == nil { + // treat nil the same as zero length non-nil + v = make([]uint64, 0) + } + str := fmt.Sprintf("%d", v) + str = strings.Replace(str, " ", ", ", -1) + str = strings.Replace(str, "[", "{", -1) + str = strings.Replace(str, "]", "}", -1) + return fmt.Sprintf("[]uint64%s", str) +} + +// Serialize allows Uint64Slice to fulfill Serializer +func (i *Uint64Slice) Serialize() string { + jsonBytes, _ := json.Marshal(i.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) +} + +// Value returns the slice of ints set by this flag +func (i *Uint64Slice) Value() []uint64 { + return i.slice +} + +// Get returns the slice of ints set by this flag +func (i *Uint64Slice) Get() interface{} { + return *i +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *Uint64SliceFlag) String() string { + return withEnvHint(f.GetEnvVars(), stringifyUint64SliceFlag(f)) +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *Uint64SliceFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *Uint64SliceFlag) GetUsage() string { + return f.Usage +} + +// GetCategory returns the category for the flag +func (f *Uint64SliceFlag) GetCategory() string { + return f.Category +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *Uint64SliceFlag) GetValue() string { + if f.Value != nil { + return f.Value.String() + } + return "" +} + +// GetDefaultText returns the default text for this flag +func (f *Uint64SliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *Uint64SliceFlag) GetEnvVars() []string { + return f.EnvVars +} + +// Apply populates the flag given the flag set and environment +func (f *Uint64SliceFlag) Apply(set *flag.FlagSet) error { + // apply any default + if f.Destination != nil && f.Value != nil { + f.Destination.slice = make([]uint64, len(f.Value.slice)) + copy(f.Destination.slice, f.Value.slice) + } + + // resolve setValue (what we will assign to the set) + var setValue *Uint64Slice + switch { + case f.Destination != nil: + setValue = f.Destination + case f.Value != nil: + setValue = f.Value.clone() + default: + setValue = new(Uint64Slice) + } + + if val, source, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok && val != "" { + for _, s := range flagSplitMultiValues(val) { + if err := setValue.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %q as uint64 slice value from %s for flag %s: %s", val, source, f.Name, err) + } + } + + // Set this to false so that we reset the slice if we then set values from + // flags that have already been set by the environment. + setValue.hasBeenSet = false + f.HasBeenSet = true + } + + for _, name := range f.Names() { + set.Var(setValue, name, f.Usage) + } + + return nil +} + +// Get returns the flag’s value in the given Context. +func (f *Uint64SliceFlag) Get(ctx *Context) []uint64 { + return ctx.Uint64Slice(f.Name) +} + +// Uint64Slice looks up the value of a local Uint64SliceFlag, returns +// nil if not found +func (cCtx *Context) Uint64Slice(name string) []uint64 { + if fs := cCtx.lookupFlagSet(name); fs != nil { + return lookupUint64Slice(name, fs) + } + return nil +} + +func lookupUint64Slice(name string, set *flag.FlagSet) []uint64 { + f := set.Lookup(name) + if f != nil { + if slice, ok := unwrapFlagValue(f.Value).(*Uint64Slice); ok { + return slice.Value() + } + } + return nil +} diff --git a/flag_uint_slice.go b/flag_uint_slice.go new file mode 100644 index 0000000000..8cdd7583c3 --- /dev/null +++ b/flag_uint_slice.go @@ -0,0 +1,203 @@ +package cli + +import ( + "encoding/json" + "flag" + "fmt" + "strconv" + "strings" +) + +// UintSlice wraps []int to satisfy flag.Value +type UintSlice struct { + slice []uint + hasBeenSet bool +} + +// NewUintSlice makes an *UintSlice with default values +func NewUintSlice(defaults ...uint) *UintSlice { + return &UintSlice{slice: append([]uint{}, defaults...)} +} + +// clone allocate a copy of self object +func (i *UintSlice) clone() *UintSlice { + n := &UintSlice{ + slice: make([]uint, len(i.slice)), + hasBeenSet: i.hasBeenSet, + } + copy(n.slice, i.slice) + return n +} + +// TODO: Consistently have specific Set function for Int64 and Float64 ? +// SetInt directly adds an integer to the list of values +func (i *UintSlice) SetUint(value uint) { + if !i.hasBeenSet { + i.slice = []uint{} + i.hasBeenSet = true + } + + i.slice = append(i.slice, value) +} + +// Set parses the value into an integer and appends it to the list of values +func (i *UintSlice) Set(value string) error { + if !i.hasBeenSet { + i.slice = []uint{} + i.hasBeenSet = true + } + + if strings.HasPrefix(value, slPfx) { + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.slice) + i.hasBeenSet = true + return nil + } + + for _, s := range flagSplitMultiValues(value) { + tmp, err := strconv.ParseUint(strings.TrimSpace(s), 0, 32) + if err != nil { + return err + } + + i.slice = append(i.slice, uint(tmp)) + } + + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (i *UintSlice) String() string { + v := i.slice + if v == nil { + // treat nil the same as zero length non-nil + v = make([]uint, 0) + } + str := fmt.Sprintf("%d", v) + str = strings.Replace(str, " ", ", ", -1) + str = strings.Replace(str, "[", "{", -1) + str = strings.Replace(str, "]", "}", -1) + return fmt.Sprintf("[]uint%s", str) +} + +// Serialize allows UintSlice to fulfill Serializer +func (i *UintSlice) Serialize() string { + jsonBytes, _ := json.Marshal(i.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) +} + +// Value returns the slice of ints set by this flag +func (i *UintSlice) Value() []uint { + return i.slice +} + +// Get returns the slice of ints set by this flag +func (i *UintSlice) Get() interface{} { + return *i +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *UintSliceFlag) String() string { + return withEnvHint(f.GetEnvVars(), stringifyUintSliceFlag(f)) +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *UintSliceFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *UintSliceFlag) GetUsage() string { + return f.Usage +} + +// GetCategory returns the category for the flag +func (f *UintSliceFlag) GetCategory() string { + return f.Category +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *UintSliceFlag) GetValue() string { + if f.Value != nil { + return f.Value.String() + } + return "" +} + +// GetDefaultText returns the default text for this flag +func (f *UintSliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *UintSliceFlag) GetEnvVars() []string { + return f.EnvVars +} + +// Apply populates the flag given the flag set and environment +func (f *UintSliceFlag) Apply(set *flag.FlagSet) error { + // apply any default + if f.Destination != nil && f.Value != nil { + f.Destination.slice = make([]uint, len(f.Value.slice)) + copy(f.Destination.slice, f.Value.slice) + } + + // resolve setValue (what we will assign to the set) + var setValue *UintSlice + switch { + case f.Destination != nil: + setValue = f.Destination + case f.Value != nil: + setValue = f.Value.clone() + default: + setValue = new(UintSlice) + } + + if val, source, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok && val != "" { + for _, s := range flagSplitMultiValues(val) { + if err := setValue.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %q as uint slice value from %s for flag %s: %s", val, source, f.Name, err) + } + } + + // Set this to false so that we reset the slice if we then set values from + // flags that have already been set by the environment. + setValue.hasBeenSet = false + f.HasBeenSet = true + } + + for _, name := range f.Names() { + set.Var(setValue, name, f.Usage) + } + + return nil +} + +// Get returns the flag’s value in the given Context. +func (f *UintSliceFlag) Get(ctx *Context) []uint { + return ctx.UintSlice(f.Name) +} + +// UintSlice looks up the value of a local UintSliceFlag, returns +// nil if not found +func (cCtx *Context) UintSlice(name string) []uint { + if fs := cCtx.lookupFlagSet(name); fs != nil { + return lookupUintSlice(name, fs) + } + return nil +} + +func lookupUintSlice(name string, set *flag.FlagSet) []uint { + f := set.Lookup(name) + if f != nil { + if slice, ok := unwrapFlagValue(f.Value).(*UintSlice); ok { + return slice.Value() + } + } + return nil +} From 0764ca2295864eac676a5292224ce7ef59bd2002 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 15 Aug 2022 10:26:36 -0400 Subject: [PATCH 075/136] Rebase --- flag.go | 23 ----------- zz_generated.flags.go | 80 ++++++++++++++++++++++++++++++++++++++ zz_generated.flags_test.go | 38 ++++++++++++++++++ 3 files changed, 118 insertions(+), 23 deletions(-) diff --git a/flag.go b/flag.go index 9616cd3670..1618b4da86 100644 --- a/flag.go +++ b/flag.go @@ -7,7 +7,6 @@ import ( "io/ioutil" "regexp" "runtime" - "strconv" "strings" "syscall" "time" @@ -303,28 +302,6 @@ func stringifyFlag(f Flag) string { fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault)) } -func stringifyUintSliceFlag(f *UintSliceFlag) string { - var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, strconv.FormatUint(uint64(i), 10)) - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - -func stringifyUint64SliceFlag(f *Uint64SliceFlag) string { - var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, strconv.FormatUint(i, 10)) - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - func stringifySliceFlag(usage string, names, defaultVals []string) string { placeholder, usage := unquoteUsage(usage) if placeholder == "" { diff --git a/zz_generated.flags.go b/zz_generated.flags.go index a40f803841..b4081d4a86 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -497,6 +497,86 @@ func (f *TimestampFlag) GetDefaultText() string { return f.GetValue() } +// Uint64SliceFlag is a flag with type *Uint64Slice +type Uint64SliceFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Uint64Slice + Destination *Uint64Slice + + Aliases []string + EnvVars []string +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Uint64SliceFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *Uint64SliceFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether or not the flag is required +func (f *Uint64SliceFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Uint64SliceFlag) IsVisible() bool { + return !f.Hidden +} + +// UintSliceFlag is a flag with type *UintSlice +type UintSliceFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *UintSlice + Destination *UintSlice + + Aliases []string + EnvVars []string +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *UintSliceFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *UintSliceFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether or not the flag is required +func (f *UintSliceFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *UintSliceFlag) IsVisible() bool { + return !f.Hidden +} + // BoolFlag is a flag with type bool type BoolFlag struct { Name string diff --git a/zz_generated.flags_test.go b/zz_generated.flags_test.go index a0769a2024..fa010145f0 100644 --- a/zz_generated.flags_test.go +++ b/zz_generated.flags_test.go @@ -160,6 +160,44 @@ func TestTimestampFlag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestUint64SliceFlag_SatisfiesFlagInterface(t *testing.T) { + var f cli.Flag = &cli.Uint64SliceFlag{} + + _ = f.IsSet() + _ = f.Names() +} + +func TestUint64SliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.Uint64SliceFlag{} + + _ = f.IsRequired() +} + +func TestUint64SliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.Uint64SliceFlag{} + + _ = f.IsVisible() +} + +func TestUintSliceFlag_SatisfiesFlagInterface(t *testing.T) { + var f cli.Flag = &cli.UintSliceFlag{} + + _ = f.IsSet() + _ = f.Names() +} + +func TestUintSliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.UintSliceFlag{} + + _ = f.IsRequired() +} + +func TestUintSliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.UintSliceFlag{} + + _ = f.IsVisible() +} + func TestBoolFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.BoolFlag{} From 7097d0eedd477ce5f152d99c6247cce7d7e6d1a0 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 15 Aug 2022 11:41:28 -0400 Subject: [PATCH 076/136] Fix tests per latest main --- flag_test.go | 16 ++++++++-------- flag_uint64_slice.go | 13 ++++++++++++- flag_uint_slice.go | 13 ++++++++++++- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/flag_test.go b/flag_test.go index 87bf3601b5..051135d412 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1201,10 +1201,10 @@ var uintSliceFlagTests = []struct { value *UintSlice expected string }{ - {"heads", nil, NewUintSlice(), "--heads value\t(accepts multiple inputs)"}, - {"H", nil, NewUintSlice(), "-H value\t(accepts multiple inputs)"}, + {"heads", nil, NewUintSlice(), "--heads value [ --heads value ]\t"}, + {"H", nil, NewUintSlice(), "-H value [ -H value ]\t"}, {"heads", []string{"H"}, NewUintSlice(uint(2), uint(17179869184)), - "--heads value, -H value\t(default: 2, 17179869184)\t(accepts multiple inputs)"}, + "--heads value, -H value [ --heads value, -H value ]\t(default: 2, 17179869184)"}, } func TestUintSliceFlagHelpOutput(t *testing.T) { @@ -1297,10 +1297,10 @@ var uint64SliceFlagTests = []struct { value *Uint64Slice expected string }{ - {"heads", nil, NewUint64Slice(), "--heads value\t(accepts multiple inputs)"}, - {"H", nil, NewUint64Slice(), "-H value\t(accepts multiple inputs)"}, + {"heads", nil, NewUint64Slice(), "--heads value [ --heads value ]\t"}, + {"H", nil, NewUint64Slice(), "-H value [ -H value ]\t"}, {"heads", []string{"H"}, NewUint64Slice(uint64(2), uint64(17179869184)), - "--heads value, -H value\t(default: 2, 17179869184)\t(accepts multiple inputs)"}, + "--heads value, -H value [ --heads value, -H value ]\t(default: 2, 17179869184)"}, } func TestUint64SliceFlagHelpOutput(t *testing.T) { @@ -2703,13 +2703,13 @@ func TestFlagDefaultValue(t *testing.T) { name: "uint64Slice", flag: &Uint64SliceFlag{Name: "flag", Value: NewUint64Slice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "uintSlice", flag: &UintSliceFlag{Name: "flag", Value: NewUintSlice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "string", diff --git a/flag_uint64_slice.go b/flag_uint64_slice.go index da01eadd25..e60c3ea8af 100644 --- a/flag_uint64_slice.go +++ b/flag_uint64_slice.go @@ -88,7 +88,7 @@ func (i *Uint64Slice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *Uint64SliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), stringifyUint64SliceFlag(f)) + return withEnvHint(f.GetEnvVars(), f.stringify()) } // TakesValue returns true of the flag takes a value, otherwise false @@ -172,6 +172,17 @@ func (f *Uint64SliceFlag) Get(ctx *Context) []uint64 { return ctx.Uint64Slice(f.Name) } +func (f *Uint64SliceFlag) stringify() string { + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.FormatUint(i, 10)) + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + // Uint64Slice looks up the value of a local Uint64SliceFlag, returns // nil if not found func (cCtx *Context) Uint64Slice(name string) []uint64 { diff --git a/flag_uint_slice.go b/flag_uint_slice.go index 8cdd7583c3..350b29ccf0 100644 --- a/flag_uint_slice.go +++ b/flag_uint_slice.go @@ -99,7 +99,7 @@ func (i *UintSlice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *UintSliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), stringifyUintSliceFlag(f)) + return withEnvHint(f.GetEnvVars(), f.stringify()) } // TakesValue returns true of the flag takes a value, otherwise false @@ -183,6 +183,17 @@ func (f *UintSliceFlag) Get(ctx *Context) []uint { return ctx.UintSlice(f.Name) } +func (f *UintSliceFlag) stringify() string { + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.FormatUint(uint64(i), 10)) + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + // UintSlice looks up the value of a local UintSliceFlag, returns // nil if not found func (cCtx *Context) UintSlice(name string) []uint { From 7405a90b381ee948c6803d08eddbcddf9dbebd58 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Wed, 31 Aug 2022 08:20:57 -0400 Subject: [PATCH 077/136] Fix formatting --- flag_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/flag_test.go b/flag_test.go index 051135d412..158a6270c8 100644 --- a/flag_test.go +++ b/flag_test.go @@ -2679,55 +2679,55 @@ func TestFlagDefaultValue(t *testing.T) { name: "stringSlice", flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")}, toParse: []string{"--flag", "parsed"}, - expect: `--flag value [ --flag value ] (default: "default1", "default2")`, + expect: `--flag value [ --flag value ] (default: "default1", "default2")`, }, { name: "float64Slice", flag: &Float64SliceFlag{Name: "flag", Value: NewFloat64Slice(1.1, 2.2)}, toParse: []string{"--flag", "13.3"}, - expect: `--flag value [ --flag value ] (default: 1.1, 2.2)`, + expect: `--flag value [ --flag value ] (default: 1.1, 2.2)`, }, { name: "int64Slice", flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value [ --flag value ] (default: 1, 2)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "intSlice", flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value [ --flag value ] (default: 1, 2)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "uint64Slice", flag: &Uint64SliceFlag{Name: "flag", Value: NewUint64Slice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value [ --flag value ] (default: 1, 2)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "uintSlice", flag: &UintSliceFlag{Name: "flag", Value: NewUintSlice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value [ --flag value ] (default: 1, 2)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "string", flag: &StringFlag{Name: "flag", Value: "default"}, toParse: []string{"--flag", "parsed"}, - expect: `--flag value (default: "default")`, + expect: `--flag value (default: "default")`, }, { name: "bool", flag: &BoolFlag{Name: "flag", Value: true}, toParse: []string{"--flag", "false"}, - expect: `--flag (default: true)`, + expect: `--flag (default: true)`, }, { name: "uint64", flag: &Uint64Flag{Name: "flag", Value: 1}, toParse: []string{"--flag", "13"}, - expect: `--flag value (default: 1)`, + expect: `--flag value (default: 1)`, }, } for i, v := range cases { From 9bcffd07a4f8784f75409682d343367446d0c9f0 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Wed, 31 Aug 2022 08:25:29 -0400 Subject: [PATCH 078/136] Merge from main --- altsrc/flag_test.go | 2 +- flag_test.go | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index 6222ec2e73..67ccf4f829 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -40,7 +40,7 @@ func (ris *racyInputSource) isSet(name string) bool { func TestGenericApplyInputSourceValue_Alias(t *testing.T) { v := &Parser{"abc", "def"} tis := testApplyInputSource{ - Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Aliases: []string{"test_alias"}, Value: &Parser{}}), + Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Aliases: []string{"test_alias"}, Value: &Parser{}}), FlagName: "test_alias", MapValue: v, } diff --git a/flag_test.go b/flag_test.go index 158a6270c8..051135d412 100644 --- a/flag_test.go +++ b/flag_test.go @@ -2679,55 +2679,55 @@ func TestFlagDefaultValue(t *testing.T) { name: "stringSlice", flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")}, toParse: []string{"--flag", "parsed"}, - expect: `--flag value [ --flag value ] (default: "default1", "default2")`, + expect: `--flag value [ --flag value ] (default: "default1", "default2")`, }, { name: "float64Slice", flag: &Float64SliceFlag{Name: "flag", Value: NewFloat64Slice(1.1, 2.2)}, toParse: []string{"--flag", "13.3"}, - expect: `--flag value [ --flag value ] (default: 1.1, 2.2)`, + expect: `--flag value [ --flag value ] (default: 1.1, 2.2)`, }, { name: "int64Slice", flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value [ --flag value ] (default: 1, 2)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "intSlice", flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value [ --flag value ] (default: 1, 2)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "uint64Slice", flag: &Uint64SliceFlag{Name: "flag", Value: NewUint64Slice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value [ --flag value ] (default: 1, 2)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "uintSlice", flag: &UintSliceFlag{Name: "flag", Value: NewUintSlice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value [ --flag value ] (default: 1, 2)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "string", flag: &StringFlag{Name: "flag", Value: "default"}, toParse: []string{"--flag", "parsed"}, - expect: `--flag value (default: "default")`, + expect: `--flag value (default: "default")`, }, { name: "bool", flag: &BoolFlag{Name: "flag", Value: true}, toParse: []string{"--flag", "false"}, - expect: `--flag (default: true)`, + expect: `--flag (default: true)`, }, { name: "uint64", flag: &Uint64Flag{Name: "flag", Value: 1}, toParse: []string{"--flag", "13"}, - expect: `--flag value (default: 1)`, + expect: `--flag value (default: 1)`, }, } for i, v := range cases { From 2a5960c1cb10a6b83a93d4e31553b21229e529bd Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Tue, 6 Sep 2022 07:10:47 -0400 Subject: [PATCH 079/136] Add coverage threshold --- .github/.codecov.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/.codecov.yml b/.github/.codecov.yml index 69cb76019a..5395ce707d 100644 --- a/.github/.codecov.yml +++ b/.github/.codecov.yml @@ -1 +1,3 @@ comment: false +coverage: + threshold: 5% From 5b96605ae5082e8a32383c0d06af5d815a7fac83 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sat, 10 Sep 2022 09:27:33 -0400 Subject: [PATCH 080/136] Add additional test to fix codecov --- flag_test.go | 223 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) diff --git a/flag_test.go b/flag_test.go index 051135d412..4434fceed2 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1044,6 +1044,47 @@ func TestIntSliceFlagApply_SetsAllNames(t *testing.T) { expect(t, err, nil) } +func TestIntSliceFlagApply_UsesEnvValues_noDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "1 , 2") + var val IntSlice + fl := IntSliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: &val} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse(nil) + expect(t, err, nil) + expect(t, val.Value(), []int(nil)) + expect(t, set.Lookup("goat").Value.(*IntSlice).Value(), []int{1, 2}) +} + +func TestIntSliceFlagApply_UsesEnvValues_withDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "1 , 2") + val := NewIntSlice(3, 4) + fl := IntSliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: val} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + err := set.Parse(nil) + expect(t, err, nil) + expect(t, val.Value(), []int{3, 4}) + expect(t, set.Lookup("goat").Value.(*IntSlice).Value(), []int{1, 2}) +} + +func TestIntSliceFlagApply_DefaultValueWithDestination(t *testing.T) { + defValue := []int{1, 2} + + fl := IntSliceFlag{Name: "country", Value: NewIntSlice(defValue...), Destination: NewIntSlice(3)} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{}) + expect(t, err, nil) + expect(t, defValue, fl.Destination.Value()) +} + func TestIntSliceFlagApply_ParentContext(t *testing.T) { _ = (&App{ Flags: []Flag{ @@ -1133,6 +1174,56 @@ func TestInt64SliceFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestInt64SliceFlagApply_SetsAllNames(t *testing.T) { + fl := Int64SliceFlag{Name: "bits", Aliases: []string{"B", "bips"}} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{"--bits", "23", "-B", "3", "--bips", "99"}) + expect(t, err, nil) +} + +func TestInt64SliceFlagApply_UsesEnvValues_noDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "1 , 2") + var val Int64Slice + fl := Int64SliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: &val} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse(nil) + expect(t, err, nil) + expect(t, val.Value(), []int64(nil)) + expect(t, set.Lookup("goat").Value.(*Int64Slice).Value(), []int64{1, 2}) +} + +func TestInt64SliceFlagApply_UsesEnvValues_withDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "1 , 2") + val := NewInt64Slice(3, 4) + fl := Int64SliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: val} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + err := set.Parse(nil) + expect(t, err, nil) + expect(t, val.Value(), []int64{3, 4}) + expect(t, set.Lookup("goat").Value.(*Int64Slice).Value(), []int64{1, 2}) +} + +func TestInt64SliceFlagApply_DefaultValueWithDestination(t *testing.T) { + defValue := []int64{1, 2} + + fl := Int64SliceFlag{Name: "country", Value: NewInt64Slice(defValue...), Destination: NewInt64Slice(3)} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{}) + expect(t, err, nil) + expect(t, defValue, fl.Destination.Value()) +} + func TestInt64SliceFlagApply_ParentContext(t *testing.T) { _ = (&App{ Flags: []Flag{ @@ -1237,6 +1328,56 @@ func TestUintSliceFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestUintSliceFlagApply_SetsAllNames(t *testing.T) { + fl := UintSliceFlag{Name: "bits", Aliases: []string{"B", "bips"}} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{"--bits", "23", "-B", "3", "--bips", "99"}) + expect(t, err, nil) +} + +func TestUintSliceFlagApply_UsesEnvValues_noDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "1 , 2") + var val UintSlice + fl := UintSliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: &val} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse(nil) + expect(t, err, nil) + expect(t, val.Value(), []uint(nil)) + expect(t, set.Lookup("goat").Value.(*UintSlice).Value(), []uint{1, 2}) +} + +func TestUintSliceFlagApply_UsesEnvValues_withDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "1 , 2") + val := NewUintSlice(3, 4) + fl := UintSliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: val} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + err := set.Parse(nil) + expect(t, err, nil) + expect(t, val.Value(), []uint{3, 4}) + expect(t, set.Lookup("goat").Value.(*UintSlice).Value(), []uint{1, 2}) +} + +func TestUintSliceFlagApply_DefaultValueWithDestination(t *testing.T) { + defValue := []uint{1, 2} + + fl := UintSliceFlag{Name: "country", Value: NewUintSlice(defValue...), Destination: NewUintSlice(3)} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{}) + expect(t, err, nil) + expect(t, defValue, fl.Destination.Value()) +} + func TestUintSliceFlagApply_ParentContext(t *testing.T) { _ = (&App{ Flags: []Flag{ @@ -1333,6 +1474,56 @@ func TestUint64SliceFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestUint64SliceFlagApply_SetsAllNames(t *testing.T) { + fl := Uint64SliceFlag{Name: "bits", Aliases: []string{"B", "bips"}} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{"--bits", "23", "-B", "3", "--bips", "99"}) + expect(t, err, nil) +} + +func TestUint64SliceFlagApply_UsesEnvValues_noDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "1 , 2") + var val Uint64Slice + fl := Uint64SliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: &val} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse(nil) + expect(t, err, nil) + expect(t, val.Value(), []uint64(nil)) + expect(t, set.Lookup("goat").Value.(*Uint64Slice).Value(), []uint64{1, 2}) +} + +func TestUint64SliceFlagApply_UsesEnvValues_withDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "1 , 2") + val := NewUint64Slice(3, 4) + fl := Uint64SliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: val} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + err := set.Parse(nil) + expect(t, err, nil) + expect(t, val.Value(), []uint64{3, 4}) + expect(t, set.Lookup("goat").Value.(*Uint64Slice).Value(), []uint64{1, 2}) +} + +func TestUint64SliceFlagApply_DefaultValueWithDestination(t *testing.T) { + defValue := []uint64{1, 2} + + fl := Uint64SliceFlag{Name: "country", Value: NewUint64Slice(defValue...), Destination: NewUint64Slice(3)} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{}) + expect(t, err, nil) + expect(t, defValue, fl.Destination.Value()) +} + func TestUint64SliceFlagApply_ParentContext(t *testing.T) { _ = (&App{ Flags: []Flag{ @@ -2577,6 +2768,38 @@ func TestInt64Slice_Serialized_Set(t *testing.T) { } } +func TestUintSlice_Serialized_Set(t *testing.T) { + sl0 := NewUintSlice(1, 2) + ser0 := sl0.Serialize() + + if len(ser0) < len(slPfx) { + t.Fatalf("serialized shorter than expected: %q", ser0) + } + + sl1 := NewUintSlice(3, 4) + _ = sl1.Set(ser0) + + if sl0.String() != sl1.String() { + t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1) + } +} + +func TestUint64Slice_Serialized_Set(t *testing.T) { + sl0 := NewUint64Slice(1, 2) + ser0 := sl0.Serialize() + + if len(ser0) < len(slPfx) { + t.Fatalf("serialized shorter than expected: %q", ser0) + } + + sl1 := NewUint64Slice(3, 4) + _ = sl1.Set(ser0) + + if sl0.String() != sl1.String() { + t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1) + } +} + func TestTimestamp_set(t *testing.T) { ts := Timestamp{ timestamp: nil, From 9c5b385796cc152b82a8c01e12dfb3822ad97aa6 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sat, 10 Sep 2022 09:36:30 -0400 Subject: [PATCH 081/136] Add additional test to fix codecov --- flag_test.go | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/flag_test.go b/flag_test.go index 4434fceed2..e7fa309fc6 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1676,6 +1676,56 @@ func TestFloat64SliceFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestFloat64SliceFlagApply_SetsAllNames(t *testing.T) { + fl := Float64SliceFlag{Name: "bits", Aliases: []string{"B", "bips"}} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{"--bits", "23", "-B", "3", "--bips", "99"}) + expect(t, err, nil) +} + +func TestFloat64SliceFlagApply_UsesEnvValues_noDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "1.0 , 2.0") + var val Float64Slice + fl := Float64SliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: &val} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse(nil) + expect(t, err, nil) + expect(t, val.Value(), []float64(nil)) + expect(t, set.Lookup("goat").Value.(*Float64Slice).Value(), []float64{1, 2}) +} + +func TestFloat64SliceFlagApply_UsesEnvValues_withDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "1.0 , 2.0") + val := NewFloat64Slice(3.0, 4.0) + fl := Float64SliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: val} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + err := set.Parse(nil) + expect(t, err, nil) + expect(t, val.Value(), []float64{3, 4}) + expect(t, set.Lookup("goat").Value.(*Float64Slice).Value(), []float64{1, 2}) +} + +func TestFloat64SliceFlagApply_DefaultValueWithDestination(t *testing.T) { + defValue := []float64{1.0, 2.0} + + fl := Float64SliceFlag{Name: "country", Value: NewFloat64Slice(defValue...), Destination: NewFloat64Slice(3)} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{}) + expect(t, err, nil) + expect(t, defValue, fl.Destination.Value()) +} + func TestFloat64SliceFlagValueFromContext(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Var(NewFloat64Slice(1.23, 4.56), "myflag", "doc") @@ -1684,6 +1734,29 @@ func TestFloat64SliceFlagValueFromContext(t *testing.T) { expect(t, f.Get(ctx), []float64{1.23, 4.56}) } +func TestFloat64SliceFlagApply_ParentContext(t *testing.T) { + _ = (&App{ + Flags: []Flag{ + &Float64SliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewFloat64Slice(1.0, 2.0, 3.0)}, + }, + Commands: []*Command{ + { + Name: "child", + Action: func(ctx *Context) error { + expected := []float64{1.0, 2.0, 3.0} + if !reflect.DeepEqual(ctx.Float64Slice("numbers"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Float64Slice("numbers")) + } + if !reflect.DeepEqual(ctx.Float64Slice("n"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Float64Slice("n")) + } + return nil + }, + }, + }, + }).Run([]string{"run", "child"}) +} + var genericFlagTests = []struct { name string value Generic From 0658d61a0e3fc5eb3717f39a4c41511f9bf5dc59 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 10 Sep 2022 10:29:41 -0400 Subject: [PATCH 082/136] Set codecov status thresholds to 5% and rename file to be less dotty --- .github/.codecov.yml | 3 --- .github/codecov.yml | 9 +++++++++ 2 files changed, 9 insertions(+), 3 deletions(-) delete mode 100644 .github/.codecov.yml create mode 100644 .github/codecov.yml diff --git a/.github/.codecov.yml b/.github/.codecov.yml deleted file mode 100644 index 5395ce707d..0000000000 --- a/.github/.codecov.yml +++ /dev/null @@ -1,3 +0,0 @@ -comment: false -coverage: - threshold: 5% diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 0000000000..256cd109a8 --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,9 @@ +comment: false +coverage: + status: + project: + default: + threshold: 5% + patch: + default: + threshold: 5% From e13c16bb10ec44e8c839dd3b03ff077c1027522a Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sat, 10 Sep 2022 18:03:25 -0400 Subject: [PATCH 083/136] Fix: dont generate pointer for dest for Generic flag --- cmd/urfave-cli-genflags/generated.gotmpl | 2 +- cmd/urfave-cli-genflags/main.go | 17 +++-- flag-spec.yaml | 89 +++++++++++++----------- zz_generated.flags.go | 2 +- 4 files changed, 63 insertions(+), 47 deletions(-) diff --git a/cmd/urfave-cli-genflags/generated.gotmpl b/cmd/urfave-cli-genflags/generated.gotmpl index 4511803a4b..85bd66fddf 100644 --- a/cmd/urfave-cli-genflags/generated.gotmpl +++ b/cmd/urfave-cli-genflags/generated.gotmpl @@ -17,7 +17,7 @@ type {{.TypeName}} struct { HasBeenSet bool Value {{if .ValuePointer}}*{{end}}{{.GoType}} - Destination *{{.GoType}} + Destination {{if .NoDestinationPointer}}{{else}}*{{end}}{{.GoType}} Aliases []string EnvVars []string diff --git a/cmd/urfave-cli-genflags/main.go b/cmd/urfave-cli-genflags/main.go index c754147bea..1b0fe532a6 100644 --- a/cmd/urfave-cli-genflags/main.go +++ b/cmd/urfave-cli-genflags/main.go @@ -223,10 +223,11 @@ func (gfs *Spec) SortedFlagTypes() []*FlagType { } type FlagTypeConfig struct { - SkipInterfaces []string `yaml:"skip_interfaces"` - StructFields []*FlagStructField `yaml:"struct_fields"` - TypeName string `yaml:"type_name"` - ValuePointer bool `yaml:"value_pointer"` + SkipInterfaces []string `yaml:"skip_interfaces"` + StructFields []*FlagStructField `yaml:"struct_fields"` + TypeName string `yaml:"type_name"` + ValuePointer bool `yaml:"value_pointer"` + NoDestinationPointer bool `yaml:"no_destination_pointer"` } type FlagStructField struct { @@ -256,6 +257,14 @@ func (ft *FlagType) ValuePointer() bool { return ft.Config.ValuePointer } +func (ft *FlagType) NoDestinationPointer() bool { + if ft.Config == nil { + return false + } + + return ft.Config.NoDestinationPointer +} + func (ft *FlagType) TypeName() string { return TypeName(ft.GoType, ft.Config) } diff --git a/flag-spec.yaml b/flag-spec.yaml index 51601f0fd1..1fdeb6e874 100644 --- a/flag-spec.yaml +++ b/flag-spec.yaml @@ -6,64 +6,71 @@ flag_types: bool: no_default_text: true struct_fields: - - { name: Count, type: int, pointer: true } - float64: {} - int64: - struct_fields: - - { name: Base, type: int } - int: - struct_fields: - - { name: Base, type: int } - time.Duration: {} - uint64: - struct_fields: - - { name: Base, type: int } - uint: - struct_fields: - - { name: Base, type: int } - - string: - no_default_text: true - struct_fields: - - { name: TakesFile, type: bool } - Generic: - struct_fields: - - { name: TakesFile, type: bool } - Path: - no_default_text: true - struct_fields: - - { name: TakesFile, type: bool } - + - name: Count + type: int + pointer: true + float64: Float64Slice: value_pointer: true skip_interfaces: - fmt.Stringer - Int64Slice: - value_pointer: true - skip_interfaces: - - fmt.Stringer + int: + struct_fields: + - name: Base + type: int IntSlice: value_pointer: true skip_interfaces: - fmt.Stringer - StringSlice: + int64: + struct_fields: + - name: Base + type: int + Int64Slice: value_pointer: true skip_interfaces: - fmt.Stringer + uint: struct_fields: - - { name: TakesFile, type: bool } - Timestamp: - value_pointer: true - struct_fields: - - { name: Layout, type: string } - - { name: Timezone, type: "*time.Location" } - + - name: Base + type: int UintSlice: value_pointer: true skip_interfaces: - fmt.Stringer + uint64: + struct_fields: + - name: Base + type: int Uint64Slice: + value_pointer: true + skip_interfaces: + - fmt.Stringer + string: + struct_fields: + - name: TakesFile + type: bool + StringSlice: value_pointer: true skip_interfaces: - fmt.Stringer - + struct_fields: + - name: TakesFile + type: bool + time.Duration: + Timestamp: + value_pointer: true + struct_fields: + - name: Layout + type: string + - name: Timezone + type: "*time.Location" + Generic: + no_destination_pointer: true + struct_fields: + - name: TakesFile + type: bool + Path: + struct_fields: + - name: TakesFile + type: bool diff --git a/zz_generated.flags.go b/zz_generated.flags.go index b4081d4a86..2c5bea0e62 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -86,7 +86,7 @@ type GenericFlag struct { HasBeenSet bool Value Generic - Destination *Generic + Destination Generic Aliases []string EnvVars []string From 8e3fa067e04a6e4d315d47e5f17abee4efb6ec89 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sun, 11 Sep 2022 10:21:56 -0400 Subject: [PATCH 084/136] Fix: Help name consistency among app/commands and subcommands --- app.go | 1 - help_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/app.go b/app.go index 3785b83230..d07839fbf2 100644 --- a/app.go +++ b/app.go @@ -133,7 +133,6 @@ func compileTime() time.Time { func NewApp() *App { return &App{ Name: filepath.Base(os.Args[0]), - HelpName: filepath.Base(os.Args[0]), Usage: "A new cli application", UsageText: "", BashComplete: DefaultAppComplete, diff --git a/help_test.go b/help_test.go index 9422b4a105..ade6f3dd6d 100644 --- a/help_test.go +++ b/help_test.go @@ -428,6 +428,58 @@ func TestShowCommandHelp_CommandAliases(t *testing.T) { } } +func TestHelpNameConsistency(t *testing.T) { + // Setup some very basic templates based on actual AppHelp, CommandHelp + // and SubcommandHelp templates to display the help name + // The inconsistency shows up when users use NewApp() as opposed to + // using App{...} directly + SubcommandHelpTemplate = `{{.HelpName}}` + app := NewApp() + app.Name = "bar" + app.CustomAppHelpTemplate = `{{.HelpName}}` + app.Commands = []*Command{ + { + Name: "command1", + CustomHelpTemplate: `{{.HelpName}}`, + Subcommands: []*Command{ + { + Name: "subcommand1", + CustomHelpTemplate: `{{.HelpName}}`, + }, + }, + }, + } + + tests := []struct { + name string + args []string + }{ + { + name: "App help", + args: []string{"foo"}, + }, + { + name: "Command help", + args: []string{"foo", "command1"}, + }, + { + name: "Subcommand help", + args: []string{"foo", "command1", "subcommand1"}, + }, + } + + for _, tt := range tests { + output := &bytes.Buffer{} + app.Writer = output + if err := app.Run(tt.args); err != nil { + t.Error(err) + } + if !strings.Contains(output.String(), "bar") { + t.Errorf("expected output to contain bar; got: %q", output.String()) + } + } +} + func TestShowSubcommandHelp_CommandAliases(t *testing.T) { app := &App{ Commands: []*Command{ From 8cc0a9c5dadd4074a706c46e78d1f2cfdfeea31c Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 11 Sep 2022 12:43:29 -0400 Subject: [PATCH 085/136] Move more functionality into internal/build/build.go and use make targets in CI, pass flag spec YAML through yq includes result of running `make v2approve` --- .github/workflows/cli.yml | 107 ++++------- Makefile | 20 ++- docs/CONTRIBUTING.md | 2 +- flag-spec.yaml | 18 +- internal/build/build.go | 368 ++++++++++++++++++++++++++++++-------- 5 files changed, 343 insertions(+), 172 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index c479c1ee83..665507f9f6 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -1,5 +1,4 @@ name: Run Tests - on: push: branches: @@ -12,10 +11,8 @@ on: branches: - main - v3-dev-main - permissions: contents: read - jobs: test: strategy: @@ -25,45 +22,26 @@ jobs: name: ${{ matrix.os }} @ Go ${{ matrix.go }} runs-on: ${{ matrix.os }} steps: - - name: Set up Go ${{ matrix.go }} - uses: actions/setup-go@v3 + - uses: actions/setup-go@v3 with: go-version: ${{ matrix.go }} - - name: Set PATH - run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}" - - - name: install goimports - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' - run: GOBIN=${PWD}/.local/bin go install golang.org/x/tools/cmd/goimports@latest - - - name: Checkout Code - uses: actions/checkout@v3 - - - name: goimports check - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' - run: test -z $(goimports -l .) - - - name: vet - run: go run internal/build/build.go vet - - - name: test with urfave_cli_no_docs tag - run: go run internal/build/build.go -tags urfave_cli_no_docs test - - - name: test - run: go run internal/build/build.go test - - - name: test urfave-cli-genflags - run: make -C cmd/urfave-cli-genflags - - - name: check-binary-size - run: go run internal/build/build.go check-binary-size - - - name: check-binary-size with tags (informational only) - run: go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size - - - name: Upload coverage to Codecov - if: success() && matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' + run: echo "${GITHUB_WORKSPACE}/.local/bin" | tee -a "${GITHUB_PATH}" >/dev/null + - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' + run: make ensure-goimports + - uses: actions/checkout@v3 + - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' + run: make lint + - run: make vet + - run: make tag-test + - run: make test + - run: make -C cmd/urfave-cli-genflags + - run: make check-binary-size + - run: make tag-check-binary-size + - run: make yamlfmt + - run: make diffcheck + - run: make v2diff + - if: success() && matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} @@ -73,36 +51,18 @@ jobs: name: test-docs runs-on: ubuntu-latest steps: - - name: Set up Go - uses: actions/setup-go@v3 + - uses: actions/setup-go@v3 with: go-version: 1.19.x - - - name: Use Node.js 16 - uses: actions/setup-node@v3 + - uses: actions/setup-node@v3 with: node-version: '16' - - name: Set PATH - run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}" - - - name: Checkout Code - uses: actions/checkout@v3 - - - name: Install Dependencies - run: | - mkdir -p "${GITHUB_WORKSPACE}/.local/bin" - curl -fsSL -o "${GITHUB_WORKSPACE}/.local/bin/gfmrun" "https://github.com/urfave/gfmrun/releases/download/v1.3.0/gfmrun-$(go env GOOS)-$(go env GOARCH)-v1.3.0" - chmod +x "${GITHUB_WORKSPACE}/.local/bin/gfmrun" - - - name: gfmrun - run: go run internal/build/build.go gfmrun --walk docs/v3/ - - - name: diff check - run: | - git diff --exit-code - git diff --cached --exit-code - + run: echo "${GITHUB_WORKSPACE}/.local/bin" | tee -a "${GITHUB_PATH}" >/dev/null + - uses: actions/checkout@v3 + - run: make ensure-gfmrun + - run: make gfmrun + - run: make diffcheck publish: permissions: contents: write @@ -114,18 +74,11 @@ jobs: needs: [test-docs] runs-on: ubuntu-latest steps: - - name: Checkout Code - uses: actions/checkout@v3 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - - name: Setup mkdocs - run: | - pip install -U pip - pip install -r mkdocs-requirements.txt - git remote rm origin - git remote add origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/urfave/cli.git - - - name: Publish Docs - run: | - mkdocs gh-deploy --force + - run: make ci-ensure-mkdocs + - run: make set-mkdocs-remote + env: + MKDOCS_REMOTE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - run: make deploy-mkdocs diff --git a/Makefile b/Makefile index 3b0e5e0bbd..46deea266f 100644 --- a/Makefile +++ b/Makefile @@ -4,8 +4,10 @@ # are very important so that maintainers and contributors can focus their # attention on files that are primarily Go. +GO_RUN_BUILD := go run internal/build/build.go + .PHONY: all -all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun v2diff +all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun yamlfmt v2diff # NOTE: this is a special catch-all rule to run any of the commands # defined in internal/build/build.go with optional arguments passed @@ -13,28 +15,28 @@ all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun v # # $ make test GFLAGS='--packages cli' %: - go run internal/build/build.go $(GFLAGS) $* $(FLAGS) + $(GO_RUN_BUILD) $(GFLAGS) $* $(FLAGS) .PHONY: tag-test tag-test: - go run internal/build/build.go -tags urfave_cli_no_docs test + $(GO_RUN_BUILD) -tags urfave_cli_no_docs test .PHONY: tag-check-binary-size tag-check-binary-size: - go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size + $(GO_RUN_BUILD) -tags urfave_cli_no_docs check-binary-size .PHONY: gfmrun gfmrun: - go run internal/build/build.go gfmrun docs/v2/manual.md + $(GO_RUN_BUILD) gfmrun --walk docs/v2/ + +.PHONY: ci-ensure-mkdocs +ci-ensure-mkdocs: + $(GO_RUN_BUILD) ensure-mkdocs --upgrade-pip .PHONY: docs docs: mkdocs build -.PHONY: docs-deps -docs-deps: - pip install -r mkdocs-requirements.txt - .PHONY: serve-docs serve-docs: mkdocs serve diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 4462899fdd..d657477f5f 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -107,7 +107,7 @@ following `make` targets may be used if desired: ```sh # install documentation dependencies with `pip` -make docs-deps +make ensure-mkdocs ``` ```sh diff --git a/flag-spec.yaml b/flag-spec.yaml index 1fdeb6e874..66745c62eb 100644 --- a/flag-spec.yaml +++ b/flag-spec.yaml @@ -3,13 +3,13 @@ # `Spec` type that maps to this file structure. flag_types: - bool: + bool: no_default_text: true struct_fields: - - name: Count + - name: Count type: int pointer: true - float64: + float64: Float64Slice: value_pointer: true skip_interfaces: @@ -34,7 +34,7 @@ flag_types: struct_fields: - name: Base type: int - UintSlice: + UintSlice: value_pointer: true skip_interfaces: - fmt.Stringer @@ -42,10 +42,10 @@ flag_types: struct_fields: - name: Base type: int - Uint64Slice: + Uint64Slice: value_pointer: true skip_interfaces: - - fmt.Stringer + - fmt.Stringer string: struct_fields: - name: TakesFile @@ -57,12 +57,12 @@ flag_types: struct_fields: - name: TakesFile type: bool - time.Duration: + time.Duration: Timestamp: value_pointer: true struct_fields: - - name: Layout - type: string + - name: Layout + type: string - name: Timezone type: "*time.Location" Generic: diff --git a/internal/build/build.go b/internal/build/build.go index e346e2d238..2c82f1324a 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -5,12 +5,17 @@ package main import ( "bufio" "bytes" + "errors" "fmt" + "io" "log" "math" + "net/http" + "net/url" "os" "os/exec" "path/filepath" + "runtime" "strings" "github.com/urfave/cli/v3" @@ -21,6 +26,8 @@ const ( goodNewsEmoji = "✨" checksPassedEmoji = "✅" + gfmrunVersion = "v1.3.0" + v2diffWarning = ` # The unified diff above indicates that the public API surface area # has changed. If you feel that the changes are acceptable and adhere @@ -45,63 +52,108 @@ func main() { log.Fatal(err) } - app := cli.NewApp() - - app.Name = "builder" - app.Usage = "Generates a new urfave/cli build!" - - app.Commands = cli.Commands{ - { - Name: "vet", - Action: VetActionFunc, - }, - { - Name: "test", - Action: TestActionFunc, - }, - { - Name: "gfmrun", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "walk", - Value: false, - Usage: "Walk the specified directory and perform validation on all markdown files", + app := &cli.App{ + Name: "builder", + Usage: "Do a thing for urfave/cli! (maybe build?)", + Commands: cli.Commands{ + { + Name: "vet", + Action: topRunAction("go", "vet", "./..."), + }, + { + Name: "test", + Action: TestActionFunc, + }, + { + Name: "gfmrun", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "walk", + Value: false, + Usage: "Walk the specified directory and perform validation on all markdown files", + }, }, + Action: GfmrunActionFunc, }, - Action: GfmrunActionFunc, - }, - { - Name: "check-binary-size", - Action: checkBinarySizeActionFunc, - }, - { - Name: "generate", - Action: GenerateActionFunc, - }, - { - Name: "v2diff", - Flags: []cli.Flag{ - &cli.BoolFlag{Name: "color", Value: false}, + { + Name: "check-binary-size", + Action: checkBinarySizeActionFunc, + }, + { + Name: "generate", + Action: GenerateActionFunc, + }, + { + Name: "yamlfmt", + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "strict", Value: false, Usage: "require presence of yq"}, + }, + Action: YAMLFmtActionFunc, + }, + { + Name: "diffcheck", + Action: DiffCheckActionFunc, + }, + { + Name: "ensure-goimports", + Action: EnsureGoimportsActionFunc, + }, + { + Name: "ensure-gfmrun", + Action: EnsureGfmrunActionFunc, + }, + { + Name: "ensure-mkdocs", + Action: EnsureMkdocsActionFunc, + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "upgrade-pip"}, + }, + }, + { + Name: "set-mkdocs-remote", + Action: SetMkdocsRemoteActionFunc, + Flags: []cli.Flag{ + &cli.StringFlag{Name: "github-token", Required: true}, + }, + }, + { + Name: "deploy-mkdocs", + Action: topRunAction("mkdocs", "gh-deploy", "--force"), + }, + { + Name: "lint", + Action: LintActionFunc, + }, + { + Name: "v2diff", + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "color", Value: false}, + }, + Action: V2Diff, + }, + { + Name: "v2approve", + Action: topRunAction( + "cp", + "-v", + "godoc-current.txt", + filepath.Join("testdata", "godoc-v2.x.txt"), + ), }, - Action: V2Diff, - }, - { - Name: "v2approve", - Action: V2Approve, - }, - } - app.Flags = []cli.Flag{ - &cli.StringFlag{ - Name: "tags", - Usage: "set build tags", - }, - &cli.PathFlag{ - Name: "top", - Value: top, }, - &cli.StringSliceFlag{ - Name: "packages", - Value: cli.NewStringSlice("cli", "altsrc", "internal/build"), + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "tags", + Usage: "set build tags", + }, + &cli.PathFlag{ + Name: "top", + Value: top, + }, + &cli.StringSliceFlag{ + Name: "packages", + Value: cli.NewStringSlice("cli", "altsrc", "internal/build"), + }, }, } @@ -120,6 +172,14 @@ func sh(exe string, args ...string) (string, error) { return string(outBytes), err } +func topRunAction(arg string, args ...string) cli.ActionFunc { + return func(cCtx *cli.Context) error { + os.Chdir(cCtx.Path("top")) + + return runCmd(arg, args...) + } +} + func runCmd(arg string, args ...string) error { cmd := exec.Command(arg, args...) @@ -131,6 +191,43 @@ func runCmd(arg string, args ...string) error { return cmd.Run() } +func downloadFile(src, dest string, dirPerm, perm os.FileMode) error { + req, err := http.NewRequest(http.MethodGet, src, nil) + if err != nil { + return err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + + defer resp.Body.Close() + + if resp.StatusCode >= 300 { + return fmt.Errorf("download response %[1]v", resp.StatusCode) + } + + if err := os.MkdirAll(filepath.Dir(dest), dirPerm); err != nil { + return err + } + + out, err := os.Create(dest) + if err != nil { + return err + } + + if _, err := io.Copy(out, resp.Body); err != nil { + return err + } + + if err := out.Close(); err != nil { + return err + } + + return os.Chmod(dest, perm) +} + func VetActionFunc(cCtx *cli.Context) error { return runCmd("go", "vet", cCtx.Path("top")+"/...") } @@ -145,15 +242,20 @@ func TestActionFunc(c *cli.Context) error { packageName = fmt.Sprintf("github.com/urfave/cli/v3/%s", pkg) } - if err := runCmd( - "go", "test", - "-tags", tags, + args := []string{"test"} + if tags != "" { + args = append(args, []string{"-tags", tags}...) + } + + args = append(args, []string{ "-v", - "--coverprofile", pkg+".coverprofile", + "--coverprofile", pkg + ".coverprofile", "--covermode", "count", "--cover", packageName, packageName, - ); err != nil { + }...) + + if err := runCmd("go", args...); err != nil { return err } } @@ -411,6 +513,125 @@ func GenerateActionFunc(cCtx *cli.Context) error { return runCmd("go", "generate", cCtx.Path("top")+"/...") } +func YAMLFmtActionFunc(cCtx *cli.Context) error { + yqBin, err := exec.LookPath("yq") + if err != nil { + if !cCtx.Bool("strict") { + fmt.Fprintln(cCtx.App.ErrWriter, "# ---> no yq found; skipping") + return nil + } + + return err + } + + os.Chdir(cCtx.Path("top")) + + return runCmd(yqBin, "eval", "--inplace", "flag-spec.yaml") +} + +func DiffCheckActionFunc(cCtx *cli.Context) error { + os.Chdir(cCtx.Path("top")) + + if err := runCmd("git", "diff", "--exit-code"); err != nil { + return err + } + + return runCmd("git", "diff", "--cached", "--exit-code") +} + +func EnsureGoimportsActionFunc(cCtx *cli.Context) error { + top := cCtx.Path("top") + os.Chdir(top) + + if err := runCmd( + "goimports", + "-d", + filepath.Join(top, "internal/build/build.go"), + ); err == nil { + return nil + } + + os.Setenv("GOBIN", filepath.Join(top, ".local/bin")) + + return runCmd("go", "install", "golang.org/x/tools/cmd/goimports@latest") +} + +func EnsureGfmrunActionFunc(cCtx *cli.Context) error { + top := cCtx.Path("top") + gfmrunExe := filepath.Join(top, ".local/bin/gfmrun") + + os.Chdir(top) + + if v, err := sh(gfmrunExe, "--version"); err == nil && strings.TrimSpace(v) == gfmrunVersion { + return nil + } + + gfmrunURL, err := url.Parse( + fmt.Sprintf( + "https://github.com/urfave/gfmrun/releases/download/%[1]s/gfmrun-%[2]s-%[3]s-%[1]s", + gfmrunVersion, runtime.GOOS, runtime.GOARCH, + ), + ) + if err != nil { + return err + } + + return downloadFile(gfmrunURL.String(), gfmrunExe, 0755, 0755) +} + +func EnsureMkdocsActionFunc(cCtx *cli.Context) error { + os.Chdir(cCtx.Path("top")) + + if err := runCmd("mkdocs", "--version"); err == nil { + return nil + } + + if cCtx.Bool("upgrade-pip") { + if err := runCmd("pip", "install", "-U", "pip"); err != nil { + return err + } + } + + return runCmd("pip", "install", "-r", "mkdocs-requirements.txt") +} + +func SetMkdocsRemoteActionFunc(cCtx *cli.Context) error { + ghToken := strings.TrimSpace(cCtx.String("github-token")) + if ghToken == "" { + return errors.New("empty github token") + } + + os.Chdir(cCtx.Path("top")) + + if err := runCmd("git", "remote", "rm", "origin"); err != nil { + return err + } + + return runCmd( + "git", "remote", "add", "origin", + fmt.Sprintf("https://x-access-token:%[1]s@github.com/urfave/cli.git", ghToken), + ) +} + +func LintActionFunc(cCtx *cli.Context) error { + top := cCtx.Path("top") + os.Chdir(top) + + out, err := sh(filepath.Join(top, ".local/bin/goimports"), "-l", ".") + if err != nil { + return err + } + + if strings.TrimSpace(out) != "" { + fmt.Fprintln(cCtx.App.ErrWriter, "# ---> goimports -l is non-empty:") + fmt.Fprintln(cCtx.App.ErrWriter, out) + + return errors.New("goimports needed") + } + + return nil +} + func V2Diff(cCtx *cli.Context) error { os.Chdir(cCtx.Path("top")) @@ -439,34 +660,29 @@ func V2Diff(cCtx *cli.Context) error { return err } -func V2Approve(cCtx *cli.Context) error { - top := cCtx.Path("top") +func getSize(sourcePath, builtPath, tags string) (int64, error) { + args := []string{"build"} - return runCmd( - "cp", - "-v", - filepath.Join(top, "godoc-current.txt"), - filepath.Join(top, "testdata", "godoc-v2.x.txt"), - ) -} + if tags != "" { + args = append(args, []string{"-tags", tags}...) + } -func getSize(sourcePath string, builtPath string, tags string) (size int64, err error) { - // build example binary - err = runCmd("go", "build", "-tags", tags, "-o", builtPath, "-ldflags", "-s -w", sourcePath) - if err != nil { + args = append(args, []string{ + "-o", builtPath, + "-ldflags", "-s -w", + sourcePath, + }...) + + if err := runCmd("go", args...); err != nil { fmt.Println("issue getting size for example binary") return 0, err } - // get file info fileInfo, err := os.Stat(builtPath) if err != nil { fmt.Println("issue getting size for example binary") return 0, err } - // size! - size = fileInfo.Size() - - return size, nil + return fileInfo.Size(), nil } From d9d960a58a1808ed36eb7ab9e8316b36ddf591c9 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 11 Sep 2022 16:12:16 -0400 Subject: [PATCH 086/136] Run make target after the Makefile is available --- .github/workflows/cli.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 665507f9f6..b666745044 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -27,9 +27,9 @@ jobs: go-version: ${{ matrix.go }} - name: Set PATH run: echo "${GITHUB_WORKSPACE}/.local/bin" | tee -a "${GITHUB_PATH}" >/dev/null + - uses: actions/checkout@v3 - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' run: make ensure-goimports - - uses: actions/checkout@v3 - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' run: make lint - run: make vet From 2bec081c3a6f9d7d9131c2d0b8b349ad551ac265 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 11 Sep 2022 16:37:56 -0400 Subject: [PATCH 087/136] Use windows compatible path append --- .github/workflows/cli.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index b666745044..341f3c31c2 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -26,7 +26,7 @@ jobs: with: go-version: ${{ matrix.go }} - name: Set PATH - run: echo "${GITHUB_WORKSPACE}/.local/bin" | tee -a "${GITHUB_PATH}" >/dev/null + run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}" - uses: actions/checkout@v3 - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' run: make ensure-goimports @@ -58,7 +58,7 @@ jobs: with: node-version: '16' - name: Set PATH - run: echo "${GITHUB_WORKSPACE}/.local/bin" | tee -a "${GITHUB_PATH}" >/dev/null + run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}" - uses: actions/checkout@v3 - run: make ensure-gfmrun - run: make gfmrun From 0593812915690af82f5fbf5e88f55d0dce02ca79 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 11 Sep 2022 18:22:43 -0400 Subject: [PATCH 088/136] Only run `make v2diff` on go `1.19.x` + `ubuntu-latest` --- .github/workflows/cli.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 341f3c31c2..addd90e8f1 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -40,7 +40,8 @@ jobs: - run: make tag-check-binary-size - run: make yamlfmt - run: make diffcheck - - run: make v2diff + - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' + run: make v2diff - if: success() && matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v3 with: From d6c9f6a8988ce5be34cef2ddce3dd6bf9683bb4a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 11 Sep 2022 18:32:04 -0400 Subject: [PATCH 089/136] Replace a few more custom make targets --- .github/workflows/cli.yml | 14 +++++++++++--- Makefile | 16 ---------------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index addd90e8f1..92f58b24e6 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -33,11 +33,15 @@ jobs: - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' run: make lint - run: make vet - - run: make tag-test + - run: make test + env: + FLAGS: -tags urfave_cli_no_docs - run: make test - run: make -C cmd/urfave-cli-genflags - run: make check-binary-size - - run: make tag-check-binary-size + env: + FLAGS: -tags urfave_cli_no_docs + - run: make check-binary-size - run: make yamlfmt - run: make diffcheck - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' @@ -63,6 +67,8 @@ jobs: - uses: actions/checkout@v3 - run: make ensure-gfmrun - run: make gfmrun + env: + FLAGS: --walk docs/v2/ - run: make diffcheck publish: permissions: @@ -78,7 +84,9 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 - - run: make ci-ensure-mkdocs + - run: make ensure-mkdocs + env: + FLAGS: --upgrade-pip - run: make set-mkdocs-remote env: MKDOCS_REMOTE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/Makefile b/Makefile index 46deea266f..797d093e9f 100644 --- a/Makefile +++ b/Makefile @@ -17,22 +17,6 @@ all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun y %: $(GO_RUN_BUILD) $(GFLAGS) $* $(FLAGS) -.PHONY: tag-test -tag-test: - $(GO_RUN_BUILD) -tags urfave_cli_no_docs test - -.PHONY: tag-check-binary-size -tag-check-binary-size: - $(GO_RUN_BUILD) -tags urfave_cli_no_docs check-binary-size - -.PHONY: gfmrun -gfmrun: - $(GO_RUN_BUILD) gfmrun --walk docs/v2/ - -.PHONY: ci-ensure-mkdocs -ci-ensure-mkdocs: - $(GO_RUN_BUILD) ensure-mkdocs --upgrade-pip - .PHONY: docs docs: mkdocs build From 9ed5a09fbbd2a0e214e310f30c74e3daa10cb43f Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 11 Sep 2022 18:34:13 -0400 Subject: [PATCH 090/136] Use correct env var for global flags --- .github/workflows/cli.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 92f58b24e6..7a3ac9b479 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -35,12 +35,12 @@ jobs: - run: make vet - run: make test env: - FLAGS: -tags urfave_cli_no_docs + GFLAGS: -tags urfave_cli_no_docs - run: make test - run: make -C cmd/urfave-cli-genflags - run: make check-binary-size env: - FLAGS: -tags urfave_cli_no_docs + GFLAGS: -tags urfave_cli_no_docs - run: make check-binary-size - run: make yamlfmt - run: make diffcheck From 1c3ebfab32b9b680c14a3cf119addc885b273c1c Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 12 Sep 2022 07:33:08 -0400 Subject: [PATCH 091/136] Accept the `MKDOCS_REMOTE_GITHUB_TOKEN` var as intended --- internal/build/build.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/build/build.go b/internal/build/build.go index 2c82f1324a..dcef50b143 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -113,7 +113,11 @@ func main() { Name: "set-mkdocs-remote", Action: SetMkdocsRemoteActionFunc, Flags: []cli.Flag{ - &cli.StringFlag{Name: "github-token", Required: true}, + &cli.StringFlag{ + Name: "github-token", + EnvVars: []string{"MKDOCS_REMOTE_GITHUB_TOKEN"}, + Required: true, + }, }, }, { From d2acd0ed5545f86a6786f0a8e7c5a005a717c581 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 12 Sep 2022 08:54:22 -0400 Subject: [PATCH 092/136] Fix:(issue_1197) Set destination field from altsrc for slice flags --- altsrc/flag.go | 6 ++++++ altsrc/yaml_file_loader.go | 4 +--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/altsrc/flag.go b/altsrc/flag.go index 35184349ad..c60e1516d7 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -109,6 +109,9 @@ func (f *StringSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSour continue } underlyingFlag.Value = &sliceValue + if f.Destination != nil { + f.Destination.Set(sliceValue.Serialize()) + } } } return nil @@ -137,6 +140,9 @@ func (f *IntSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceC continue } underlyingFlag.Value = &sliceValue + if f.Destination != nil { + f.Destination.Set(sliceValue.Serialize()) + } } } return nil diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 66afe588a9..b1f6c843f1 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -33,11 +33,9 @@ func NewYamlSourceFromFile(file string) (InputSourceContext, error) { // NewYamlSourceFromFlagFunc creates a new Yaml InputSourceContext from a provided flag name and source context. func NewYamlSourceFromFlagFunc(flagFileName string) func(cCtx *cli.Context) (InputSourceContext, error) { return func(cCtx *cli.Context) (InputSourceContext, error) { - if cCtx.IsSet(flagFileName) { - filePath := cCtx.String(flagFileName) + if filePath := cCtx.String(flagFileName); filePath != "" { return NewYamlSourceFromFile(filePath) } - return defaultInputSource() } } From e9e87f624de33c2ca946a2b8ab7333e2ce04b91a Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 12 Sep 2022 13:40:08 -0400 Subject: [PATCH 093/136] Add unit tests --- altsrc/flag.go | 12 ++++----- altsrc/flag_test.go | 64 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/altsrc/flag.go b/altsrc/flag.go index c60e1516d7..a12262b42c 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -109,9 +109,9 @@ func (f *StringSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSour continue } underlyingFlag.Value = &sliceValue - if f.Destination != nil { - f.Destination.Set(sliceValue.Serialize()) - } + } + if f.Destination != nil { + f.Destination.Set(sliceValue.Serialize()) } } return nil @@ -140,9 +140,9 @@ func (f *IntSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceC continue } underlyingFlag.Value = &sliceValue - if f.Destination != nil { - f.Destination.Set(sliceValue.Serialize()) - } + } + if f.Destination != nil { + f.Destination.Set(sliceValue.Serialize()) } } return nil diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index 67ccf4f829..1a5da157d1 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -100,39 +100,61 @@ func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) { } func TestStringSliceApplyInputSourceValue_Alias(t *testing.T) { + dest := cli.NewStringSlice() tis := testApplyInputSource{ - Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", Aliases: []string{"test_alias"}}), + Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", Aliases: []string{"test_alias"}, Destination: dest}), FlagName: "test_alias", MapValue: []interface{}{"hello", "world"}, } c := runTest(t, tis) expect(t, c.StringSlice("test_alias"), []string{"hello", "world"}) + expect(t, dest.Value(), []string{"hello", "world"}) + // reset dest + dest = cli.NewStringSlice() + tis = testApplyInputSource{ + Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", Aliases: []string{"test_alias"}, Destination: dest}), + FlagName: "test_alias", + MapValue: []interface{}{"hello", "world"}, + } c = runRacyTest(t, tis) refute(t, c.StringSlice("test_alias"), []string{"hello", "world"}) + refute(t, dest.Value(), []string{"hello", "world"}) } func TestStringSliceApplyInputSourceValue(t *testing.T) { + dest := cli.NewStringSlice() tis := testApplyInputSource{ - Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}), + Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", Destination: dest}), FlagName: "test", MapValue: []interface{}{"hello", "world"}, } c := runTest(t, tis) expect(t, c.StringSlice("test"), []string{"hello", "world"}) + expect(t, dest.Value(), []string{"hello", "world"}) + // reset dest + dest = cli.NewStringSlice() + tis = testApplyInputSource{ + Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", Destination: dest}), + FlagName: "test", + MapValue: []interface{}{"hello", "world"}, + } c = runRacyTest(t, tis) refute(t, c.StringSlice("test"), []string{"hello", "world"}) + refute(t, dest.Value(), []string{"hello", "world"}) } func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) { + dest := cli.NewStringSlice() c := runTest(t, testApplyInputSource{ - Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}), + Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", Destination: dest}), FlagName: "test", MapValue: []interface{}{"hello", "world"}, ContextValueString: "ohno", }) expect(t, c.StringSlice("test"), []string{"ohno"}) + expect(t, dest.Value(), []string{"ohno"}) } func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { @@ -151,43 +173,73 @@ func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { } func TestIntSliceApplyInputSourceValue_Alias(t *testing.T) { + dest := cli.NewIntSlice() tis := testApplyInputSource{ - Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Aliases: []string{"test_alias"}}), + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Aliases: []string{"test_alias"}, Destination: dest}), FlagName: "test_alias", MapValue: []interface{}{1, 2}, } c := runTest(t, tis) expect(t, c.IntSlice("test_alias"), []int{1, 2}) + expect(t, dest.Value(), []int{1, 2}) + dest = cli.NewIntSlice() + tis = testApplyInputSource{ + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Aliases: []string{"test_alias"}, Destination: dest}), + FlagName: "test_alias", + MapValue: []interface{}{1, 2}, + } c = runRacyTest(t, tis) refute(t, c.IntSlice("test_alias"), []int{1, 2}) + refute(t, dest.Value(), []int{1, 2}) } func TestIntSliceApplyInputSourceValue(t *testing.T) { + dest := cli.NewIntSlice() tis := testApplyInputSource{ - Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}), + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Destination: dest}), FlagName: "test", MapValue: []interface{}{1, 2}, } c := runTest(t, tis) expect(t, c.IntSlice("test"), []int{1, 2}) + expect(t, dest.Value(), []int{1, 2}) + // reset dest + dest = cli.NewIntSlice() + tis = testApplyInputSource{ + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Destination: dest}), + FlagName: "test", + MapValue: []interface{}{1, 2}, + } c = runRacyTest(t, tis) refute(t, c.IntSlice("test"), []int{1, 2}) + refute(t, dest.Value(), []int{1, 2}) } func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) { + dest := cli.NewIntSlice() tis := testApplyInputSource{ - Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}), + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Destination: dest}), FlagName: "test", MapValue: []interface{}{1, 2}, ContextValueString: "3", } c := runTest(t, tis) expect(t, c.IntSlice("test"), []int{3}) + expect(t, dest.Value(), []int{3}) + // reset dest + dest = cli.NewIntSlice() + tis = testApplyInputSource{ + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Destination: dest}), + FlagName: "test", + MapValue: []interface{}{1, 2}, + ContextValueString: "3", + } c = runRacyTest(t, tis) refute(t, c.IntSlice("test"), []int{3}) + refute(t, dest.Value(), []int{3}) } func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { From a2c3729797c5277dcaaada09d8c363d629808f5e Mon Sep 17 00:00:00 2001 From: torwang Date: Tue, 20 Sep 2022 16:15:59 +0800 Subject: [PATCH 094/136] fix: Context.Set no such flag --- context.go | 8 +++++--- context_test.go | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index 312efb5b38..81416dac5f 100644 --- a/context.go +++ b/context.go @@ -3,6 +3,7 @@ package cli import ( "context" "flag" + "fmt" "strings" ) @@ -46,10 +47,11 @@ func (cCtx *Context) NumFlags() int { // Set sets a context flag to a value. func (cCtx *Context) Set(name, value string) error { - if cCtx.flagSet.Lookup(name) == nil { - cCtx.onInvalidFlag(name) + if fs := cCtx.lookupFlagSet(name); fs != nil { + return fs.Set(name, value) } - return cCtx.flagSet.Set(name, value) + + return fmt.Errorf("no such flag -%s", name) } // IsSet determines if the flag was actually set diff --git a/context_test.go b/context_test.go index 6601155702..246590d339 100644 --- a/context_test.go +++ b/context_test.go @@ -643,3 +643,19 @@ func TestCheckRequiredFlags(t *testing.T) { }) } } + +func TestContext_ParentContext_Set(t *testing.T) { + parentSet := flag.NewFlagSet("parent", flag.ContinueOnError) + parentSet.String("Name", "", "") + + context := NewContext( + nil, + flag.NewFlagSet("child", flag.ContinueOnError), + NewContext(nil, parentSet, nil), + ) + + err := context.Set("Name", "aaa") + if err != nil { + t.Errorf("expect nil. set parent context flag return err: %s", err) + } +} From e2e14ec6ef1638253e6aaf076374ffb06aef2662 Mon Sep 17 00:00:00 2001 From: Wendell Sun Date: Tue, 15 Feb 2022 23:49:41 +0800 Subject: [PATCH 095/136] feat: flag action --- app.go | 26 +++++++++++++++++ app_test.go | 67 +++++++++++++++++++++++++++++++++++++++++++ command.go | 4 +++ flag.go | 2 ++ flag_bool.go | 9 ++++++ flag_duration.go | 9 ++++++ flag_float64.go | 9 ++++++ flag_float64_slice.go | 9 ++++++ flag_generic.go | 9 ++++++ flag_int.go | 9 ++++++ flag_int64.go | 9 ++++++ flag_int64_slice.go | 9 ++++++ flag_int_slice.go | 9 ++++++ flag_path.go | 9 ++++++ flag_string.go | 9 ++++++ flag_string_slice.go | 9 ++++++ flag_timestamp.go | 9 ++++++ flag_uint.go | 9 ++++++ flag_uint64.go | 9 ++++++ 19 files changed, 234 insertions(+) diff --git a/app.go b/app.go index d07839fbf2..7be6f7694d 100644 --- a/app.go +++ b/app.go @@ -340,6 +340,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() { @@ -521,6 +525,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() @@ -644,6 +652,24 @@ 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 err := f.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/app_test.go b/app_test.go index 59e03b5b19..8ae01e8b0a 100644 --- a/app_test.go +++ b/app_test.go @@ -2357,6 +2357,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 } @@ -2596,3 +2600,66 @@ func TestSetupInitializesOnlyNilWriters(t *testing.T) { t.Errorf("expected a.Writer to be os.Stdout") } } + +func TestFlagAction(t *testing.T) { + r := []string{} + actionFunc := func(c *Context, s string) error { + r = append(r, s) + return nil + } + + app := &App{ + Name: "command", + Writer: io.Discard, + Flags: []Flag{&StringFlag{Name: "flag", Action: actionFunc}}, + Commands: []*Command{ + { + Name: "command1", + Flags: []Flag{&StringFlag{Name: "flag1", Aliases: []string{"f1"}, Action: actionFunc}}, + Subcommands: []*Command{ + { + Name: "command2", + Flags: []Flag{&StringFlag{Name: "flag2", Action: actionFunc}}, + }, + }, + }, + }, + } + + tests := []struct { + args []string + exp []string + }{ + { + args: []string{"command", "--flag=f"}, + exp: []string{"f"}, + }, + { + args: []string{"command", "command1", "-f1=f1", "command2"}, + exp: []string{"f1"}, + }, + { + args: []string{"command", "command1", "-f1=f1", "command2", "--flag2=f2"}, + exp: []string{"f1", "f2"}, + }, + { + args: []string{"command", "--flag=f", "command1", "-flag1=f1"}, + exp: []string{"f", "f1"}, + }, + { + args: []string{"command", "--flag=f", "command1", "-f1=f1"}, + exp: []string{"f", "f1"}, + }, + { + args: []string{"command", "--flag=f", "command1", "-f1=f1", "command2", "--flag2=f2"}, + exp: []string{"f", "f1", "f2"}, + }, + } + + for _, test := range tests { + r = []string{} + err := app.Run(test.args) + expect(t, err, nil) + expect(t, r, test.exp) + } +} diff --git a/command.go b/command.go index 13b79de46d..cd8ea91c70 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.go b/flag.go index 1618b4da86..aade1de2e8 100644 --- a/flag.go +++ b/flag.go @@ -122,6 +122,8 @@ type Flag interface { // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. GetValue() string + + RunAction(*Context) error } // Countable is an interface to enable detection of flag values which support diff --git a/flag_bool.go b/flag_bool.go index e5fa700e1a..55b719af39 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -72,6 +72,15 @@ func (f *BoolFlag) GetDefaultText() string { return fmt.Sprintf("%v", f.Value) } +// 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 35b376da7f..b6adce3f48 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -42,6 +42,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 b7b8044a53..3e21a01df7 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -42,6 +42,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 833e65ce33..5bb2693114 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -153,6 +153,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 42640158ac..9f0633808e 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -45,6 +45,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 b269e231d3..bcaab7fa7e 100644 --- a/flag_int.go +++ b/flag_int.go @@ -43,6 +43,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 35d95d9bf3..d8e591d864 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -42,6 +42,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 d848b45c01..89fd44550e 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -151,6 +151,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 96fb7a3e91..f7a6b06f0c 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -162,6 +162,15 @@ func (f *IntSliceFlag) stringify() string { return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } +// 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 +} + // IntSlice looks up the value of a local IntSliceFlag, returns // nil if not found func (cCtx *Context) IntSlice(name string) []int { diff --git a/flag_path.go b/flag_path.go index e622c40819..f8bb9c3d0d 100644 --- a/flag_path.go +++ b/flag_path.go @@ -47,6 +47,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 31cd89dc27..669f25415b 100644 --- a/flag_string.go +++ b/flag_string.go @@ -45,6 +45,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 c5c9b7829e..2a24ec56e5 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -143,6 +143,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 7b525b062d..671be8b986 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -120,6 +120,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 417a9efc34..e1b04508c8 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -42,6 +42,15 @@ func (f *UintFlag) Get(ctx *Context) uint { return ctx.Uint(f.Name) } +// 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 +} + // Uint looks up the value of a local UintFlag, returns // 0 if not found func (cCtx *Context) Uint(name string) uint { diff --git a/flag_uint64.go b/flag_uint64.go index 875635dbc9..593b2606ae 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -42,6 +42,15 @@ func (f *Uint64Flag) Get(ctx *Context) uint64 { return ctx.Uint64(f.Name) } +// 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 +} + // Uint64 looks up the value of a local Uint64Flag, returns // 0 if not found func (cCtx *Context) Uint64(name string) uint64 { From f9ceca5dfae694fa2d79ede8b4cc58fea2c5dcc9 Mon Sep 17 00:00:00 2001 From: Wendell Sun Date: Sun, 1 May 2022 02:49:01 +0800 Subject: [PATCH 096/136] Add more tests about flag-level action --- app_test.go | 253 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 224 insertions(+), 29 deletions(-) diff --git a/app_test.go b/app_test.go index 8ae01e8b0a..ec4e2369ac 100644 --- a/app_test.go +++ b/app_test.go @@ -9,8 +9,10 @@ import ( "io/ioutil" "os" "reflect" + "strconv" "strings" "testing" + "time" ) var ( @@ -2601,65 +2603,258 @@ func TestSetupInitializesOnlyNilWriters(t *testing.T) { } } +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) { - r := []string{} - actionFunc := func(c *Context, s string) error { - r = append(r, s) - return nil + stringFlag := &StringFlag{ + Name: "f_string", + Action: func(c *Context, v string) error { + c.App.Writer.Write([]byte(v + " ")) + return nil + }, } - app := &App{ - Name: "command", - Writer: io.Discard, - Flags: []Flag{&StringFlag{Name: "flag", Action: actionFunc}}, + Name: "app", Commands: []*Command{ { - Name: "command1", - Flags: []Flag{&StringFlag{Name: "flag1", Aliases: []string{"f1"}, Action: actionFunc}}, + Name: "c1", + Flags: []Flag{stringFlag}, + Action: func(ctx *Context) error { return nil }, Subcommands: []*Command{ { - Name: "command2", - Flags: []Flag{&StringFlag{Name: "flag2", Action: actionFunc}}, + Name: "sub1", + Action: func(ctx *Context) error { return nil }, + Flags: []Flag{stringFlag}, }, }, }, }, + Flags: []Flag{ + stringFlag, + &StringSliceFlag{ + Name: "f_string_slice", + Action: func(c *Context, v []string) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &BoolFlag{ + Name: "f_bool", + Action: func(c *Context, v bool) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%t ", v))) + return nil + }, + }, + &DurationFlag{ + Name: "f_duration", + Action: func(c *Context, v time.Duration) error { + c.App.Writer.Write([]byte(v.String() + " ")) + return nil + }, + }, + &Float64Flag{ + Name: "f_float64", + Action: func(c *Context, v float64) error { + 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 { + 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 { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &IntFlag{ + Name: "f_int", + Action: func(c *Context, v int) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &IntSliceFlag{ + Name: "f_int_slice", + Action: func(c *Context, v []int) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &Int64Flag{ + Name: "f_int64", + Action: func(c *Context, v int64) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &Int64SliceFlag{ + Name: "f_int64_slice", + Action: func(c *Context, v []int64) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &PathFlag{ + Name: "f_path", + Action: func(c *Context, v string) error { + 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 { + c.App.Writer.Write([]byte(v.Format(time.RFC3339) + " ")) + return nil + }, + }, + &UintFlag{ + Name: "f_uint", + Action: func(c *Context, v uint) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &Uint64Flag{ + Name: "f_uint64", + Action: func(c *Context, v uint64) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + }, + Action: func(ctx *Context) error { return nil }, } tests := []struct { + name string args []string - exp []string + exp string }{ { - args: []string{"command", "--flag=f"}, - exp: []string{"f"}, + name: "flag_empty", + args: []string{"app"}, + exp: "", + }, + { + name: "flag_string", + args: []string{"app", "--f_string=string"}, + exp: "string ", + }, + { + name: "flag_string_slice", + args: []string{"app", "--f_string_slice=s1,s2,s3"}, + exp: "[s1 s2 s3] ", + }, + { + name: "flag_bool", + args: []string{"app", "--f_bool"}, + exp: "true ", + }, + { + name: "flag_duration", + args: []string{"app", "--f_duration=1h30m20s"}, + exp: "1h30m20s ", }, { - args: []string{"command", "command1", "-f1=f1", "command2"}, - exp: []string{"f1"}, + name: "flag_float64", + args: []string{"app", "--f_float64=3.14159"}, + exp: "3.14159 ", }, { - args: []string{"command", "command1", "-f1=f1", "command2", "--flag2=f2"}, - exp: []string{"f1", "f2"}, + name: "flag_float64_slice", + args: []string{"app", "--f_float64_slice=1.1,2.2,3.3"}, + exp: "[1.1 2.2 3.3] ", }, { - args: []string{"command", "--flag=f", "command1", "-flag1=f1"}, - exp: []string{"f", "f1"}, + name: "flag_generic", + args: []string{"app", "--f_generic=1"}, + exp: "1 ", }, { - args: []string{"command", "--flag=f", "command1", "-f1=f1"}, - exp: []string{"f", "f1"}, + name: "flag_int", + args: []string{"app", "--f_int=1"}, + exp: "1 ", }, { - args: []string{"command", "--flag=f", "command1", "-f1=f1", "command2", "--flag2=f2"}, - exp: []string{"f", "f1", "f2"}, + name: "flag_int_slice", + args: []string{"app", "--f_int_slice=1,2,3"}, + exp: "[1 2 3] ", + }, + { + name: "flag_int64", + args: []string{"app", "--f_int64=1"}, + exp: "1 ", + }, + { + name: "flag_int64_slice", + args: []string{"app", "--f_int64_slice=1,2,3"}, + exp: "[1 2 3] ", + }, + { + name: "flag_path", + args: []string{"app", "--f_path=/root"}, + exp: "/root ", + }, + { + name: "flag_timestamp", + args: []string{"app", "--f_timestamp", "2022-05-01 02:26:20"}, + exp: "2022-05-01T02:26:20Z ", + }, + { + name: "flag_uint", + args: []string{"app", "--f_uint=1"}, + exp: "1 ", + }, + { + name: "flag_uint64", + args: []string{"app", "--f_uint64=1"}, + exp: "1 ", + }, + { + 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 { - r = []string{} - err := app.Run(test.args) - expect(t, err, nil) - expect(t, r, test.exp) + t.Run(test.name, func(t *testing.T) { + buf := new(bytes.Buffer) + app.Writer = buf + err := app.Run(test.args) + expect(t, err, nil) + expect(t, buf.String(), test.exp) + }) } } From d213683beec892938ab61dac6d05ac40e2d4ab00 Mon Sep 17 00:00:00 2001 From: Wendell Sun Date: Sun, 22 May 2022 23:07:10 +0800 Subject: [PATCH 097/136] Rebase main, update flag-spec.yaml to add Action field --- cmd/urfave-cli-genflags/go.sum | 3 --- flag-spec.yaml | 41 ++++++++++++++++++++++++++++++++++ zz_generated.flags.go | 34 ++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/cmd/urfave-cli-genflags/go.sum b/cmd/urfave-cli-genflags/go.sum index 98211271c8..e59916dc6f 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/flag-spec.yaml b/flag-spec.yaml index 66745c62eb..ea75670c6b 100644 --- a/flag-spec.yaml +++ b/flag-spec.yaml @@ -9,47 +9,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: @@ -57,7 +87,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: @@ -65,12 +100,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/zz_generated.flags.go b/zz_generated.flags.go index 2c5bea0e62..68934c7707 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 @@ -92,6 +94,8 @@ type GenericFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, interface{}) error } // String returns a readable representation of this value (for usage defaults) @@ -165,6 +169,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 @@ -233,6 +239,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 @@ -303,6 +311,8 @@ type PathFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, Path) error } // String returns a readable representation of this value (for usage defaults) @@ -370,6 +380,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 @@ -442,6 +454,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) @@ -515,6 +529,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 @@ -555,6 +571,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 @@ -597,6 +615,8 @@ type BoolFlag struct { EnvVars []string Count *int + + Action func(*Context, bool) error } // String returns a readable representation of this value (for usage defaults) @@ -662,6 +682,8 @@ type Float64Flag struct { Aliases []string EnvVars []string + + Action func(*Context, float64) error } // String returns a readable representation of this value (for usage defaults) @@ -737,6 +759,8 @@ type IntFlag struct { EnvVars []string Base int + + Action func(*Context, int) error } // String returns a readable representation of this value (for usage defaults) @@ -812,6 +836,8 @@ type Int64Flag struct { EnvVars []string Base int + + Action func(*Context, int64) error } // String returns a readable representation of this value (for usage defaults) @@ -887,6 +913,8 @@ type StringFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, string) error } // String returns a readable representation of this value (for usage defaults) @@ -952,6 +980,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) @@ -1027,6 +1057,8 @@ type UintFlag struct { EnvVars []string Base int + + Action func(*Context, uint) error } // String returns a readable representation of this value (for usage defaults) @@ -1102,6 +1134,8 @@ type Uint64Flag struct { EnvVars []string Base int + + Action func(*Context, uint64) error } // String returns a readable representation of this value (for usage defaults) From 1e30f50959e08bc8d7a249ec702e476bd25c9a03 Mon Sep 17 00:00:00 2001 From: Wendell Sun Date: Fri, 16 Sep 2022 00:42:24 +0900 Subject: [PATCH 098/136] make v2approve --- testdata/godoc-v2.x.txt | 52 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt index 1ba48cc659..562b7ff22a 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 @@ -487,6 +494,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) @@ -777,6 +787,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) @@ -950,6 +963,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) @@ -1035,6 +1051,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) @@ -1108,6 +1127,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) @@ -1168,6 +1190,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) @@ -1253,6 +1278,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) @@ -1317,6 +1345,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) @@ -1406,6 +1437,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) @@ -1502,6 +1536,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) @@ -1639,6 +1676,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) @@ -1726,6 +1766,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) @@ -1827,6 +1870,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) @@ -1887,6 +1933,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) @@ -1947,6 +1996,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) From 126297af13c1f846e7a2cc583c75167ae6950640 Mon Sep 17 00:00:00 2001 From: Wendell Sun Date: Sat, 17 Sep 2022 16:05:26 +0900 Subject: [PATCH 099/136] Add more test cases --- app_test.go | 147 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 140 insertions(+), 7 deletions(-) diff --git a/app_test.go b/app_test.go index ec4e2369ac..45876a6792 100644 --- a/app_test.go +++ b/app_test.go @@ -2620,6 +2620,9 @@ 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 }, @@ -2642,9 +2645,15 @@ func TestFlagAction(t *testing.T) { }, 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 }, @@ -2652,6 +2661,9 @@ func TestFlagAction(t *testing.T) { &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 }, @@ -2659,6 +2671,9 @@ func TestFlagAction(t *testing.T) { &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 }, @@ -2666,6 +2681,9 @@ func TestFlagAction(t *testing.T) { &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 }, @@ -2673,6 +2691,9 @@ func TestFlagAction(t *testing.T) { &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 }, @@ -2681,6 +2702,14 @@ func TestFlagAction(t *testing.T) { 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 }, @@ -2688,6 +2717,9 @@ func TestFlagAction(t *testing.T) { &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 }, @@ -2695,6 +2727,9 @@ func TestFlagAction(t *testing.T) { &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 }, @@ -2702,6 +2737,9 @@ func TestFlagAction(t *testing.T) { &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 }, @@ -2709,6 +2747,9 @@ func TestFlagAction(t *testing.T) { &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 }, @@ -2716,6 +2757,9 @@ func TestFlagAction(t *testing.T) { &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 }, @@ -2724,6 +2768,9 @@ func TestFlagAction(t *testing.T) { 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 }, @@ -2731,6 +2778,9 @@ func TestFlagAction(t *testing.T) { &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 }, @@ -2738,6 +2788,9 @@ func TestFlagAction(t *testing.T) { &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 }, @@ -2749,88 +2802,164 @@ func TestFlagAction(t *testing.T) { tests := []struct { name string args []string + err error exp string }{ - { - name: "flag_empty", - args: []string{"app"}, - exp: "", - }, { 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"}, @@ -2853,8 +2982,12 @@ func TestFlagAction(t *testing.T) { buf := new(bytes.Buffer) app.Writer = buf err := app.Run(test.args) - expect(t, err, nil) - expect(t, buf.String(), test.exp) + if test.err != nil { + expect(t, err, test.err) + } else { + expect(t, err, nil) + expect(t, buf.String(), test.exp) + } }) } } From 4c637d8ac73e3ff113dddf4fbebebb0aaa99062d Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sun, 21 Aug 2022 14:21:23 -0400 Subject: [PATCH 100/136] Fix:(issue_557) Change app help name --- app.go | 1 + 1 file changed, 1 insertion(+) diff --git a/app.go b/app.go index 7be6f7694d..689ee04d7a 100644 --- a/app.go +++ b/app.go @@ -133,6 +133,7 @@ func compileTime() time.Time { func NewApp() *App { return &App{ Name: filepath.Base(os.Args[0]), + HelpName: "", // setup will fill this later Usage: "A new cli application", UsageText: "", BashComplete: DefaultAppComplete, From 57ff098ca7523826b0226d4bf46650197e8ac42a Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Tue, 20 Sep 2022 21:18:03 -0400 Subject: [PATCH 101/136] Fix:(issue_557) Make help output consistent between different invocations --- app_test.go | 5 +++- command.go | 50 +++++++++++++++++++++++++++++++++++++--- help.go | 64 ++++++++++++++++++++++++++++++++++++++++------------ help_test.go | 12 ++++++---- template.go | 9 +++----- 5 files changed, 111 insertions(+), 29 deletions(-) diff --git a/app_test.go b/app_test.go index 45876a6792..824776974c 100644 --- a/app_test.go +++ b/app_test.go @@ -174,13 +174,16 @@ func ExampleApp_Run_commandHelp() { _ = app.Run(os.Args) // Output: // NAME: - // greet describeit - use it to see a description + // greet describeit [command options] [arguments...] // // USAGE: // greet describeit [arguments...] // // DESCRIPTION: // This is how we describe describeit the function + // + // OPTIONS: + // --help, -h show help (default: false) } func ExampleApp_Run_noAction() { diff --git a/command.go b/command.go index cd8ea91c70..ea03a55993 100644 --- a/command.go +++ b/command.go @@ -62,6 +62,9 @@ type Command struct { // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. CustomHelpTemplate string + + // categories contains the categorized commands and is populated on app startup + categories CommandCategories } type Commands []*Command @@ -170,7 +173,7 @@ func (c *Command) Run(ctx *Context) (err error) { } if c.Action == nil { - c.Action = helpSubcommand.Action + c.Action = helpCommand.Action } cCtx.Command = c @@ -284,7 +287,7 @@ func (c *Command) startApp(ctx *Context) error { if c.Action != nil { app.Action = c.Action } else { - app.Action = helpSubcommand.Action + app.Action = helpCommand.Action } app.OnUsageError = c.OnUsageError @@ -298,7 +301,12 @@ func (c *Command) startApp(ctx *Context) error { // VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain func (c *Command) VisibleFlagCategories() []VisibleFlagCategory { if c.flagCategories == nil { - return []VisibleFlagCategory{} + c.flagCategories = newFlagCategories() + for _, fl := range c.Flags { + if cf, ok := fl.(CategorizableFlag); ok { + c.flagCategories.AddFlag(cf.GetCategory(), cf) + } + } } return c.flagCategories.VisibleCategories() } @@ -308,6 +316,42 @@ func (c *Command) VisibleFlags() []Flag { return visibleFlags(c.Flags) } +// VisibleCategories returns a slice of categories and commands that are +// Hidden=false +func (c *Command) VisibleCategories() []CommandCategory { + ret := []CommandCategory{} + if c.categories == nil { + c.categories = newCommandCategories() + for _, command := range c.Subcommands { + c.categories.AddCommand(command.Category, command) + } + sort.Sort(c.categories.(*commandCategories)) + } + for _, category := range c.categories.Categories() { + if visible := func() CommandCategory { + if len(category.VisibleCommands()) > 0 { + return category + } + return nil + }(); visible != nil { + ret = append(ret, visible) + } + } + + return ret +} + +// VisibleCommands returns a slice of the Commands with Hidden=false +func (c *Command) VisibleCommands() []*Command { + var ret []*Command + for _, command := range c.Subcommands { + if !command.Hidden { + ret = append(ret, command) + } + } + return ret +} + func (c *Command) appendFlag(fl Flag) { if !hasFlag(c.Flags, fl) { c.Flags = append(c.Flags, fl) diff --git a/help.go b/help.go index d6caea4263..ba5f803176 100644 --- a/help.go +++ b/help.go @@ -15,33 +15,59 @@ const ( helpAlias = "h" ) -var helpCommand = &Command{ +// this instance is to avoid recursion in the ShowCommandHelp which can +// add a help command again +var helpCommandDontUse = &Command{ Name: helpName, Aliases: []string{helpAlias}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", - Action: func(cCtx *Context) error { - args := cCtx.Args() - if args.Present() { - return ShowCommandHelp(cCtx, args.First()) - } - - _ = ShowAppHelp(cCtx) - return nil - }, } -var helpSubcommand = &Command{ +var helpCommand = &Command{ Name: helpName, Aliases: []string{helpAlias}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", Action: func(cCtx *Context) error { args := cCtx.Args() - if args.Present() { - return ShowCommandHelp(cCtx, args.First()) + argsPresent := args.First() != "" + firstArg := args.First() + + // This action can be triggered by a "default" action of a command + // or via cmd.Run when cmd == helpCmd. So we have following possibilities + // + // 1 $ app + // 2 $ app help + // 3 $ app foo + // 4 $ app help foo + // 5 $ app foo help + + // Case 4. when executing a help command set the context to parent + // to allow resolution of subsequent args. This will transform + // $ app help foo + // to + // $ app foo + // which will then be handled as case 3 + if cCtx.Command.Name == helpName || cCtx.Command.Name == helpAlias { + cCtx = cCtx.parentContext } + // Case 4. $ app hello foo + // foo is the command for which help needs to be shown + if argsPresent { + return ShowCommandHelp(cCtx, firstArg) + } + + // Case 1 & 2 + // Special case when running help on main app itself as opposed to indivdual + // commands/subcommands + if cCtx.parentContext.App == nil { + _ = ShowAppHelp(cCtx) + return nil + } + + // Case 3, 5 return ShowSubcommandHelp(cCtx) }, } @@ -212,9 +238,19 @@ func ShowCommandHelp(ctx *Context, command string) error { for _, c := range ctx.App.Commands { if c.HasName(command) { + if !ctx.App.HideHelpCommand && !c.HasName(helpName) && len(c.Subcommands) != 0 { + c.Subcommands = append(c.Subcommands, helpCommandDontUse) + } + if !ctx.App.HideHelp && HelpFlag != nil { + c.appendFlag(HelpFlag) + } templ := c.CustomHelpTemplate if templ == "" { - templ = CommandHelpTemplate + if len(c.Subcommands) == 0 { + templ = CommandHelpTemplate + } else { + templ = SubcommandHelpTemplate + } } HelpPrinter(ctx.App.Writer, templ, c) diff --git a/help_test.go b/help_test.go index ade6f3dd6d..5a50586483 100644 --- a/help_test.go +++ b/help_test.go @@ -186,7 +186,7 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { c := NewContext(app, set, nil) - err := helpSubcommand.Action(c) + err := helpCommand.Action(c) if err == nil { t.Fatalf("expected error from helpCommand.Action(), but got nil") @@ -248,7 +248,7 @@ func TestShowCommandHelp_HelpPrinter(t *testing.T) { fmt.Fprint(w, "yo") }, command: "", - wantTemplate: SubcommandHelpTemplate, + wantTemplate: AppHelpTemplate, wantOutput: "yo", }, { @@ -333,7 +333,7 @@ func TestShowCommandHelp_HelpPrinterCustom(t *testing.T) { fmt.Fprint(w, "yo") }, command: "", - wantTemplate: SubcommandHelpTemplate, + wantTemplate: AppHelpTemplate, wantOutput: "yo", }, { @@ -1357,10 +1357,13 @@ DESCRIPTION: and a description long enough to wrap in this test case + +OPTIONS: + --help, -h show help (default: false) ` if output.String() != expected { - t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s", + t.Errorf("Unexpected wrapping, got:\n%s\nexpected:\n%s", output.String(), expected) } } @@ -1426,7 +1429,6 @@ USAGE: OPTIONS: --help, -h show help (default: false) - ` if output.String() != expected { diff --git a/template.go b/template.go index 7ed2370994..9e13604f35 100644 --- a/template.go +++ b/template.go @@ -54,12 +54,10 @@ DESCRIPTION: OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}} - {{end}}{{end}}{{else}}{{if .VisibleFlags}} + {{end}}{{range .Flags}}{{.}}{{end}}{{end}}{{else}}{{if .VisibleFlags}} OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}}{{end}} + {{range .VisibleFlags}}{{.}}{{end}}{{end}}{{end}} ` // SubcommandHelpTemplate is the text template for the subcommand help topic. @@ -80,8 +78,7 @@ COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}} + {{range .VisibleFlags}}{{.}}{{end}}{{end}} ` var MarkdownDocTemplate = `{{if gt .SectionNum 0}}% {{ .App.Name }} {{ .SectionNum }} From c2ecb4469f400da2ca97b935358cc804b8a1a932 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Wed, 21 Sep 2022 18:18:36 -0400 Subject: [PATCH 102/136] Remove un-needed func --- command.go | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/command.go b/command.go index ea03a55993..ad83ca118f 100644 --- a/command.go +++ b/command.go @@ -316,31 +316,6 @@ func (c *Command) VisibleFlags() []Flag { return visibleFlags(c.Flags) } -// VisibleCategories returns a slice of categories and commands that are -// Hidden=false -func (c *Command) VisibleCategories() []CommandCategory { - ret := []CommandCategory{} - if c.categories == nil { - c.categories = newCommandCategories() - for _, command := range c.Subcommands { - c.categories.AddCommand(command.Category, command) - } - sort.Sort(c.categories.(*commandCategories)) - } - for _, category := range c.categories.Categories() { - if visible := func() CommandCategory { - if len(category.VisibleCommands()) > 0 { - return category - } - return nil - }(); visible != nil { - ret = append(ret, visible) - } - } - - return ret -} - // VisibleCommands returns a slice of the Commands with Hidden=false func (c *Command) VisibleCommands() []*Command { var ret []*Command From 8ef92d2a2407eb8051a7d3de4da3bc5d022a77be Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Wed, 21 Sep 2022 18:24:03 -0400 Subject: [PATCH 103/136] Remove un-needed func --- app.go | 1 - command.go | 14 -------------- 2 files changed, 15 deletions(-) diff --git a/app.go b/app.go index 689ee04d7a..7be6f7694d 100644 --- a/app.go +++ b/app.go @@ -133,7 +133,6 @@ func compileTime() time.Time { func NewApp() *App { return &App{ Name: filepath.Base(os.Args[0]), - HelpName: "", // setup will fill this later Usage: "A new cli application", UsageText: "", BashComplete: DefaultAppComplete, diff --git a/command.go b/command.go index ad83ca118f..d24b61e239 100644 --- a/command.go +++ b/command.go @@ -62,9 +62,6 @@ type Command struct { // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. CustomHelpTemplate string - - // categories contains the categorized commands and is populated on app startup - categories CommandCategories } type Commands []*Command @@ -316,17 +313,6 @@ func (c *Command) VisibleFlags() []Flag { return visibleFlags(c.Flags) } -// VisibleCommands returns a slice of the Commands with Hidden=false -func (c *Command) VisibleCommands() []*Command { - var ret []*Command - for _, command := range c.Subcommands { - if !command.Hidden { - ret = append(ret, command) - } - } - return ret -} - func (c *Command) appendFlag(fl Flag) { if !hasFlag(c.Flags, fl) { c.Flags = append(c.Flags, fl) From 5db9db6d380c1c51db7f52ce7c4db3c58fea700b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 29 Sep 2022 11:24:42 -0400 Subject: [PATCH 104/136] Remove nonexistent phony targets so that running `make` or `make all` only runs targets that exist. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 797d093e9f..f0d41905ea 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ GO_RUN_BUILD := go run internal/build/build.go .PHONY: all -all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun yamlfmt v2diff +all: generate vet test check-binary-size gfmrun yamlfmt v2diff # NOTE: this is a special catch-all rule to run any of the commands # defined in internal/build/build.go with optional arguments passed From f8faf77e4309e9abb86bd1b3512b18dba73db673 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 3 Oct 2022 10:06:01 -0400 Subject: [PATCH 105/136] Post-porting fixes for v3 --- app_test.go | 4 +- command.go | 4 +- flag-spec.yaml | 1 - flag_uint64_slice.go | 9 + flag_uint_slice.go | 9 + godoc-current.txt | 338 ++++++++++++++++++++++++-- sliceflag.go | 23 +- testdata/godoc-v2.x.txt | 476 +++++++++++++++++++++++++++---------- zz_generated.flags_test.go | 24 -- 9 files changed, 691 insertions(+), 197 deletions(-) diff --git a/app_test.go b/app_test.go index 824776974c..1428c391dd 100644 --- a/app_test.go +++ b/app_test.go @@ -174,10 +174,10 @@ func ExampleApp_Run_commandHelp() { _ = app.Run(os.Args) // Output: // NAME: - // greet describeit [command options] [arguments...] + // greet describeit - use it to see a description // // USAGE: - // greet describeit [arguments...] + // greet describeit [command options] [arguments...] // // DESCRIPTION: // This is how we describe describeit the function diff --git a/command.go b/command.go index d24b61e239..decfb7359c 100644 --- a/command.go +++ b/command.go @@ -300,9 +300,7 @@ func (c *Command) VisibleFlagCategories() []VisibleFlagCategory { if c.flagCategories == nil { c.flagCategories = newFlagCategories() for _, fl := range c.Flags { - if cf, ok := fl.(CategorizableFlag); ok { - c.flagCategories.AddFlag(cf.GetCategory(), cf) - } + c.flagCategories.AddFlag(fl.GetCategory(), fl) } } return c.flagCategories.VisibleCategories() diff --git a/flag-spec.yaml b/flag-spec.yaml index ea75670c6b..a38b933379 100644 --- a/flag-spec.yaml +++ b/flag-spec.yaml @@ -1,7 +1,6 @@ # NOTE: this file is used by the tool defined in # ./cmd/urfave-cli-genflags/main.go which uses the # `Spec` type that maps to this file structure. - flag_types: bool: no_default_text: true diff --git a/flag_uint64_slice.go b/flag_uint64_slice.go index e60c3ea8af..662a03eac6 100644 --- a/flag_uint64_slice.go +++ b/flag_uint64_slice.go @@ -183,6 +183,15 @@ func (f *Uint64SliceFlag) stringify() string { return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } +// RunAction executes flag action if set +func (f *Uint64SliceFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Uint64Slice(f.Name)) + } + + return nil +} + // Uint64Slice looks up the value of a local Uint64SliceFlag, returns // nil if not found func (cCtx *Context) Uint64Slice(name string) []uint64 { diff --git a/flag_uint_slice.go b/flag_uint_slice.go index 350b29ccf0..3689eaa39b 100644 --- a/flag_uint_slice.go +++ b/flag_uint_slice.go @@ -194,6 +194,15 @@ func (f *UintSliceFlag) stringify() string { return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } +// RunAction executes flag action if set +func (f *UintSliceFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.UintSlice(f.Name)) + } + + return nil +} + // UintSlice looks up the value of a local UintSliceFlag, returns // nil if not found func (cCtx *Context) UintSlice(name string) []uint { diff --git a/godoc-current.txt b/godoc-current.txt index 0e09211a70..2c8b5e040c 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -5,24 +5,24 @@ line Go applications. cli is designed to be easy to understand and write, the most simple cli application can be written as follows: func main() { - (&cli.App{}).Run(os.Args) + (&cli.App{}).Run(os.Args) } Of course this application does not do much, so let's make this an actual application: - func main() { - app := &cli.App{ - Name: "greet", - Usage: "say a greeting", - Action: func(c *cli.Context) error { - fmt.Println("Greetings") - return nil - }, - } - - app.Run(os.Args) - } + func main() { + app := &cli.App{ + Name: "greet", + Usage: "say a greeting", + Action: func(c *cli.Context) error { + fmt.Println("Greetings") + return nil + }, + } + + app.Run(os.Args) + } VARIABLES @@ -49,8 +49,8 @@ AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} + {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} GLOBAL OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} @@ -82,12 +82,10 @@ DESCRIPTION: OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}} - {{end}}{{end}}{{else}}{{if .VisibleFlags}} + {{end}}{{range .Flags}}{{.}}{{end}}{{end}}{{else}}{{if .VisibleFlags}} OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}}{{end}} + {{range .VisibleFlags}}{{.}}{{end}}{{end}}{{end}} ` CommandHelpTemplate is the text template for the command help topic. cli.go uses text/template to render templates. You can render custom help text by @@ -157,12 +155,11 @@ DESCRIPTION: COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} + {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}} + {{range .VisibleFlags}}{{.}}{{end}}{{end}} ` SubcommandHelpTemplate is the text template for the subcommand help topic. cli.go uses text/template to render templates. You can render custom help @@ -300,6 +297,8 @@ type App struct { CommandNotFound CommandNotFoundFunc // Execute this function if a usage error occurs OnUsageError OnUsageErrorFunc + // Execute this function when an invalid flag is accessed from the context + InvalidFlagAccessHandler InvalidFlagAccessFunc // Compilation date Compiled time.Time // List of all authors who contributed @@ -450,6 +449,10 @@ type BoolFlag struct { Aliases []string EnvVars []string + + Count *int + + Action func(*Context, bool) error } BoolFlag is a flag with type bool @@ -487,6 +490,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) @@ -622,7 +628,7 @@ func (cCtx *Context) Bool(name string) bool Bool looks up the value of a local BoolFlag, returns false if not found func (cCtx *Context) Count(name string) int - NumOccurrences returns the num of occurences of this flag + Count returns the num of occurences of this flag func (cCtx *Context) Duration(name string) time.Duration Duration looks up the value of a local DurationFlag, returns 0 if not found @@ -693,9 +699,23 @@ func (cCtx *Context) Uint(name string) uint func (cCtx *Context) Uint64(name string) uint64 Uint64 looks up the value of a local Uint64Flag, returns 0 if not found +func (cCtx *Context) Uint64Slice(name string) []uint64 + Uint64Slice looks up the value of a local Uint64SliceFlag, returns nil if + not found + +func (cCtx *Context) UintSlice(name string) []uint + UintSlice looks up the value of a local UintSliceFlag, returns nil if not + found + func (cCtx *Context) Value(name string) interface{} Value returns the value of the flag corresponding to `name` +type Countable interface { + Count() int +} + Countable is an interface to enable detection of flag values which support + repetitive flags + type DurationFlag struct { Name string @@ -713,6 +733,8 @@ type DurationFlag struct { Aliases []string EnvVars []string + + Action func(*Context, time.Duration) error } DurationFlag is a flag with type time.Duration @@ -750,6 +772,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) @@ -823,6 +848,8 @@ type Flag interface { // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. GetValue() string + + 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 @@ -916,6 +943,8 @@ type Float64Flag struct { Aliases []string EnvVars []string + + Action func(*Context, float64) error } Float64Flag is a flag with type float64 @@ -953,6 +982,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) @@ -999,6 +1031,8 @@ type Float64SliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []float64) error } Float64SliceFlag is a flag with type *Float64Slice @@ -1038,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) @@ -1067,16 +1104,18 @@ type GenericFlag struct { HasBeenSet bool Value Generic - Destination *Generic + Destination Generic Aliases []string EnvVars []string TakesFile bool + + Action func(*Context, interface{}) error } GenericFlag is a flag with type Generic -func (f GenericFlag) Apply(set *flag.FlagSet) error +func (f *GenericFlag) Apply(set *flag.FlagSet) error Apply takes the flagset and calls Set on the generic flag with the value provided by the user for parsing by the flag @@ -1111,6 +1150,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) @@ -1134,6 +1176,10 @@ type Int64Flag struct { Aliases []string EnvVars []string + + Base int + + Action func(*Context, int64) error } Int64Flag is a flag with type int64 @@ -1171,6 +1217,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) @@ -1217,6 +1266,8 @@ type Int64SliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []int64) error } Int64SliceFlag is a flag with type *Int64Slice @@ -1256,6 +1307,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) @@ -1283,6 +1337,10 @@ type IntFlag struct { Aliases []string EnvVars []string + + Base int + + Action func(*Context, int) error } IntFlag is a flag with type int @@ -1320,6 +1378,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) @@ -1370,6 +1431,8 @@ type IntSliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []int) error } IntSliceFlag is a flag with type *IntSlice @@ -1409,6 +1472,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) @@ -1419,6 +1485,10 @@ func (f *IntSliceFlag) String() string func (f *IntSliceFlag) TakesValue() bool TakesValue returns true if the flag takes a value, otherwise false +type InvalidFlagAccessFunc func(*Context, string) + InvalidFlagAccessFunc is executed when an invalid flag is accessed from the + context. + type MultiError interface { error Errors() []error @@ -1468,6 +1538,8 @@ type PathFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, Path) error } PathFlag is a flag with type Path @@ -1505,6 +1577,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) @@ -1548,6 +1623,8 @@ func (x *SliceFlag[T, S, E]) IsVisible() bool func (x *SliceFlag[T, S, E]) Names() []string +func (x *SliceFlag[T, S, E]) RunAction(c *Context) error + func (x *SliceFlag[T, S, E]) SetDestination(slice S) func (x *SliceFlag[T, S, E]) SetValue(slice S) @@ -1592,6 +1669,8 @@ type StringFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, string) error } StringFlag is a flag with type string @@ -1629,6 +1708,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) @@ -1677,6 +1759,8 @@ type StringSliceFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, []string) error } StringSliceFlag is a flag with type *StringSlice @@ -1716,6 +1800,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) @@ -1780,6 +1867,8 @@ type TimestampFlag struct { Layout string Timezone *time.Location + + Action func(*Context, *time.Time) error } TimestampFlag is a flag with type *Timestamp @@ -1817,6 +1906,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) @@ -1840,6 +1932,10 @@ type Uint64Flag struct { Aliases []string EnvVars []string + + Base int + + Action func(*Context, uint64) error } Uint64Flag is a flag with type uint64 @@ -1877,12 +1973,103 @@ 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) func (f *Uint64Flag) TakesValue() bool TakesValue returns true if the flag takes a value, otherwise false +type Uint64Slice struct { + // Has unexported fields. +} + Uint64Slice wraps []int64 to satisfy flag.Value + +func NewUint64Slice(defaults ...uint64) *Uint64Slice + NewUint64Slice makes an *Uint64Slice with default values + +func (i *Uint64Slice) Get() interface{} + Get returns the slice of ints set by this flag + +func (i *Uint64Slice) Serialize() string + Serialize allows Uint64Slice to fulfill Serializer + +func (i *Uint64Slice) Set(value string) error + Set parses the value into an integer and appends it to the list of values + +func (i *Uint64Slice) String() string + String returns a readable representation of this value (for usage defaults) + +func (i *Uint64Slice) Value() []uint64 + Value returns the slice of ints set by this flag + +type Uint64SliceFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Uint64Slice + Destination *Uint64Slice + + Aliases []string + EnvVars []string + + Action func(*Context, []uint64) error +} + Uint64SliceFlag is a flag with type *Uint64Slice + +func (f *Uint64SliceFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *Uint64SliceFlag) Get(ctx *Context) []uint64 + Get returns the flag’s value in the given Context. + +func (f *Uint64SliceFlag) GetCategory() string + GetCategory returns the category for the flag + +func (f *Uint64SliceFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *Uint64SliceFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *Uint64SliceFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *Uint64SliceFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *Uint64SliceFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *Uint64SliceFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *Uint64SliceFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *Uint64SliceFlag) Names() []string + Names returns the names of the flag + +func (f *Uint64SliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + +func (f *Uint64SliceFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Uint64SliceFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + type UintFlag struct { Name string @@ -1900,6 +2087,10 @@ type UintFlag struct { Aliases []string EnvVars []string + + Base int + + Action func(*Context, uint) error } UintFlag is a flag with type uint @@ -1937,12 +2128,107 @@ 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) func (f *UintFlag) TakesValue() bool TakesValue returns true if the flag takes a value, otherwise false +type UintSlice struct { + // Has unexported fields. +} + UintSlice wraps []int to satisfy flag.Value + +func NewUintSlice(defaults ...uint) *UintSlice + NewUintSlice makes an *UintSlice with default values + +func (i *UintSlice) Get() interface{} + Get returns the slice of ints set by this flag + +func (i *UintSlice) Serialize() string + Serialize allows UintSlice to fulfill Serializer + +func (i *UintSlice) Set(value string) error + Set parses the value into an integer and appends it to the list of values + +func (i *UintSlice) SetUint(value uint) + TODO: Consistently have specific Set function for Int64 and Float64 ? SetInt + directly adds an integer to the list of values + +func (i *UintSlice) String() string + String returns a readable representation of this value (for usage defaults) + +func (i *UintSlice) Value() []uint + Value returns the slice of ints set by this flag + +type UintSliceFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *UintSlice + Destination *UintSlice + + Aliases []string + EnvVars []string + + Action func(*Context, []uint) error +} + UintSliceFlag is a flag with type *UintSlice + +func (f *UintSliceFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *UintSliceFlag) Get(ctx *Context) []uint + Get returns the flag’s value in the given Context. + +func (f *UintSliceFlag) GetCategory() string + GetCategory returns the category for the flag + +func (f *UintSliceFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *UintSliceFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *UintSliceFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *UintSliceFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *UintSliceFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *UintSliceFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *UintSliceFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *UintSliceFlag) Names() []string + Names returns the names of the flag + +func (f *UintSliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + +func (f *UintSliceFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *UintSliceFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + type VisibleFlagCategory interface { // Name returns the category name string Name() string diff --git a/sliceflag.go b/sliceflag.go index 89f3e0cc89..c8670cb560 100644 --- a/sliceflag.go +++ b/sliceflag.go @@ -110,17 +110,18 @@ func (x *SliceFlag[T, S, E]) GetDestination() S { return nil } -func (x *SliceFlag[T, S, E]) String() string { return x.Target.String() } -func (x *SliceFlag[T, S, E]) Names() []string { return x.Target.Names() } -func (x *SliceFlag[T, S, E]) IsSet() bool { return x.Target.IsSet() } -func (x *SliceFlag[T, S, E]) IsRequired() bool { return x.Target.IsRequired() } -func (x *SliceFlag[T, S, E]) TakesValue() bool { return x.Target.TakesValue() } -func (x *SliceFlag[T, S, E]) GetUsage() string { return x.Target.GetUsage() } -func (x *SliceFlag[T, S, E]) GetValue() string { return x.Target.GetValue() } -func (x *SliceFlag[T, S, E]) GetDefaultText() string { return x.Target.GetDefaultText() } -func (x *SliceFlag[T, S, E]) GetEnvVars() []string { return x.Target.GetEnvVars() } -func (x *SliceFlag[T, S, E]) IsVisible() bool { return x.Target.IsVisible() } -func (x *SliceFlag[T, S, E]) GetCategory() string { return x.Target.GetCategory() } +func (x *SliceFlag[T, S, E]) String() string { return x.Target.String() } +func (x *SliceFlag[T, S, E]) Names() []string { return x.Target.Names() } +func (x *SliceFlag[T, S, E]) IsSet() bool { return x.Target.IsSet() } +func (x *SliceFlag[T, S, E]) IsRequired() bool { return x.Target.IsRequired() } +func (x *SliceFlag[T, S, E]) TakesValue() bool { return x.Target.TakesValue() } +func (x *SliceFlag[T, S, E]) GetUsage() string { return x.Target.GetUsage() } +func (x *SliceFlag[T, S, E]) GetValue() string { return x.Target.GetValue() } +func (x *SliceFlag[T, S, E]) GetDefaultText() string { return x.Target.GetDefaultText() } +func (x *SliceFlag[T, S, E]) GetEnvVars() []string { return x.Target.GetEnvVars() } +func (x *SliceFlag[T, S, E]) IsVisible() bool { return x.Target.IsVisible() } +func (x *SliceFlag[T, S, E]) GetCategory() string { return x.Target.GetCategory() } +func (x *SliceFlag[T, S, E]) RunAction(c *Context) error { return x.Target.RunAction(c) } func (x *flagValueHook) Set(value string) error { if err := x.value.Set(value); err != nil { diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt index 562b7ff22a..2c8b5e040c 100644 --- a/testdata/godoc-v2.x.txt +++ b/testdata/godoc-v2.x.txt @@ -1,28 +1,28 @@ -package cli // import "github.com/urfave/cli/v2" +package cli // import "github.com/urfave/cli/v3" Package cli provides a minimal framework for creating and organizing command line Go applications. cli is designed to be easy to understand and write, the most simple cli application can be written as follows: func main() { - (&cli.App{}).Run(os.Args) + (&cli.App{}).Run(os.Args) } Of course this application does not do much, so let's make this an actual application: - func main() { - app := &cli.App{ - Name: "greet", - Usage: "say a greeting", - Action: func(c *cli.Context) error { - fmt.Println("Greetings") - return nil - }, - } - - app.Run(os.Args) - } + func main() { + app := &cli.App{ + Name: "greet", + Usage: "say a greeting", + Action: func(c *cli.Context) error { + fmt.Println("Greetings") + return nil + }, + } + + app.Run(os.Args) + } VARIABLES @@ -49,8 +49,8 @@ AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} + {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} GLOBAL OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} @@ -64,8 +64,8 @@ GLOBAL OPTIONS: COPYRIGHT: {{wrap .Copyright 3}}{{end}} ` - AppHelpTemplate is the text template for the Default help topic. cli.go uses - text/template to render templates. You can render custom help text by + AppHelpTemplate is the text template for the Default help topic. cli.go + uses text/template to render templates. You can render custom help text by setting this variable. var CommandHelpTemplate = `NAME: @@ -82,12 +82,10 @@ DESCRIPTION: OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}} - {{end}}{{end}}{{else}}{{if .VisibleFlags}} + {{end}}{{range .Flags}}{{.}}{{end}}{{end}}{{else}}{{if .VisibleFlags}} OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}}{{end}} + {{range .VisibleFlags}}{{.}}{{end}}{{end}}{{end}} ` CommandHelpTemplate is the text template for the command help topic. cli.go uses text/template to render templates. You can render custom help text by @@ -157,12 +155,11 @@ DESCRIPTION: COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} + {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}} + {{range .VisibleFlags}}{{.}}{{end}}{{end}} ` SubcommandHelpTemplate is the text template for the subcommand help topic. cli.go uses text/template to render templates. You can render custom help @@ -201,9 +198,9 @@ func DefaultAppComplete(cCtx *Context) func DefaultCompleteWithFlags(cmd *Command) func(cCtx *Context) func FlagNames(name string, aliases []string) []string func HandleAction(action interface{}, cCtx *Context) (err error) - HandleAction attempts to figure out which Action signature was used. If it's - an ActionFunc or a func with the legacy signature for Action, the func is - run! + HandleAction attempts to figure out which Action signature was used. + If it's an ActionFunc or a func with the legacy signature for Action, + the func is run! func HandleExitCoder(err error) HandleExitCoder handles errors implementing ExitCoder by printing their @@ -250,13 +247,6 @@ 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 @@ -307,6 +297,8 @@ type App struct { CommandNotFound CommandNotFoundFunc // Execute this function if a usage error occurs OnUsageError OnUsageErrorFunc + // Execute this function when an invalid flag is accessed from the context + InvalidFlagAccessHandler InvalidFlagAccessFunc // Compilation date Compiled time.Time // List of all authors who contributed @@ -367,14 +359,14 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) to generate command-specific flags func (a *App) RunContext(ctx context.Context, arguments []string) (err error) - RunContext is like Run except it takes a Context that will be passed to its - commands and sub-commands. Through this, you can propagate timeouts and + RunContext is like Run except it takes a Context that will be passed to + its commands and sub-commands. Through this, you can propagate timeouts and cancellation requests func (a *App) Setup() - Setup runs initialization code to ensure all data structures are ready for - `Run` or inspection prior to `Run`. It is internally called by `Run`, but - will return early if setup has already happened. + Setup runs initialization code to ensure all data structures are ready + for `Run` or inspection prior to `Run`. It is internally called by `Run`, + but will return early if setup has already happened. func (a *App) ToFishCompletion() (string, error) ToFishCompletion creates a fish completion string for the `*App` The @@ -457,6 +449,10 @@ type BoolFlag struct { Aliases []string EnvVars []string + + Count *int + + Action func(*Context, bool) error } BoolFlag is a flag with type bool @@ -467,7 +463,7 @@ func (f *BoolFlag) Get(ctx *Context) bool Get returns the flag’s value in the given Context. func (f *BoolFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *BoolFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -501,15 +497,7 @@ func (f *BoolFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *BoolFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false - -type CategorizableFlag interface { - VisibleFlag - - GetCategory() string -} - CategorizableFlag is an interface that allows us to potentially use a flag - in a categorized representation. + TakesValue returns true if the flag takes a value, otherwise false type Command struct { // The name of the command @@ -639,6 +627,9 @@ func (cCtx *Context) Args() Args func (cCtx *Context) Bool(name string) bool Bool looks up the value of a local BoolFlag, returns false if not found +func (cCtx *Context) Count(name string) int + Count returns the num of occurences of this flag + func (cCtx *Context) Duration(name string) time.Duration Duration looks up the value of a local DurationFlag, returns 0 if not found @@ -708,30 +699,22 @@ func (cCtx *Context) Uint(name string) uint func (cCtx *Context) Uint64(name string) uint64 Uint64 looks up the value of a local Uint64Flag, returns 0 if not found -func (cCtx *Context) Value(name string) interface{} - Value returns the value of the flag corresponding to `name` - -type DocGenerationFlag interface { - Flag - - // TakesValue returns true if the flag takes a value, otherwise false - TakesValue() bool - - // GetUsage returns the usage string for the flag - GetUsage() string +func (cCtx *Context) Uint64Slice(name string) []uint64 + Uint64Slice looks up the value of a local Uint64SliceFlag, returns nil if + not found - // GetValue returns the flags value as string representation and an empty - // string if the flag takes no value at all. - GetValue() string +func (cCtx *Context) UintSlice(name string) []uint + UintSlice looks up the value of a local UintSliceFlag, returns nil if not + found - // GetDefaultText returns the default text for this flag - GetDefaultText() string +func (cCtx *Context) Value(name string) interface{} + Value returns the value of the flag corresponding to `name` - // GetEnvVars returns the env vars for this flag - GetEnvVars() []string +type Countable interface { + Count() int } - DocGenerationFlag is an interface that allows documentation generation for - the flag + Countable is an interface to enable detection of flag values which support + repetitive flags type DurationFlag struct { Name string @@ -750,6 +733,8 @@ type DurationFlag struct { Aliases []string EnvVars []string + + Action func(*Context, time.Duration) error } DurationFlag is a flag with type time.Duration @@ -760,7 +745,7 @@ func (f *DurationFlag) Get(ctx *Context) time.Duration Get returns the flag’s value in the given Context. func (f *DurationFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *DurationFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -794,7 +779,7 @@ func (f *DurationFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *DurationFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type ErrorFormatter interface { Format(s fmt.State, verb rune) @@ -812,9 +797,9 @@ func Exit(message interface{}, exitCode int) ExitCoder Exit wraps a message and exit code into an error, which by default is handled with a call to os.Exit during default error handling. - This is the simplest way to trigger a non-zero exit code for an App without - having to call os.Exit manually. During testing, this behavior can be - avoided by overiding the ExitErrHandler function on an App or the + This is the simplest way to trigger a non-zero exit code for an App + without having to call os.Exit manually. During testing, this behavior + can be avoided by overiding the ExitErrHandler function on an App or the package-global OsExiter function. func NewExitError(message interface{}, exitCode int) ExitCoder @@ -829,10 +814,42 @@ type ExitErrHandlerFunc func(cCtx *Context, err error) type Flag interface { fmt.Stringer + // Apply Flag settings to the given flag set Apply(*flag.FlagSet) error + + // All possible names for this flag Names() []string + + // Whether the flag has been set or not IsSet() bool + + // whether the flag is a required flag or not + IsRequired() bool + + // IsVisible returns true if the flag is not hidden, otherwise false + IsVisible() bool + + // Returns the category of the flag + GetCategory() string + + // GetUsage returns the usage string for the flag + GetUsage() string + + // GetEnvVars returns the env vars for this flag + GetEnvVars() []string + + // TakesValue returns true if the flag takes a value, otherwise false + TakesValue() bool + + // GetDefaultText returns the default text for this flag + GetDefaultText() string + + // GetValue returns the flags value as string representation and an empty + // string if the flag takes no value at all. + GetValue() string + + 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 @@ -926,6 +943,8 @@ type Float64Flag struct { Aliases []string EnvVars []string + + Action func(*Context, float64) error } Float64Flag is a flag with type float64 @@ -936,7 +955,7 @@ func (f *Float64Flag) Get(ctx *Context) float64 Get returns the flag’s value in the given Context. func (f *Float64Flag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *Float64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -970,7 +989,7 @@ func (f *Float64Flag) String() string String returns a readable representation of this value (for usage defaults) func (f *Float64Flag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type Float64Slice struct { // Has unexported fields. @@ -1012,6 +1031,8 @@ type Float64SliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []float64) error } Float64SliceFlag is a flag with type *Float64Slice @@ -1022,7 +1043,7 @@ func (f *Float64SliceFlag) Get(ctx *Context) []float64 Get returns the flag’s value in the given Context. func (f *Float64SliceFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *Float64SliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1083,16 +1104,18 @@ type GenericFlag struct { HasBeenSet bool Value Generic - Destination *Generic + Destination Generic Aliases []string EnvVars []string TakesFile bool + + Action func(*Context, interface{}) error } GenericFlag is a flag with type Generic -func (f GenericFlag) Apply(set *flag.FlagSet) error +func (f *GenericFlag) Apply(set *flag.FlagSet) error Apply takes the flagset and calls Set on the generic flag with the value provided by the user for parsing by the flag @@ -1100,7 +1123,7 @@ func (f *GenericFlag) Get(ctx *Context) interface{} Get returns the flag’s value in the given Context. func (f *GenericFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *GenericFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1134,7 +1157,7 @@ func (f *GenericFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *GenericFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type Int64Flag struct { Name string @@ -1153,6 +1176,10 @@ type Int64Flag struct { Aliases []string EnvVars []string + + Base int + + Action func(*Context, int64) error } Int64Flag is a flag with type int64 @@ -1163,7 +1190,7 @@ func (f *Int64Flag) Get(ctx *Context) int64 Get returns the flag’s value in the given Context. func (f *Int64Flag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *Int64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1197,7 +1224,7 @@ func (f *Int64Flag) String() string String returns a readable representation of this value (for usage defaults) func (f *Int64Flag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type Int64Slice struct { // Has unexported fields. @@ -1239,6 +1266,8 @@ type Int64SliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []int64) error } Int64SliceFlag is a flag with type *Int64Slice @@ -1249,7 +1278,7 @@ func (f *Int64SliceFlag) Get(ctx *Context) []int64 Get returns the flag’s value in the given Context. func (f *Int64SliceFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *Int64SliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1289,7 +1318,7 @@ func (f *Int64SliceFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *Int64SliceFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type IntFlag struct { Name string @@ -1308,6 +1337,10 @@ type IntFlag struct { Aliases []string EnvVars []string + + Base int + + Action func(*Context, int) error } IntFlag is a flag with type int @@ -1318,7 +1351,7 @@ func (f *IntFlag) Get(ctx *Context) int Get returns the flag’s value in the given Context. func (f *IntFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *IntFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1352,7 +1385,7 @@ func (f *IntFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *IntFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type IntSlice struct { // Has unexported fields. @@ -1398,6 +1431,8 @@ type IntSliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []int) error } IntSliceFlag is a flag with type *IntSlice @@ -1408,7 +1443,7 @@ func (f *IntSliceFlag) Get(ctx *Context) []int Get returns the flag’s value in the given Context. func (f *IntSliceFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *IntSliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1448,7 +1483,11 @@ func (f *IntSliceFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *IntSliceFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false + +type InvalidFlagAccessFunc func(*Context, string) + InvalidFlagAccessFunc is executed when an invalid flag is accessed from the + context. type MultiError interface { error @@ -1465,8 +1504,8 @@ type MultiInt64Flag = SliceFlag[*Int64SliceFlag, []int64, int64] directly, as Value and/or Destination. See also SliceFlag. type MultiIntFlag = SliceFlag[*IntSliceFlag, []int, int] - MultiIntFlag extends IntSliceFlag with support for using slices directly, as - Value and/or Destination. See also SliceFlag. + MultiIntFlag extends IntSliceFlag with support for using slices directly, + as Value and/or Destination. See also SliceFlag. type MultiStringFlag = SliceFlag[*StringSliceFlag, []string, string] MultiStringFlag extends StringSliceFlag with support for using slices @@ -1499,6 +1538,8 @@ type PathFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, Path) error } PathFlag is a flag with type Path @@ -1509,7 +1550,7 @@ func (f *PathFlag) Get(ctx *Context) string Get returns the flag’s value in the given Context. func (f *PathFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *PathFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1543,16 +1584,7 @@ func (f *PathFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *PathFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false - -type RequiredFlag interface { - Flag - - IsRequired() bool -} - RequiredFlag is an interface that allows us to mark flags as required it - allows flags required flags to be backwards compatible with the Flag - interface + TakesValue returns true if the flag takes a value, otherwise false type Serializer interface { Serialize() string @@ -1564,9 +1596,9 @@ type SliceFlag[T SliceFlagTarget[E], S ~[]E, E any] struct { Value S Destination *S } - SliceFlag extends implementations like StringSliceFlag and IntSliceFlag with - support for using slices directly, as Value and/or Destination. See also - SliceFlagTarget, MultiStringFlag, MultiFloat64Flag, MultiInt64Flag, + SliceFlag extends implementations like StringSliceFlag and IntSliceFlag + with support for using slices directly, as Value and/or Destination. + See also SliceFlagTarget, MultiStringFlag, MultiFloat64Flag, MultiInt64Flag, MultiIntFlag. func (x *SliceFlag[T, S, E]) Apply(set *flag.FlagSet) error @@ -1591,6 +1623,8 @@ func (x *SliceFlag[T, S, E]) IsVisible() bool func (x *SliceFlag[T, S, E]) Names() []string +func (x *SliceFlag[T, S, E]) RunAction(c *Context) error + func (x *SliceFlag[T, S, E]) SetDestination(slice S) func (x *SliceFlag[T, S, E]) SetValue(slice S) @@ -1601,10 +1635,6 @@ func (x *SliceFlag[T, S, E]) TakesValue() bool type SliceFlagTarget[E any] interface { Flag - RequiredFlag - DocGenerationFlag - VisibleFlag - CategorizableFlag // SetValue should propagate the given slice to the target, ideally as a new value. // Note that a nil slice should nil/clear any existing value (modelled as ~[]E). @@ -1639,6 +1669,8 @@ type StringFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, string) error } StringFlag is a flag with type string @@ -1649,7 +1681,7 @@ func (f *StringFlag) Get(ctx *Context) string Get returns the flag’s value in the given Context. func (f *StringFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *StringFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1683,7 +1715,7 @@ func (f *StringFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *StringFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type StringSlice struct { // Has unexported fields. @@ -1727,6 +1759,8 @@ type StringSliceFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, []string) error } StringSliceFlag is a flag with type *StringSlice @@ -1737,7 +1771,7 @@ func (f *StringSliceFlag) Get(ctx *Context) []string Get returns the flag’s value in the given Context. func (f *StringSliceFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *StringSliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1777,7 +1811,7 @@ func (f *StringSliceFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *StringSliceFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type SuggestCommandFunc func(commands []*Command, provided string) string @@ -1833,6 +1867,8 @@ type TimestampFlag struct { Layout string Timezone *time.Location + + Action func(*Context, *time.Time) error } TimestampFlag is a flag with type *Timestamp @@ -1843,7 +1879,7 @@ func (f *TimestampFlag) Get(ctx *Context) *time.Time Get returns the flag’s value in the given Context. func (f *TimestampFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *TimestampFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1877,7 +1913,7 @@ func (f *TimestampFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *TimestampFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type Uint64Flag struct { Name string @@ -1896,6 +1932,10 @@ type Uint64Flag struct { Aliases []string EnvVars []string + + Base int + + Action func(*Context, uint64) error } Uint64Flag is a flag with type uint64 @@ -1906,7 +1946,7 @@ func (f *Uint64Flag) Get(ctx *Context) uint64 Get returns the flag’s value in the given Context. func (f *Uint64Flag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *Uint64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1940,6 +1980,94 @@ func (f *Uint64Flag) String() string String returns a readable representation of this value (for usage defaults) func (f *Uint64Flag) TakesValue() bool + TakesValue returns true if the flag takes a value, otherwise false + +type Uint64Slice struct { + // Has unexported fields. +} + Uint64Slice wraps []int64 to satisfy flag.Value + +func NewUint64Slice(defaults ...uint64) *Uint64Slice + NewUint64Slice makes an *Uint64Slice with default values + +func (i *Uint64Slice) Get() interface{} + Get returns the slice of ints set by this flag + +func (i *Uint64Slice) Serialize() string + Serialize allows Uint64Slice to fulfill Serializer + +func (i *Uint64Slice) Set(value string) error + Set parses the value into an integer and appends it to the list of values + +func (i *Uint64Slice) String() string + String returns a readable representation of this value (for usage defaults) + +func (i *Uint64Slice) Value() []uint64 + Value returns the slice of ints set by this flag + +type Uint64SliceFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Uint64Slice + Destination *Uint64Slice + + Aliases []string + EnvVars []string + + Action func(*Context, []uint64) error +} + Uint64SliceFlag is a flag with type *Uint64Slice + +func (f *Uint64SliceFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *Uint64SliceFlag) Get(ctx *Context) []uint64 + Get returns the flag’s value in the given Context. + +func (f *Uint64SliceFlag) GetCategory() string + GetCategory returns the category for the flag + +func (f *Uint64SliceFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *Uint64SliceFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *Uint64SliceFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *Uint64SliceFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *Uint64SliceFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *Uint64SliceFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *Uint64SliceFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *Uint64SliceFlag) Names() []string + Names returns the names of the flag + +func (f *Uint64SliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + +func (f *Uint64SliceFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Uint64SliceFlag) TakesValue() bool TakesValue returns true of the flag takes a value, otherwise false type UintFlag struct { @@ -1959,6 +2087,10 @@ type UintFlag struct { Aliases []string EnvVars []string + + Base int + + Action func(*Context, uint) error } UintFlag is a flag with type uint @@ -1969,7 +2101,7 @@ func (f *UintFlag) Get(ctx *Context) uint Get returns the flag’s value in the given Context. func (f *UintFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *UintFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -2003,25 +2135,109 @@ func (f *UintFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *UintFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false -type VisibleFlag interface { - Flag +type UintSlice struct { + // Has unexported fields. +} + UintSlice wraps []int to satisfy flag.Value - // IsVisible returns true if the flag is not hidden, otherwise false - IsVisible() bool +func NewUintSlice(defaults ...uint) *UintSlice + NewUintSlice makes an *UintSlice with default values + +func (i *UintSlice) Get() interface{} + Get returns the slice of ints set by this flag + +func (i *UintSlice) Serialize() string + Serialize allows UintSlice to fulfill Serializer + +func (i *UintSlice) Set(value string) error + Set parses the value into an integer and appends it to the list of values + +func (i *UintSlice) SetUint(value uint) + TODO: Consistently have specific Set function for Int64 and Float64 ? SetInt + directly adds an integer to the list of values + +func (i *UintSlice) String() string + String returns a readable representation of this value (for usage defaults) + +func (i *UintSlice) Value() []uint + Value returns the slice of ints set by this flag + +type UintSliceFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *UintSlice + Destination *UintSlice + + Aliases []string + EnvVars []string + + Action func(*Context, []uint) error } - VisibleFlag is an interface that allows to check if a flag is visible + UintSliceFlag is a flag with type *UintSlice + +func (f *UintSliceFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *UintSliceFlag) Get(ctx *Context) []uint + Get returns the flag’s value in the given Context. + +func (f *UintSliceFlag) GetCategory() string + GetCategory returns the category for the flag + +func (f *UintSliceFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *UintSliceFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *UintSliceFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *UintSliceFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *UintSliceFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *UintSliceFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *UintSliceFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *UintSliceFlag) Names() []string + Names returns the names of the flag + +func (f *UintSliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + +func (f *UintSliceFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *UintSliceFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false type VisibleFlagCategory interface { // Name returns the category name string Name() string // Flags returns a slice of VisibleFlag sorted by name - Flags() []VisibleFlag + Flags() []Flag } VisibleFlagCategory is a category containing flags. -package altsrc // import "github.com/urfave/cli/v2/altsrc" +package altsrc // import "github.com/urfave/cli/v3/altsrc" FUNCTIONS @@ -2038,9 +2254,9 @@ func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceCont that are supported by the input source func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(cCtx *cli.Context) (InputSourceContext, error)) cli.BeforeFunc - InitInputSourceWithContext is used to to setup an InputSourceContext on a - cli.Command Before method. It will create a new input source based on the - func provided with potentially using existing cli.Context values to + InitInputSourceWithContext is used to to setup an InputSourceContext on + a cli.Command Before method. It will create a new input source based on + the func provided with potentially using existing cli.Context values to initialize itself. If there is no error it will then apply the new input source to any flags that are supported by the input source diff --git a/zz_generated.flags_test.go b/zz_generated.flags_test.go index fa010145f0..be6dadf84b 100644 --- a/zz_generated.flags_test.go +++ b/zz_generated.flags_test.go @@ -167,18 +167,6 @@ func TestUint64SliceFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } -func TestUint64SliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.RequiredFlag = &cli.Uint64SliceFlag{} - - _ = f.IsRequired() -} - -func TestUint64SliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.VisibleFlag = &cli.Uint64SliceFlag{} - - _ = f.IsVisible() -} - func TestUintSliceFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.UintSliceFlag{} @@ -186,18 +174,6 @@ func TestUintSliceFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } -func TestUintSliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.RequiredFlag = &cli.UintSliceFlag{} - - _ = f.IsRequired() -} - -func TestUintSliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.VisibleFlag = &cli.UintSliceFlag{} - - _ = f.IsVisible() -} - func TestBoolFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.BoolFlag{} From a851a773a88fd2ea9014a30b2b2e9f5f600ef4de Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 3 Oct 2022 10:12:37 -0400 Subject: [PATCH 106/136] Point docs tests at v3 --- .github/workflows/cli.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 7a3ac9b479..800bbeb2dd 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -68,7 +68,7 @@ jobs: - run: make ensure-gfmrun - run: make gfmrun env: - FLAGS: --walk docs/v2/ + FLAGS: --walk docs/v3/ - run: make diffcheck publish: permissions: From 1a31a6e2fce716fd377c220c0a1e8f0de6324272 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sat, 8 Oct 2022 09:47:46 -0500 Subject: [PATCH 107/136] Revert "Remove all flag interfaces" This reverts commit 268cb973f8f7b1f38999f43122fb3e39b90b0522. --- app.go | 10 +- app_test.go | 4 - category.go | 18 ++-- cmd/urfave-cli-genflags/generated.gotmpl | 10 -- cmd/urfave-cli-genflags/generated_test.gotmpl | 4 +- command.go | 4 +- context.go | 2 +- docs.go | 8 +- fish.go | 9 +- flag.go | 72 ++++++++++---- flag_duration.go | 8 ++ flag_float64.go | 8 ++ flag_float64_slice.go | 8 ++ flag_generic.go | 8 ++ flag_int.go | 8 ++ flag_int64.go | 8 ++ flag_int64_slice.go | 8 ++ flag_int_slice.go | 8 ++ flag_string_slice.go | 8 ++ flag_test.go | 16 +++- flag_timestamp.go | 8 ++ flag_uint.go | 8 ++ flag_uint64.go | 8 ++ godoc-current.txt | 91 ++++++++++++------ sliceflag.go | 27 +++--- zz_generated.flags.go | 96 ------------------- zz_generated.flags_test.go | 60 ++++++------ 27 files changed, 306 insertions(+), 221 deletions(-) diff --git a/app.go b/app.go index 7be6f7694d..9f72f1b4f3 100644 --- a/app.go +++ b/app.go @@ -228,7 +228,9 @@ func (a *App) Setup() { a.flagCategories = newFlagCategories() for _, fl := range a.Flags { - a.flagCategories.AddFlag(fl.GetCategory(), fl) + if cf, ok := fl.(CategorizableFlag); ok { + a.flagCategories.AddFlag(cf.GetCategory(), cf) + } } if a.Metadata == nil { @@ -662,8 +664,10 @@ func runFlagActions(c *Context, fs []Flag) error { } } if isSet { - if err := f.RunAction(c); err != nil { - return err + if af, ok := f.(ActionableFlag); ok { + if err := af.RunAction(c); err != nil { + return err + } } } } diff --git a/app_test.go b/app_test.go index 1428c391dd..dbe7a463b4 100644 --- a/app_test.go +++ b/app_test.go @@ -2386,10 +2386,6 @@ func (c *customBoolFlag) GetEnvVars() []string { return nil } -func (c *customBoolFlag) GetDefaultText() string { - return "" -} - func TestCustomFlagsUnused(t *testing.T) { app := &App{ Flags: []Flag{&customBoolFlag{"custom"}}, diff --git a/category.go b/category.go index 188937d5bd..8bf325e203 100644 --- a/category.go +++ b/category.go @@ -101,7 +101,9 @@ func newFlagCategories() FlagCategories { func newFlagCategoriesFromFlags(fs []Flag) FlagCategories { fc := newFlagCategories() for _, fl := range fs { - fc.AddFlag(fl.GetCategory(), fl) + if cf, ok := fl.(CategorizableFlag); ok { + fc.AddFlag(cf.GetCategory(), cf) + } } return fc @@ -136,7 +138,7 @@ type VisibleFlagCategory interface { // Name returns the category name string Name() string // Flags returns a slice of VisibleFlag sorted by name - Flags() []Flag + Flags() []VisibleFlag } type defaultVisibleFlagCategory struct { @@ -148,19 +150,21 @@ func (fc *defaultVisibleFlagCategory) Name() string { return fc.name } -func (fc *defaultVisibleFlagCategory) Flags() []Flag { +func (fc *defaultVisibleFlagCategory) Flags() []VisibleFlag { vfNames := []string{} for flName, fl := range fc.m { - if fl.IsVisible() { - vfNames = append(vfNames, flName) + if vf, ok := fl.(VisibleFlag); ok { + if vf.IsVisible() { + vfNames = append(vfNames, flName) + } } } sort.Strings(vfNames) - ret := make([]Flag, len(vfNames)) + ret := make([]VisibleFlag, len(vfNames)) for i, flName := range vfNames { - ret[i] = fc.m[flName] + ret[i] = fc.m[flName].(VisibleFlag) } return ret diff --git a/cmd/urfave-cli-genflags/generated.gotmpl b/cmd/urfave-cli-genflags/generated.gotmpl index 85bd66fddf..bee11d52da 100644 --- a/cmd/urfave-cli-genflags/generated.gotmpl +++ b/cmd/urfave-cli-genflags/generated.gotmpl @@ -75,16 +75,6 @@ func (f *{{.TypeName}}) TakesValue() bool { return "{{.TypeName }}" != "BoolFlag" } -{{if .GenerateDefaultText}} -// GetDefaultText returns the default text for this flag -func (f *{{.TypeName}}) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} -{{end}} - {{end}}{{/* /if .GenerateFlagInterface */}} {{end}}{{/* /range .SortedFlagTypes */}} diff --git a/cmd/urfave-cli-genflags/generated_test.gotmpl b/cmd/urfave-cli-genflags/generated_test.gotmpl index c91f562ef2..83229b06ba 100644 --- a/cmd/urfave-cli-genflags/generated_test.gotmpl +++ b/cmd/urfave-cli-genflags/generated_test.gotmpl @@ -12,13 +12,13 @@ func Test{{.TypeName}}_SatisfiesFlagInterface(t *testing.T) { } func Test{{.TypeName}}_SatisfiesRequiredFlagInterface(t *testing.T) { - var f {{$.UrfaveCLITestNamespace}}Flag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} + var f {{$.UrfaveCLITestNamespace}}RequiredFlag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} _ = f.IsRequired() } func Test{{.TypeName}}_SatisfiesVisibleFlagInterface(t *testing.T) { - var f {{$.UrfaveCLITestNamespace}}Flag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} + var f {{$.UrfaveCLITestNamespace}}VisibleFlag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} _ = f.IsVisible() } diff --git a/command.go b/command.go index decfb7359c..de78faa02b 100644 --- a/command.go +++ b/command.go @@ -300,7 +300,9 @@ func (c *Command) VisibleFlagCategories() []VisibleFlagCategory { if c.flagCategories == nil { c.flagCategories = newFlagCategories() for _, fl := range c.Flags { - c.flagCategories.AddFlag(fl.GetCategory(), fl) + if cf, ok := fl.(CategorizableFlag); ok { + c.flagCategories.AddFlag(cf.GetCategory(), fl) + } } } return c.flagCategories.VisibleCategories() diff --git a/context.go b/context.go index 81416dac5f..315c2fb596 100644 --- a/context.go +++ b/context.go @@ -180,7 +180,7 @@ func (cCtx *Context) lookupFlagSet(name string) *flag.FlagSet { func (cCtx *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr { var missingFlags []string for _, f := range flags { - if f.IsRequired() { + if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() { var flagPresent bool var flagName string diff --git a/docs.go b/docs.go index 5a8be0ac60..8b1c9c8a2c 100644 --- a/docs.go +++ b/docs.go @@ -116,7 +116,11 @@ func prepareFlags( addDetails bool, ) []string { args := []string{} - for _, flag := range flags { + for _, f := range flags { + flag, ok := f.(DocGenerationFlag) + if !ok { + continue + } modifiedArg := opener for _, s := range flag.Names() { @@ -147,7 +151,7 @@ func prepareFlags( } // flagDetails returns a string containing the flags metadata -func flagDetails(flag Flag) string { +func flagDetails(flag DocGenerationFlag) string { description := flag.GetUsage() value := flag.GetValue() if value != "" { diff --git a/fish.go b/fish.go index 70946e1d9e..eec3253cda 100644 --- a/fish.go +++ b/fish.go @@ -114,7 +114,12 @@ func (a *App) prepareFishCommands(commands []*Command, allCommands *[]string, pr func (a *App) prepareFishFlags(flags []Flag, previousCommands []string) []string { completions := []string{} - for _, flag := range flags { + for _, f := range flags { + flag, ok := f.(DocGenerationFlag) + if !ok { + continue + } + completion := &strings.Builder{} completion.WriteString(fmt.Sprintf( "complete -c %s -n '%s'", @@ -122,7 +127,7 @@ func (a *App) prepareFishFlags(flags []Flag, previousCommands []string) []string a.fishSubcommandHelper(previousCommands), )) - fishAddFileFlag(flag, completion) + fishAddFileFlag(f, completion) for idx, opt := range flag.Names() { if idx == 0 { diff --git a/flag.go b/flag.go index aade1de2e8..68d882cde1 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. @@ -97,33 +103,36 @@ type Flag interface { // Whether the flag has been set or not IsSet() bool +} + +// RequiredFlag is an interface that allows us to mark flags as required +// it allows flags required flags to be backwards compatible with the Flag interface +type RequiredFlag interface { + Flag // whether the flag is a required flag or not IsRequired() bool +} - // IsVisible returns true if the flag is not hidden, otherwise false - IsVisible() bool - - // Returns the category of the flag - GetCategory() string - - // GetUsage returns the usage string for the flag - GetUsage() string - - // GetEnvVars returns the env vars for this flag - GetEnvVars() []string +// DocGenerationFlag is an interface that allows documentation generation for the flag +type DocGenerationFlag interface { + Flag // TakesValue returns true if the flag takes a value, otherwise false TakesValue() bool - // GetDefaultText returns the default text for this flag - GetDefaultText() string + // GetUsage returns the usage string for the flag + GetUsage() string // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. GetValue() string - RunAction(*Context) error + // GetDefaultText returns the default text for this flag + GetDefaultText() string + + // GetEnvVars returns the env vars for this flag + GetEnvVars() []string } // Countable is an interface to enable detection of flag values which support @@ -132,6 +141,23 @@ type Countable interface { Count() int } +// VisibleFlag is an interface that allows to check if a flag is visible +type VisibleFlag interface { + Flag + + // IsVisible returns true if the flag is not hidden, otherwise false + IsVisible() bool +} + +// CategorizableFlag is an interface that allows us to potentially +// use a flag in a categorized representation. +type CategorizableFlag interface { + VisibleFlag + + // Returns the category of the flag + GetCategory() string +} + func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { set := flag.NewFlagSet(name, flag.ContinueOnError) @@ -189,7 +215,7 @@ func normalizeFlags(flags []Flag, set *flag.FlagSet) error { func visibleFlags(fl []Flag) []Flag { var visible []Flag for _, f := range fl { - if f.IsVisible() { + if vf, ok := f.(VisibleFlag); ok && vf.IsVisible() { visible = append(visible, f) } } @@ -285,8 +311,14 @@ func formatDefault(format string) string { } func stringifyFlag(f Flag) string { - placeholder, usage := unquoteUsage(f.GetUsage()) - needsPlaceholder := f.TakesValue() + // enforce DocGeneration interface on flags to avoid reflection + df, ok := f.(DocGenerationFlag) + if !ok { + return "" + } + + placeholder, usage := unquoteUsage(df.GetUsage()) + needsPlaceholder := df.TakesValue() if needsPlaceholder && placeholder == "" { placeholder = defaultPlaceholder @@ -294,14 +326,14 @@ func stringifyFlag(f Flag) string { defaultValueString := "" - if s := f.GetDefaultText(); s != "" { + if s := df.GetDefaultText(); s != "" { defaultValueString = fmt.Sprintf(formatDefault("%s"), s) } usageWithDefault := strings.TrimSpace(usage + defaultValueString) - return withEnvHint(f.GetEnvVars(), - fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault)) + return withEnvHint(df.GetEnvVars(), + fmt.Sprintf("%s\t%s", prefixedNames(df.Names(), placeholder), usageWithDefault)) } func stringifySliceFlag(usage string, names, defaultVals []string) string { diff --git a/flag_duration.go b/flag_duration.go index b6adce3f48..06391f38e9 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -12,6 +12,14 @@ func (f *DurationFlag) GetValue() string { return f.Value.String() } +// GetDefaultText returns the default text for this flag +func (f *DurationFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Apply populates the flag given the flag set and environment func (f *DurationFlag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_float64.go b/flag_float64.go index 3e21a01df7..d522ac0436 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -12,6 +12,14 @@ func (f *Float64Flag) GetValue() string { return fmt.Sprintf("%v", f.Value) } +// GetDefaultText returns the default text for this flag +func (f *Float64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Apply populates the flag given the flag set and environment func (f *Float64Flag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 5bb2693114..203aa42442 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -95,6 +95,14 @@ func (f *Float64SliceFlag) GetValue() string { return "" } +// GetDefaultText returns the default text for this flag +func (f *Float64SliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Apply populates the flag given the flag set and environment func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { // apply any default diff --git a/flag_generic.go b/flag_generic.go index 9f0633808e..183a5882bb 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -20,6 +20,14 @@ func (f *GenericFlag) GetValue() string { return "" } +// GetDefaultText returns the default text for this flag +func (f *GenericFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Apply takes the flagset and calls Set on the generic flag with the value // provided by the user for parsing by the flag func (f *GenericFlag) Apply(set *flag.FlagSet) error { diff --git a/flag_int.go b/flag_int.go index bcaab7fa7e..a716f3914f 100644 --- a/flag_int.go +++ b/flag_int.go @@ -12,6 +12,14 @@ func (f *IntFlag) GetValue() string { return fmt.Sprintf("%d", f.Value) } +// GetDefaultText returns the default text for this flag +func (f *IntFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Apply populates the flag given the flag set and environment func (f *IntFlag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_int64.go b/flag_int64.go index d8e591d864..05d8506bed 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -12,6 +12,14 @@ func (f *Int64Flag) GetValue() string { return fmt.Sprintf("%d", f.Value) } +// GetDefaultText returns the default text for this flag +func (f *Int64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Apply populates the flag given the flag set and environment func (f *Int64Flag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 89fd44550e..e3575cdc78 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -96,6 +96,14 @@ func (f *Int64SliceFlag) GetValue() string { return "" } +// GetDefaultText returns the default text for this flag +func (f *Int64SliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Apply populates the flag given the flag set and environment func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { // apply any default diff --git a/flag_int_slice.go b/flag_int_slice.go index f7a6b06f0c..5c1b6b613f 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -107,6 +107,14 @@ func (f *IntSliceFlag) GetValue() string { return "" } +// GetDefaultText returns the default text for this flag +func (f *IntSliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Apply populates the flag given the flag set and environment func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { // apply any default diff --git a/flag_string_slice.go b/flag_string_slice.go index 2a24ec56e5..086b18d192 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -86,6 +86,14 @@ func (f *StringSliceFlag) GetValue() string { return "" } +// GetDefaultText returns the default text for this flag +func (f *StringSliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Apply populates the flag given the flag set and environment func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { // apply any default diff --git a/flag_test.go b/flag_test.go index e7fa309fc6..ddd3e67e49 100644 --- a/flag_test.go +++ b/flag_test.go @@ -223,7 +223,10 @@ func TestFlagsFromEnv(t *testing.T) { defer resetEnv(os.Environ()) os.Clearenv() - f := test.flag + f, ok := test.flag.(DocGenerationFlag) + if !ok { + t.Errorf("flag %v needs to implement DocGenerationFlag to retrieve env vars", test.flag) + } envVarSlice := f.GetEnvVars() _ = os.Setenv(envVarSlice[0], test.input) @@ -259,6 +262,12 @@ func TestFlagsFromEnv(t *testing.T) { } } +type nodocFlag struct { + Flag + + Name string +} + func TestFlagStringifying(t *testing.T) { for _, tc := range []struct { name string @@ -435,6 +444,11 @@ func TestFlagStringifying(t *testing.T) { fl: &UintFlag{Name: "tubes", DefaultText: "13"}, expected: "--tubes value\t(default: 13)", }, + { + name: "nodoc-flag", + fl: &nodocFlag{Name: "scarecrow"}, + expected: "", + }, } { t.Run(tc.name, func(ct *testing.T) { s := stringifyFlag(tc.fl) diff --git a/flag_timestamp.go b/flag_timestamp.go index 671be8b986..4302f130ed 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -81,6 +81,14 @@ func (f *TimestampFlag) GetValue() string { return "" } +// GetDefaultText returns the default text for this flag +func (f *TimestampFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Apply populates the flag given the flag set and environment func (f *TimestampFlag) Apply(set *flag.FlagSet) error { if f.Layout == "" { diff --git a/flag_uint.go b/flag_uint.go index e1b04508c8..2214386849 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -37,6 +37,14 @@ func (f *UintFlag) GetValue() string { return fmt.Sprintf("%d", f.Value) } +// GetDefaultText returns the default text for this flag +func (f *UintFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Get returns the flag’s value in the given Context. func (f *UintFlag) Get(ctx *Context) uint { return ctx.Uint(f.Name) diff --git a/flag_uint64.go b/flag_uint64.go index 593b2606ae..e314a4a307 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -37,6 +37,14 @@ func (f *Uint64Flag) GetValue() string { return fmt.Sprintf("%d", f.Value) } +// GetDefaultText returns the default text for this flag +func (f *Uint64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Get returns the flag’s value in the given Context. func (f *Uint64Flag) Get(ctx *Context) uint64 { return ctx.Uint64(f.Name) diff --git a/godoc-current.txt b/godoc-current.txt index 2c8b5e040c..03b76f8c79 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -247,6 +247,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 @@ -499,6 +506,15 @@ func (f *BoolFlag) String() string func (f *BoolFlag) TakesValue() bool TakesValue returns true if the flag takes a value, otherwise false +type CategorizableFlag interface { + VisibleFlag + + // Returns the category of the flag + GetCategory() string +} + CategorizableFlag is an interface that allows us to potentially use a flag + in a categorized representation. + type Command struct { // The name of the command Name string @@ -716,6 +732,28 @@ type Countable interface { Countable is an interface to enable detection of flag values which support repetitive flags +type DocGenerationFlag interface { + Flag + + // TakesValue returns true if the flag takes a value, otherwise false + TakesValue() bool + + // GetUsage returns the usage string for the flag + GetUsage() string + + // GetValue returns the flags value as string representation and an empty + // string if the flag takes no value at all. + GetValue() string + + // GetDefaultText returns the default text for this flag + GetDefaultText() string + + // GetEnvVars returns the env vars for this flag + GetEnvVars() []string +} + DocGenerationFlag is an interface that allows documentation generation for + the flag + type DurationFlag struct { Name string @@ -823,33 +861,6 @@ type Flag interface { // Whether the flag has been set or not IsSet() bool - - // whether the flag is a required flag or not - IsRequired() bool - - // IsVisible returns true if the flag is not hidden, otherwise false - IsVisible() bool - - // Returns the category of the flag - GetCategory() string - - // GetUsage returns the usage string for the flag - GetUsage() string - - // GetEnvVars returns the env vars for this flag - GetEnvVars() []string - - // TakesValue returns true if the flag takes a value, otherwise false - TakesValue() bool - - // GetDefaultText returns the default text for this flag - GetDefaultText() string - - // GetValue returns the flags value as string representation and an empty - // string if the flag takes no value at all. - GetValue() string - - 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 @@ -1586,6 +1597,16 @@ func (f *PathFlag) String() string func (f *PathFlag) TakesValue() bool TakesValue returns true if the flag takes a value, otherwise false +type RequiredFlag interface { + Flag + + // whether the flag is a required flag or not + IsRequired() bool +} + RequiredFlag is an interface that allows us to mark flags as required + it allows flags required flags to be backwards compatible with the Flag + interface + type Serializer interface { Serialize() string } @@ -1623,8 +1644,6 @@ func (x *SliceFlag[T, S, E]) IsVisible() bool func (x *SliceFlag[T, S, E]) Names() []string -func (x *SliceFlag[T, S, E]) RunAction(c *Context) error - func (x *SliceFlag[T, S, E]) SetDestination(slice S) func (x *SliceFlag[T, S, E]) SetValue(slice S) @@ -1635,6 +1654,10 @@ func (x *SliceFlag[T, S, E]) TakesValue() bool type SliceFlagTarget[E any] interface { Flag + RequiredFlag + DocGenerationFlag + VisibleFlag + CategorizableFlag // SetValue should propagate the given slice to the target, ideally as a new value. // Note that a nil slice should nil/clear any existing value (modelled as ~[]E). @@ -2229,11 +2252,19 @@ func (f *UintSliceFlag) String() string func (f *UintSliceFlag) TakesValue() bool TakesValue returns true of the flag takes a value, otherwise false +type VisibleFlag interface { + Flag + + // IsVisible returns true if the flag is not hidden, otherwise false + IsVisible() bool +} + VisibleFlag is an interface that allows to check if a flag is visible + type VisibleFlagCategory interface { // Name returns the category name string Name() string // Flags returns a slice of VisibleFlag sorted by name - Flags() []Flag + Flags() []VisibleFlag } VisibleFlagCategory is a category containing flags. diff --git a/sliceflag.go b/sliceflag.go index c8670cb560..b2ca59041f 100644 --- a/sliceflag.go +++ b/sliceflag.go @@ -21,6 +21,10 @@ type ( // update). SliceFlagTarget[E any] interface { Flag + RequiredFlag + DocGenerationFlag + VisibleFlag + CategorizableFlag // SetValue should propagate the given slice to the target, ideally as a new value. // Note that a nil slice should nil/clear any existing value (modelled as ~[]E). @@ -110,18 +114,17 @@ func (x *SliceFlag[T, S, E]) GetDestination() S { return nil } -func (x *SliceFlag[T, S, E]) String() string { return x.Target.String() } -func (x *SliceFlag[T, S, E]) Names() []string { return x.Target.Names() } -func (x *SliceFlag[T, S, E]) IsSet() bool { return x.Target.IsSet() } -func (x *SliceFlag[T, S, E]) IsRequired() bool { return x.Target.IsRequired() } -func (x *SliceFlag[T, S, E]) TakesValue() bool { return x.Target.TakesValue() } -func (x *SliceFlag[T, S, E]) GetUsage() string { return x.Target.GetUsage() } -func (x *SliceFlag[T, S, E]) GetValue() string { return x.Target.GetValue() } -func (x *SliceFlag[T, S, E]) GetDefaultText() string { return x.Target.GetDefaultText() } -func (x *SliceFlag[T, S, E]) GetEnvVars() []string { return x.Target.GetEnvVars() } -func (x *SliceFlag[T, S, E]) IsVisible() bool { return x.Target.IsVisible() } -func (x *SliceFlag[T, S, E]) GetCategory() string { return x.Target.GetCategory() } -func (x *SliceFlag[T, S, E]) RunAction(c *Context) error { return x.Target.RunAction(c) } +func (x *SliceFlag[T, S, E]) String() string { return x.Target.String() } +func (x *SliceFlag[T, S, E]) Names() []string { return x.Target.Names() } +func (x *SliceFlag[T, S, E]) IsSet() bool { return x.Target.IsSet() } +func (x *SliceFlag[T, S, E]) IsRequired() bool { return x.Target.IsRequired() } +func (x *SliceFlag[T, S, E]) TakesValue() bool { return x.Target.TakesValue() } +func (x *SliceFlag[T, S, E]) GetUsage() string { return x.Target.GetUsage() } +func (x *SliceFlag[T, S, E]) GetValue() string { return x.Target.GetValue() } +func (x *SliceFlag[T, S, E]) GetDefaultText() string { return x.Target.GetDefaultText() } +func (x *SliceFlag[T, S, E]) GetEnvVars() []string { return x.Target.GetEnvVars() } +func (x *SliceFlag[T, S, E]) IsVisible() bool { return x.Target.IsVisible() } +func (x *SliceFlag[T, S, E]) GetCategory() string { return x.Target.GetCategory() } func (x *flagValueHook) Set(value string) error { if err := x.value.Set(value); err != nil { diff --git a/zz_generated.flags.go b/zz_generated.flags.go index 68934c7707..d2e1cdeb2b 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -66,14 +66,6 @@ func (f *Float64SliceFlag) TakesValue() bool { return "Float64SliceFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *Float64SliceFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // GenericFlag is a flag with type Generic type GenericFlag struct { Name string @@ -143,14 +135,6 @@ func (f *GenericFlag) TakesValue() bool { return "GenericFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *GenericFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Int64SliceFlag is a flag with type *Int64Slice type Int64SliceFlag struct { Name string @@ -213,14 +197,6 @@ func (f *Int64SliceFlag) TakesValue() bool { return "Int64SliceFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *Int64SliceFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // IntSliceFlag is a flag with type *IntSlice type IntSliceFlag struct { Name string @@ -283,14 +259,6 @@ func (f *IntSliceFlag) TakesValue() bool { return "IntSliceFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *IntSliceFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // PathFlag is a flag with type Path type PathFlag struct { Name string @@ -424,14 +392,6 @@ func (f *StringSliceFlag) TakesValue() bool { return "StringSliceFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *StringSliceFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // TimestampFlag is a flag with type *Timestamp type TimestampFlag struct { Name string @@ -503,14 +463,6 @@ func (f *TimestampFlag) TakesValue() bool { return "TimestampFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *TimestampFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Uint64SliceFlag is a flag with type *Uint64Slice type Uint64SliceFlag struct { Name string @@ -731,14 +683,6 @@ func (f *Float64Flag) TakesValue() bool { return "Float64Flag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *Float64Flag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // IntFlag is a flag with type int type IntFlag struct { Name string @@ -808,14 +752,6 @@ func (f *IntFlag) TakesValue() bool { return "IntFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *IntFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Int64Flag is a flag with type int64 type Int64Flag struct { Name string @@ -885,14 +821,6 @@ func (f *Int64Flag) TakesValue() bool { return "Int64Flag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *Int64Flag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // StringFlag is a flag with type string type StringFlag struct { Name string @@ -1029,14 +957,6 @@ func (f *DurationFlag) TakesValue() bool { return "DurationFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *DurationFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // UintFlag is a flag with type uint type UintFlag struct { Name string @@ -1106,14 +1026,6 @@ func (f *UintFlag) TakesValue() bool { return "UintFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *UintFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Uint64Flag is a flag with type uint64 type Uint64Flag struct { Name string @@ -1183,12 +1095,4 @@ func (f *Uint64Flag) TakesValue() bool { return "Uint64Flag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *Uint64Flag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // vim:ro diff --git a/zz_generated.flags_test.go b/zz_generated.flags_test.go index be6dadf84b..6862afc36e 100644 --- a/zz_generated.flags_test.go +++ b/zz_generated.flags_test.go @@ -17,13 +17,13 @@ func TestFloat64SliceFlag_SatisfiesFlagInterface(t *testing.T) { } func TestFloat64SliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.Flag = &cli.Float64SliceFlag{} + var f cli.RequiredFlag = &cli.Float64SliceFlag{} _ = f.IsRequired() } func TestFloat64SliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.Flag = &cli.Float64SliceFlag{} + var f cli.VisibleFlag = &cli.Float64SliceFlag{} _ = f.IsVisible() } @@ -36,13 +36,13 @@ func TestGenericFlag_SatisfiesFlagInterface(t *testing.T) { } func TestGenericFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.Flag = &cli.GenericFlag{} + var f cli.RequiredFlag = &cli.GenericFlag{} _ = f.IsRequired() } func TestGenericFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.Flag = &cli.GenericFlag{} + var f cli.VisibleFlag = &cli.GenericFlag{} _ = f.IsVisible() } @@ -61,13 +61,13 @@ func TestInt64SliceFlag_SatisfiesFlagInterface(t *testing.T) { } func TestInt64SliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.Flag = &cli.Int64SliceFlag{} + var f cli.RequiredFlag = &cli.Int64SliceFlag{} _ = f.IsRequired() } func TestInt64SliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.Flag = &cli.Int64SliceFlag{} + var f cli.VisibleFlag = &cli.Int64SliceFlag{} _ = f.IsVisible() } @@ -80,13 +80,13 @@ func TestIntSliceFlag_SatisfiesFlagInterface(t *testing.T) { } func TestIntSliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.Flag = &cli.IntSliceFlag{} + var f cli.RequiredFlag = &cli.IntSliceFlag{} _ = f.IsRequired() } func TestIntSliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.Flag = &cli.IntSliceFlag{} + var f cli.VisibleFlag = &cli.IntSliceFlag{} _ = f.IsVisible() } @@ -99,13 +99,13 @@ func TestPathFlag_SatisfiesFlagInterface(t *testing.T) { } func TestPathFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.Flag = &cli.PathFlag{} + var f cli.RequiredFlag = &cli.PathFlag{} _ = f.IsRequired() } func TestPathFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.Flag = &cli.PathFlag{} + var f cli.VisibleFlag = &cli.PathFlag{} _ = f.IsVisible() } @@ -124,13 +124,13 @@ func TestStringSliceFlag_SatisfiesFlagInterface(t *testing.T) { } func TestStringSliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.Flag = &cli.StringSliceFlag{} + var f cli.RequiredFlag = &cli.StringSliceFlag{} _ = f.IsRequired() } func TestStringSliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.Flag = &cli.StringSliceFlag{} + var f cli.VisibleFlag = &cli.StringSliceFlag{} _ = f.IsVisible() } @@ -143,13 +143,13 @@ func TestTimestampFlag_SatisfiesFlagInterface(t *testing.T) { } func TestTimestampFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.Flag = &cli.TimestampFlag{} + var f cli.RequiredFlag = &cli.TimestampFlag{} _ = f.IsRequired() } func TestTimestampFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.Flag = &cli.TimestampFlag{} + var f cli.VisibleFlag = &cli.TimestampFlag{} _ = f.IsVisible() } @@ -182,13 +182,13 @@ func TestBoolFlag_SatisfiesFlagInterface(t *testing.T) { } func TestBoolFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.Flag = &cli.BoolFlag{} + var f cli.RequiredFlag = &cli.BoolFlag{} _ = f.IsRequired() } func TestBoolFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.Flag = &cli.BoolFlag{} + var f cli.VisibleFlag = &cli.BoolFlag{} _ = f.IsVisible() } @@ -207,13 +207,13 @@ func TestFloat64Flag_SatisfiesFlagInterface(t *testing.T) { } func TestFloat64Flag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.Flag = &cli.Float64Flag{} + var f cli.RequiredFlag = &cli.Float64Flag{} _ = f.IsRequired() } func TestFloat64Flag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.Flag = &cli.Float64Flag{} + var f cli.VisibleFlag = &cli.Float64Flag{} _ = f.IsVisible() } @@ -232,13 +232,13 @@ func TestIntFlag_SatisfiesFlagInterface(t *testing.T) { } func TestIntFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.Flag = &cli.IntFlag{} + var f cli.RequiredFlag = &cli.IntFlag{} _ = f.IsRequired() } func TestIntFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.Flag = &cli.IntFlag{} + var f cli.VisibleFlag = &cli.IntFlag{} _ = f.IsVisible() } @@ -257,13 +257,13 @@ func TestInt64Flag_SatisfiesFlagInterface(t *testing.T) { } func TestInt64Flag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.Flag = &cli.Int64Flag{} + var f cli.RequiredFlag = &cli.Int64Flag{} _ = f.IsRequired() } func TestInt64Flag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.Flag = &cli.Int64Flag{} + var f cli.VisibleFlag = &cli.Int64Flag{} _ = f.IsVisible() } @@ -282,13 +282,13 @@ func TestStringFlag_SatisfiesFlagInterface(t *testing.T) { } func TestStringFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.Flag = &cli.StringFlag{} + var f cli.RequiredFlag = &cli.StringFlag{} _ = f.IsRequired() } func TestStringFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.Flag = &cli.StringFlag{} + var f cli.VisibleFlag = &cli.StringFlag{} _ = f.IsVisible() } @@ -307,13 +307,13 @@ func TestDurationFlag_SatisfiesFlagInterface(t *testing.T) { } func TestDurationFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.Flag = &cli.DurationFlag{} + var f cli.RequiredFlag = &cli.DurationFlag{} _ = f.IsRequired() } func TestDurationFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.Flag = &cli.DurationFlag{} + var f cli.VisibleFlag = &cli.DurationFlag{} _ = f.IsVisible() } @@ -332,13 +332,13 @@ func TestUintFlag_SatisfiesFlagInterface(t *testing.T) { } func TestUintFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.Flag = &cli.UintFlag{} + var f cli.RequiredFlag = &cli.UintFlag{} _ = f.IsRequired() } func TestUintFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.Flag = &cli.UintFlag{} + var f cli.VisibleFlag = &cli.UintFlag{} _ = f.IsVisible() } @@ -357,13 +357,13 @@ func TestUint64Flag_SatisfiesFlagInterface(t *testing.T) { } func TestUint64Flag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.Flag = &cli.Uint64Flag{} + var f cli.RequiredFlag = &cli.Uint64Flag{} _ = f.IsRequired() } func TestUint64Flag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.Flag = &cli.Uint64Flag{} + var f cli.VisibleFlag = &cli.Uint64Flag{} _ = f.IsVisible() } From cf41aab4513242a13a34c52ef3265af9b2d29372 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sat, 8 Oct 2022 09:57:52 -0500 Subject: [PATCH 108/136] Revert changes --- app_test.go | 4 + cmd/urfave-cli-genflags/generated.gotmpl | 10 ++ cmd/urfave-cli-genflags/generated_test.gotmpl | 4 +- flag_duration.go | 8 -- flag_float64.go | 8 -- flag_float64_slice.go | 8 -- flag_generic.go | 8 -- flag_int.go | 8 -- flag_int64.go | 8 -- flag_int64_slice.go | 8 -- flag_int_slice.go | 8 -- flag_string_slice.go | 8 -- flag_timestamp.go | 8 -- flag_uint.go | 8 -- flag_uint64.go | 8 -- zz_generated.flags.go | 96 +++++++++++++++++++ 16 files changed, 112 insertions(+), 98 deletions(-) diff --git a/app_test.go b/app_test.go index dbe7a463b4..1428c391dd 100644 --- a/app_test.go +++ b/app_test.go @@ -2386,6 +2386,10 @@ func (c *customBoolFlag) GetEnvVars() []string { return nil } +func (c *customBoolFlag) GetDefaultText() string { + return "" +} + func TestCustomFlagsUnused(t *testing.T) { app := &App{ Flags: []Flag{&customBoolFlag{"custom"}}, diff --git a/cmd/urfave-cli-genflags/generated.gotmpl b/cmd/urfave-cli-genflags/generated.gotmpl index bee11d52da..85bd66fddf 100644 --- a/cmd/urfave-cli-genflags/generated.gotmpl +++ b/cmd/urfave-cli-genflags/generated.gotmpl @@ -75,6 +75,16 @@ func (f *{{.TypeName}}) TakesValue() bool { return "{{.TypeName }}" != "BoolFlag" } +{{if .GenerateDefaultText}} +// GetDefaultText returns the default text for this flag +func (f *{{.TypeName}}) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} +{{end}} + {{end}}{{/* /if .GenerateFlagInterface */}} {{end}}{{/* /range .SortedFlagTypes */}} diff --git a/cmd/urfave-cli-genflags/generated_test.gotmpl b/cmd/urfave-cli-genflags/generated_test.gotmpl index 83229b06ba..c91f562ef2 100644 --- a/cmd/urfave-cli-genflags/generated_test.gotmpl +++ b/cmd/urfave-cli-genflags/generated_test.gotmpl @@ -12,13 +12,13 @@ func Test{{.TypeName}}_SatisfiesFlagInterface(t *testing.T) { } func Test{{.TypeName}}_SatisfiesRequiredFlagInterface(t *testing.T) { - var f {{$.UrfaveCLITestNamespace}}RequiredFlag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} + var f {{$.UrfaveCLITestNamespace}}Flag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} _ = f.IsRequired() } func Test{{.TypeName}}_SatisfiesVisibleFlagInterface(t *testing.T) { - var f {{$.UrfaveCLITestNamespace}}VisibleFlag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} + var f {{$.UrfaveCLITestNamespace}}Flag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} _ = f.IsVisible() } diff --git a/flag_duration.go b/flag_duration.go index 06391f38e9..b6adce3f48 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -12,14 +12,6 @@ func (f *DurationFlag) GetValue() string { return f.Value.String() } -// GetDefaultText returns the default text for this flag -func (f *DurationFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Apply populates the flag given the flag set and environment func (f *DurationFlag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_float64.go b/flag_float64.go index d522ac0436..3e21a01df7 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -12,14 +12,6 @@ func (f *Float64Flag) GetValue() string { return fmt.Sprintf("%v", f.Value) } -// GetDefaultText returns the default text for this flag -func (f *Float64Flag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Apply populates the flag given the flag set and environment func (f *Float64Flag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 203aa42442..5bb2693114 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -95,14 +95,6 @@ func (f *Float64SliceFlag) GetValue() string { return "" } -// GetDefaultText returns the default text for this flag -func (f *Float64SliceFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Apply populates the flag given the flag set and environment func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { // apply any default diff --git a/flag_generic.go b/flag_generic.go index 183a5882bb..9f0633808e 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -20,14 +20,6 @@ func (f *GenericFlag) GetValue() string { return "" } -// GetDefaultText returns the default text for this flag -func (f *GenericFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Apply takes the flagset and calls Set on the generic flag with the value // provided by the user for parsing by the flag func (f *GenericFlag) Apply(set *flag.FlagSet) error { diff --git a/flag_int.go b/flag_int.go index a716f3914f..bcaab7fa7e 100644 --- a/flag_int.go +++ b/flag_int.go @@ -12,14 +12,6 @@ func (f *IntFlag) GetValue() string { return fmt.Sprintf("%d", f.Value) } -// GetDefaultText returns the default text for this flag -func (f *IntFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Apply populates the flag given the flag set and environment func (f *IntFlag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_int64.go b/flag_int64.go index 05d8506bed..d8e591d864 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -12,14 +12,6 @@ func (f *Int64Flag) GetValue() string { return fmt.Sprintf("%d", f.Value) } -// GetDefaultText returns the default text for this flag -func (f *Int64Flag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Apply populates the flag given the flag set and environment func (f *Int64Flag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_int64_slice.go b/flag_int64_slice.go index e3575cdc78..89fd44550e 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -96,14 +96,6 @@ func (f *Int64SliceFlag) GetValue() string { return "" } -// GetDefaultText returns the default text for this flag -func (f *Int64SliceFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Apply populates the flag given the flag set and environment func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { // apply any default diff --git a/flag_int_slice.go b/flag_int_slice.go index 5c1b6b613f..f7a6b06f0c 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -107,14 +107,6 @@ func (f *IntSliceFlag) GetValue() string { return "" } -// GetDefaultText returns the default text for this flag -func (f *IntSliceFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Apply populates the flag given the flag set and environment func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { // apply any default diff --git a/flag_string_slice.go b/flag_string_slice.go index 086b18d192..2a24ec56e5 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -86,14 +86,6 @@ func (f *StringSliceFlag) GetValue() string { return "" } -// GetDefaultText returns the default text for this flag -func (f *StringSliceFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Apply populates the flag given the flag set and environment func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { // apply any default diff --git a/flag_timestamp.go b/flag_timestamp.go index 4302f130ed..671be8b986 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -81,14 +81,6 @@ func (f *TimestampFlag) GetValue() string { return "" } -// GetDefaultText returns the default text for this flag -func (f *TimestampFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Apply populates the flag given the flag set and environment func (f *TimestampFlag) Apply(set *flag.FlagSet) error { if f.Layout == "" { diff --git a/flag_uint.go b/flag_uint.go index 2214386849..e1b04508c8 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -37,14 +37,6 @@ func (f *UintFlag) GetValue() string { return fmt.Sprintf("%d", f.Value) } -// GetDefaultText returns the default text for this flag -func (f *UintFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Get returns the flag’s value in the given Context. func (f *UintFlag) Get(ctx *Context) uint { return ctx.Uint(f.Name) diff --git a/flag_uint64.go b/flag_uint64.go index e314a4a307..593b2606ae 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -37,14 +37,6 @@ func (f *Uint64Flag) GetValue() string { return fmt.Sprintf("%d", f.Value) } -// GetDefaultText returns the default text for this flag -func (f *Uint64Flag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Get returns the flag’s value in the given Context. func (f *Uint64Flag) Get(ctx *Context) uint64 { return ctx.Uint64(f.Name) diff --git a/zz_generated.flags.go b/zz_generated.flags.go index d2e1cdeb2b..68934c7707 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -66,6 +66,14 @@ func (f *Float64SliceFlag) TakesValue() bool { return "Float64SliceFlag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *Float64SliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // GenericFlag is a flag with type Generic type GenericFlag struct { Name string @@ -135,6 +143,14 @@ func (f *GenericFlag) TakesValue() bool { return "GenericFlag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *GenericFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Int64SliceFlag is a flag with type *Int64Slice type Int64SliceFlag struct { Name string @@ -197,6 +213,14 @@ func (f *Int64SliceFlag) TakesValue() bool { return "Int64SliceFlag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *Int64SliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // IntSliceFlag is a flag with type *IntSlice type IntSliceFlag struct { Name string @@ -259,6 +283,14 @@ func (f *IntSliceFlag) TakesValue() bool { return "IntSliceFlag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *IntSliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // PathFlag is a flag with type Path type PathFlag struct { Name string @@ -392,6 +424,14 @@ func (f *StringSliceFlag) TakesValue() bool { return "StringSliceFlag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *StringSliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // TimestampFlag is a flag with type *Timestamp type TimestampFlag struct { Name string @@ -463,6 +503,14 @@ func (f *TimestampFlag) TakesValue() bool { return "TimestampFlag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *TimestampFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Uint64SliceFlag is a flag with type *Uint64Slice type Uint64SliceFlag struct { Name string @@ -683,6 +731,14 @@ func (f *Float64Flag) TakesValue() bool { return "Float64Flag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *Float64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // IntFlag is a flag with type int type IntFlag struct { Name string @@ -752,6 +808,14 @@ func (f *IntFlag) TakesValue() bool { return "IntFlag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *IntFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Int64Flag is a flag with type int64 type Int64Flag struct { Name string @@ -821,6 +885,14 @@ func (f *Int64Flag) TakesValue() bool { return "Int64Flag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *Int64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // StringFlag is a flag with type string type StringFlag struct { Name string @@ -957,6 +1029,14 @@ func (f *DurationFlag) TakesValue() bool { return "DurationFlag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *DurationFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // UintFlag is a flag with type uint type UintFlag struct { Name string @@ -1026,6 +1106,14 @@ func (f *UintFlag) TakesValue() bool { return "UintFlag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *UintFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Uint64Flag is a flag with type uint64 type Uint64Flag struct { Name string @@ -1095,4 +1183,12 @@ func (f *Uint64Flag) TakesValue() bool { return "Uint64Flag" != "BoolFlag" } +// GetDefaultText returns the default text for this flag +func (f *Uint64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // vim:ro From 662deaa57b58f8ca709586a4bf3b145f0961a424 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sat, 8 Oct 2022 10:09:54 -0500 Subject: [PATCH 109/136] Run v2approve --- testdata/godoc-v2.x.txt | 91 +++++++++++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 30 deletions(-) diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt index 2c8b5e040c..03b76f8c79 100644 --- a/testdata/godoc-v2.x.txt +++ b/testdata/godoc-v2.x.txt @@ -247,6 +247,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 @@ -499,6 +506,15 @@ func (f *BoolFlag) String() string func (f *BoolFlag) TakesValue() bool TakesValue returns true if the flag takes a value, otherwise false +type CategorizableFlag interface { + VisibleFlag + + // Returns the category of the flag + GetCategory() string +} + CategorizableFlag is an interface that allows us to potentially use a flag + in a categorized representation. + type Command struct { // The name of the command Name string @@ -716,6 +732,28 @@ type Countable interface { Countable is an interface to enable detection of flag values which support repetitive flags +type DocGenerationFlag interface { + Flag + + // TakesValue returns true if the flag takes a value, otherwise false + TakesValue() bool + + // GetUsage returns the usage string for the flag + GetUsage() string + + // GetValue returns the flags value as string representation and an empty + // string if the flag takes no value at all. + GetValue() string + + // GetDefaultText returns the default text for this flag + GetDefaultText() string + + // GetEnvVars returns the env vars for this flag + GetEnvVars() []string +} + DocGenerationFlag is an interface that allows documentation generation for + the flag + type DurationFlag struct { Name string @@ -823,33 +861,6 @@ type Flag interface { // Whether the flag has been set or not IsSet() bool - - // whether the flag is a required flag or not - IsRequired() bool - - // IsVisible returns true if the flag is not hidden, otherwise false - IsVisible() bool - - // Returns the category of the flag - GetCategory() string - - // GetUsage returns the usage string for the flag - GetUsage() string - - // GetEnvVars returns the env vars for this flag - GetEnvVars() []string - - // TakesValue returns true if the flag takes a value, otherwise false - TakesValue() bool - - // GetDefaultText returns the default text for this flag - GetDefaultText() string - - // GetValue returns the flags value as string representation and an empty - // string if the flag takes no value at all. - GetValue() string - - 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 @@ -1586,6 +1597,16 @@ func (f *PathFlag) String() string func (f *PathFlag) TakesValue() bool TakesValue returns true if the flag takes a value, otherwise false +type RequiredFlag interface { + Flag + + // whether the flag is a required flag or not + IsRequired() bool +} + RequiredFlag is an interface that allows us to mark flags as required + it allows flags required flags to be backwards compatible with the Flag + interface + type Serializer interface { Serialize() string } @@ -1623,8 +1644,6 @@ func (x *SliceFlag[T, S, E]) IsVisible() bool func (x *SliceFlag[T, S, E]) Names() []string -func (x *SliceFlag[T, S, E]) RunAction(c *Context) error - func (x *SliceFlag[T, S, E]) SetDestination(slice S) func (x *SliceFlag[T, S, E]) SetValue(slice S) @@ -1635,6 +1654,10 @@ func (x *SliceFlag[T, S, E]) TakesValue() bool type SliceFlagTarget[E any] interface { Flag + RequiredFlag + DocGenerationFlag + VisibleFlag + CategorizableFlag // SetValue should propagate the given slice to the target, ideally as a new value. // Note that a nil slice should nil/clear any existing value (modelled as ~[]E). @@ -2229,11 +2252,19 @@ func (f *UintSliceFlag) String() string func (f *UintSliceFlag) TakesValue() bool TakesValue returns true of the flag takes a value, otherwise false +type VisibleFlag interface { + Flag + + // IsVisible returns true if the flag is not hidden, otherwise false + IsVisible() bool +} + VisibleFlag is an interface that allows to check if a flag is visible + type VisibleFlagCategory interface { // Name returns the category name string Name() string // Flags returns a slice of VisibleFlag sorted by name - Flags() []Flag + Flags() []VisibleFlag } VisibleFlagCategory is a category containing flags. From a6f5ca69f4df8bffe894af051c571e267fcb45c5 Mon Sep 17 00:00:00 2001 From: Anatoli Babenia Date: Mon, 3 Oct 2022 18:53:26 +0300 Subject: [PATCH 110/136] wrap: Avoid trailing whitespace for empty lines --- help.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/help.go b/help.go index ba5f803176..77be2b060a 100644 --- a/help.go +++ b/help.go @@ -476,7 +476,9 @@ func wrap(input string, offset int, wrapAt int) string { for i, line := range lines { if i != 0 { - sb.WriteString(padding) + if len(line) > 0 { + sb.WriteString(padding) + } } sb.WriteString(wrapLine(line, offset, wrapAt, padding)) From d3bb381a0bd5b6e049ac8bdb8fabaf27802d5aaf Mon Sep 17 00:00:00 2001 From: Anatoli Babenia Date: Mon, 3 Oct 2022 19:05:46 +0300 Subject: [PATCH 111/136] Fix test for removed trailing whitespace --- help_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/help_test.go b/help_test.go index 5a50586483..9c99b98913 100644 --- a/help_test.go +++ b/help_test.go @@ -1276,7 +1276,7 @@ DESCRIPTION: App.Description string long enough that it should be wrapped in this test - + with a newline and an indented line @@ -1294,8 +1294,8 @@ COPYRIGHT: that it should be wrapped. Including newlines. And also indented lines. - - + + And then another long line. Blah blah blah does anybody ever read these things? From 05fb755b6a135b2702bc480cba1d9a975727dc7b Mon Sep 17 00:00:00 2001 From: Anatoli Babenia Date: Tue, 4 Oct 2022 10:30:31 +0300 Subject: [PATCH 112/136] wrap: Simplify loop logic Suggested by @julian7 --- help.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/help.go b/help.go index 77be2b060a..697f0e174b 100644 --- a/help.go +++ b/help.go @@ -475,10 +475,14 @@ func wrap(input string, offset int, wrapAt int) string { padding := strings.Repeat(" ", offset) for i, line := range lines { + if line == "" { + sb.WriteString("\n") + continue + } + + // the first line is not indented if i != 0 { - if len(line) > 0 { - sb.WriteString(padding) - } + sb.WriteString(padding) } sb.WriteString(wrapLine(line, offset, wrapAt, padding)) From e14dca7a181fdda99f95833f1cd1df315ff5d607 Mon Sep 17 00:00:00 2001 From: Anatoli Babenia Date: Tue, 4 Oct 2022 10:40:11 +0300 Subject: [PATCH 113/136] Run `go fmt` --- help.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/help.go b/help.go index 697f0e174b..5db669665c 100644 --- a/help.go +++ b/help.go @@ -479,7 +479,7 @@ func wrap(input string, offset int, wrapAt int) string { sb.WriteString("\n") continue } - + // the first line is not indented if i != 0 { sb.WriteString(padding) From 4959a9fa9bd327058c66be0f1d782ec0e70d3965 Mon Sep 17 00:00:00 2001 From: Anatoli Babenia Date: Tue, 4 Oct 2022 13:13:52 +0300 Subject: [PATCH 114/136] Refactor `wrap()` and add test for empty line --- help.go | 23 ++++++++++------------- help_test.go | 7 +++++++ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/help.go b/help.go index 5db669665c..6929a226ab 100644 --- a/help.go +++ b/help.go @@ -468,7 +468,7 @@ func nindent(spaces int, v string) string { } func wrap(input string, offset int, wrapAt int) string { - var sb strings.Builder + var ss []string lines := strings.Split(input, "\n") @@ -476,23 +476,20 @@ func wrap(input string, offset int, wrapAt int) string { for i, line := range lines { if line == "" { - sb.WriteString("\n") - continue - } - - // the first line is not indented - if i != 0 { - sb.WriteString(padding) - } + ss = append(ss, line) + } else { + wrapped := wrapLine(line, offset, wrapAt, padding) + if i == 0 { + ss = append(ss, wrapped) + } else { + ss = append(ss, padding+wrapped) - sb.WriteString(wrapLine(line, offset, wrapAt, padding)) + } - if i != len(lines)-1 { - sb.WriteString("\n") } } - return sb.String() + return strings.Join(ss, "\n") } func wrapLine(input string, offset int, wrapAt int, padding string) string { diff --git a/help_test.go b/help_test.go index 9c99b98913..d7cdfa2d8f 100644 --- a/help_test.go +++ b/help_test.go @@ -1213,6 +1213,13 @@ func TestDefaultCompleteWithFlags(t *testing.T) { } } +func TestWrap(t *testing.T) { + emptywrap := wrap("", 4, 16) + if emptywrap != "" { + t.Errorf("Wrapping empty line should return empty line. Got '%s'.", emptywrap) + } +} + func TestWrappedHelp(t *testing.T) { // Reset HelpPrinter after this test. From 82ea9f70c03602d9bf7a4d7d07e789c757c5bdc8 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 30 Sep 2022 15:01:22 +0200 Subject: [PATCH 115/136] Call FlagStringer in String() method of slice flags The default help template relies on the String() method of Flag to render the flag. For most flag types, String() indirects through FlagStringer, so that is the best place to customize flag rendering. FlagStringer was not called for slice flags because their help output differs from other flags in two ways: there can be multiple default values, and the flag name is shown two times to indicate that the flag can be specified multiple times. To make multiple values work in the FlagStringer, I simply changed GetValue() to return all values. Showing the flag more than once is achieved through a new interface, DocGenerationSliceFlag, which the FlagStringer uses to decide whether the flag is a slice flag type. --- flag.go | 29 +++++++++++++---------------- flag_float64_slice.go | 28 ++++++++++++---------------- flag_int64_slice.go | 27 ++++++++++++--------------- flag_int_slice.go | 27 ++++++++++++--------------- flag_string_slice.go | 31 ++++++++++++++----------------- flag_test.go | 20 ++++++++++---------- flag_uint64_slice.go | 27 ++++++++++++--------------- flag_uint_slice.go | 27 ++++++++++++--------------- 8 files changed, 97 insertions(+), 119 deletions(-) diff --git a/flag.go b/flag.go index aade1de2e8..6aff19b509 100644 --- a/flag.go +++ b/flag.go @@ -126,6 +126,14 @@ type Flag interface { RunAction(*Context) error } +// DocGenerationSliceFlag extends DocGenerationFlag for slice-based flags. +type DocGenerationSliceFlag interface { + DocGenerationFlag + + // IsSliceFlag returns true for flags that can be given multiple times. + IsSliceFlag() bool +} + // Countable is an interface to enable detection of flag values which support // repetitive flags type Countable interface { @@ -300,24 +308,13 @@ func stringifyFlag(f Flag) string { usageWithDefault := strings.TrimSpace(usage + defaultValueString) - return withEnvHint(f.GetEnvVars(), - fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault)) -} - -func stringifySliceFlag(usage string, names, defaultVals []string) string { - placeholder, usage := unquoteUsage(usage) - if placeholder == "" { - placeholder = defaultPlaceholder - } - - defaultVal := "" - if len(defaultVals) > 0 { - defaultVal = fmt.Sprintf(formatDefault("%s"), strings.Join(defaultVals, ", ")) + pn := prefixedNames(df.Names(), placeholder) + sliceFlag, ok := f.(DocGenerationSliceFlag) + if ok && sliceFlag.IsSliceFlag() { + pn = pn + " [ " + pn + " ]" } - usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) - pn := prefixedNames(names, placeholder) - return fmt.Sprintf("%s [ %s ]\t%s", pn, pn, usageWithDefault) + return withEnvHint(df.GetEnvVars(), fmt.Sprintf("%s\t%s", pn, usageWithDefault)) } func hasFlag(flags []Flag, fl Flag) bool { diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 5bb2693114..3f8aef1d55 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -83,16 +83,24 @@ func (f *Float64Slice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *Float64SliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), f.stringify()) + return FlagStringer(f) } // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *Float64SliceFlag) GetValue() string { - if f.Value != nil { - return f.Value.String() + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strings.TrimRight(strings.TrimRight(fmt.Sprintf("%f", i), "0"), ".")) + } } - return "" + return strings.Join(defaultVals, ", ") +} + +// IsSliceFlag implements DocGenerationSliceFlag. +func (f *Float64SliceFlag) IsSliceFlag() bool { + return true } // Apply populates the flag given the flag set and environment @@ -141,18 +149,6 @@ func (f *Float64SliceFlag) Get(ctx *Context) []float64 { return ctx.Float64Slice(f.Name) } -func (f *Float64SliceFlag) stringify() string { - var defaultVals []string - - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, strings.TrimRight(strings.TrimRight(fmt.Sprintf("%f", i), "0"), ".")) - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - // RunAction executes flag action if set func (f *Float64SliceFlag) RunAction(c *Context) error { if f.Action != nil { diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 89fd44550e..6844e6d1a6 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -84,16 +84,24 @@ func (i *Int64Slice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *Int64SliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), f.stringify()) + return FlagStringer(f) } // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *Int64SliceFlag) GetValue() string { - if f.Value != nil { - return f.Value.String() + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.FormatInt(i, 10)) + } } - return "" + return strings.Join(defaultVals, ", ") +} + +// IsSliceFlag implements DocGenerationSliceFlag. +func (f *Int64SliceFlag) IsSliceFlag() bool { + return true } // Apply populates the flag given the flag set and environment @@ -140,17 +148,6 @@ func (f *Int64SliceFlag) Get(ctx *Context) []int64 { return ctx.Int64Slice(f.Name) } -func (f *Int64SliceFlag) stringify() string { - var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, strconv.FormatInt(i, 10)) - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - // RunAction executes flag action if set func (f *Int64SliceFlag) RunAction(c *Context) error { if f.Action != nil { diff --git a/flag_int_slice.go b/flag_int_slice.go index f7a6b06f0c..d3d3ea750b 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -95,16 +95,24 @@ func (i *IntSlice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *IntSliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), f.stringify()) + return FlagStringer(f) } // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *IntSliceFlag) GetValue() string { - if f.Value != nil { - return f.Value.String() + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.Itoa(i)) + } } - return "" + return strings.Join(defaultVals, ", ") +} + +// IsSliceFlag implements DocGenerationSliceFlag. +func (f *IntSliceFlag) IsSliceFlag() bool { + return true } // Apply populates the flag given the flag set and environment @@ -151,17 +159,6 @@ func (f *IntSliceFlag) Get(ctx *Context) []int { return ctx.IntSlice(f.Name) } -func (f *IntSliceFlag) stringify() string { - var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, strconv.Itoa(i)) - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - // RunAction executes flag action if set func (f *IntSliceFlag) RunAction(c *Context) error { if f.Action != nil { diff --git a/flag_string_slice.go b/flag_string_slice.go index 2a24ec56e5..bdeb674fb2 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -74,16 +74,26 @@ func (s *StringSlice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *StringSliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), f.stringify()) + return FlagStringer(f) } // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *StringSliceFlag) GetValue() string { - if f.Value != nil { - return f.Value.String() + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, s := range f.Value.Value() { + if len(s) > 0 { + defaultVals = append(defaultVals, strconv.Quote(s)) + } + } } - return "" + return strings.Join(defaultVals, ", ") +} + +// IsSliceFlag implements DocGenerationSliceFlag. +func (f *StringSliceFlag) IsSliceFlag() bool { + return true } // Apply populates the flag given the flag set and environment @@ -130,19 +140,6 @@ func (f *StringSliceFlag) Get(ctx *Context) []string { return ctx.StringSlice(f.Name) } -func (f *StringSliceFlag) stringify() string { - var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, s := range f.Value.Value() { - if len(s) > 0 { - defaultVals = append(defaultVals, strconv.Quote(s)) - } - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - // RunAction executes flag action if set func (f *StringSliceFlag) RunAction(c *Context) error { if f.Action != nil { diff --git a/flag_test.go b/flag_test.go index e7fa309fc6..6b82339892 100644 --- a/flag_test.go +++ b/flag_test.go @@ -298,12 +298,12 @@ func TestFlagStringifying(t *testing.T) { { name: "float64-slice-flag", fl: &Float64SliceFlag{Name: "pizzas"}, - expected: "--pizzas value\t", + expected: "--pizzas value [ --pizzas value ]\t", }, { name: "float64-slice-flag-with-default-text", fl: &Float64SliceFlag{Name: "pepperonis", DefaultText: "shaved"}, - expected: "--pepperonis value\t(default: shaved)", + expected: "--pepperonis value [ --pepperonis value ]\t(default: shaved)", }, { name: "generic-flag", @@ -328,7 +328,7 @@ func TestFlagStringifying(t *testing.T) { { name: "int-slice-flag", fl: &IntSliceFlag{Name: "pencils"}, - expected: "--pencils value\t", + expected: "--pencils value [ --pencils value ]\t", }, { name: "int-slice-flag-with-default-text", @@ -338,7 +338,7 @@ func TestFlagStringifying(t *testing.T) { { name: "uint-slice-flag", fl: &UintSliceFlag{Name: "pencils"}, - expected: "--pencils value\t", + expected: "--pencils value [ --pencils value ]\t", }, { name: "uint-slice-flag-with-default-text", @@ -358,22 +358,22 @@ func TestFlagStringifying(t *testing.T) { { name: "int64-slice-flag", fl: &Int64SliceFlag{Name: "drawers"}, - expected: "--drawers value\t", + expected: "--drawers value [ --drawers value ]\t", }, { name: "int64-slice-flag-with-default-text", fl: &Int64SliceFlag{Name: "handles", DefaultText: "-2"}, - expected: "--handles value\t(default: -2)", + expected: "--handles value [ --handles value ]\t(default: -2)", }, { name: "uint64-slice-flag", fl: &Uint64SliceFlag{Name: "drawers"}, - expected: "--drawers value\t", + expected: "--drawers value [ --drawers value ]\t", }, { name: "uint64-slice-flag-with-default-text", fl: &Uint64SliceFlag{Name: "handles", DefaultText: "-2"}, - expected: "--handles value\t(default: -2)", + expected: "--handles value [ --handles value ]\t(default: -2)", }, { name: "path-flag", @@ -398,12 +398,12 @@ func TestFlagStringifying(t *testing.T) { { name: "string-slice-flag", fl: &StringSliceFlag{Name: "meow-sounds"}, - expected: "--meow-sounds value\t", + expected: "--meow-sounds value [ --meow-sounds value ]\t", }, { name: "string-slice-flag-with-default-text", fl: &StringSliceFlag{Name: "moo-sounds", DefaultText: "awoo"}, - expected: "--moo-sounds value\t(default: awoo)", + expected: "--moo-sounds value [ --moo-sounds value ]\t(default: awoo)", }, { name: "timestamp-flag", diff --git a/flag_uint64_slice.go b/flag_uint64_slice.go index 662a03eac6..873ad9bd55 100644 --- a/flag_uint64_slice.go +++ b/flag_uint64_slice.go @@ -88,7 +88,7 @@ func (i *Uint64Slice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *Uint64SliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), f.stringify()) + return FlagStringer(f) } // TakesValue returns true of the flag takes a value, otherwise false @@ -109,10 +109,13 @@ func (f *Uint64SliceFlag) GetCategory() string { // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *Uint64SliceFlag) GetValue() string { - if f.Value != nil { - return f.Value.String() + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.FormatUint(i, 10)) + } } - return "" + return strings.Join(defaultVals, ", ") } // GetDefaultText returns the default text for this flag @@ -128,6 +131,11 @@ func (f *Uint64SliceFlag) GetEnvVars() []string { return f.EnvVars } +// IsSliceFlag implements DocGenerationSliceFlag. +func (f *Uint64SliceFlag) IsSliceFlag() bool { + return true +} + // Apply populates the flag given the flag set and environment func (f *Uint64SliceFlag) Apply(set *flag.FlagSet) error { // apply any default @@ -172,17 +180,6 @@ func (f *Uint64SliceFlag) Get(ctx *Context) []uint64 { return ctx.Uint64Slice(f.Name) } -func (f *Uint64SliceFlag) stringify() string { - var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, strconv.FormatUint(i, 10)) - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - // RunAction executes flag action if set func (f *Uint64SliceFlag) RunAction(c *Context) error { if f.Action != nil { diff --git a/flag_uint_slice.go b/flag_uint_slice.go index 3689eaa39b..2a1fcc17f1 100644 --- a/flag_uint_slice.go +++ b/flag_uint_slice.go @@ -99,7 +99,7 @@ func (i *UintSlice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *UintSliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), f.stringify()) + return FlagStringer(f) } // TakesValue returns true of the flag takes a value, otherwise false @@ -120,10 +120,13 @@ func (f *UintSliceFlag) GetCategory() string { // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *UintSliceFlag) GetValue() string { - if f.Value != nil { - return f.Value.String() + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.FormatUint(uint64(i), 10)) + } } - return "" + return strings.Join(defaultVals, ", ") } // GetDefaultText returns the default text for this flag @@ -139,6 +142,11 @@ func (f *UintSliceFlag) GetEnvVars() []string { return f.EnvVars } +// IsSliceFlag implements DocGenerationSliceFlag. +func (f *UintSliceFlag) IsSliceFlag() bool { + return true +} + // Apply populates the flag given the flag set and environment func (f *UintSliceFlag) Apply(set *flag.FlagSet) error { // apply any default @@ -183,17 +191,6 @@ func (f *UintSliceFlag) Get(ctx *Context) []uint { return ctx.UintSlice(f.Name) } -func (f *UintSliceFlag) stringify() string { - var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, strconv.FormatUint(uint64(i), 10)) - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - // RunAction executes flag action if set func (f *UintSliceFlag) RunAction(c *Context) error { if f.Action != nil { From 65c98c86ad1e753645287bbae0b7f418376a72d3 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 29 Sep 2022 11:31:40 -0400 Subject: [PATCH 116/136] Ensure "generate" step runs in CI prior to diff check --- .github/workflows/cli.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 800bbeb2dd..54afe58f4d 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -43,6 +43,7 @@ jobs: GFLAGS: -tags urfave_cli_no_docs - run: make check-binary-size - run: make yamlfmt + - run: make generate - run: make diffcheck - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' run: make v2diff From c5057d195efd46b589b7e2ecddf800f5f44f2068 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 29 Sep 2022 20:44:37 -0400 Subject: [PATCH 117/136] Only run generate on go 1.19 --- .github/workflows/cli.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 54afe58f4d..39298b851b 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -43,7 +43,8 @@ jobs: GFLAGS: -tags urfave_cli_no_docs - run: make check-binary-size - run: make yamlfmt - - run: make generate + - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' + run: make generate - run: make diffcheck - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' run: make v2diff From 01bdec784f5289c8ed4ce6b9520a9571686e976e Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 26 Jul 2022 16:16:02 +0200 Subject: [PATCH 118/136] Set destination in GenericFlag apply function The function was missing destination configuration. --- flag_generic.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flag_generic.go b/flag_generic.go index 9f0633808e..daed474833 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -34,6 +34,10 @@ func (f *GenericFlag) Apply(set *flag.FlagSet) error { } for _, name := range f.Names() { + if f.Destination != nil { + set.Var(f.Destination, name, f.Usage) + continue + } set.Var(f.Value, name, f.Usage) } From b82e628617754df5f25c0e160679a714755c377b Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 26 Jul 2022 16:16:43 +0200 Subject: [PATCH 119/136] Add unit test for GenericFlag Destination parsing The test checks if Destination provided in GenericFlag is being set as expected. --- flag_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/flag_test.go b/flag_test.go index 6b82339892..8e4033915e 100644 --- a/flag_test.go +++ b/flag_test.go @@ -2710,6 +2710,53 @@ func TestParseGeneric(t *testing.T) { }).Run([]string{"run", "-s", "10,20"}) } +type genericType struct { + s []string +} + +func (g *genericType) Set(value string) error { + g.s = strings.Split(value, "-") + return nil +} + +func (g *genericType) String() string { + return strings.Join(g.s, "-") +} + +func TestParseDestinationGeneric(t *testing.T) { + expectedString := "abc1-123d" + expectedGeneric := &genericType{[]string{"abc1", "123d"}} + dest := &genericType{} + + _ = (&App{ + Flags: []Flag{ + &GenericFlag{ + Name: "dest", + Destination: dest, + }, + }, + Action: func(ctx *Context) error { + + if !reflect.DeepEqual(dest, expectedGeneric) { + t.Errorf( + "expected destination generic: %+v, actual: %+v", + expectedGeneric, + dest, + ) + } + + if dest.String() != expectedString { + t.Errorf( + "expected destination string: %s, actual: %s", + expectedString, + dest.String(), + ) + } + return nil + }, + }).Run([]string{"run", "--dest", expectedString}) +} + func TestParseGenericFromEnv(t *testing.T) { defer resetEnv(os.Environ()) os.Clearenv() From 13860a7c4db6bc0169afc52b95872b4b258a0939 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Thu, 29 Sep 2022 20:49:22 -0400 Subject: [PATCH 120/136] Fix:(issue_1505) Fix flag alignment in help --- template.go | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/template.go b/template.go index 9e13604f35..53a76b74cf 100644 --- a/template.go +++ b/template.go @@ -56,8 +56,8 @@ OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} {{end}}{{range .Flags}}{{.}}{{end}}{{end}}{{else}}{{if .VisibleFlags}} -OPTIONS: - {{range .VisibleFlags}}{{.}}{{end}}{{end}}{{end}} +OPTIONS:{{range .VisibleFlags}} + {{.}}{{end}}{{end}}{{end}} ` // SubcommandHelpTemplate is the text template for the subcommand help topic. @@ -70,15 +70,24 @@ USAGE: {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{wrap .Description 3}}{{end}} + {{wrap .Description 3}}{{end}}{{if .VisibleCommands}} COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{range .VisibleCommands}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} - {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} -OPTIONS: - {{range .VisibleFlags}}{{.}}{{end}}{{end}} +COMMANDS:{{range .VisibleCategories}}{{if .Name}} + {{.Name}}:{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} + {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} + +OPTIONS:{{range .VisibleFlagCategories}} + {{if .Name}}{{.Name}}{{end}}{{range .Flags}}{{.}} + {{end}}{{end}}{{else}}{{if .VisibleFlags}} + +OPTIONS:{{range $index, $option := .VisibleFlags}}{{if $index}}{{end}} + {{wrap $option.String 6}}{{end}}{{end}}{{end}} ` var MarkdownDocTemplate = `{{if gt .SectionNum 0}}% {{ .App.Name }} {{ .SectionNum }} From ae8c5118f2154b1cac5f3988347a0b87a5aaed0d Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Thu, 29 Sep 2022 21:06:07 -0400 Subject: [PATCH 121/136] Fix command help subcommand --- command.go | 11 +++++++++++ template.go | 11 ++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/command.go b/command.go index decfb7359c..b7f6b73b1f 100644 --- a/command.go +++ b/command.go @@ -295,6 +295,17 @@ func (c *Command) startApp(ctx *Context) error { return app.RunAsSubcommand(ctx) } +// VisibleCommands returns a slice of the Commands with Hidden=false +func (c *Command) VisibleCommands() []*Command { + var ret []*Command + for _, command := range c.Subcommands { + if !command.Hidden { + ret = append(ret, command) + } + } + return ret +} + // VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain func (c *Command) VisibleFlagCategories() []VisibleFlagCategory { if c.flagCategories == nil { diff --git a/template.go b/template.go index 53a76b74cf..bb9cbc42b8 100644 --- a/template.go +++ b/template.go @@ -72,16 +72,9 @@ USAGE: DESCRIPTION: {{wrap .Description 3}}{{end}}{{if .VisibleCommands}} -COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} - {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} +COMMANDS:{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} + {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{if .VisibleFlagCategories}} -COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} - {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} - OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}}{{end}}{{range .Flags}}{{.}} {{end}}{{end}}{{else}}{{if .VisibleFlags}} From c86805de7c074ef985d3f8c7b3e4ac853ca9d24f Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Fri, 30 Sep 2022 08:23:18 -0400 Subject: [PATCH 122/136] Add test case --- app_test.go | 2 +- help.go | 6 +++- help_test.go | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++ template.go | 8 +++--- 4 files changed, 89 insertions(+), 6 deletions(-) diff --git a/app_test.go b/app_test.go index 1428c391dd..600f2cbc63 100644 --- a/app_test.go +++ b/app_test.go @@ -177,7 +177,7 @@ func ExampleApp_Run_commandHelp() { // greet describeit - use it to see a description // // USAGE: - // greet describeit [command options] [arguments...] + // greet describeit [arguments...] // // DESCRIPTION: // This is how we describe describeit the function diff --git a/help.go b/help.go index 6929a226ab..45e6f2b3cd 100644 --- a/help.go +++ b/help.go @@ -242,7 +242,11 @@ func ShowCommandHelp(ctx *Context, command string) error { c.Subcommands = append(c.Subcommands, helpCommandDontUse) } if !ctx.App.HideHelp && HelpFlag != nil { - c.appendFlag(HelpFlag) + if c.flagCategories == nil { + c.flagCategories = newFlagCategoriesFromFlags([]Flag{HelpFlag}) + } else { + c.flagCategories.AddFlag("", HelpFlag) + } } templ := c.CustomHelpTemplate if templ == "" { diff --git a/help_test.go b/help_test.go index d7cdfa2d8f..6b1e8c6b4e 100644 --- a/help_test.go +++ b/help_test.go @@ -1367,6 +1367,7 @@ DESCRIPTION: OPTIONS: --help, -h show help (default: false) + ` if output.String() != expected { @@ -1436,6 +1437,84 @@ USAGE: OPTIONS: --help, -h show help (default: false) + +` + + if output.String() != expected { + t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s", + output.String(), expected) + } +} + +func TestWrappedHelpSubcommand(t *testing.T) { + + // Reset HelpPrinter after this test. + defer func(old helpPrinter) { + HelpPrinter = old + }(HelpPrinter) + + output := new(bytes.Buffer) + app := &App{ + Name: "cli.test", + Writer: output, + Commands: []*Command{ + { + Name: "bar", + Aliases: []string{"a"}, + Usage: "add a task to the list", + UsageText: "this is an even longer way of describing adding a task to the list", + Description: "and a description long enough to wrap in this test case", + Action: func(c *Context) error { + return nil + }, + Subcommands: []*Command{ + { + Name: "grok", + Usage: "remove an existing template", + UsageText: "longer usage text goes here, la la la, hopefully this is long enough to wrap even more", + Action: func(c *Context) error { + return nil + }, + Flags: []Flag{ + &StringFlag{ + Name: "test-f", + Usage: "my test usage", + }, + }, + }, + }, + }, + }, + } + + HelpPrinter = func(w io.Writer, templ string, data interface{}) { + funcMap := map[string]interface{}{ + "wrapAt": func() int { + return 30 + }, + } + + HelpPrinterCustom(w, templ, data, funcMap) + } + + _ = app.Run([]string{"foo", "bar", "help", "grok"}) + + expected := `NAME: + cli.test bar grok - remove + an + existing + template + +USAGE: + longer usage text goes + here, la la la, hopefully + this is long enough to wrap + even more + +OPTIONS: + --help, -h show help (default: false) + --test-f value my test usage + ` if output.String() != expected { diff --git a/template.go b/template.go index bb9cbc42b8..d67931bba0 100644 --- a/template.go +++ b/template.go @@ -54,10 +54,10 @@ DESCRIPTION: OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}}{{end}}{{end}}{{else}}{{if .VisibleFlags}} - -OPTIONS:{{range .VisibleFlags}} - {{.}}{{end}}{{end}}{{end}} + {{end}}{{range .Flags}}{{.}} + {{end}}{{end}}{{else}}{{if .VisibleFlags}} +OPTIONS:{{range $index, $option := .VisibleFlags}}{{if $index}}{{end}} + {{wrap $option.String 6}}{{end}}{{end}}{{end}} ` // SubcommandHelpTemplate is the text template for the subcommand help topic. From dccd762cbb153cff25c4d924312a77787d05a39f Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Fri, 30 Sep 2022 12:12:41 -0400 Subject: [PATCH 123/136] Componentize template --- help.go | 11 +++++++ help_test.go | 9 ++---- template.go | 81 ++++++++++++++++++++++++++++------------------------ 3 files changed, 58 insertions(+), 43 deletions(-) diff --git a/help.go b/help.go index 45e6f2b3cd..d78444a0c6 100644 --- a/help.go +++ b/help.go @@ -362,6 +362,17 @@ func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0) t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) + t.New("helpNameTemplate").Parse(helpNameTemplate) + t.New("usageTemplate").Parse(usageTemplate) + t.New("descriptionTemplate").Parse(descriptionTemplate) + t.New("visibleCommandTemplate").Parse(visibleCommandTemplate) + t.New("copyrightTemplate").Parse(copyrightTemplate) + t.New("versionTemplate").Parse(versionTemplate) + t.New("visibleFlagCategoryTemplate").Parse(visibleFlagCategoryTemplate) + t.New("visibleFlagTemplate").Parse(visibleFlagTemplate) + t.New("visibleGlobalFlagCategoryTemplate").Parse(strings.Replace(visibleFlagCategoryTemplate, "OPTIONS", "GLOBAL OPTIONS", -1)) + t.New("authorsTemplate").Parse(authorsTemplate) + t.New("visibleCommandCategoryTemplate").Parse(visibleCommandCategoryTemplate) err := t.Execute(w, data) if err != nil { diff --git a/help_test.go b/help_test.go index 6b1e8c6b4e..de8e2f0cd9 100644 --- a/help_test.go +++ b/help_test.go @@ -1367,8 +1367,7 @@ DESCRIPTION: OPTIONS: --help, -h show help (default: false) - -` + ` if output.String() != expected { t.Errorf("Unexpected wrapping, got:\n%s\nexpected:\n%s", @@ -1437,8 +1436,7 @@ USAGE: OPTIONS: --help, -h show help (default: false) - -` + ` if output.String() != expected { t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s", @@ -1514,8 +1512,7 @@ USAGE: OPTIONS: --help, -h show help (default: false) --test-f value my test usage - -` + ` if output.String() != expected { t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s", diff --git a/template.go b/template.go index d67931bba0..dfdd434cce 100644 --- a/template.go +++ b/template.go @@ -1,10 +1,36 @@ package cli +var helpNameTemplate = `{{$v := offset .HelpName 6}}{{wrap .HelpName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}}` +var usageTemplate = `{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}` +var descriptionTemplate = `{{wrap .Description 3}}` +var authorsTemplate = `{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: + {{range $index, $author := .Authors}}{{if $index}} + {{end}}{{$author}}{{end}}` +var visibleCommandTemplate = `{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} + {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}` +var visibleCommandCategoryTemplate = `{{range .VisibleCategories}}{{if .Name}} + {{.Name}}:{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{template "visibleCommandTemplate" .}}{{end}}{{end}}` +var visibleFlagCategoryTemplate = `{{range .VisibleFlagCategories}} + {{if .Name}}{{.Name}} + {{end}}{{range .Flags}}{{.}} + {{end}}{{end}}` + +var visibleFlagTemplate = `{{range $index, $option := .VisibleFlags}}{{if $index}}{{end}} + {{wrap $option.String 6}}{{end}}` + +var versionTemplate = `{{if .Version}}{{if not .HideVersion}} + +VERSION: + {{.Version}}{{end}}{{end}}` + +var copyrightTemplate = `{{wrap .Copyright 3}}` + // AppHelpTemplate is the text template for the Default help topic. // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var AppHelpTemplate = `NAME: - {{$v := offset .Name 6}}{{wrap .Name 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}} + {{template "helpNameTemplate" .}} USAGE: {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} @@ -13,75 +39,56 @@ VERSION: {{.Version}}{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{wrap .Description 3}}{{end}}{{if len .Authors}} + {{template "descriptionTemplate" .}}{{end}}{{if len .Authors}} -AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: - {{range $index, $author := .Authors}}{{if $index}} - {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} +AUTHOR{{template "authorsTemplate" .}}{{end}}{{if .VisibleCommands}} -COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} - {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} +COMMANDS:{{template "visibleCommandCategoryTemplate" .}}{{end}}{{if .VisibleFlagCategories}} -GLOBAL OPTIONS:{{range .VisibleFlagCategories}} - {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}} - {{end}}{{end}}{{else}}{{if .VisibleFlags}} +GLOBAL OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} -GLOBAL OPTIONS: - {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{wrap $option.String 6}}{{end}}{{end}}{{end}}{{if .Copyright}} +GLOBAL OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}{{if .Copyright}} COPYRIGHT: - {{wrap .Copyright 3}}{{end}} + {{template "copyrightTemplate" .}}{{end}} ` // CommandHelpTemplate is the text template for the command help topic. // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var CommandHelpTemplate = `NAME: - {{$v := offset .HelpName 6}}{{wrap .HelpName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}} + {{template "helpNameTemplate" .}} USAGE: - {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} + {{template "usageTemplate" .}}{{if .Category}} CATEGORY: {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{wrap .Description 3}}{{end}}{{if .VisibleFlagCategories}} + {{template "descriptionTemplate" .}}{{end}}{{if .VisibleFlagCategories}} -OPTIONS:{{range .VisibleFlagCategories}} - {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}} - {{end}}{{end}}{{else}}{{if .VisibleFlags}} -OPTIONS:{{range $index, $option := .VisibleFlags}}{{if $index}}{{end}} - {{wrap $option.String 6}}{{end}}{{end}}{{end}} -` +OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} + +OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}` // SubcommandHelpTemplate is the text template for the subcommand help topic. // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var SubcommandHelpTemplate = `NAME: - {{.HelpName}} - {{.Usage}} + {{template "helpNameTemplate" .}} USAGE: {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{wrap .Description 3}}{{end}}{{if .VisibleCommands}} + {{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}} -COMMANDS:{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} - {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{if .VisibleFlagCategories}} +COMMANDS:{{template "visibleCommandTemplate" .}}{{end}}{{if .VisibleFlagCategories}} -OPTIONS:{{range .VisibleFlagCategories}} - {{if .Name}}{{.Name}}{{end}}{{range .Flags}}{{.}} - {{end}}{{end}}{{else}}{{if .VisibleFlags}} +OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} -OPTIONS:{{range $index, $option := .VisibleFlags}}{{if $index}}{{end}} - {{wrap $option.String 6}}{{end}}{{end}}{{end}} -` +OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}` var MarkdownDocTemplate = `{{if gt .SectionNum 0}}% {{ .App.Name }} {{ .SectionNum }} From a4b7759ad11e9d93120d68b9b34284130c10caec Mon Sep 17 00:00:00 2001 From: dearchap Date: Wed, 5 Oct 2022 08:35:11 -0400 Subject: [PATCH 124/136] Update template.go Co-authored-by: Anatoli Babenia --- template.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/template.go b/template.go index dfdd434cce..f48e5099f3 100644 --- a/template.go +++ b/template.go @@ -39,7 +39,8 @@ VERSION: {{.Version}}{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{template "descriptionTemplate" .}}{{end}}{{if len .Authors}} + {{template "descriptionTemplate" .}}{{end}} +{{- if len .Authors}} AUTHOR{{template "authorsTemplate" .}}{{end}}{{if .VisibleCommands}} From 9a9461928aa1c0917f6526c0077c4c9ac6203067 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Wed, 5 Oct 2022 13:52:05 -0400 Subject: [PATCH 125/136] Add test coverage for Command.VisibleCommands() --- command_test.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/command_test.go b/command_test.go index 9dfd46f476..a953e943cd 100644 --- a/command_test.go +++ b/command_test.go @@ -422,3 +422,30 @@ func TestCommand_CanAddVFlagOnCommands(t *testing.T) { err := app.Run([]string{"foo", "bar"}) expect(t, err, nil) } + +func TestCommand_VisibleSubcCommands(t *testing.T) { + + subc1 := &Command{ + Name: "subc1", + Usage: "subc1 command1", + } + subc3 := &Command{ + Name: "subc3", + Usage: "subc3 command2", + } + c := &Command{ + Name: "bar", + Usage: "this is for testing", + Subcommands: []*Command{ + subc1, + { + Name: "subc2", + Usage: "subc2 command2", + Hidden: true, + }, + subc3, + }, + } + + expect(t, c.VisibleCommands(), []*Command{subc1, subc3}) +} From ea2893084b92f3cdd9a3bb7dcf1b85bfc12fa595 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Wed, 5 Oct 2022 14:40:59 -0400 Subject: [PATCH 126/136] Remove extra 3 spaces in last line --- help_test.go | 6 +++--- template.go | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/help_test.go b/help_test.go index de8e2f0cd9..37410a5449 100644 --- a/help_test.go +++ b/help_test.go @@ -1367,7 +1367,7 @@ DESCRIPTION: OPTIONS: --help, -h show help (default: false) - ` +` if output.String() != expected { t.Errorf("Unexpected wrapping, got:\n%s\nexpected:\n%s", @@ -1436,7 +1436,7 @@ USAGE: OPTIONS: --help, -h show help (default: false) - ` +` if output.String() != expected { t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s", @@ -1512,7 +1512,7 @@ USAGE: OPTIONS: --help, -h show help (default: false) --test-f value my test usage - ` +` if output.String() != expected { t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s", diff --git a/template.go b/template.go index f48e5099f3..1133b8907c 100644 --- a/template.go +++ b/template.go @@ -13,8 +13,10 @@ var visibleCommandCategoryTemplate = `{{range .VisibleCategories}}{{if .Name}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{template "visibleCommandTemplate" .}}{{end}}{{end}}` var visibleFlagCategoryTemplate = `{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}} - {{end}}{{end}}` + + {{end}}{{$flglen := len .Flags}}{{range $i, $e := .Flags}}{{if eq (subtract $flglen $i) 1}}{{$e}} +{{else}}{{$e}} + {{end}}{{end}}{{end}}` var visibleFlagTemplate = `{{range $index, $option := .VisibleFlags}}{{if $index}}{{end}} {{wrap $option.String 6}}{{end}}` From 924ebdaab2b099fd121e721f1f118cfc0d625f64 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Thu, 29 Sep 2022 10:33:48 -0400 Subject: [PATCH 127/136] Fix:(issue_1500). Fix slice flag value duplication issue --- app_test.go | 31 +++++++++++++++++++++++++++++++ flag.go | 27 ++++++++++++++++++++++++--- parse.go | 5 ++++- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/app_test.go b/app_test.go index 600f2cbc63..9a1fcd1586 100644 --- a/app_test.go +++ b/app_test.go @@ -956,6 +956,37 @@ func TestApp_UseShortOptionHandlingSubCommand_missing_value(t *testing.T) { expect(t, err, errors.New("flag needs an argument: -n")) } +func TestApp_UseShortOptionAfterSliceFlag(t *testing.T) { + var one, two bool + var name string + var sliceValDest StringSlice + var sliceVal []string + expected := "expectedName" + + app := newTestApp() + app.UseShortOptionHandling = true + app.Flags = []Flag{ + &StringSliceFlag{Name: "env", Aliases: []string{"e"}, Destination: &sliceValDest}, + &BoolFlag{Name: "one", Aliases: []string{"o"}}, + &BoolFlag{Name: "two", Aliases: []string{"t"}}, + &StringFlag{Name: "name", Aliases: []string{"n"}}, + } + app.Action = func(c *Context) error { + sliceVal = c.StringSlice("env") + one = c.Bool("one") + two = c.Bool("two") + name = c.String("name") + return nil + } + + _ = app.Run([]string{"", "-e", "foo", "-on", expected}) + expect(t, sliceVal, []string{"foo"}) + expect(t, sliceValDest.Value(), []string{"foo"}) + expect(t, one, true) + expect(t, two, false) + expect(t, name, expected) +} + func TestApp_Float64Flag(t *testing.T) { var meters float64 diff --git a/flag.go b/flag.go index 6aff19b509..b719f8b772 100644 --- a/flag.go +++ b/flag.go @@ -126,6 +126,21 @@ type Flag interface { RunAction(*Context) error } +// DocGenerationFlag is an interface that allows documentation generation for the flag +type DocGenerationFlag interface { + Flag + + // TakesValue returns true if the flag takes a value, otherwise false + TakesValue() bool + + // GetValue returns the flags value as string representation and an empty + // string if the flag takes no value at all. + GetValue() string + + // GetEnvVars returns the env vars for this flag + GetEnvVars() []string +} + // DocGenerationSliceFlag extends DocGenerationFlag for slice-based flags. type DocGenerationSliceFlag interface { DocGenerationFlag @@ -293,8 +308,14 @@ func formatDefault(format string) string { } func stringifyFlag(f Flag) string { - placeholder, usage := unquoteUsage(f.GetUsage()) - needsPlaceholder := f.TakesValue() + // enforce DocGeneration interface on flags to avoid reflection + df, ok := f.(DocGenerationFlag) + if !ok { + return "" + } + + placeholder, usage := unquoteUsage(df.GetUsage()) + needsPlaceholder := df.TakesValue() if needsPlaceholder && placeholder == "" { placeholder = defaultPlaceholder @@ -302,7 +323,7 @@ func stringifyFlag(f Flag) string { defaultValueString := "" - if s := f.GetDefaultText(); s != "" { + if s := df.GetDefaultText(); s != "" { defaultValueString = fmt.Sprintf(formatDefault("%s"), s) } diff --git a/parse.go b/parse.go index a2db306e12..28e24f1020 100644 --- a/parse.go +++ b/parse.go @@ -46,7 +46,10 @@ func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComple } // swap current argument with the split version - args = append(args[:i], append(shortOpts, args[i+1:]...)...) + // do not include args that parsed correctly so far as it would + // trigger Value.Set() on those args and would result in + // duplicates for slice type flags + args = append(shortOpts, args[i+1:]...) argsWereSplit = true break } From fcb0bce79727532b01100803da496a00f940dcb0 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Wed, 5 Oct 2022 09:33:41 -0400 Subject: [PATCH 128/136] Fix failed test --- parse.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/parse.go b/parse.go index 28e24f1020..d79f15a18e 100644 --- a/parse.go +++ b/parse.go @@ -59,13 +59,6 @@ func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComple if !argsWereSplit { return err } - - // Since custom parsing failed, replace the flag set before retrying - newSet, err := ip.newFlagSet() - if err != nil { - return err - } - *set = *newSet } } From 96216756c27da395338d9b79f62fbb4227b7a1a3 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 9 Oct 2022 12:41:30 -0400 Subject: [PATCH 129/136] Remove duplicate DocGenerationFlag interface introduced via merge --- flag.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/flag.go b/flag.go index 6e4a51edec..cec8266b72 100644 --- a/flag.go +++ b/flag.go @@ -135,21 +135,6 @@ type DocGenerationFlag interface { GetEnvVars() []string } -// DocGenerationFlag is an interface that allows documentation generation for the flag -type DocGenerationFlag interface { - Flag - - // TakesValue returns true if the flag takes a value, otherwise false - TakesValue() bool - - // GetValue returns the flags value as string representation and an empty - // string if the flag takes no value at all. - GetValue() string - - // GetEnvVars returns the env vars for this flag - GetEnvVars() []string -} - // DocGenerationSliceFlag extends DocGenerationFlag for slice-based flags. type DocGenerationSliceFlag interface { DocGenerationFlag From 6404f1d1b4cedcc47e6bf82caee8eeffa910bdda Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 10 Oct 2022 10:57:06 -0400 Subject: [PATCH 130/136] Backfill drop of generated GetDefaultText method --- cmd/urfave-cli-genflags/generated.gotmpl | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/cmd/urfave-cli-genflags/generated.gotmpl b/cmd/urfave-cli-genflags/generated.gotmpl index 85bd66fddf..bee11d52da 100644 --- a/cmd/urfave-cli-genflags/generated.gotmpl +++ b/cmd/urfave-cli-genflags/generated.gotmpl @@ -75,16 +75,6 @@ func (f *{{.TypeName}}) TakesValue() bool { return "{{.TypeName }}" != "BoolFlag" } -{{if .GenerateDefaultText}} -// GetDefaultText returns the default text for this flag -func (f *{{.TypeName}}) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} -{{end}} - {{end}}{{/* /if .GenerateFlagInterface */}} {{end}}{{/* /range .SortedFlagTypes */}} From b45820714d0990850b6208bbc02c4a0b4c989e34 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 9 Oct 2022 11:25:02 -0400 Subject: [PATCH 131/136] Build and run `urfave-cli-genflags` via its `Makefile` so that `go.mod` files don't get all confused --- cli.go | 2 +- cmd/urfave-cli-genflags/Makefile | 4 ++++ cmd/urfave-cli-genflags/main.go | 16 ++++++++++++++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/cli.go b/cli.go index b3b864cf5a..28ad0582b6 100644 --- a/cli.go +++ b/cli.go @@ -22,4 +22,4 @@ // } package cli -//go:generate go run cmd/urfave-cli-genflags/main.go +//go:generate make -C cmd/urfave-cli-genflags run diff --git a/cmd/urfave-cli-genflags/Makefile b/cmd/urfave-cli-genflags/Makefile index 3b11415d62..95b932d7b5 100644 --- a/cmd/urfave-cli-genflags/Makefile +++ b/cmd/urfave-cli-genflags/Makefile @@ -19,3 +19,7 @@ smoke-test: build .PHONY: show-cover show-cover: go tool cover -func main.coverprofile + +.PHONY: run +run: build + ./urfave-cli-genflags diff --git a/cmd/urfave-cli-genflags/main.go b/cmd/urfave-cli-genflags/main.go index 1b0fe532a6..7f8deb6d46 100644 --- a/cmd/urfave-cli-genflags/main.go +++ b/cmd/urfave-cli-genflags/main.go @@ -4,6 +4,7 @@ import ( "bytes" "context" _ "embed" + "fmt" "log" "os" "os/exec" @@ -37,6 +38,9 @@ var ( func sh(ctx context.Context, exe string, args ...string) (string, error) { cmd := exec.CommandContext(ctx, exe, args...) cmd.Stderr = os.Stderr + + fmt.Fprintf(os.Stderr, "# ---> %s\n", cmd) + outBytes, err := cmd.Output() return string(outBytes), err } @@ -89,10 +93,18 @@ func main() { Aliases: []string{"N"}, Value: "cli.", }, + &cli.PathFlag{ + Name: "goimports", + Value: filepath.Join(top, ".local/bin/goimports"), + }, }, Action: runGenFlags, } + if err := os.Chdir(top); err != nil { + log.Fatal(err) + } + if err := app.RunContext(ctx, os.Args); err != nil { log.Fatal(err) } @@ -163,11 +175,11 @@ func runGenFlags(cCtx *cli.Context) error { return err } - if _, err := sh(cCtx.Context, "goimports", "-w", cCtx.Path("generated-output")); err != nil { + if _, err := sh(cCtx.Context, cCtx.Path("goimports"), "-w", cCtx.Path("generated-output")); err != nil { return err } - if _, err := sh(cCtx.Context, "goimports", "-w", cCtx.Path("generated-test-output")); err != nil { + if _, err := sh(cCtx.Context, cCtx.Path("goimports"), "-w", cCtx.Path("generated-test-output")); err != nil { return err } From 75aabac5941f414b03946948d36669e1bd47b3f1 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 10 Oct 2022 08:48:50 -0400 Subject: [PATCH 132/136] Use existing goimports installation if available --- cmd/urfave-cli-genflags/Makefile | 3 +++ cmd/urfave-cli-genflags/main.go | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/urfave-cli-genflags/Makefile b/cmd/urfave-cli-genflags/Makefile index 95b932d7b5..acede8e8e5 100644 --- a/cmd/urfave-cli-genflags/Makefile +++ b/cmd/urfave-cli-genflags/Makefile @@ -1,6 +1,9 @@ +GOIMPORTS_BIN ?= $(shell which goimports || true) GOTEST_FLAGS ?= -v --coverprofile main.coverprofile --covermode count --cover github.com/urfave/cli/v2/cmd/urfave-cli-genflags GOBUILD_FLAGS ?= -x +export GOIMPORTS_BIN + .PHONY: all all: test build smoke-test diff --git a/cmd/urfave-cli-genflags/main.go b/cmd/urfave-cli-genflags/main.go index 7f8deb6d46..54cc83e39a 100644 --- a/cmd/urfave-cli-genflags/main.go +++ b/cmd/urfave-cli-genflags/main.go @@ -94,8 +94,9 @@ func main() { Value: "cli.", }, &cli.PathFlag{ - Name: "goimports", - Value: filepath.Join(top, ".local/bin/goimports"), + Name: "goimports", + EnvVars: []string{"GOIMPORTS_BIN"}, + Value: filepath.Join(top, ".local/bin/goimports"), }, }, Action: runGenFlags, From 85ff0c550a4124bfa742cc97b90034b638ca0ece Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 13 Oct 2022 08:15:14 -0400 Subject: [PATCH 133/136] Un-regress from v3 porting losses --- .github/workflows/cli.yml | 5 +- Makefile | 2 +- cmd/urfave-cli-genflags/generated_test.gotmpl | 4 +- flag_duration.go | 8 ++ flag_float64.go | 8 ++ flag_float64_slice.go | 8 ++ flag_generic.go | 8 ++ flag_int.go | 8 ++ flag_int64.go | 8 ++ flag_int64_slice.go | 8 ++ flag_int_slice.go | 8 ++ flag_string_slice.go | 8 ++ flag_test.go | 2 +- flag_timestamp.go | 8 ++ flag_uint.go | 8 ++ flag_uint64.go | 8 ++ flag_uint64_slice.go | 20 --- flag_uint_slice.go | 20 --- godoc-current.txt | 91 +++++++----- zz_generated.flags.go | 136 ++++++------------ zz_generated.flags_test.go | 24 ++++ 21 files changed, 220 insertions(+), 180 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 39298b851b..5c8db7a668 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -46,8 +46,11 @@ jobs: - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' run: make generate - run: make diffcheck - - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' + # TODO: switch once v3 is released {{ + # - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' + - if: 'false' run: make v2diff + # }} - if: success() && matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v3 with: diff --git a/Makefile b/Makefile index f0d41905ea..a167052506 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ GO_RUN_BUILD := go run internal/build/build.go .PHONY: all -all: generate vet test check-binary-size gfmrun yamlfmt v2diff +all: generate vet test check-binary-size gfmrun yamlfmt # NOTE: this is a special catch-all rule to run any of the commands # defined in internal/build/build.go with optional arguments passed diff --git a/cmd/urfave-cli-genflags/generated_test.gotmpl b/cmd/urfave-cli-genflags/generated_test.gotmpl index c91f562ef2..83229b06ba 100644 --- a/cmd/urfave-cli-genflags/generated_test.gotmpl +++ b/cmd/urfave-cli-genflags/generated_test.gotmpl @@ -12,13 +12,13 @@ func Test{{.TypeName}}_SatisfiesFlagInterface(t *testing.T) { } func Test{{.TypeName}}_SatisfiesRequiredFlagInterface(t *testing.T) { - var f {{$.UrfaveCLITestNamespace}}Flag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} + var f {{$.UrfaveCLITestNamespace}}RequiredFlag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} _ = f.IsRequired() } func Test{{.TypeName}}_SatisfiesVisibleFlagInterface(t *testing.T) { - var f {{$.UrfaveCLITestNamespace}}Flag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} + var f {{$.UrfaveCLITestNamespace}}VisibleFlag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} _ = f.IsVisible() } diff --git a/flag_duration.go b/flag_duration.go index b6adce3f48..06391f38e9 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -12,6 +12,14 @@ func (f *DurationFlag) GetValue() string { return f.Value.String() } +// GetDefaultText returns the default text for this flag +func (f *DurationFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Apply populates the flag given the flag set and environment func (f *DurationFlag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_float64.go b/flag_float64.go index 3e21a01df7..d522ac0436 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -12,6 +12,14 @@ func (f *Float64Flag) GetValue() string { return fmt.Sprintf("%v", f.Value) } +// GetDefaultText returns the default text for this flag +func (f *Float64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Apply populates the flag given the flag set and environment func (f *Float64Flag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 3f8aef1d55..d060432f06 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -98,6 +98,14 @@ func (f *Float64SliceFlag) GetValue() string { return strings.Join(defaultVals, ", ") } +// GetDefaultText returns the default text for this flag +func (f *Float64SliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // IsSliceFlag implements DocGenerationSliceFlag. func (f *Float64SliceFlag) IsSliceFlag() bool { return true diff --git a/flag_generic.go b/flag_generic.go index daed474833..d0f6527ce1 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -20,6 +20,14 @@ func (f *GenericFlag) GetValue() string { return "" } +// GetDefaultText returns the default text for this flag +func (f *GenericFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Apply takes the flagset and calls Set on the generic flag with the value // provided by the user for parsing by the flag func (f *GenericFlag) Apply(set *flag.FlagSet) error { diff --git a/flag_int.go b/flag_int.go index bcaab7fa7e..a716f3914f 100644 --- a/flag_int.go +++ b/flag_int.go @@ -12,6 +12,14 @@ func (f *IntFlag) GetValue() string { return fmt.Sprintf("%d", f.Value) } +// GetDefaultText returns the default text for this flag +func (f *IntFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Apply populates the flag given the flag set and environment func (f *IntFlag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_int64.go b/flag_int64.go index d8e591d864..05d8506bed 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -12,6 +12,14 @@ func (f *Int64Flag) GetValue() string { return fmt.Sprintf("%d", f.Value) } +// GetDefaultText returns the default text for this flag +func (f *Int64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Apply populates the flag given the flag set and environment func (f *Int64Flag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 6844e6d1a6..2db2ceab24 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -99,6 +99,14 @@ func (f *Int64SliceFlag) GetValue() string { return strings.Join(defaultVals, ", ") } +// GetDefaultText returns the default text for this flag +func (f *Int64SliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // IsSliceFlag implements DocGenerationSliceFlag. func (f *Int64SliceFlag) IsSliceFlag() bool { return true diff --git a/flag_int_slice.go b/flag_int_slice.go index d3d3ea750b..a2c9f21301 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -110,6 +110,14 @@ func (f *IntSliceFlag) GetValue() string { return strings.Join(defaultVals, ", ") } +// GetDefaultText returns the default text for this flag +func (f *IntSliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // IsSliceFlag implements DocGenerationSliceFlag. func (f *IntSliceFlag) IsSliceFlag() bool { return true diff --git a/flag_string_slice.go b/flag_string_slice.go index bdeb674fb2..f53c0e6098 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -91,6 +91,14 @@ func (f *StringSliceFlag) GetValue() string { return strings.Join(defaultVals, ", ") } +// GetDefaultText returns the default text for this flag +func (f *StringSliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // IsSliceFlag implements DocGenerationSliceFlag. func (f *StringSliceFlag) IsSliceFlag() bool { return true diff --git a/flag_test.go b/flag_test.go index 3aafa8bea2..7601b4afec 100644 --- a/flag_test.go +++ b/flag_test.go @@ -225,7 +225,7 @@ func TestFlagsFromEnv(t *testing.T) { f, ok := test.flag.(DocGenerationFlag) if !ok { - t.Errorf("flag %v needs to implement DocGenerationFlag to retrieve env vars", test.flag) + t.Errorf("flag %[1]q (%[1]T) needs to implement DocGenerationFlag to retrieve env vars", test.flag) } envVarSlice := f.GetEnvVars() _ = os.Setenv(envVarSlice[0], test.input) diff --git a/flag_timestamp.go b/flag_timestamp.go index 671be8b986..4302f130ed 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -81,6 +81,14 @@ func (f *TimestampFlag) GetValue() string { return "" } +// GetDefaultText returns the default text for this flag +func (f *TimestampFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Apply populates the flag given the flag set and environment func (f *TimestampFlag) Apply(set *flag.FlagSet) error { if f.Layout == "" { diff --git a/flag_uint.go b/flag_uint.go index e1b04508c8..2214386849 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -37,6 +37,14 @@ func (f *UintFlag) GetValue() string { return fmt.Sprintf("%d", f.Value) } +// GetDefaultText returns the default text for this flag +func (f *UintFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Get returns the flag’s value in the given Context. func (f *UintFlag) Get(ctx *Context) uint { return ctx.Uint(f.Name) diff --git a/flag_uint64.go b/flag_uint64.go index 593b2606ae..e314a4a307 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -37,6 +37,14 @@ func (f *Uint64Flag) GetValue() string { return fmt.Sprintf("%d", f.Value) } +// GetDefaultText returns the default text for this flag +func (f *Uint64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Get returns the flag’s value in the given Context. func (f *Uint64Flag) Get(ctx *Context) uint64 { return ctx.Uint64(f.Name) diff --git a/flag_uint64_slice.go b/flag_uint64_slice.go index 873ad9bd55..ae251ac793 100644 --- a/flag_uint64_slice.go +++ b/flag_uint64_slice.go @@ -91,21 +91,6 @@ func (f *Uint64SliceFlag) String() string { return FlagStringer(f) } -// TakesValue returns true of the flag takes a value, otherwise false -func (f *Uint64SliceFlag) TakesValue() bool { - return true -} - -// GetUsage returns the usage string for the flag -func (f *Uint64SliceFlag) GetUsage() string { - return f.Usage -} - -// GetCategory returns the category for the flag -func (f *Uint64SliceFlag) GetCategory() string { - return f.Category -} - // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *Uint64SliceFlag) GetValue() string { @@ -126,11 +111,6 @@ func (f *Uint64SliceFlag) GetDefaultText() string { return f.GetValue() } -// GetEnvVars returns the env vars for this flag -func (f *Uint64SliceFlag) GetEnvVars() []string { - return f.EnvVars -} - // IsSliceFlag implements DocGenerationSliceFlag. func (f *Uint64SliceFlag) IsSliceFlag() bool { return true diff --git a/flag_uint_slice.go b/flag_uint_slice.go index 2a1fcc17f1..92848f22fc 100644 --- a/flag_uint_slice.go +++ b/flag_uint_slice.go @@ -102,21 +102,6 @@ func (f *UintSliceFlag) String() string { return FlagStringer(f) } -// TakesValue returns true of the flag takes a value, otherwise false -func (f *UintSliceFlag) TakesValue() bool { - return true -} - -// GetUsage returns the usage string for the flag -func (f *UintSliceFlag) GetUsage() string { - return f.Usage -} - -// GetCategory returns the category for the flag -func (f *UintSliceFlag) GetCategory() string { - return f.Category -} - // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *UintSliceFlag) GetValue() string { @@ -137,11 +122,6 @@ func (f *UintSliceFlag) GetDefaultText() string { return f.GetValue() } -// GetEnvVars returns the env vars for this flag -func (f *UintSliceFlag) GetEnvVars() []string { - return f.EnvVars -} - // IsSliceFlag implements DocGenerationSliceFlag. func (f *UintSliceFlag) IsSliceFlag() bool { return true diff --git a/godoc-current.txt b/godoc-current.txt index 03b76f8c79..e5d2116983 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -32,7 +32,7 @@ var ( SuggestDidYouMeanTemplate string = suggestDidYouMeanTemplate ) var AppHelpTemplate = `NAME: - {{$v := offset .Name 6}}{{wrap .Name 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}} + {{template "helpNameTemplate" .}} USAGE: {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} @@ -41,52 +41,39 @@ VERSION: {{.Version}}{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{wrap .Description 3}}{{end}}{{if len .Authors}} + {{template "descriptionTemplate" .}}{{end}} +{{- if len .Authors}} -AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: - {{range $index, $author := .Authors}}{{if $index}} - {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} +AUTHOR{{template "authorsTemplate" .}}{{end}}{{if .VisibleCommands}} -COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} - {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} +COMMANDS:{{template "visibleCommandCategoryTemplate" .}}{{end}}{{if .VisibleFlagCategories}} -GLOBAL OPTIONS:{{range .VisibleFlagCategories}} - {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}} - {{end}}{{end}}{{else}}{{if .VisibleFlags}} +GLOBAL OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} -GLOBAL OPTIONS: - {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{wrap $option.String 6}}{{end}}{{end}}{{end}}{{if .Copyright}} +GLOBAL OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}{{if .Copyright}} COPYRIGHT: - {{wrap .Copyright 3}}{{end}} + {{template "copyrightTemplate" .}}{{end}} ` AppHelpTemplate is the text template for the Default help topic. cli.go uses text/template to render templates. You can render custom help text by setting this variable. var CommandHelpTemplate = `NAME: - {{$v := offset .HelpName 6}}{{wrap .HelpName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}} + {{template "helpNameTemplate" .}} USAGE: - {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} + {{template "usageTemplate" .}}{{if .Category}} CATEGORY: {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{wrap .Description 3}}{{end}}{{if .VisibleFlagCategories}} + {{template "descriptionTemplate" .}}{{end}}{{if .VisibleFlagCategories}} -OPTIONS:{{range .VisibleFlagCategories}} - {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}}{{end}}{{end}}{{else}}{{if .VisibleFlags}} +OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} -OPTIONS: - {{range .VisibleFlags}}{{.}}{{end}}{{end}}{{end}} -` +OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}` CommandHelpTemplate is the text template for the command help topic. cli.go uses text/template to render templates. You can render custom help text by setting this variable. @@ -145,22 +132,19 @@ var OsExiter = os.Exit os.Exit. var SubcommandHelpTemplate = `NAME: - {{.HelpName}} - {{.Usage}} + {{template "helpNameTemplate" .}} USAGE: {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{wrap .Description 3}}{{end}} + {{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}} -COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} - {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} +COMMANDS:{{template "visibleCommandTemplate" .}}{{end}}{{if .VisibleFlagCategories}} -OPTIONS: - {{range .VisibleFlags}}{{.}}{{end}}{{end}} -` +OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} + +OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}` SubcommandHelpTemplate is the text template for the subcommand help topic. cli.go uses text/template to render templates. You can render custom help text by setting this variable. @@ -586,6 +570,9 @@ func (c *Command) Run(ctx *Context) (err error) Run invokes the command given the context, parses ctx.Args() to generate command-specific flags +func (c *Command) VisibleCommands() []*Command + VisibleCommands returns a slice of the Commands with Hidden=false + func (c *Command) VisibleFlagCategories() []VisibleFlagCategory VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain @@ -754,6 +741,14 @@ type DocGenerationFlag interface { DocGenerationFlag is an interface that allows documentation generation for the flag +type DocGenerationSliceFlag interface { + DocGenerationFlag + + // IsSliceFlag returns true for flags that can be given multiple times. + IsSliceFlag() bool +} + DocGenerationSliceFlag extends DocGenerationFlag for slice-based flags. + type DurationFlag struct { Name string @@ -1077,6 +1072,9 @@ func (f *Float64SliceFlag) IsRequired() bool func (f *Float64SliceFlag) IsSet() bool IsSet returns whether or not the flag has been set through env or file +func (f *Float64SliceFlag) IsSliceFlag() bool + IsSliceFlag implements DocGenerationSliceFlag. + func (f *Float64SliceFlag) IsVisible() bool IsVisible returns true if the flag is not hidden, otherwise false @@ -1312,6 +1310,9 @@ func (f *Int64SliceFlag) IsRequired() bool func (f *Int64SliceFlag) IsSet() bool IsSet returns whether or not the flag has been set through env or file +func (f *Int64SliceFlag) IsSliceFlag() bool + IsSliceFlag implements DocGenerationSliceFlag. + func (f *Int64SliceFlag) IsVisible() bool IsVisible returns true if the flag is not hidden, otherwise false @@ -1477,6 +1478,9 @@ func (f *IntSliceFlag) IsRequired() bool func (f *IntSliceFlag) IsSet() bool IsSet returns whether or not the flag has been set through env or file +func (f *IntSliceFlag) IsSliceFlag() bool + IsSliceFlag implements DocGenerationSliceFlag. + func (f *IntSliceFlag) IsVisible() bool IsVisible returns true if the flag is not hidden, otherwise false @@ -1817,6 +1821,9 @@ func (f *StringSliceFlag) IsRequired() bool func (f *StringSliceFlag) IsSet() bool IsSet returns whether or not the flag has been set through env or file +func (f *StringSliceFlag) IsSliceFlag() bool + IsSliceFlag implements DocGenerationSliceFlag. + func (f *StringSliceFlag) IsVisible() bool IsVisible returns true if the flag is not hidden, otherwise false @@ -2057,7 +2064,7 @@ func (f *Uint64SliceFlag) Get(ctx *Context) []uint64 Get returns the flag’s value in the given Context. func (f *Uint64SliceFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *Uint64SliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -2078,6 +2085,9 @@ func (f *Uint64SliceFlag) IsRequired() bool func (f *Uint64SliceFlag) IsSet() bool IsSet returns whether or not the flag has been set through env or file +func (f *Uint64SliceFlag) IsSliceFlag() bool + IsSliceFlag implements DocGenerationSliceFlag. + func (f *Uint64SliceFlag) IsVisible() bool IsVisible returns true if the flag is not hidden, otherwise false @@ -2091,7 +2101,7 @@ func (f *Uint64SliceFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *Uint64SliceFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type UintFlag struct { Name string @@ -2216,7 +2226,7 @@ func (f *UintSliceFlag) Get(ctx *Context) []uint Get returns the flag’s value in the given Context. func (f *UintSliceFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *UintSliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -2237,6 +2247,9 @@ func (f *UintSliceFlag) IsRequired() bool func (f *UintSliceFlag) IsSet() bool IsSet returns whether or not the flag has been set through env or file +func (f *UintSliceFlag) IsSliceFlag() bool + IsSliceFlag implements DocGenerationSliceFlag. + func (f *UintSliceFlag) IsVisible() bool IsVisible returns true if the flag is not hidden, otherwise false @@ -2250,7 +2263,7 @@ func (f *UintSliceFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *UintSliceFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type VisibleFlag interface { Flag diff --git a/zz_generated.flags.go b/zz_generated.flags.go index 68934c7707..8c4e6face5 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -66,14 +66,6 @@ func (f *Float64SliceFlag) TakesValue() bool { return "Float64SliceFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *Float64SliceFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // GenericFlag is a flag with type Generic type GenericFlag struct { Name string @@ -143,14 +135,6 @@ func (f *GenericFlag) TakesValue() bool { return "GenericFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *GenericFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Int64SliceFlag is a flag with type *Int64Slice type Int64SliceFlag struct { Name string @@ -213,14 +197,6 @@ func (f *Int64SliceFlag) TakesValue() bool { return "Int64SliceFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *Int64SliceFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // IntSliceFlag is a flag with type *IntSlice type IntSliceFlag struct { Name string @@ -283,14 +259,6 @@ func (f *IntSliceFlag) TakesValue() bool { return "IntSliceFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *IntSliceFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // PathFlag is a flag with type Path type PathFlag struct { Name string @@ -424,14 +392,6 @@ func (f *StringSliceFlag) TakesValue() bool { return "StringSliceFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *StringSliceFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // TimestampFlag is a flag with type *Timestamp type TimestampFlag struct { Name string @@ -503,14 +463,6 @@ func (f *TimestampFlag) TakesValue() bool { return "TimestampFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *TimestampFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Uint64SliceFlag is a flag with type *Uint64Slice type Uint64SliceFlag struct { Name string @@ -553,6 +505,26 @@ func (f *Uint64SliceFlag) IsVisible() bool { return !f.Hidden } +// GetCategory returns the category of the flag +func (f *Uint64SliceFlag) GetCategory() string { + return f.Category +} + +// GetUsage returns the usage string for the flag +func (f *Uint64SliceFlag) GetUsage() string { + return f.Usage +} + +// GetEnvVars returns the env vars for this flag +func (f *Uint64SliceFlag) GetEnvVars() []string { + return f.EnvVars +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *Uint64SliceFlag) TakesValue() bool { + return "Uint64SliceFlag" != "BoolFlag" +} + // UintSliceFlag is a flag with type *UintSlice type UintSliceFlag struct { Name string @@ -595,6 +567,26 @@ func (f *UintSliceFlag) IsVisible() bool { return !f.Hidden } +// GetCategory returns the category of the flag +func (f *UintSliceFlag) GetCategory() string { + return f.Category +} + +// GetUsage returns the usage string for the flag +func (f *UintSliceFlag) GetUsage() string { + return f.Usage +} + +// GetEnvVars returns the env vars for this flag +func (f *UintSliceFlag) GetEnvVars() []string { + return f.EnvVars +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *UintSliceFlag) TakesValue() bool { + return "UintSliceFlag" != "BoolFlag" +} + // BoolFlag is a flag with type bool type BoolFlag struct { Name string @@ -731,14 +723,6 @@ func (f *Float64Flag) TakesValue() bool { return "Float64Flag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *Float64Flag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // IntFlag is a flag with type int type IntFlag struct { Name string @@ -808,14 +792,6 @@ func (f *IntFlag) TakesValue() bool { return "IntFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *IntFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Int64Flag is a flag with type int64 type Int64Flag struct { Name string @@ -885,14 +861,6 @@ func (f *Int64Flag) TakesValue() bool { return "Int64Flag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *Int64Flag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // StringFlag is a flag with type string type StringFlag struct { Name string @@ -1029,14 +997,6 @@ func (f *DurationFlag) TakesValue() bool { return "DurationFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *DurationFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // UintFlag is a flag with type uint type UintFlag struct { Name string @@ -1106,14 +1066,6 @@ func (f *UintFlag) TakesValue() bool { return "UintFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *UintFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Uint64Flag is a flag with type uint64 type Uint64Flag struct { Name string @@ -1183,12 +1135,4 @@ func (f *Uint64Flag) TakesValue() bool { return "Uint64Flag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *Uint64Flag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // vim:ro diff --git a/zz_generated.flags_test.go b/zz_generated.flags_test.go index 6862afc36e..b7c68153c4 100644 --- a/zz_generated.flags_test.go +++ b/zz_generated.flags_test.go @@ -167,6 +167,18 @@ func TestUint64SliceFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } +func TestUint64SliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.Uint64SliceFlag{} + + _ = f.IsRequired() +} + +func TestUint64SliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.Uint64SliceFlag{} + + _ = f.IsVisible() +} + func TestUintSliceFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.UintSliceFlag{} @@ -174,6 +186,18 @@ func TestUintSliceFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } +func TestUintSliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.UintSliceFlag{} + + _ = f.IsRequired() +} + +func TestUintSliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.UintSliceFlag{} + + _ = f.IsVisible() +} + func TestBoolFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.BoolFlag{} From d294a88a475662fe366f7968cb8605c418327ba5 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 8 Nov 2022 08:17:56 -0500 Subject: [PATCH 134/136] Adjust expectations around categorized flags --- command.go | 10 ++++++---- command_test.go | 18 +++++++++++------- godoc-current.txt | 26 +++++++++++++------------- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/command.go b/command.go index 8ccf486347..f02fe2f3b1 100644 --- a/command.go +++ b/command.go @@ -377,12 +377,14 @@ func (c *Command) VisibleCommands() []*Command { func (c *Command) VisibleFlagCategories() []VisibleFlagCategory { if c.flagCategories == nil { c.flagCategories = newFlagCategories() - for _, fl := range c.Flags { - if cf, ok := fl.(CategorizableFlag); ok { - c.flagCategories.AddFlag(cf.GetCategory(), fl) - } + } + + for _, fl := range c.Flags { + if cf, ok := fl.(CategorizableFlag); ok { + c.flagCategories.AddFlag(cf.GetCategory(), fl) } } + return c.flagCategories.VisibleCategories() } diff --git a/command_test.go b/command_test.go index 478c3251a6..6c2ecdec4b 100644 --- a/command_test.go +++ b/command_test.go @@ -470,17 +470,21 @@ func TestCommand_VisibleFlagCategories(t *testing.T) { } vfc := c.VisibleFlagCategories() - if len(vfc) != 1 { - t.Fatalf("unexpected visible flag categories %+v", vfc) + if len(vfc) < 2 { + t.Fatalf("unexpected visible flag categories %+#v", vfc) } - if vfc[0].Name() != "cat1" { - t.Errorf("expected category name cat1 got %s", vfc[0].Name()) + + intdCatFlag := vfc[1] + + if intdCatFlag.Name() != "cat1" { + t.Errorf("expected category name cat1 got %q", intdCatFlag.Name()) } - if len(vfc[0].Flags()) != 1 { - t.Fatalf("expected flag category to have just one flag got %+v", vfc[0].Flags()) + if len(intdCatFlag.Flags()) != 1 { + t.Fatalf("expected flag category to have just one flag got %+v", intdCatFlag.Flags()) } - fl := vfc[0].Flags()[0] + fl := intdCatFlag.Flags()[0] + if !reflect.DeepEqual(fl.Names(), []string{"intd", "altd1", "altd2"}) { t.Errorf("unexpected flag %+v", fl.Names()) } diff --git a/godoc-current.txt b/godoc-current.txt index a6ea145e5e..a6cb6f069b 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -5,24 +5,24 @@ line Go applications. cli is designed to be easy to understand and write, the most simple cli application can be written as follows: func main() { - (&cli.App{}).Run(os.Args) + (&cli.App{}).Run(os.Args) } Of course this application does not do much, so let's make this an actual application: - func main() { - app := &cli.App{ - Name: "greet", - Usage: "say a greeting", - Action: func(c *cli.Context) error { - fmt.Println("Greetings") - return nil - }, - } - - app.Run(os.Args) - } + func main() { + app := &cli.App{ + Name: "greet", + Usage: "say a greeting", + Action: func(c *cli.Context) error { + fmt.Println("Greetings") + return nil + }, + } + + app.Run(os.Args) + } VARIABLES From 5e86a2c44d9b1c36c7179f97ce8073d872f81208 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 8 Nov 2022 09:13:10 -0500 Subject: [PATCH 135/136] Leave imports to goimports --- cmd/urfave-cli-genflags/generated_altsrc.gotmpl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cmd/urfave-cli-genflags/generated_altsrc.gotmpl b/cmd/urfave-cli-genflags/generated_altsrc.gotmpl index 737eb82d17..8fc6d0ea2e 100644 --- a/cmd/urfave-cli-genflags/generated_altsrc.gotmpl +++ b/cmd/urfave-cli-genflags/generated_altsrc.gotmpl @@ -2,11 +2,6 @@ package {{.PackageName}} -import ( - "flag" - "github.com/urfave/cli/v2" -) - {{range .SortedFlagTypes}} // {{.TypeName}} is the flag type that wraps cli.{{.TypeName}} to allow // for other values to be specified From a5ec63b31f602fd9b0979d18a4df4780e103c217 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 8 Nov 2022 10:49:42 -0500 Subject: [PATCH 136/136] More porting fixes and un-lost changes --- app.go | 4 +++- app_test.go | 33 ++++++++++++++++++++++++++++----- category.go | 4 +++- command.go | 9 +-------- command_test.go | 18 +++++++----------- godoc-current.txt | 6 ++++-- help.go | 6 +----- help_test.go | 12 ++++++++---- template.go | 10 ++++++---- 9 files changed, 61 insertions(+), 41 deletions(-) diff --git a/app.go b/app.go index 2f1557ade8..e7f79c5130 100644 --- a/app.go +++ b/app.go @@ -251,7 +251,9 @@ func (a *App) Setup() { a.flagCategories = newFlagCategories() for _, fl := range a.Flags { if cf, ok := fl.(CategorizableFlag); ok { - a.flagCategories.AddFlag(cf.GetCategory(), cf) + if cf.GetCategory() != "" { + a.flagCategories.AddFlag(cf.GetCategory(), cf) + } } } diff --git a/app_test.go b/app_test.go index 2e21c476f2..b85848f740 100644 --- a/app_test.go +++ b/app_test.go @@ -144,8 +144,8 @@ func ExampleApp_Run_appHelp() { // help, h Shows a list of commands or help for one command // // GLOBAL OPTIONS: - // --help, -h show help (default: false) // --name value a name to say (default: "bob") + // --help, -h show help (default: false) // --version, -v print the version (default: false) } @@ -177,7 +177,7 @@ func ExampleApp_Run_commandHelp() { // greet describeit - use it to see a description // // USAGE: - // greet describeit [arguments...] + // greet describeit [command options] [arguments...] // // DESCRIPTION: // This is how we describe describeit the function @@ -2304,10 +2304,33 @@ func TestApp_VisibleCategories(t *testing.T) { } func TestApp_VisibleFlagCategories(t *testing.T) { - app := &App{} + app := &App{ + Flags: []Flag{ + &StringFlag{ + Name: "strd", // no category set + }, + &Int64Flag{ + Name: "intd", + Aliases: []string{"altd1", "altd2"}, + Category: "cat1", + }, + }, + } + app.Setup() vfc := app.VisibleFlagCategories() - if len(vfc) != 0 { - t.Errorf("unexpected visible flag categories %+v", vfc) + if len(vfc) != 1 { + t.Fatalf("unexpected visible flag categories %+v", vfc) + } + if vfc[0].Name() != "cat1" { + t.Errorf("expected category name cat1 got %s", vfc[0].Name()) + } + if len(vfc[0].Flags()) != 1 { + t.Fatalf("expected flag category to have just one flag got %+v", vfc[0].Flags()) + } + + fl := vfc[0].Flags()[0] + if !reflect.DeepEqual(fl.Names(), []string{"intd", "altd1", "altd2"}) { + t.Errorf("unexpected flag %+v", fl.Names()) } } diff --git a/category.go b/category.go index 8bf325e203..7aca0c7684 100644 --- a/category.go +++ b/category.go @@ -102,7 +102,9 @@ func newFlagCategoriesFromFlags(fs []Flag) FlagCategories { fc := newFlagCategories() for _, fl := range fs { if cf, ok := fl.(CategorizableFlag); ok { - fc.AddFlag(cf.GetCategory(), cf) + if cf.GetCategory() != "" { + fc.AddFlag(cf.GetCategory(), cf) + } } } diff --git a/command.go b/command.go index f02fe2f3b1..c5939d4ec8 100644 --- a/command.go +++ b/command.go @@ -376,15 +376,8 @@ func (c *Command) VisibleCommands() []*Command { // VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain func (c *Command) VisibleFlagCategories() []VisibleFlagCategory { if c.flagCategories == nil { - c.flagCategories = newFlagCategories() + c.flagCategories = newFlagCategoriesFromFlags(c.Flags) } - - for _, fl := range c.Flags { - if cf, ok := fl.(CategorizableFlag); ok { - c.flagCategories.AddFlag(cf.GetCategory(), fl) - } - } - return c.flagCategories.VisibleCategories() } diff --git a/command_test.go b/command_test.go index 6c2ecdec4b..478c3251a6 100644 --- a/command_test.go +++ b/command_test.go @@ -470,21 +470,17 @@ func TestCommand_VisibleFlagCategories(t *testing.T) { } vfc := c.VisibleFlagCategories() - if len(vfc) < 2 { - t.Fatalf("unexpected visible flag categories %+#v", vfc) + if len(vfc) != 1 { + t.Fatalf("unexpected visible flag categories %+v", vfc) } - - intdCatFlag := vfc[1] - - if intdCatFlag.Name() != "cat1" { - t.Errorf("expected category name cat1 got %q", intdCatFlag.Name()) + if vfc[0].Name() != "cat1" { + t.Errorf("expected category name cat1 got %s", vfc[0].Name()) } - if len(intdCatFlag.Flags()) != 1 { - t.Fatalf("expected flag category to have just one flag got %+v", intdCatFlag.Flags()) + if len(vfc[0].Flags()) != 1 { + t.Fatalf("expected flag category to have just one flag got %+v", vfc[0].Flags()) } - fl := intdCatFlag.Flags()[0] - + fl := vfc[0].Flags()[0] if !reflect.DeepEqual(fl.Names(), []string{"intd", "altd1", "altd2"}) { t.Errorf("unexpected flag %+v", fl.Names()) } diff --git a/godoc-current.txt b/godoc-current.txt index a6cb6f069b..9abda4be5f 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -73,7 +73,8 @@ DESCRIPTION: OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} -OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}` +OPTIONS:{{template "visibleFlagTemplate" .}}{{end}} +` CommandHelpTemplate is the text template for the command help topic. cli.go uses text/template to render templates. You can render custom help text by setting this variable. @@ -144,7 +145,8 @@ COMMANDS:{{template "visibleCommandTemplate" .}}{{end}}{{if .VisibleFlagCategori OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} -OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}` +OPTIONS:{{template "visibleFlagTemplate" .}}{{end}} +` SubcommandHelpTemplate is the text template for the subcommand help topic. cli.go uses text/template to render templates. You can render custom help text by setting this variable. diff --git a/help.go b/help.go index a7cc96a880..2ccd3b71e6 100644 --- a/help.go +++ b/help.go @@ -250,11 +250,7 @@ func ShowCommandHelp(ctx *Context, command string) error { c.Subcommands = append(c.Subcommands, helpCommandDontUse) } if !ctx.App.HideHelp && HelpFlag != nil { - if c.flagCategories == nil { - c.flagCategories = newFlagCategoriesFromFlags([]Flag{HelpFlag}) - } else { - c.flagCategories.AddFlag("", HelpFlag) - } + c.appendFlag(HelpFlag) } templ := c.CustomHelpTemplate if templ == "" { diff --git a/help_test.go b/help_test.go index e5f37e30c6..b1b456c8ae 100644 --- a/help_test.go +++ b/help_test.go @@ -1366,7 +1366,8 @@ DESCRIPTION: case OPTIONS: - --help, -h show help (default: false) + --help, -h show help + (default: false) ` if output.String() != expected { @@ -1435,7 +1436,8 @@ USAGE: even more OPTIONS: - --help, -h show help (default: false) + --help, -h show help + (default: false) ` if output.String() != expected { @@ -1510,8 +1512,10 @@ USAGE: even more OPTIONS: - --help, -h show help (default: false) - --test-f value my test usage + --test-f value my test + usage + --help, -h show help + (default: false) ` if output.String() != expected { diff --git a/template.go b/template.go index 23390c7ddb..b565ba61eb 100644 --- a/template.go +++ b/template.go @@ -18,8 +18,8 @@ var visibleFlagCategoryTemplate = `{{range .VisibleFlagCategories}} {{else}}{{$e}} {{end}}{{end}}{{end}}` -var visibleFlagTemplate = `{{range $index, $option := .VisibleFlags}}{{if $index}}{{end}} - {{wrap $option.String 6}}{{end}}` +var visibleFlagTemplate = `{{range $i, $e := .VisibleFlags}} + {{wrap $e.String 6}}{{end}}` var versionTemplate = `{{if .Version}}{{if not .HideVersion}} @@ -73,7 +73,8 @@ DESCRIPTION: OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} -OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}` +OPTIONS:{{template "visibleFlagTemplate" .}}{{end}} +` // SubcommandHelpTemplate is the text template for the subcommand help topic. // cli.go uses text/template to render templates. You can @@ -91,7 +92,8 @@ COMMANDS:{{template "visibleCommandTemplate" .}}{{end}}{{if .VisibleFlagCategori OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} -OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}` +OPTIONS:{{template "visibleFlagTemplate" .}}{{end}} +` var MarkdownDocTemplate = `{{if gt .SectionNum 0}}% {{ .App.Name }} {{ .SectionNum }}