-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Make command suggestion messages configurable #2218
base: main
Are you sure you want to change the base?
Conversation
command.go
Outdated
// SuggestFunc returns either the function set by SetSuggestFunc for this command | ||
// or a parent, or it returns a function with default suggestion behavior. | ||
func (c *Command) SuggestFunc() func(string) string { | ||
if c.suggestFunc != nil && !c.DisableSuggestions { | ||
return c.suggestFunc | ||
} | ||
if c.HasParent() { | ||
return c.Parent().SuggestFunc() | ||
} | ||
return c.findSuggestions | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't you need a something to check the parent has a suggestion func ?
// SuggestFunc returns either the function set by SetSuggestFunc for this command | |
// or a parent, or it returns a function with default suggestion behavior. | |
func (c *Command) SuggestFunc() func(string) string { | |
if c.suggestFunc != nil && !c.DisableSuggestions { | |
return c.suggestFunc | |
} | |
if c.HasParent() { | |
return c.Parent().SuggestFunc() | |
} | |
return c.findSuggestions | |
} | |
func (c *Command) HasSuggestFunc() bool { | |
return c.suggestFunc != nil && !c.DisableSuggestions | |
} | |
// SuggestFunc returns either the function set by SetSuggestFunc for this command | |
// or a parent, or it returns a function with default suggestion behavior. | |
func (c *Command) SuggestFunc() func(string) string { | |
if c.HasSuggestFunc() { | |
return c.suggestFunc | |
} | |
if c.HasParent() && c.Parent().HasSuggestFunc() { | |
return c.Parent().SuggestFunc() | |
} | |
return c.findSuggestions | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be problematic from 2nd level onward (e.g. parent doesn't have it, but grandparent does).
Current logic would recursively check the whole parent tree and use the default (findSuggestions
) if none is set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But, then
Won't this cause a problem?
https://github.com/spf13/cobra/blob/main/command.go#L752-L767
if you have a parent but no suggestFunc you will use the findSuggestion of the parent.
It's what you add in your code.
But then the suggestion will be computed only against the args supported by the parent, not by the one in the Command
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, it would take the root's default suggestion function, which would fail to properly suggest a nested subcommand.
I can imagine someone wanting to bubble up to the root, though, and use a single custom suggestion function for the entire tree. So stopping early would be too limiting.
Will have to think about the best way to detect if it bubbled all the way to the root and use sucommand's parent (if it exists) when calling findSuggestions
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got the logic down, but it didn't have the desired effect. Checked how subcommands are detected and apparently you can't have a type on a lower level (e.g. grandchild).
Find
: after finding the last command detected in the args, it callslegacyArgs
, where it checks for typos for root command, only, and returns an unknown command error with suggestions.Traverse
: simply returns the last found command and doesn't check for possible typos (doesn't calllegacyArgs
).
Aside from Traverse not checking for possible typos (?), I'd say it makes sense, since an argument could very well be a valid one and not a typo.
Edit:
Will rework the SuggestFunc
to work in this context.
There is OnlyValidArgs
, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't look fun, have fun !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reworked with the use of a helper function which gets called recursively until it finds the first custom suggestion function. If none is found, it's going to default to the parent's default.
I've also reworked the tests so they better test and represent the logic + added additional ones for the OnlyValidArgs
path, which test the different levels of nesting.
command.go
Outdated
func (c *Command) SuggestFunc() func(string) string { | ||
if c.suggestFunc != nil && !c.DisableSuggestions { | ||
return c.suggestFunc | ||
} | ||
if c.HasParent() { | ||
return c.Parent().SuggestFunc() | ||
} | ||
return c.findSuggestions | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using this
func (c *Command) SuggestFunc() func(string) string { | |
if c.suggestFunc != nil && !c.DisableSuggestions { | |
return c.suggestFunc | |
} | |
if c.HasParent() { | |
return c.Parent().SuggestFunc() | |
} | |
return c.findSuggestions | |
} | |
func (c *Command) SuggestFunc(arg string) string { | |
if c.suggestFunc != nil && !c.DisableSuggestions { | |
return c.suggestFunc(arg) | |
} | |
if c.HasParent() { | |
return c.Parent().SuggestFunc(arg) | |
} | |
return c.findSuggestions(arg) | |
} |
Would avoid the strange way the function is called
- return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.SuggestFunc()(args[0]))
+ return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.SuggestFunc(args[0]))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Went with the return of a function because I wanted to mimic UsageFunc
and HelpFunc
logic as much as possible.
Pushed the simplified version.
This implements the proposal #1394.