Merge pull request #1390 from urfave/saschagrunert-suggestions
Add suggestions support (#977)
This commit is contained in:
commit
e770ee9794
4
.github/workflows/cli.yml
vendored
4
.github/workflows/cli.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
|||||||
- name: vet
|
- name: vet
|
||||||
run: go run internal/build/build.go vet
|
run: go run internal/build/build.go vet
|
||||||
|
|
||||||
- name: test with tags
|
- name: test with urfave_cli_no_docs tag
|
||||||
run: go run internal/build/build.go -tags urfave_cli_no_docs test
|
run: go run internal/build/build.go -tags urfave_cli_no_docs test
|
||||||
|
|
||||||
- name: test
|
- name: test
|
||||||
@ -47,7 +47,7 @@ jobs:
|
|||||||
run: go run internal/build/build.go check-binary-size
|
run: go run internal/build/build.go check-binary-size
|
||||||
|
|
||||||
- name: check-binary-size with tags (informational only)
|
- name: check-binary-size with tags (informational only)
|
||||||
run: go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size || true
|
run: go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size
|
||||||
|
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
if: success() && matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest'
|
if: success() && matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest'
|
||||||
|
@ -63,7 +63,7 @@ You can use the following build tags:
|
|||||||
|
|
||||||
When set, this removes `ToMarkdown` and `ToMan` methods, so your application
|
When set, this removes `ToMarkdown` and `ToMan` methods, so your application
|
||||||
won't be able to call those. This reduces the resulting binary size by about
|
won't be able to call those. This reduces the resulting binary size by about
|
||||||
300-400 KB (measured using Go 1.18.1 on Linux/amd64), due to less dependencies.
|
300-400 KB (measured using Go 1.18.1 on Linux/amd64), due to fewer dependencies.
|
||||||
|
|
||||||
### GOPATH
|
### GOPATH
|
||||||
|
|
||||||
|
12
app.go
12
app.go
@ -94,6 +94,8 @@ type App struct {
|
|||||||
// single-character bool arguments into one
|
// single-character bool arguments into one
|
||||||
// i.e. foobar -o -v -> foobar -ov
|
// i.e. foobar -o -v -> foobar -ov
|
||||||
UseShortOptionHandling bool
|
UseShortOptionHandling bool
|
||||||
|
// Enable suggestions for commands and flags
|
||||||
|
Suggest bool
|
||||||
|
|
||||||
didSetup bool
|
didSetup bool
|
||||||
}
|
}
|
||||||
@ -264,6 +266,11 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
||||||
|
if a.Suggest {
|
||||||
|
if suggestion, err := a.suggestFlagFromError(err, ""); err == nil {
|
||||||
|
fmt.Fprintf(a.Writer, suggestion)
|
||||||
|
}
|
||||||
|
}
|
||||||
_ = ShowAppHelp(cCtx)
|
_ = ShowAppHelp(cCtx)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -383,6 +390,11 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
||||||
|
if a.Suggest {
|
||||||
|
if suggestion, err := a.suggestFlagFromError(err, cCtx.Command.Name); err == nil {
|
||||||
|
fmt.Fprintf(a.Writer, suggestion)
|
||||||
|
}
|
||||||
|
}
|
||||||
_ = ShowSubcommandHelp(cCtx)
|
_ = ShowSubcommandHelp(cCtx)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -119,6 +119,11 @@ func (c *Command) Run(ctx *Context) (err error) {
|
|||||||
}
|
}
|
||||||
_, _ = fmt.Fprintln(cCtx.App.Writer, "Incorrect Usage:", err.Error())
|
_, _ = fmt.Fprintln(cCtx.App.Writer, "Incorrect Usage:", err.Error())
|
||||||
_, _ = fmt.Fprintln(cCtx.App.Writer)
|
_, _ = fmt.Fprintln(cCtx.App.Writer)
|
||||||
|
if ctx.App.Suggest {
|
||||||
|
if suggestion, err := ctx.App.suggestFlagFromError(err, c.Name); err == nil {
|
||||||
|
fmt.Fprintf(cCtx.App.Writer, suggestion)
|
||||||
|
}
|
||||||
|
}
|
||||||
_ = ShowCommandHelp(cCtx, c.Name)
|
_ = ShowCommandHelp(cCtx, c.Name)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -249,6 +254,7 @@ func (c *Command) startApp(ctx *Context) error {
|
|||||||
app.ErrWriter = ctx.App.ErrWriter
|
app.ErrWriter = ctx.App.ErrWriter
|
||||||
app.ExitErrHandler = ctx.App.ExitErrHandler
|
app.ExitErrHandler = ctx.App.ExitErrHandler
|
||||||
app.UseShortOptionHandling = ctx.App.UseShortOptionHandling
|
app.UseShortOptionHandling = ctx.App.UseShortOptionHandling
|
||||||
|
app.Suggest = ctx.App.Suggest
|
||||||
|
|
||||||
app.categories = newCommandCategories()
|
app.categories = newCommandCategories()
|
||||||
for _, command := range c.Subcommands {
|
for _, command := range c.Subcommands {
|
||||||
|
@ -1410,6 +1410,13 @@ In this example the flag could be used like this :
|
|||||||
|
|
||||||
Side note: quotes may be necessary around the date depending on your layout (if you have spaces for instance)
|
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
|
### Full API Example
|
||||||
|
|
||||||
**Notice**: This is a contrived (functioning) example meant strictly for API
|
**Notice**: This is a contrived (functioning) example meant strictly for API
|
||||||
|
1
go.mod
1
go.mod
@ -4,6 +4,7 @@ go 1.18
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.1.0
|
github.com/BurntSushi/toml v1.1.0
|
||||||
|
github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1
|
github.com/cpuguy83/go-md2man/v2 v2.0.1
|
||||||
golang.org/x/text v0.3.7
|
golang.org/x/text v0.3.7
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
|
8
go.sum
8
go.sum
@ -1,9 +1,17 @@
|
|||||||
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
||||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
|
github.com/antzucaro/matchr v0.0.0-20180616170659-cbc221335f3c h1:CucViv7orgFBMkehuFFdkCVF5ERovbkRRyhvaYaHu/k=
|
||||||
|
github.com/antzucaro/matchr v0.0.0-20180616170659-cbc221335f3c/go.mod h1:bV/CkX4+ANGDaBwbHkt9kK287al/i9BsB18PRBvyqYo=
|
||||||
|
github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0 h1:R/qAiUxFT3mNgQaNqJe0IVznjKRNm23ohAIh9lgtlzc=
|
||||||
|
github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0/go.mod h1:v3ZDlfVAL1OrkKHbGSFFK60k0/7hruHPDq2XMs9Gu6U=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
|
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.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
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/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 h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
|
||||||
|
@ -305,6 +305,8 @@ type App struct {
|
|||||||
// single-character bool arguments into one
|
// single-character bool arguments into one
|
||||||
// i.e. foobar -o -v -> foobar -ov
|
// i.e. foobar -o -v -> foobar -ov
|
||||||
UseShortOptionHandling bool
|
UseShortOptionHandling bool
|
||||||
|
// Enable suggestions for commands and flags
|
||||||
|
Suggest bool
|
||||||
|
|
||||||
// Has unexported fields.
|
// Has unexported fields.
|
||||||
}
|
}
|
||||||
|
21
help.go
21
help.go
@ -10,9 +10,14 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
helpName = "help"
|
||||||
|
helpAlias = "h"
|
||||||
|
)
|
||||||
|
|
||||||
var helpCommand = &Command{
|
var helpCommand = &Command{
|
||||||
Name: "help",
|
Name: helpName,
|
||||||
Aliases: []string{"h"},
|
Aliases: []string{helpAlias},
|
||||||
Usage: "Shows a list of commands or help for one command",
|
Usage: "Shows a list of commands or help for one command",
|
||||||
ArgsUsage: "[command]",
|
ArgsUsage: "[command]",
|
||||||
Action: func(cCtx *Context) error {
|
Action: func(cCtx *Context) error {
|
||||||
@ -27,8 +32,8 @@ var helpCommand = &Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var helpSubcommand = &Command{
|
var helpSubcommand = &Command{
|
||||||
Name: "help",
|
Name: helpName,
|
||||||
Aliases: []string{"h"},
|
Aliases: []string{helpAlias},
|
||||||
Usage: "Shows a list of commands or help for one command",
|
Usage: "Shows a list of commands or help for one command",
|
||||||
ArgsUsage: "[command]",
|
ArgsUsage: "[command]",
|
||||||
Action: func(cCtx *Context) error {
|
Action: func(cCtx *Context) error {
|
||||||
@ -214,7 +219,13 @@ func ShowCommandHelp(ctx *Context, command string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ctx.App.CommandNotFound == nil {
|
if ctx.App.CommandNotFound == nil {
|
||||||
return Exit(fmt.Sprintf("No help topic for '%v'", command), 3)
|
errMsg := fmt.Sprintf("No help topic for '%v'", command)
|
||||||
|
if ctx.App.Suggest {
|
||||||
|
if suggestion := suggestCommand(ctx.App.Commands, command); suggestion != "" {
|
||||||
|
errMsg += ". " + suggestion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Exit(errMsg, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.App.CommandNotFound(ctx, command)
|
ctx.App.CommandNotFound(ctx, command)
|
||||||
|
18
parse.go
18
parse.go
@ -26,9 +26,8 @@ func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComple
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
errStr := err.Error()
|
trimmed, trimErr := flagFromError(err)
|
||||||
trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: -")
|
if trimErr != nil {
|
||||||
if errStr == trimmed {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,6 +66,19 @@ func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComple
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const providedButNotDefinedErrMsg = "flag provided but not defined: -"
|
||||||
|
|
||||||
|
// flagFromError tries to parse a provided flag from an error message. If the
|
||||||
|
// parsing fials, it returns the input error and an empty string
|
||||||
|
func flagFromError(err error) (string, error) {
|
||||||
|
errStr := err.Error()
|
||||||
|
trimmed := strings.TrimPrefix(errStr, providedButNotDefinedErrMsg)
|
||||||
|
if errStr == trimmed {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return trimmed, nil
|
||||||
|
}
|
||||||
|
|
||||||
func splitShortOptions(set *flag.FlagSet, arg string) []string {
|
func splitShortOptions(set *flag.FlagSet, arg string) []string {
|
||||||
shortFlagsExist := func(s string) bool {
|
shortFlagsExist := func(s string) bool {
|
||||||
for _, c := range s[1:] {
|
for _, c := range s[1:] {
|
||||||
|
75
suggestions.go
Normal file
75
suggestions.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/antzucaro/matchr"
|
||||||
|
)
|
||||||
|
|
||||||
|
const didYouMeanTemplate = "Did you mean '%s'?"
|
||||||
|
|
||||||
|
func (a *App) suggestFlagFromError(err error, command string) (string, error) {
|
||||||
|
flag, parseErr := flagFromError(err)
|
||||||
|
if parseErr != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := a.Flags
|
||||||
|
if command != "" {
|
||||||
|
cmd := a.Command(command)
|
||||||
|
if cmd == nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
flags = cmd.Flags
|
||||||
|
}
|
||||||
|
|
||||||
|
suggestion := a.suggestFlag(flags, flag)
|
||||||
|
if len(suggestion) == 0 {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(didYouMeanTemplate+"\n\n", suggestion), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) suggestFlag(flags []Flag, provided string) (suggestion string) {
|
||||||
|
distance := 0.0
|
||||||
|
|
||||||
|
for _, flag := range flags {
|
||||||
|
flagNames := flag.Names()
|
||||||
|
if !a.HideHelp {
|
||||||
|
flagNames = append(flagNames, HelpFlag.Names()...)
|
||||||
|
}
|
||||||
|
for _, name := range flagNames {
|
||||||
|
newDistance := matchr.JaroWinkler(name, provided, true)
|
||||||
|
if newDistance > distance {
|
||||||
|
distance = newDistance
|
||||||
|
suggestion = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(suggestion) == 1 {
|
||||||
|
suggestion = "-" + suggestion
|
||||||
|
} else if len(suggestion) > 1 {
|
||||||
|
suggestion = "--" + suggestion
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggestion
|
||||||
|
}
|
||||||
|
|
||||||
|
// suggestCommand takes a list of commands and a provided string to suggest a
|
||||||
|
// command name
|
||||||
|
func suggestCommand(commands []*Command, provided string) (suggestion string) {
|
||||||
|
distance := 0.0
|
||||||
|
for _, command := range commands {
|
||||||
|
for _, name := range append(command.Names(), helpName, helpAlias) {
|
||||||
|
newDistance := matchr.JaroWinkler(name, provided, true)
|
||||||
|
if newDistance > distance {
|
||||||
|
distance = newDistance
|
||||||
|
suggestion = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(didYouMeanTemplate, suggestion)
|
||||||
|
}
|
188
suggestions_test.go
Normal file
188
suggestions_test.go
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSuggestFlag(t *testing.T) {
|
||||||
|
// Given
|
||||||
|
app := testApp()
|
||||||
|
|
||||||
|
for _, testCase := range []struct {
|
||||||
|
provided, expected string
|
||||||
|
}{
|
||||||
|
{"", ""},
|
||||||
|
{"a", "--another-flag"},
|
||||||
|
{"hlp", "--help"},
|
||||||
|
{"k", ""},
|
||||||
|
{"s", "-s"},
|
||||||
|
} {
|
||||||
|
// When
|
||||||
|
res := app.suggestFlag(app.Flags, testCase.provided)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(t, res, testCase.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuggestFlagHideHelp(t *testing.T) {
|
||||||
|
// Given
|
||||||
|
app := testApp()
|
||||||
|
app.HideHelp = true
|
||||||
|
|
||||||
|
// When
|
||||||
|
res := app.suggestFlag(app.Flags, "hlp")
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(t, res, "--fl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuggestFlagFromError(t *testing.T) {
|
||||||
|
// Given
|
||||||
|
app := testApp()
|
||||||
|
|
||||||
|
for _, testCase := range []struct {
|
||||||
|
command, provided, expected string
|
||||||
|
}{
|
||||||
|
{"", "hel", "--help"},
|
||||||
|
{"", "soccer", "--socket"},
|
||||||
|
{"config", "anot", "--another-flag"},
|
||||||
|
} {
|
||||||
|
// When
|
||||||
|
res, _ := app.suggestFlagFromError(
|
||||||
|
errors.New(providedButNotDefinedErrMsg+testCase.provided),
|
||||||
|
testCase.command,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(t, res, fmt.Sprintf(didYouMeanTemplate+"\n\n", testCase.expected))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuggestFlagFromErrorWrongError(t *testing.T) {
|
||||||
|
// Given
|
||||||
|
app := testApp()
|
||||||
|
|
||||||
|
// When
|
||||||
|
_, err := app.suggestFlagFromError(errors.New("invalid"), "")
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(t, true, err != nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuggestFlagFromErrorWrongCommand(t *testing.T) {
|
||||||
|
// Given
|
||||||
|
app := testApp()
|
||||||
|
|
||||||
|
// When
|
||||||
|
_, err := app.suggestFlagFromError(
|
||||||
|
errors.New(providedButNotDefinedErrMsg+"flag"),
|
||||||
|
"invalid",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(t, true, err != nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuggestFlagFromErrorNoSuggestion(t *testing.T) {
|
||||||
|
// Given
|
||||||
|
app := testApp()
|
||||||
|
|
||||||
|
// When
|
||||||
|
_, err := app.suggestFlagFromError(
|
||||||
|
errors.New(providedButNotDefinedErrMsg+""),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(t, true, err != nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuggestCommand(t *testing.T) {
|
||||||
|
// Given
|
||||||
|
app := testApp()
|
||||||
|
|
||||||
|
for _, testCase := range []struct {
|
||||||
|
provided, expected string
|
||||||
|
}{
|
||||||
|
{"", ""},
|
||||||
|
{"conf", "config"},
|
||||||
|
{"i", "i"},
|
||||||
|
{"information", "info"},
|
||||||
|
{"not-existing", "info"},
|
||||||
|
} {
|
||||||
|
// When
|
||||||
|
res := suggestCommand(app.Commands, testCase.provided)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(t, res, fmt.Sprintf(didYouMeanTemplate, testCase.expected))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleApp_Suggest() {
|
||||||
|
app := &App{
|
||||||
|
Name: "greet",
|
||||||
|
Suggest: true,
|
||||||
|
HideHelp: true,
|
||||||
|
HideHelpCommand: true,
|
||||||
|
CustomAppHelpTemplate: "(this space intentionally left blank)\n",
|
||||||
|
Flags: []Flag{
|
||||||
|
&StringFlag{Name: "name", Value: "squirrel", Usage: "a name to say"},
|
||||||
|
},
|
||||||
|
Action: func(cCtx *Context) error {
|
||||||
|
fmt.Printf("Hello %v\n", cCtx.String("name"))
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run([]string{"greet", "--nema", "chipmunk"})
|
||||||
|
// Output:
|
||||||
|
// Incorrect Usage. flag provided but not defined: -nema
|
||||||
|
//
|
||||||
|
// Did you mean '--name'?
|
||||||
|
//
|
||||||
|
// (this space intentionally left blank)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleApp_Suggest_command() {
|
||||||
|
app := &App{
|
||||||
|
Name: "greet",
|
||||||
|
Suggest: true,
|
||||||
|
HideHelp: true,
|
||||||
|
HideHelpCommand: true,
|
||||||
|
CustomAppHelpTemplate: "(this space intentionally left blank)\n",
|
||||||
|
Flags: []Flag{
|
||||||
|
&StringFlag{Name: "name", Value: "squirrel", Usage: "a name to say"},
|
||||||
|
},
|
||||||
|
Action: func(cCtx *Context) error {
|
||||||
|
fmt.Printf("Hello %v\n", cCtx.String("name"))
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
Commands: []*Command{
|
||||||
|
{
|
||||||
|
Name: "neighbors",
|
||||||
|
CustomHelpTemplate: "(this space intentionally left blank)\n",
|
||||||
|
Flags: []Flag{
|
||||||
|
&BoolFlag{Name: "smiling"},
|
||||||
|
},
|
||||||
|
Action: func(cCtx *Context) error {
|
||||||
|
if cCtx.Bool("smiling") {
|
||||||
|
fmt.Println("😀")
|
||||||
|
}
|
||||||
|
fmt.Println("Hello, neighbors")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run([]string{"greet", "neighbors", "--sliming"})
|
||||||
|
// Output:
|
||||||
|
// Incorrect Usage: flag provided but not defined: -sliming
|
||||||
|
//
|
||||||
|
// Did you mean '--smiling'?
|
||||||
|
//
|
||||||
|
// (this space intentionally left blank)
|
||||||
|
}
|
2
testdata/godoc-v2.x.txt
vendored
2
testdata/godoc-v2.x.txt
vendored
@ -305,6 +305,8 @@ type App struct {
|
|||||||
// single-character bool arguments into one
|
// single-character bool arguments into one
|
||||||
// i.e. foobar -o -v -> foobar -ov
|
// i.e. foobar -o -v -> foobar -ov
|
||||||
UseShortOptionHandling bool
|
UseShortOptionHandling bool
|
||||||
|
// Enable suggestions for commands and flags
|
||||||
|
Suggest bool
|
||||||
|
|
||||||
// Has unexported fields.
|
// Has unexported fields.
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user