Conversation
Signed-off-by: Marc Khouzam <[email protected]>
|
This PR exceeds the recommended size of 200 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size. |
| // Always show help if requested, even if SilenceErrors is in | ||
| // effect | ||
| if errors.Is(err, flag.ErrHelp) { | ||
| cmd.HelpFunc()(cmd, args) |
There was a problem hiding this comment.
The use of args here was added in this commit and it does seem that it was a mistake; I believe the flags variable should have been used instead.
command.go
Outdated
| if cmd.DisableFlagParsing { | ||
| argWoFlags = flags | ||
| } | ||
| cmd.HelpFunc()(cmd, argWoFlags) |
There was a problem hiding this comment.
I wanted to discuss backwards-compatibility for this change.
Before this PR cobra would pass all the arguments on the command-line (which was incorrect).
With this PR cobra will pass only the arguments after removing the sub-command and flags, as it does when calling the *Run/RunE functions.
It is possible that some programs worked around this bug and fixing it may affect them.
However, considering that before this PR the help function sometime received all the args (when using the --help flag) but no args at all when called from the help command, it makes it less likely that a workaround used the actual args parameter.
So, I feel it is ok to make this change but if we do maybe we should include a note in the release notes.
@jpmcb how do you feel about this?
There was a problem hiding this comment.
Sending only the remaining arguments is better when using -h. If we think this may break someone, we can do this only if a new flag is set like HelpGetRemainingArgs so users can opt-int to use the new behavior.
When using help command I'm not sure arguments make sense. You use this to learn about a command before you use it. You use -h,--help to get help for a command while trying to use it. You expect that adding -h,--help anywhere in the current command will show the help and getting more specific help for the current argument is nice improvement. Personally I never use help command since -h is much easier to type.
-h, --help use case:
Trying to run a command:
$ foo bar --baz
error: ...
It did not work, add -h:
$ foo bar --baz -h
help on foo bar...
Use it correctly:
$ foo bar --baz missing-value
success
help use case
What is the foo bar command?
$ foo help bar
help on foo bar
There was a problem hiding this comment.
Your use case description is correct. However, there is another (obscure) situation, which is actually the one I'm really trying to fix 😄.
The tanzu CLI uses plugins to provide many of its sub-commands. So a user may do:
tanzu help cluster list
to get the help on the cluster list command.
However, the tanzu CLI only has cluster as a sub-command and needs to call that "cluster" plugin to ask it for the help for the cluster list command. Therefore, the arguments passed to the original tanzu help command are required to know which sub-command of the plugin the user is asking about.
There was a problem hiding this comment.
Currently, the tanzu CLI code has to use os.Args to get that info: https://github.com/vmware-tanzu/tanzu-cli/blob/6a11ce93cd4e811e213e8439e090e1d73a053fd3/pkg/cli/plugin_cmd.go#L192-L201
There was a problem hiding this comment.
Thanks for explaning this, so this is needed when a command loads its sub commands dynamically. The help commands may need to do the same dynamic loading to display the help.
| cmd.InitDefaultHelpFlag() // make possible 'help' flag to be shown | ||
| cmd.InitDefaultVersionFlag() // make possible 'version' flag to be shown | ||
| CheckErr(cmd.Help()) | ||
| cmd.HelpFunc()(cmd, remainingArgs) |
There was a problem hiding this comment.
I don't feel this has any backwards-compatibility implications since the args value was always empty before this change for this case.
There was a problem hiding this comment.
I think we are missing documentation explaning that Run, RunE, and HelpFunc accept the remaining arguments after parsing. Previously HelpFunc sometimes accepted no arguments and sometimes accepted the raw parsed arguments.
Can be here:
// SetHelpFunc sets help function. Can be defined by Application.
// The help function is called with the remaining arguments after parsing
// the flags. If flag parsing is disabled it is called with all arguments.
func (c *Command) SetHelpFunc(f func(*Command, []string)) {
c.helpFunc = f
}
command.go
Outdated
| if cmd.DisableFlagParsing { | ||
| argWoFlags = flags | ||
| } | ||
| cmd.HelpFunc()(cmd, argWoFlags) |
There was a problem hiding this comment.
Sending only the remaining arguments is better when using -h. If we think this may break someone, we can do this only if a new flag is set like HelpGetRemainingArgs so users can opt-int to use the new behavior.
When using help command I'm not sure arguments make sense. You use this to learn about a command before you use it. You use -h,--help to get help for a command while trying to use it. You expect that adding -h,--help anywhere in the current command will show the help and getting more specific help for the current argument is nice improvement. Personally I never use help command since -h is much easier to type.
-h, --help use case:
Trying to run a command:
$ foo bar --baz
error: ...
It did not work, add -h:
$ foo bar --baz -h
help on foo bar...
Use it correctly:
$ foo bar --baz missing-value
success
help use case
What is the foo bar command?
$ foo help bar
help on foo bar
command_test.go
Outdated
| if len(args) != 2 || args[0] != "arg1" || args[1] != "arg2" { | ||
| t.Errorf("Expected args [args1 arg2], got %v", args) | ||
| } | ||
| }) |
There was a problem hiding this comment.
We repeat the code of the help function many times, maybe add a helper struct keeping the , so we can do:
h := helper{t: t, command: "child", args: []string{"args1", "arg2"}}
rootCmd.SetHelpFunc(h.helpFunc)
_, err := executeCommand(rootCmd, "help", "child", "arg1", "arg2")
if !h.helpCalled {
...
}
There was a problem hiding this comment.
I like it!
I tried to do what you suggest.
I think this change is not needed and we can keep the current behavior, and fix the
Why would someone type: and then type work hard to remove the help command to run the actual command, instead of adding -h to the actual command: and finally remove the -h for running the actual command? I guess tanszu smart help works both for help and -h/--help, so if we fix -h,--help, we can remove the workarounds? It we spend time on the help command, we should make it easy to show different content for "help command". For example try |
|
This PR exceeds the recommended size of 200 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size. |
Signed-off-by: Marc Khouzam <[email protected]>
bec76b4 to
9fbba57
Compare
|
This PR exceeds the recommended size of 200 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size. |
Answered here: #2158 (comment)
I agree that the
The same override of
This sounds like a good idea. Programs using Cobra can do this already by overriding the |
| o.err = fmt.Errorf("Expected args %v, got %v", o.expectedArgs, args) | ||
| return | ||
| } | ||
| } |
There was a problem hiding this comment.
We can extract lines 1098 to 1108 to a argsEqual() helper that can be useful in other tests, and can be replaced with slices.Equal when we can required Go 1.18. Can also be a good place to document why we cannot use slices.Equal.
Would be little bit nicer since since we don't have duplicate the error here:
if !argsEqual(args, o,expectedArgs) {
o.err = fmt.Errorf("Expected args %v, got %v", o.expectedArgs, args)
}
| } | ||
|
|
||
| func (o *overridingHelp) helpFunc() func(c *Command, args []string) { | ||
| return func(c *Command, args []string) { |
There was a problem hiding this comment.
Any reason for returning a help function instead of implementing one?
This could be:
func (o *overridingHelp) helpFunc(c *Command, args []string) {
...
}
And used later as:
rootCmd.SetHelpFunc(override.helpFunc)
| } | ||
| rootCmd.SetHelpFunc(override.helpFunc()) | ||
|
|
||
| _, err := executeCommand(rootCmd, "child", "arg1", "--myflag", "arg2", "--help") |
There was a problem hiding this comment.
Why test additional flags separately? this way we don't cover all cases.
| if err = override.checkError(); err != nil { | ||
| t.Errorf("Unexpected error from help function: %v", err) | ||
| } | ||
| } |
There was a problem hiding this comment.
In both help command you don't test additional flags - if you want to have the same behavior for help command, would it be good to ensure that parsed flags are not passed to the help function?
| // was not done and cmd.Flags().Args() is not filled. We therefore | ||
| // use the full set of arguments, which include flags. | ||
| remainingArgs = flags | ||
| } |
There was a problem hiding this comment.
Thanks for explaining and adding the comment.
I think this will communicates better the intent of the code:
// The call to execute() above has parsed the flags.
// We therefore only pass the remaining arguments to the help function.
var remainingArgs []string
if cmd.DisableFlagParsing {
// For commands that have DisableFlagParsing == true, the flag parsing was
// not done, therefore use the full set of arguments, which include flags.
remainingArgs = flags
} else {
remainingArgs = cmd.Flags().Args()
}
command.go
Outdated
| if cmd.DisableFlagParsing { | ||
| argWoFlags = flags | ||
| } | ||
| cmd.HelpFunc()(cmd, argWoFlags) |
There was a problem hiding this comment.
Thanks for explaning this, so this is needed when a command loads its sub commands dynamically. The help commands may need to do the same dynamic loading to display the help.
| cmd.InitDefaultHelpFlag() // make possible 'help' flag to be shown | ||
| cmd.InitDefaultVersionFlag() // make possible 'version' flag to be shown | ||
| CheckErr(cmd.Help()) | ||
| cmd.HelpFunc()(cmd, remainingArgs) |
There was a problem hiding this comment.
I think we are missing documentation explaning that Run, RunE, and HelpFunc accept the remaining arguments after parsing. Previously HelpFunc sometimes accepted no arguments and sometimes accepted the raw parsed arguments.
Can be here:
// SetHelpFunc sets help function. Can be defined by Application.
// The help function is called with the remaining arguments after parsing
// the flags. If flag parsing is disabled it is called with all arguments.
func (c *Command) SetHelpFunc(f func(*Command, []string)) {
c.helpFunc = f
}
Fixes #2154
This PR makes sure that when Cobra calls
HelpFunc()(cmd, args), the proper arguments are passed to the function.With this, when the help function is overridden, the new function can choose to print the help output based on what the args are.
For example, say I write my own
lsprogram, I could override the help function usingSetHelpFunc()so thatls -hwould printList the content of the current directoryls mydir -hwould instead printList the content of the "mydir" directoryThis PR fixed the two cases where the help function is called:
--help/-hflag (e.g.,prog sub arg1 -h)helpcommand (e.g.,prog help sub arg1)Another value of this fix is for programs that use plugins and want to ask the plugin what the help output should be.
For instance the
tanzuCLI does this, where if I typetanzu help cluster listthe help function will call theclusterplugin but needs to tell it that it needs the help for thelistcommand. Thislistargument needs this PR to get passed to the help function. One can see how thetanzucode had to useos.Argsto work around this bug.Unit tests have been added which also illustrate the different cases that that PR fixes.
A test program is provided in #2154 to show the problem before this PR.
Please refer to a couple of comments I will add in the PR review for a concern about backwards-compatibility.