-
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
Show the actual available flags in useline #2105
Conversation
Use common useline notation for available flags: - `[-f foo]` for (standard) optional flags - `[-f foo | -b bar]` for mutually exclusive flags - `-f foo` for required flags - `{-f foo | -b bar}` for mutually exclusive flags where one is required - `[-f foo -b bar]` for flags required as a group For a boolean flag `f`, ` foo` is dropped from the above. The foo/bar comes from `flag.UnquoteUsage`.
This sounds like a nice improvement @lugray. Thanks! I’ll try to find some time in the coming weeks. |
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.
Thanks @lugray! can you share complete help output of complex commands with many arguments using this feature? How long lines are handled?
For example other tools with complex command lines options using this style use multiple lines to make the output nicer.
This example is using manual formatting - hard work but you have complete control on the output:
https://gitlab.com/nbdkit/libnbd/-/blob/master/copy/main.c?ref_type=heads#L69
@@ -1066,6 +1066,72 @@ func TestFlagsInUsage(t *testing.T) { | |||
checkStringContains(t, output, "[flags]") | |||
} | |||
|
|||
func TestMutuallyExclusiveFlagsInUsage(t *testing.T) { | |||
rootCmd := &Command{Use: "root", Args: NoArgs, Run: func(*Command, []string) {}} |
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.
Instead of func(*Command, []string) {}}
we can use emptyRun
t.Errorf("Unexpected error: %v", err) | ||
} | ||
|
||
checkStringContains(t, output, "[--foo | --bar]") |
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.
Did you consider more condensed format?
command [--foo|--bar]
popular tools like python argparse use the same format suggested, for example:
$ cat args.py
import argparse
p = argparse.ArgumentParser()
me = p.add_mutually_exclusive_group()
me.add_argument("--foo")
me.add_argument("--bar")
p.parse_args()
$ python args.py -h | head -1
usage: args.py [-h] [--foo FOO | --bar BAR]
t.Errorf("Unexpected error: %v", err) | ||
} | ||
|
||
checkStringContains(t, output, "{--foo | --bar}") |
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.
Any reason to chose {}
for required choice from mutually exclusive arguments?
python argparse uses ()
for the same:
$ cat args.py
import argparse
p = argparse.ArgumentParser()
me = p.add_mutually_exclusive_group(required=True)
me.add_argument("--foo")
me.add_argument("--bar")
p.parse_args()
$ python args.py -h | head -1
usage: args.py [-h] (--foo FOO | --bar BAR)
if varname != "" { | ||
usage += " " + varname | ||
} | ||
return |
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 bare return is not consistent with other code added.
} else { | ||
flagsLine += " [" + shortUsage(f) + "]" | ||
} | ||
}) |
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.
The code to list the short usage strings is repeated twice, making this function even more complicated and hard to follow. We have here many very short variable names (arg, me, or, gr, fl) and complex string formatting. It is pain to follow this code.
We can make it little better by extracting few helpers. I'm not sure about the names, they can be better.
func groupShortUsage(flags map[string]*flag.Flag, included map[*flag.Flag]struct{}) []string {
gr := []string{}
for _, fl := range flags {
included[fl] = struct{}{}
gr = append(gr, shortUsage(fl))
}
return gr
}
func isOneRequired(f *flag.Flag) bool {
req, found := f.Annotations[BashCompOneRequiredFlag]
return found && req[0] == "true"
}
Now we have only 3 very short variables names which is easy to grasp, the rest is the obvious formatting.
rag := flagsFromAnnotation(c, f, requiredAsGroup)
me := flagsFromAnnotation(c, f, mutuallyExclusive)
or := flagsFromAnnotation(c, f, oneRequired)
if len(rag) > 0 {
usages := groupShortUsage(rag, included)
flagsLine += " [" + strings.Join(usages, " ") + "]"
} else if len(me) > 0 {
usages := groupShortUsage(me, included)
if sameFlags(me, or) {
flagsLine += " {" + strings.Join(usages, " | ") + "}"
} else {
flagsLine += " [" + strings.Join(usages, " | ") + "]"
}
} else if isOneRequired(f) {
flagsLine += " " + shortUsage(f)
} else {
flagsLine += " [" + shortUsage(f) + "]"
}
// DisableVerboseFlagsInUseLine will add only [flags] to the usage line of | ||
// a command when printing help or generating docs instead of a more verbose | ||
// form showing the actual available flags | ||
DisableVerboseFlagsInUseLine bool |
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 is consistent with other options, but this change may visually break existing tools using this library. Should we use EnableVerboseFlagsInUsageLine
so one can opt in to use the new feature?
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 is an important point. Let's see where the final change lands and we can decide if we need to be less aggressive in pushing this change
Add `Annotation` suffix to the private annotations to allow nicer code using the constants. For example one can use the current annotation names as a temporary variable instead of unclear shortcut. Instead of this: rag := flagsFromAnnotation(c, f, requiredAsGroup) me := flagsFromAnnotation(c, f, mutuallyExclusive) or := flagsFromAnnotation(c, f, oneRequired) We can use now: requiredAsGrop := flagsFromAnnotation(c, f, requiredAsGroupAnnotation) mutuallyExclusive := flagsFromAnnotation(c, f, mutuallyExclusiveAnnotation) oneRequired := flagsFromAnnotation(c, f, oneRequiredAnnotation) Example taken from spf13#2105.
Add `Annotation` suffix to the private annotations to allow nicer code using the constants. For example one can use the current annotation names as a temporary variable instead of unclear shortcut. Instead of this: rag := flagsFromAnnotation(c, f, requiredAsGroup) me := flagsFromAnnotation(c, f, mutuallyExclusive) or := flagsFromAnnotation(c, f, oneRequired) We can use now: requiredAsGrop := flagsFromAnnotation(c, f, requiredAsGroupAnnotation) mutuallyExclusive := flagsFromAnnotation(c, f, mutuallyExclusiveAnnotation) oneRequired := flagsFromAnnotation(c, f, oneRequiredAnnotation) Example taken from spf13#2105.
I like the idea behind this improvement. Thanks @lugray ! I do prefer the multi-line format @nirs suggests. Currently for example, here is the change to a helm command help:
As you can see this is problematic for tools with lots of flags. The above gives me a couple of ideas:
|
Add `Annotation` suffix to the private annotations to allow nicer code using the constants. For example one can use the current annotation names as a temporary variable instead of unclear shortcut. Instead of this: rag := flagsFromAnnotation(c, f, requiredAsGroup) me := flagsFromAnnotation(c, f, mutuallyExclusive) or := flagsFromAnnotation(c, f, oneRequired) We can use now: requiredAsGrop := flagsFromAnnotation(c, f, requiredAsGroupAnnotation) mutuallyExclusive := flagsFromAnnotation(c, f, mutuallyExclusiveAnnotation) oneRequired := flagsFromAnnotation(c, f, oneRequiredAnnotation) Example taken from #2105.
Trying helm help with multiple lines (manually formatted):
Much better than long line but still overwhelming. The current output is better. Finding the right flag in the list of flags is much easier.
Trying without global flags looks much better:
Maybe we need 2 options?
Another way , add MarkShowFlagInUseLine() so it is possible to include or exclude flags from the use line to show only the most important flags in the use line. This allows making insecure options like |
Seeing the whole output makes me realize we have a duplication of flag names very close together (in the usage line and listed below it). This makes me wonder if the proposed change brings much value to the end user? I’m leaning towards keeping things as is to avoid unnecessarily complicating the code base. But I’m open to hear if someone sees more value in this modified help text. |
I'm going to close this as "won't implement". |
Use common useline notation for available flags:
[-f foo]
for (standard) optional flags[-f foo | -b bar]
for mutually exclusive flags-f foo
for required flags{-f foo | -b bar}
for mutually exclusive flags where one is required[-f foo -b bar]
for flags required as a groupFor a boolean flag
f
,foo
is dropped from the above. The foo/bar comes fromflag.UnquoteUsage
.