mirror of
https://github.com/spf13/cobra
synced 2024-11-24 22:57:12 +00:00
Add Bash support for command-substitute flags
These are useful to mark a flag as taking the place of a command. For example, `kubectl create` can be called using either a subcommand or by providing a file as input: kubectl create --filename=file-input.yml kubectl create secret generic [...] This allows the generated Bash completions to reflect this and suggest both commands and command-substitute flags.
This commit is contained in:
parent
f368244301
commit
913b78e7a9
3 changed files with 87 additions and 0 deletions
|
@ -14,6 +14,7 @@ const (
|
||||||
BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extentions"
|
BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extentions"
|
||||||
BashCompCustom = "cobra_annotation_bash_completion_custom"
|
BashCompCustom = "cobra_annotation_bash_completion_custom"
|
||||||
BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"
|
BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"
|
||||||
|
BashCompFlagIsCommand = "cobra_annocation_bash_completion_flag_is_command"
|
||||||
BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir"
|
BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -122,6 +123,7 @@ __handle_reply()
|
||||||
completions=("${must_have_one_noun[@]}")
|
completions=("${must_have_one_noun[@]}")
|
||||||
else
|
else
|
||||||
completions=("${commands[@]}")
|
completions=("${commands[@]}")
|
||||||
|
completions+=("${flag_command_substitutes[@]}")
|
||||||
fi
|
fi
|
||||||
COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") )
|
COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") )
|
||||||
|
|
||||||
|
@ -167,6 +169,12 @@ __handle_flag()
|
||||||
must_have_one_flag=()
|
must_have_one_flag=()
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# if the flag is a substitute for a command, unset commands() and flag_command_substitutes()
|
||||||
|
if __contains_word "${flagname}" "${flag_command_substitutes[@]}"; then
|
||||||
|
commands=()
|
||||||
|
flag_command_substitutes=()
|
||||||
|
fi
|
||||||
|
|
||||||
# keep flag value with flagname as flaghash
|
# keep flag value with flagname as flaghash
|
||||||
if [ -n "${flagvalue}" ] ; then
|
if [ -n "${flagvalue}" ] ; then
|
||||||
flaghash[${flagname}]=${flagvalue}
|
flaghash[${flagname}]=${flagvalue}
|
||||||
|
@ -466,6 +474,39 @@ func writeRequiredFlag(cmd *Command, w io.Writer) error {
|
||||||
return visitErr
|
return visitErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeFlagCommandSubstitutes(cmd *Command, w io.Writer) error {
|
||||||
|
if _, err := fmt.Fprintf(w, " flag_command_substitutes=()\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
flags := cmd.NonInheritedFlags()
|
||||||
|
var visitErr error
|
||||||
|
flags.VisitAll(func(flag *pflag.Flag) {
|
||||||
|
for key := range flag.Annotations {
|
||||||
|
switch key {
|
||||||
|
case BashCompFlagIsCommand:
|
||||||
|
format := " flag_command_substitutes+=(\"--%s"
|
||||||
|
b := (flag.Value.Type() == "bool")
|
||||||
|
if !b {
|
||||||
|
format += "="
|
||||||
|
}
|
||||||
|
format += "\")\n"
|
||||||
|
if _, err := fmt.Fprintf(w, format, flag.Name); err != nil {
|
||||||
|
visitErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(flag.Shorthand) > 0 {
|
||||||
|
if _, err := fmt.Fprintf(w, " flag_command_substitutes+=(\"-%s\")\n", flag.Shorthand); err != nil {
|
||||||
|
visitErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return visitErr
|
||||||
|
}
|
||||||
|
|
||||||
func writeRequiredNouns(cmd *Command, w io.Writer) error {
|
func writeRequiredNouns(cmd *Command, w io.Writer) error {
|
||||||
if _, err := fmt.Fprintf(w, " must_have_one_noun=()\n"); err != nil {
|
if _, err := fmt.Fprintf(w, " must_have_one_noun=()\n"); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -519,6 +560,9 @@ func gen(cmd *Command, w io.Writer) error {
|
||||||
if err := writeRequiredFlag(cmd, w); err != nil {
|
if err := writeRequiredFlag(cmd, w); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := writeFlagCommandSubstitutes(cmd, w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := writeRequiredNouns(cmd, w); err != nil {
|
if err := writeRequiredNouns(cmd, w); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -571,6 +615,21 @@ func MarkFlagRequired(flags *pflag.FlagSet, name string) error {
|
||||||
return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"})
|
return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarkFlagCommandSubstitute adds the BashCompFlagIsCommand annotation to the named flag, if it exists.
|
||||||
|
func (cmd *Command) MarkFlagCommandSubstitute(name string) error {
|
||||||
|
return MarkFlagCommandSubstitute(cmd.Flags(), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkPersistentFlagCommandSubstitute adds the BashCompFlagIsCommand annotation to the named flag, if it exists.
|
||||||
|
func (cmd *Command) MarkPersistentFlagCommandSubstitute(name string) error {
|
||||||
|
return MarkFlagCommandSubstitute(cmd.PersistentFlags(), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkFlagCommandSubstitute adds the BashCompFlagIsCommand annotation to the named flag in the flag set, if it exists.
|
||||||
|
func MarkFlagCommandSubstitute(flags *pflag.FlagSet, name string) error {
|
||||||
|
return flags.SetAnnotation(name, BashCompFlagIsCommand, []string{"true"})
|
||||||
|
}
|
||||||
|
|
||||||
// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists.
|
// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists.
|
||||||
// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
|
// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
|
||||||
func (cmd *Command) MarkFlagFilename(name string, extensions ...string) error {
|
func (cmd *Command) MarkFlagFilename(name string, extensions ...string) error {
|
||||||
|
|
|
@ -143,6 +143,21 @@ and you'll get something like
|
||||||
-c --container= -p --pod=
|
-c --container= -p --pod=
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Mark flags as substitutes for subcommands
|
||||||
|
|
||||||
|
A flag may be sufficient input to a command that would normally require subcommands. Most commonly, this is the case when the flag specifies a file containing operations to run. In this case, we mark the flag as a command substitute:
|
||||||
|
|
||||||
|
```go
|
||||||
|
cmd.MarkFlagCommandSubstitute("filename")
|
||||||
|
```
|
||||||
|
|
||||||
|
This will result in something like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# kubectl create [tab][tab]
|
||||||
|
--filename= -f secret user
|
||||||
|
```
|
||||||
|
|
||||||
# Specify valid filename extensions for flags that take a filename
|
# Specify valid filename extensions for flags that take a filename
|
||||||
|
|
||||||
In this example we use --filename= and expect to get a json or yaml file as the argument. To make this easier we annotate the --filename flag with valid filename extensions.
|
In this example we use --filename= and expect to get a json or yaml file as the argument. To make this easier we annotate the --filename flag with valid filename extensions.
|
||||||
|
|
|
@ -97,6 +97,16 @@ func TestBashCompletions(t *testing.T) {
|
||||||
c.Flags().StringVar(&flagvalTheme, "theme", "", "theme to use (located in /themes/THEMENAME/)")
|
c.Flags().StringVar(&flagvalTheme, "theme", "", "theme to use (located in /themes/THEMENAME/)")
|
||||||
c.Flags().SetAnnotation("theme", BashCompSubdirsInDir, []string{"themes"})
|
c.Flags().SetAnnotation("theme", BashCompSubdirsInDir, []string{"themes"})
|
||||||
|
|
||||||
|
// command-substitute flag
|
||||||
|
var flagvalCommand string
|
||||||
|
c.Flags().StringVar(&flagvalCommand, "commandflag", "", "This is a command")
|
||||||
|
c.MarkFlagCommandSubstitute("commandflag")
|
||||||
|
|
||||||
|
// persistent command-substitute flag
|
||||||
|
var flagvalPersistentCommand string
|
||||||
|
c.PersistentFlags().StringVar(&flagvalPersistentCommand, "persistent-commandflag", "", "This is a command")
|
||||||
|
c.MarkPersistentFlagCommandSubstitute("persistent-commandflag")
|
||||||
|
|
||||||
out := new(bytes.Buffer)
|
out := new(bytes.Buffer)
|
||||||
c.GenBashCompletion(out)
|
c.GenBashCompletion(out)
|
||||||
str := out.String()
|
str := out.String()
|
||||||
|
@ -110,6 +120,9 @@ func TestBashCompletions(t *testing.T) {
|
||||||
// check for required flags
|
// check for required flags
|
||||||
check(t, str, `must_have_one_flag+=("--introot=")`)
|
check(t, str, `must_have_one_flag+=("--introot=")`)
|
||||||
check(t, str, `must_have_one_flag+=("--persistent-filename=")`)
|
check(t, str, `must_have_one_flag+=("--persistent-filename=")`)
|
||||||
|
// check for command-substitute flags
|
||||||
|
check(t, str, `flag_command_substitutes+=("--commandflag=")`)
|
||||||
|
check(t, str, `flag_command_substitutes+=("--persistent-commandflag=")`)
|
||||||
// check for custom completion function
|
// check for custom completion function
|
||||||
check(t, str, `COMPREPLY=( "hello" )`)
|
check(t, str, `COMPREPLY=( "hello" )`)
|
||||||
// check for required nouns
|
// check for required nouns
|
||||||
|
|
Loading…
Reference in a new issue