Merge pull request #1396 from urfave/suggestion-pluggability

Introduce override hooks for suggestions
This commit is contained in:
Dan Buch 2022-05-22 11:14:46 -04:00 committed by GitHub
commit 9de0cd3c4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 62 additions and 36 deletions

33
app.go
View File

@ -11,6 +11,8 @@ import (
"time" "time"
) )
const suggestDidYouMeanTemplate = "Did you mean %q?"
var ( var (
changeLogURL = "https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md" changeLogURL = "https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md"
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
@ -18,6 +20,10 @@ var (
errInvalidActionType = NewExitError("ERROR invalid Action type. "+ errInvalidActionType = NewExitError("ERROR invalid Action type. "+
fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+ fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+
fmt.Sprintf("See %s", appActionDeprecationURL), 2) fmt.Sprintf("See %s", appActionDeprecationURL), 2)
SuggestFlag SuggestFlagFunc = suggestFlag
SuggestCommand SuggestCommandFunc = suggestCommand
SuggestDidYouMeanTemplate string = suggestDidYouMeanTemplate
) )
// App is the main structure of a cli application. It is recommended that // App is the main structure of a cli application. It is recommended that
@ -102,6 +108,10 @@ type App struct {
didSetup bool didSetup bool
} }
type SuggestFlagFunc func(flags []Flag, provided string, hideHelp bool) string
type SuggestCommandFunc func(commands []*Command, provided string) string
// Tries to find out when this binary was compiled. // Tries to find out when this binary was compiled.
// Returns the current time if it fails to find it. // Returns the current time if it fails to find it.
func compileTime() time.Time { func compileTime() time.Time {
@ -343,6 +353,29 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
return err return err
} }
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 := SuggestFlag(flags, flag, a.HideHelp)
if len(suggestion) == 0 {
return "", err
}
return fmt.Sprintf(SuggestDidYouMeanTemplate+"\n\n", suggestion), nil
}
// RunAndExitOnError calls .Run() and exits non-zero if an error was returned // RunAndExitOnError calls .Run() and exits non-zero if an error was returned
// //
// Deprecated: instead you should return an error that fulfills cli.ExitCoder // Deprecated: instead you should return an error that fulfills cli.ExitCoder

View File

@ -26,6 +26,11 @@ application:
VARIABLES VARIABLES
var (
SuggestFlag SuggestFlagFunc = suggestFlag
SuggestCommand SuggestCommandFunc = suggestCommand
SuggestDidYouMeanTemplate string = suggestDidYouMeanTemplate
)
var AppHelpTemplate = `NAME: var AppHelpTemplate = `NAME:
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}} {{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
@ -1622,6 +1627,10 @@ func (f *StringSliceFlag) String() string
func (f *StringSliceFlag) TakesValue() bool func (f *StringSliceFlag) TakesValue() bool
TakesValue returns true of the flag takes a value, otherwise false TakesValue returns true of the flag takes a value, otherwise false
type SuggestCommandFunc func(commands []*Command, provided string) string
type SuggestFlagFunc func(flags []Flag, provided string, hideHelp bool) string
type Timestamp struct { type Timestamp struct {
// Has unexported fields. // Has unexported fields.
} }

View File

@ -221,7 +221,7 @@ func ShowCommandHelp(ctx *Context, command string) error {
if ctx.App.CommandNotFound == nil { if ctx.App.CommandNotFound == nil {
errMsg := fmt.Sprintf("No help topic for '%v'", command) errMsg := fmt.Sprintf("No help topic for '%v'", command)
if ctx.App.Suggest { if ctx.App.Suggest {
if suggestion := suggestCommand(ctx.App.Commands, command); suggestion != "" { if suggestion := SuggestCommand(ctx.App.Commands, command); suggestion != "" {
errMsg += ". " + suggestion errMsg += ". " + suggestion
} }
} }

View File

@ -6,37 +6,13 @@ import (
"github.com/antzucaro/matchr" "github.com/antzucaro/matchr"
) )
const didYouMeanTemplate = "Did you mean '%s'?" func suggestFlag(flags []Flag, provided string, hideHelp bool) string {
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 distance := 0.0
suggestion := ""
for _, flag := range flags { for _, flag := range flags {
flagNames := flag.Names() flagNames := flag.Names()
if !a.HideHelp { if !hideHelp {
flagNames = append(flagNames, HelpFlag.Names()...) flagNames = append(flagNames, HelpFlag.Names()...)
} }
for _, name := range flagNames { for _, name := range flagNames {
@ -71,5 +47,5 @@ func suggestCommand(commands []*Command, provided string) (suggestion string) {
} }
} }
return fmt.Sprintf(didYouMeanTemplate, suggestion) return fmt.Sprintf(SuggestDidYouMeanTemplate, suggestion)
} }

View File

@ -20,7 +20,7 @@ func TestSuggestFlag(t *testing.T) {
{"s", "-s"}, {"s", "-s"},
} { } {
// When // When
res := app.suggestFlag(app.Flags, testCase.provided) res := suggestFlag(app.Flags, testCase.provided, false)
// Then // Then
expect(t, res, testCase.expected) expect(t, res, testCase.expected)
@ -30,10 +30,9 @@ func TestSuggestFlag(t *testing.T) {
func TestSuggestFlagHideHelp(t *testing.T) { func TestSuggestFlagHideHelp(t *testing.T) {
// Given // Given
app := testApp() app := testApp()
app.HideHelp = true
// When // When
res := app.suggestFlag(app.Flags, "hlp") res := suggestFlag(app.Flags, "hlp", true)
// Then // Then
expect(t, res, "--fl") expect(t, res, "--fl")
@ -57,7 +56,7 @@ func TestSuggestFlagFromError(t *testing.T) {
) )
// Then // Then
expect(t, res, fmt.Sprintf(didYouMeanTemplate+"\n\n", testCase.expected)) expect(t, res, fmt.Sprintf(SuggestDidYouMeanTemplate+"\n\n", testCase.expected))
} }
} }
@ -117,7 +116,7 @@ func TestSuggestCommand(t *testing.T) {
res := suggestCommand(app.Commands, testCase.provided) res := suggestCommand(app.Commands, testCase.provided)
// Then // Then
expect(t, res, fmt.Sprintf(didYouMeanTemplate, testCase.expected)) expect(t, res, fmt.Sprintf(SuggestDidYouMeanTemplate, testCase.expected))
} }
} }
@ -141,7 +140,7 @@ func ExampleApp_Suggest() {
// Output: // Output:
// Incorrect Usage. flag provided but not defined: -nema // Incorrect Usage. flag provided but not defined: -nema
// //
// Did you mean '--name'? // Did you mean "--name"?
// //
// (this space intentionally left blank) // (this space intentionally left blank)
} }
@ -182,7 +181,7 @@ func ExampleApp_Suggest_command() {
// Output: // Output:
// Incorrect Usage: flag provided but not defined: -sliming // Incorrect Usage: flag provided but not defined: -sliming
// //
// Did you mean '--smiling'? // Did you mean "--smiling"?
// //
// (this space intentionally left blank) // (this space intentionally left blank)
} }

View File

@ -26,6 +26,11 @@ application:
VARIABLES VARIABLES
var (
SuggestFlag SuggestFlagFunc = suggestFlag
SuggestCommand SuggestCommandFunc = suggestCommand
SuggestDidYouMeanTemplate string = suggestDidYouMeanTemplate
)
var AppHelpTemplate = `NAME: var AppHelpTemplate = `NAME:
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}} {{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
@ -1622,6 +1627,10 @@ func (f *StringSliceFlag) String() string
func (f *StringSliceFlag) TakesValue() bool func (f *StringSliceFlag) TakesValue() bool
TakesValue returns true of the flag takes a value, otherwise false TakesValue returns true of the flag takes a value, otherwise false
type SuggestCommandFunc func(commands []*Command, provided string) string
type SuggestFlagFunc func(flags []Flag, provided string, hideHelp bool) string
type Timestamp struct { type Timestamp struct {
// Has unexported fields. // Has unexported fields.
} }