Repository: urfave/cli
Pull request #1847 (2023-12-21 15:03:13)

v2: Add build tag urfave_cli_no_suggest
## What type of PR is this?

- cleanup
- feature

## What this PR does / why we need it:

Like `urfave_cli_no_docs` (in docs.go), allow to disable flag and command suggestions at build time (and drop dependency on module [`github.com/xrash/smetrics`](https://pkg.go.dev/github.com/xrash/smetrics)) with a new `urfave_cli_no_suggest` build tag:

```console
$ go build -tags urfave_cli_no_suggest,urfave_cli_no_docs
```


## Release Notes

```release-note
Add `urfave_cli_no_suggest` build tag to disable suggestions and drop dependency on module [`github.com/xrash/smetrics`](https://pkg.go.dev/github.com/xrash/smetrics) for smaller binaries.
```


dearchap: @dolmen Can you fix the test failures ? 

Patch:
diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml
index fe0fc7a..74afea8 100644
--- a/.github/workflows/cli.yml
+++ b/.github/workflows/cli.yml
@@ -15,19 +15,19 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
-        go: [stable, oldstable]
+        go: [1.19.x, 1.20.x]
     name: ${{ matrix.os }} @ Go ${{ matrix.go }}
     runs-on: ${{ matrix.os }}
     steps:
-      - uses: actions/checkout@v4
-      - uses: actions/setup-go@v4
+      - uses: actions/setup-go@v3
         with:
           go-version: ${{ matrix.go }}
       - name: Set PATH
         run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}"
-      - if: matrix.go == 'stable' && matrix.os == 'ubuntu-latest'
+      - uses: actions/checkout@v3
+      - if: matrix.go == '1.20.x' && matrix.os == 'ubuntu-latest'
         run: make ensure-goimports
-      - if: matrix.go == 'stable' && matrix.os == 'ubuntu-latest'
+      - if: matrix.go == '1.20.x' && matrix.os == 'ubuntu-latest'
         run: make lint
       - run: make vet
       - run: make test
@@ -40,12 +40,12 @@ jobs:
           GFLAGS: -tags urfave_cli_no_docs
       - run: make check-binary-size
       - run: make yamlfmt
-      - if: matrix.go == 'stable' && matrix.os == 'ubuntu-latest'
+      - if: matrix.go == '1.20.x' && matrix.os == 'ubuntu-latest'
         run: make generate
       - run: make diffcheck
-      - if: matrix.go == 'stable' && matrix.os == 'ubuntu-latest'
+      - if: matrix.go == '1.20.x' && matrix.os == 'ubuntu-latest'
         run: make v2diff
-      - if: success() && matrix.go == 'stable' && matrix.os == 'ubuntu-latest'
+      - if: success() && matrix.go == '1.20.x' && matrix.os == 'ubuntu-latest'
         uses: codecov/codecov-action@v3
         with:
           token: ${{ secrets.CODECOV_TOKEN }}
@@ -54,15 +54,15 @@ jobs:
     name: test-docs
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
-      - uses: actions/setup-go@v4
+      - uses: actions/setup-go@v3
         with:
-          go-version: stable
-      - uses: actions/setup-node@v4
+          go-version: 1.20.x
+      - uses: actions/setup-node@v3
         with:
           node-version: '16'
       - name: Set PATH
         run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}"
+      - uses: actions/checkout@v3
       - run: make ensure-gfmrun
       - run: make gfmrun
         env:
@@ -76,7 +76,7 @@ jobs:
     needs: [test-docs]
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v3
         with:
           fetch-depth: 0
       - run: make ensure-mkdocs
diff --git a/app.go b/app.go
index 741163a..1915d4f 100644
--- a/app.go
+++ b/app.go
@@ -23,8 +23,8 @@ var (
 		fmt.Sprintf("See %s", appActionDeprecationURL), 2)
 	ignoreFlagPrefix = "test." // this is to ignore test flags when adding flags from other packages
 
-	SuggestFlag               SuggestFlagFunc    = suggestFlag
-	SuggestCommand            SuggestCommandFunc = suggestCommand
+	SuggestFlag               SuggestFlagFunc    = nil // initialized in suggestions.go unless built with urfave_cli_no_suggest
+	SuggestCommand            SuggestCommandFunc = nil // initialized in suggestions.go unless built with urfave_cli_no_suggest
 	SuggestDidYouMeanTemplate string             = suggestDidYouMeanTemplate
 )
 
@@ -39,8 +39,6 @@ type App struct {
 	Usage string
 	// Text to override the USAGE section of help
 	UsageText string
-	// Whether this command supports arguments
-	Args bool
 	// Description of the program argument format.
 	ArgsUsage string
 	// Version of the program
@@ -368,6 +366,9 @@ func (a *App) suggestFlagFromError(err error, command string) (string, error) {
 		hideHelp = hideHelp || cmd.HideHelp
 	}
 
+	if SuggestFlag == nil {
+		return "", err
+	}
 	suggestion := SuggestFlag(flags, flag, hideHelp)
 	if len(suggestion) == 0 {
 		return "", err
diff --git a/command.go b/command.go
index c20a571..c318da9 100644
--- a/command.go
+++ b/command.go
@@ -20,8 +20,6 @@ type Command struct {
 	UsageText string
 	// A longer explanation of how the command works
 	Description string
-	// Whether this command supports arguments
-	Args bool
 	// A short description of the arguments of this command
 	ArgsUsage string
 	// The category the command is part of
diff --git a/flag_uint64_slice.go b/flag_uint64_slice.go
index d342018..e845dd5 100644
--- a/flag_uint64_slice.go
+++ b/flag_uint64_slice.go
@@ -190,15 +190,6 @@ func (f *Uint64SliceFlag) Get(ctx *Context) []uint64 {
 	return ctx.Uint64Slice(f.Name)
 }
 
-// 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 4dc13e1..d2aed48 100644
--- a/flag_uint_slice.go
+++ b/flag_uint_slice.go
@@ -201,15 +201,6 @@ func (f *UintSliceFlag) Get(ctx *Context) []uint {
 	return ctx.UintSlice(f.Name)
 }
 
-// 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 e87b13a..46a260c 100644
--- a/godoc-current.txt
+++ b/godoc-current.txt
@@ -27,15 +27,15 @@ application:
 VARIABLES
 
 var (
-	SuggestFlag               SuggestFlagFunc    = suggestFlag
-	SuggestCommand            SuggestCommandFunc = suggestCommand
+	SuggestFlag               SuggestFlagFunc    = nil // initialized in suggestions.go unless built with urfave_cli_no_suggest
+	SuggestCommand            SuggestCommandFunc = nil // initialized in suggestions.go unless built with urfave_cli_no_suggest
 	SuggestDidYouMeanTemplate string             = suggestDidYouMeanTemplate
 )
 var AppHelpTemplate = `NAME:
    {{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}}{{if .Args}}[arguments...]{{end}}{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
+   {{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}}
 
 VERSION:
    {{.Version}}{{end}}{{end}}{{if .Description}}
@@ -136,7 +136,7 @@ var SubcommandHelpTemplate = `NAME:
    {{template "helpNameTemplate" .}}
 
 USAGE:
-   {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}{{if .Args}}[arguments...]{{end}}{{end}}{{end}}{{if .Description}}
+   {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}}
 
 DESCRIPTION:
    {{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}}
@@ -253,8 +253,6 @@ type App struct {
 	Usage string
 	// Text to override the USAGE section of help
 	UsageText string
-	// Whether this command supports arguments
-	Args bool
 	// Description of the program argument format.
 	ArgsUsage string
 	// Version of the program
@@ -525,8 +523,6 @@ type Command struct {
 	UsageText string
 	// A longer explanation of how the command works
 	Description string
-	// Whether this command supports arguments
-	Args bool
 	// A short description of the arguments of this command
 	ArgsUsage string
 	// The category the command is part of
@@ -2146,9 +2142,6 @@ func (f *Uint64SliceFlag) IsVisible() bool
 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)
 
@@ -2314,9 +2307,6 @@ func (f *UintSliceFlag) IsVisible() bool
 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)
 
diff --git a/help.go b/help.go
index 84bd77b..640e290 100644
--- a/help.go
+++ b/help.go
@@ -278,7 +278,7 @@ func ShowCommandHelp(ctx *Context, command string) error {
 
 	if ctx.App.CommandNotFound == nil {
 		errMsg := fmt.Sprintf("No help topic for '%v'", command)
-		if ctx.App.Suggest {
+		if ctx.App.Suggest && SuggestCommand != nil {
 			if suggestion := SuggestCommand(ctx.Command.Subcommands, command); suggestion != "" {
 				errMsg += ". " + suggestion
 			}
diff --git a/suggestions.go b/suggestions.go
index c73a001..9d2b7a8 100644
--- a/suggestions.go
+++ b/suggestions.go
@@ -1,3 +1,6 @@
+//go:build !urfave_cli_no_suggest
+// +build !urfave_cli_no_suggest
+
 package cli
 
 import (
@@ -6,6 +9,11 @@ import (
 	"github.com/xrash/smetrics"
 )
 
+func init() {
+	SuggestFlag = suggestFlag
+	SuggestCommand = suggestCommand
+}
+
 func jaroWinkler(a, b string) float64 {
 	// magic values are from https://github.com/xrash/smetrics/blob/039620a656736e6ad994090895784a7af15e0b80/jaro-winkler.go#L8
 	const (
diff --git a/template.go b/template.go
index daad470..da98890 100644
--- a/template.go
+++ b/template.go
@@ -1,7 +1,7 @@
 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}}{{if .Args}}[arguments...]{{end}}[arguments...]{{end}}{{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}}
@@ -35,7 +35,7 @@ var AppHelpTemplate = `NAME:
    {{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}}{{if .Args}}[arguments...]{{end}}{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
+   {{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}}
 
 VERSION:
    {{.Version}}{{end}}{{end}}{{if .Description}}
@@ -83,7 +83,7 @@ var SubcommandHelpTemplate = `NAME:
    {{template "helpNameTemplate" .}}
 
 USAGE:
-   {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}{{if .Args}}[arguments...]{{end}}{{end}}{{end}}{{if .Description}}
+   {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}}
 
 DESCRIPTION:
    {{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}}


Test patch:
diff --git a/app_test.go b/app_test.go
index cf9680f..f27c3d5 100644
--- a/app_test.go
+++ b/app_test.go
@@ -101,7 +101,6 @@ func ExampleApp_Run_appHelp() {
 	app := &App{
 		Name:        "greet",
 		Version:     "0.1.0",
-		Args:        true,
 		Description: "This is how we describe greet the app",
 		Authors: []*Author{
 			{Name: "Harrison", Email: "harrison@lolwut.com"},
@@ -157,7 +156,6 @@ func ExampleApp_Run_commandHelp() {
 
 	app := &App{
 		Name: "greet",
-		Args: true,
 		Flags: []Flag{
 			&StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
 		},
@@ -192,7 +190,6 @@ func ExampleApp_Run_commandHelp() {
 func ExampleApp_Run_noAction() {
 	app := App{}
 	app.Name = "greet"
-	app.Args = true
 	_ = app.Run([]string{"greet"})
 	// Output:
 	// NAME:
@@ -211,7 +208,6 @@ func ExampleApp_Run_noAction() {
 func ExampleApp_Run_subcommandNoAction() {
 	app := &App{
 		Name: "greet",
-		Args: true,
 		Commands: []*Command{
 			{
 				Name:        "describeit",
@@ -2021,7 +2017,6 @@ func TestApp_Run_CommandSubcommandHelpName(t *testing.T) {
 		Name:        "foo",
 		Usage:       "foo commands",
 		Description: "This is a description",
-		Args:        true,
 		Subcommands: []*Command{subCmd},
 	}
 	app.Commands = []*Command{cmd}
@@ -2888,16 +2883,6 @@ func TestFlagAction(t *testing.T) {
 					return err
 				},
 			},
-			&UintSliceFlag{
-				Name: "f_uint_slice",
-				Action: func(c *Context, v []uint) error {
-					if len(v) > 0 && v[0] == 0 {
-						return fmt.Errorf("invalid uint slice")
-					}
-					_, err := c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
-					return err
-				},
-			},
 			&Uint64Flag{
 				Name: "f_uint64",
 				Action: func(c *Context, v uint64) error {
@@ -2908,16 +2893,6 @@ func TestFlagAction(t *testing.T) {
 					return err
 				},
 			},
-			&Uint64SliceFlag{
-				Name: "f_uint64_slice",
-				Action: func(c *Context, v []uint64) error {
-					if len(v) > 0 && v[0] == 0 {
-						return fmt.Errorf("invalid uint64 slice")
-					}
-					_, err := c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
-					return err
-				},
-			},
 		},
 		Action: func(ctx *Context) error { return nil },
 	}
@@ -3068,31 +3043,11 @@ func TestFlagAction(t *testing.T) {
 			args: []string{"app", "--f_uint=0"},
 			err:  fmt.Errorf("zero uint"),
 		},
-		{
-			name: "flag_uint_slice",
-			args: []string{"app", "--f_uint_slice=1,2,3"},
-			exp:  "[1 2 3] ",
-		},
-		{
-			name: "flag_uint_slice",
-			args: []string{"app", "--f_uint_slice=0"},
-			err:  fmt.Errorf("invalid uint slice"),
-		},
 		{
 			name: "flag_uint64",
 			args: []string{"app", "--f_uint64=1"},
 			exp:  "1 ",
 		},
-		{
-			name: "flag_uint64_slice",
-			args: []string{"app", "--f_uint64_slice=1,2,3"},
-			exp:  "[1 2 3] ",
-		},
-		{
-			name: "flag_uint64_slice",
-			args: []string{"app", "--f_uint64_slice=0"},
-			err:  fmt.Errorf("invalid uint64 slice"),
-		},
 		{
 			name: "flag_uint64_error",
 			args: []string{"app", "--f_uint64=0"},
diff --git a/help_test.go b/help_test.go
index a39f818..80b38ac 100644
--- a/help_test.go
+++ b/help_test.go
@@ -1658,7 +1658,6 @@ func TestCategorizedHelp(t *testing.T) {
 	output := new(bytes.Buffer)
 	app := &App{
 		Name:   "cli.test",
-		Args:   true,
 		Writer: output,
 		Action: func(ctx *Context) error { return nil },
 		Flags: []Flag{
diff --git a/suggestions_test.go b/suggestions_test.go
index 2136acd..8ccc317 100644
--- a/suggestions_test.go
+++ b/suggestions_test.go
@@ -1,3 +1,6 @@
+//go:build !urfave_cli_no_suggest
+// +build !urfave_cli_no_suggest
+
 package cli
 
 import (
diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt
index e87b13a..46a260c 100644
--- a/testdata/godoc-v2.x.txt
+++ b/testdata/godoc-v2.x.txt
@@ -27,15 +27,15 @@ application:
 VARIABLES
 
 var (
-	SuggestFlag               SuggestFlagFunc    = suggestFlag
-	SuggestCommand            SuggestCommandFunc = suggestCommand
+	SuggestFlag               SuggestFlagFunc    = nil // initialized in suggestions.go unless built with urfave_cli_no_suggest
+	SuggestCommand            SuggestCommandFunc = nil // initialized in suggestions.go unless built with urfave_cli_no_suggest
 	SuggestDidYouMeanTemplate string             = suggestDidYouMeanTemplate
 )
 var AppHelpTemplate = `NAME:
    {{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}}{{if .Args}}[arguments...]{{end}}{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
+   {{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}}
 
 VERSION:
    {{.Version}}{{end}}{{end}}{{if .Description}}
@@ -136,7 +136,7 @@ var SubcommandHelpTemplate = `NAME:
    {{template "helpNameTemplate" .}}
 
 USAGE:
-   {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}{{if .Args}}[arguments...]{{end}}{{end}}{{end}}{{if .Description}}
+   {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}}
 
 DESCRIPTION:
    {{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}}
@@ -253,8 +253,6 @@ type App struct {
 	Usage string
 	// Text to override the USAGE section of help
 	UsageText string
-	// Whether this command supports arguments
-	Args bool
 	// Description of the program argument format.
 	ArgsUsage string
 	// Version of the program
@@ -525,8 +523,6 @@ type Command struct {
 	UsageText string
 	// A longer explanation of how the command works
 	Description string
-	// Whether this command supports arguments
-	Args bool
 	// A short description of the arguments of this command
 	ArgsUsage string
 	// The category the command is part of
@@ -2146,9 +2142,6 @@ func (f *Uint64SliceFlag) IsVisible() bool
 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)
 
@@ -2314,9 +2307,6 @@ func (f *UintSliceFlag) IsVisible() bool
 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)