mirror of
https://github.com/spf13/cobra
synced 2024-11-04 21:07:19 +00:00
Show the actual available flags in useline
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 commit is contained in:
parent
bcfcff729e
commit
9867fb647f
2 changed files with 170 additions and 11 deletions
101
command.go
101
command.go
|
@ -247,6 +247,11 @@ type Command struct {
|
|||
// line of a command when printing help or generating docs
|
||||
DisableFlagsInUseLine bool
|
||||
|
||||
// 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
|
||||
|
||||
// DisableSuggestions disables the suggestions based on Levenshtein distance
|
||||
// that go along with 'unknown command' messages.
|
||||
DisableSuggestions bool
|
||||
|
@ -1449,13 +1454,101 @@ func (c *Command) UseLine() string {
|
|||
} else {
|
||||
useline = use
|
||||
}
|
||||
return useline + c.uselineFlags(useline)
|
||||
}
|
||||
|
||||
func (c *Command) uselineFlags(useline string) string {
|
||||
if c.DisableFlagsInUseLine {
|
||||
return useline
|
||||
return ""
|
||||
}
|
||||
if c.HasAvailableFlags() && !strings.Contains(useline, "[flags]") {
|
||||
useline += " [flags]"
|
||||
if !c.HasAvailableFlags() {
|
||||
return ""
|
||||
}
|
||||
return useline
|
||||
if strings.Contains(useline, "[flags]") {
|
||||
return ""
|
||||
}
|
||||
if c.DisableVerboseFlagsInUseLine {
|
||||
return " [flags]"
|
||||
}
|
||||
|
||||
included := map[*flag.Flag]struct{}{}
|
||||
flagsLine := ""
|
||||
|
||||
c.flags.VisitAll(func(f *flag.Flag) {
|
||||
if _, ok := included[f]; ok || f.Hidden {
|
||||
return
|
||||
}
|
||||
included[f] = struct{}{}
|
||||
|
||||
rag := flagsFromAnnotation(c, f, requiredAsGroup)
|
||||
me := flagsFromAnnotation(c, f, mutuallyExclusive)
|
||||
or := flagsFromAnnotation(c, f, oneRequired)
|
||||
|
||||
if len(rag) > 0 {
|
||||
gr := []string{}
|
||||
for _, fl := range rag {
|
||||
included[fl] = struct{}{}
|
||||
gr = append(gr, shortUsage(fl))
|
||||
}
|
||||
flagsLine += " [" + strings.Join(gr, " ") + "]"
|
||||
} else if len(me) > 0 {
|
||||
gr := []string{}
|
||||
for _, fl := range me {
|
||||
included[fl] = struct{}{}
|
||||
gr = append(gr, shortUsage(fl))
|
||||
}
|
||||
if sameFlags(me, or) {
|
||||
flagsLine += " {" + strings.Join(gr, " | ") + "}"
|
||||
} else {
|
||||
flagsLine += " [" + strings.Join(gr, " | ") + "]"
|
||||
}
|
||||
} else if req, found := f.Annotations[BashCompOneRequiredFlag]; found && req[0] == "true" {
|
||||
flagsLine += " " + shortUsage(f)
|
||||
} else {
|
||||
flagsLine += " [" + shortUsage(f) + "]"
|
||||
}
|
||||
})
|
||||
return flagsLine
|
||||
}
|
||||
|
||||
func shortUsage(f *flag.Flag) (usage string) {
|
||||
if f.Shorthand != "" {
|
||||
usage = "-" + f.Shorthand
|
||||
} else {
|
||||
usage = "--" + f.Name
|
||||
}
|
||||
|
||||
varname, _ := flag.UnquoteUsage(f)
|
||||
if varname != "" {
|
||||
usage += " " + varname
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func flagsFromAnnotation(c *Command, f *flag.Flag, name string) map[string]*flag.Flag {
|
||||
flags := map[string]*flag.Flag{}
|
||||
a := f.Annotations[name]
|
||||
for _, s := range a {
|
||||
for _, name := range strings.Split(s, " ") {
|
||||
fl := c.flags.Lookup(name)
|
||||
if fl != nil {
|
||||
flags[name] = fl
|
||||
}
|
||||
}
|
||||
}
|
||||
return flags
|
||||
}
|
||||
|
||||
func sameFlags(a, b map[string]*flag.Flag) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for k, v := range a {
|
||||
if b[k] != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// DebugFlags used to determine which flags have been assigned to which commands
|
||||
|
|
|
@ -384,7 +384,7 @@ func TestPlugin(t *testing.T) {
|
|||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
checkStringContains(t, cmdHelp, "kubectl plugin [flags]")
|
||||
checkStringContains(t, cmdHelp, "kubectl plugin [-h]")
|
||||
checkStringContains(t, cmdHelp, "help for kubectl plugin")
|
||||
}
|
||||
|
||||
|
@ -398,7 +398,7 @@ func TestPluginWithSubCommands(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
subCmd := &Command{Use: "sub [flags]", Args: NoArgs, Run: emptyRun}
|
||||
subCmd := &Command{Use: "sub [-h]", Args: NoArgs, Run: emptyRun}
|
||||
rootCmd.AddCommand(subCmd)
|
||||
|
||||
rootHelp, err := executeCommand(rootCmd, "-h")
|
||||
|
@ -415,7 +415,7 @@ func TestPluginWithSubCommands(t *testing.T) {
|
|||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
checkStringContains(t, childHelp, "kubectl plugin sub [flags]")
|
||||
checkStringContains(t, childHelp, "kubectl plugin sub [-h]")
|
||||
checkStringContains(t, childHelp, "help for sub")
|
||||
|
||||
helpHelp, err := executeCommand(rootCmd, "help", "-h")
|
||||
|
@ -975,7 +975,7 @@ func TestHelpCommandExecutedOnChildWithFlagThatShadowsParentFlag(t *testing.T) {
|
|||
}
|
||||
|
||||
expected := `Usage:
|
||||
parent child [flags]
|
||||
parent child [--bar] [--baz] [--foo] [-h]
|
||||
|
||||
Flags:
|
||||
--baz child baz usage
|
||||
|
@ -1053,11 +1053,11 @@ func TestHelpFlagInHelp(t *testing.T) {
|
|||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
checkStringContains(t, output, "[flags]")
|
||||
checkStringContains(t, output, "[-h]")
|
||||
}
|
||||
|
||||
func TestFlagsInUsage(t *testing.T) {
|
||||
rootCmd := &Command{Use: "root", Args: NoArgs, Run: func(*Command, []string) {}}
|
||||
rootCmd := &Command{Use: "root", Args: NoArgs, Run: func(*Command, []string) {}, DisableVerboseFlagsInUseLine: true}
|
||||
output, err := executeCommand(rootCmd, "--help")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
|
@ -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) {}}
|
||||
rootCmd.Flags().SortFlags = false
|
||||
rootCmd.Flags().Bool("foo", false, "foo desc")
|
||||
rootCmd.Flags().Bool("bar", false, "bar desc")
|
||||
rootCmd.MarkFlagsMutuallyExclusive("foo", "bar")
|
||||
output, err := executeCommand(rootCmd, "--help")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
checkStringContains(t, output, "[--foo | --bar]")
|
||||
}
|
||||
|
||||
func TestMutuallyExclusiveRequiredFlagsInUsage(t *testing.T) {
|
||||
rootCmd := &Command{Use: "root", Args: NoArgs, Run: func(*Command, []string) {}}
|
||||
rootCmd.Flags().SortFlags = false
|
||||
rootCmd.Flags().Bool("foo", false, "foo desc")
|
||||
rootCmd.Flags().Bool("bar", false, "bar desc")
|
||||
rootCmd.MarkFlagsMutuallyExclusive("foo", "bar")
|
||||
rootCmd.MarkFlagsOneRequired("foo", "bar")
|
||||
output, err := executeCommand(rootCmd, "--help")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
checkStringContains(t, output, "{--foo | --bar}")
|
||||
}
|
||||
|
||||
func TestRequiredFlagInUsage(t *testing.T) {
|
||||
rootCmd := &Command{Use: "root", Args: NoArgs, Run: func(*Command, []string) {}}
|
||||
rootCmd.Flags().Bool("foo", false, "foo desc")
|
||||
rootCmd.MarkFlagRequired("foo")
|
||||
output, err := executeCommand(rootCmd, "--help")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
checkStringContains(t, output, " --foo ")
|
||||
}
|
||||
|
||||
func TestRequiredTogetherFlagsInUsage(t *testing.T) {
|
||||
rootCmd := &Command{Use: "root", Args: NoArgs, Run: func(*Command, []string) {}}
|
||||
rootCmd.Flags().SortFlags = false
|
||||
rootCmd.Flags().Bool("foo", false, "foo desc")
|
||||
rootCmd.Flags().Bool("bar", false, "bar desc")
|
||||
rootCmd.MarkFlagsRequiredTogether("foo", "bar")
|
||||
output, err := executeCommand(rootCmd, "--help")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
checkStringContains(t, output, "[--foo --bar]")
|
||||
}
|
||||
|
||||
func TestFlagWithArgInUsage(t *testing.T) {
|
||||
rootCmd := &Command{Use: "root", Args: NoArgs, Run: func(*Command, []string) {}}
|
||||
rootCmd.Flags().String("output", "", "the output `file`")
|
||||
output, err := executeCommand(rootCmd, "--help")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
checkStringContains(t, output, "[--output file]")
|
||||
}
|
||||
|
||||
func TestHelpExecutedOnNonRunnableChild(t *testing.T) {
|
||||
rootCmd := &Command{Use: "root", Run: emptyRun}
|
||||
childCmd := &Command{Use: "child", Long: "Long description"}
|
||||
|
@ -2141,7 +2207,7 @@ func TestFlagErrorFuncHelp(t *testing.T) {
|
|||
}
|
||||
|
||||
expected := `Usage:
|
||||
c [flags]
|
||||
c [--help]
|
||||
|
||||
Flags:
|
||||
--help help for c
|
||||
|
|
Loading…
Reference in a new issue