Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce override hooks for suggestions #1396

Merged
merged 2 commits into from May 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 33 additions & 0 deletions app.go
Expand Up @@ -11,13 +11,19 @@ import (
"time"
)

const suggestDidYouMeanTemplate = "Did you mean %q?"

var (
changeLogURL = "https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md"
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
errInvalidActionType = NewExitError("ERROR invalid Action type. "+
fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+
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
Expand Down Expand Up @@ -100,6 +106,10 @@ type App struct {
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.
// Returns the current time if it fails to find it.
func compileTime() time.Time {
Expand Down Expand Up @@ -332,6 +342,29 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
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
//
// Deprecated: instead you should return an error that fulfills cli.ExitCoder
Expand Down
9 changes: 9 additions & 0 deletions godoc-current.txt
Expand Up @@ -26,6 +26,11 @@ application:

VARIABLES

var (
SuggestFlag SuggestFlagFunc = suggestFlag
SuggestCommand SuggestCommandFunc = suggestCommand
SuggestDidYouMeanTemplate string = suggestDidYouMeanTemplate
)
var AppHelpTemplate = `NAME:
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}

Expand Down Expand Up @@ -1539,6 +1544,10 @@ func (f *StringSliceFlag) String() string
func (f *StringSliceFlag) TakesValue() bool
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 {
// Has unexported fields.
}
Expand Down
2 changes: 1 addition & 1 deletion help.go
Expand Up @@ -221,7 +221,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 suggestion := suggestCommand(ctx.App.Commands, command); suggestion != "" {
if suggestion := SuggestCommand(ctx.App.Commands, command); suggestion != "" {
errMsg += ". " + suggestion
}
}
Expand Down
32 changes: 4 additions & 28 deletions suggestions.go
Expand Up @@ -6,37 +6,13 @@ import (
"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) {
func suggestFlag(flags []Flag, provided string, hideHelp bool) string {
distance := 0.0
suggestion := ""

for _, flag := range flags {
flagNames := flag.Names()
if !a.HideHelp {
if !hideHelp {
flagNames = append(flagNames, HelpFlag.Names()...)
}
for _, name := range flagNames {
Expand Down Expand Up @@ -71,5 +47,5 @@ func suggestCommand(commands []*Command, provided string) (suggestion string) {
}
}

return fmt.Sprintf(didYouMeanTemplate, suggestion)
return fmt.Sprintf(SuggestDidYouMeanTemplate, suggestion)
}
13 changes: 6 additions & 7 deletions suggestions_test.go
Expand Up @@ -20,7 +20,7 @@ func TestSuggestFlag(t *testing.T) {
{"s", "-s"},
} {
// When
res := app.suggestFlag(app.Flags, testCase.provided)
res := suggestFlag(app.Flags, testCase.provided, false)

// Then
expect(t, res, testCase.expected)
Expand All @@ -30,10 +30,9 @@ func TestSuggestFlag(t *testing.T) {
func TestSuggestFlagHideHelp(t *testing.T) {
// Given
app := testApp()
app.HideHelp = true

// When
res := app.suggestFlag(app.Flags, "hlp")
res := suggestFlag(app.Flags, "hlp", true)

// Then
expect(t, res, "--fl")
Expand All @@ -57,7 +56,7 @@ func TestSuggestFlagFromError(t *testing.T) {
)

// Then
expect(t, res, fmt.Sprintf(didYouMeanTemplate+"\n\n", testCase.expected))
expect(t, res, fmt.Sprintf(SuggestDidYouMeanTemplate+"\n\n", testCase.expected))
}
}

Expand Down Expand Up @@ -117,7 +116,7 @@ func TestSuggestCommand(t *testing.T) {
res := suggestCommand(app.Commands, testCase.provided)

// Then
expect(t, res, fmt.Sprintf(didYouMeanTemplate, testCase.expected))
expect(t, res, fmt.Sprintf(SuggestDidYouMeanTemplate, testCase.expected))
}
}

Expand All @@ -141,7 +140,7 @@ func ExampleApp_Suggest() {
// Output:
// Incorrect Usage. flag provided but not defined: -nema
//
// Did you mean '--name'?
// Did you mean "--name"?
//
// (this space intentionally left blank)
}
Expand Down Expand Up @@ -182,7 +181,7 @@ func ExampleApp_Suggest_command() {
// Output:
// Incorrect Usage: flag provided but not defined: -sliming
//
// Did you mean '--smiling'?
// Did you mean "--smiling"?
//
// (this space intentionally left blank)
}
9 changes: 9 additions & 0 deletions testdata/godoc-v2.x.txt
Expand Up @@ -26,6 +26,11 @@ application:

VARIABLES

var (
SuggestFlag SuggestFlagFunc = suggestFlag
SuggestCommand SuggestCommandFunc = suggestCommand
SuggestDidYouMeanTemplate string = suggestDidYouMeanTemplate
)
var AppHelpTemplate = `NAME:
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}

Expand Down Expand Up @@ -1539,6 +1544,10 @@ func (f *StringSliceFlag) String() string
func (f *StringSliceFlag) TakesValue() bool
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 {
// Has unexported fields.
}
Expand Down