Skip to content

Commit

Permalink
Introduce override hooks for suggestions
Browse files Browse the repository at this point in the history
Related to #1390 (comment)
  • Loading branch information
meatballhat committed May 14, 2022
1 parent 9bd6349 commit 34ee6c1
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 33 deletions.
32 changes: 32 additions & 0 deletions app.go
Expand Up @@ -11,13 +11,18 @@ import (
"time"
)

const didYouMeanTemplate = "Did you mean '%s'?"

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
)

// App is the main structure of a cli application. It is recommended that
Expand Down Expand Up @@ -100,6 +105,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 +341,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(didYouMeanTemplate+"\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
8 changes: 8 additions & 0 deletions godoc-current.txt
Expand Up @@ -26,6 +26,10 @@ application:

VARIABLES

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

Expand Down Expand Up @@ -1539,6 +1543,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
30 changes: 3 additions & 27 deletions suggestions.go
Expand Up @@ -9,37 +9,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
4 changes: 2 additions & 2 deletions suggestions_stubs.go
Expand Up @@ -3,8 +3,8 @@

package cli

func (a *App) suggestFlagFromError(err error, _ string) (string, error) {
return "", err
func suggestFlag(_ []Flag, _ string, _ bool) string {
return ""
}

func suggestCommand([]*Command, string) string {
Expand Down
5 changes: 2 additions & 3 deletions suggestions_test.go
Expand Up @@ -23,7 +23,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 @@ -33,10 +33,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 Down
8 changes: 8 additions & 0 deletions testdata/godoc-v2.x.txt
Expand Up @@ -26,6 +26,10 @@ application:

VARIABLES

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

Expand Down Expand Up @@ -1539,6 +1543,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

0 comments on commit 34ee6c1

Please sign in to comment.