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"
|
||||
BashCompCustom = "cobra_annotation_bash_completion_custom"
|
||||
BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"
|
||||
BashCompFlagIsCommand = "cobra_annocation_bash_completion_flag_is_command"
|
||||
BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir"
|
||||
)
|
||||
|
||||
|
@ -122,6 +123,7 @@ __handle_reply()
|
|||
completions=("${must_have_one_noun[@]}")
|
||||
else
|
||||
completions=("${commands[@]}")
|
||||
completions+=("${flag_command_substitutes[@]}")
|
||||
fi
|
||||
COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") )
|
||||
|
||||
|
@ -167,6 +169,12 @@ __handle_flag()
|
|||
must_have_one_flag=()
|
||||
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
|
||||
if [ -n "${flagvalue}" ] ; then
|
||||
flaghash[${flagname}]=${flagvalue}
|
||||
|
@ -466,6 +474,39 @@ func writeRequiredFlag(cmd *Command, w io.Writer) error {
|
|||
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 {
|
||||
if _, err := fmt.Fprintf(w, " must_have_one_noun=()\n"); err != nil {
|
||||
return err
|
||||
|
@ -519,6 +560,9 @@ func gen(cmd *Command, w io.Writer) error {
|
|||
if err := writeRequiredFlag(cmd, w); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeFlagCommandSubstitutes(cmd, w); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeRequiredNouns(cmd, w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -571,6 +615,21 @@ func MarkFlagRequired(flags *pflag.FlagSet, name string) error {
|
|||
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.
|
||||
// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
|
||||
func (cmd *Command) MarkFlagFilename(name string, extensions ...string) error {
|
||||
|
|
|
@ -143,6 +143,21 @@ and you'll get something like
|
|||
-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
|
||||
|
||||
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().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)
|
||||
c.GenBashCompletion(out)
|
||||
str := out.String()
|
||||
|
@ -110,6 +120,9 @@ func TestBashCompletions(t *testing.T) {
|
|||
// check for required flags
|
||||
check(t, str, `must_have_one_flag+=("--introot=")`)
|
||||
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(t, str, `COMPREPLY=( "hello" )`)
|
||||
// check for required nouns
|
||||
|
|
Loading…
Reference in a new issue