mirror of https://github.com/k3d-io/k3d
parent
0af0207691
commit
3a962b3226
@ -0,0 +1,22 @@ |
|||||||
|
# Cobra Changelog |
||||||
|
|
||||||
|
## Pending |
||||||
|
* Fix man page doc generation - no auto generated tag when `cmd.DisableAutoGenTag = true` @jpmcb |
||||||
|
|
||||||
|
## v1.0.0 |
||||||
|
Announcing v1.0.0 of Cobra. 🎉 |
||||||
|
**Notable Changes** |
||||||
|
* Fish completion (including support for Go custom completion) @marckhouzam |
||||||
|
* API (urgent): Rename BashCompDirectives to ShellCompDirectives @marckhouzam |
||||||
|
* Remove/replace SetOutput on Command - deprecated @jpmcb |
||||||
|
* add support for autolabel stale PR @xchapter7x |
||||||
|
* Add Labeler Actions @xchapter7x |
||||||
|
* Custom completions coded in Go (instead of Bash) @marckhouzam |
||||||
|
* Partial Revert of #922 @jharshman |
||||||
|
* Add Makefile to project @jharshman |
||||||
|
* Correct documentation for InOrStdin @desponda |
||||||
|
* Apply formatting to templates @jharshman |
||||||
|
* Revert change so help is printed on stdout again @marckhouzam |
||||||
|
* Update md2man to v2.0.0 @pdf |
||||||
|
* update viper to v1.4.0 @umarcor |
||||||
|
* Update cmd/root.go example in README.md @jharshman |
@ -0,0 +1,27 @@ |
|||||||
|
## Projects using Cobra |
||||||
|
|
||||||
|
- [Bleve](http://www.blevesearch.com/) |
||||||
|
- [CockroachDB](http://www.cockroachlabs.com/) |
||||||
|
- [Delve](https://github.com/derekparker/delve) |
||||||
|
- [Docker (distribution)](https://github.com/docker/distribution) |
||||||
|
- [Gardener](https://github.com/gardener/gardenctl) |
||||||
|
- [Giant Swarm's gsctl](https://github.com/giantswarm/gsctl) |
||||||
|
- [Github CLI](https://github.com/cli/cli) |
||||||
|
- [GopherJS](http://www.gopherjs.org/) |
||||||
|
- [Helm](https://helm.sh) |
||||||
|
- [Hugo](https://gohugo.io) |
||||||
|
- [Istio](https://istio.io) |
||||||
|
- [Kubernetes](http://kubernetes.io/) |
||||||
|
- [Linkerd](https://linkerd.io/) |
||||||
|
- [Mattermost-server](https://github.com/mattermost/mattermost-server) |
||||||
|
- [Metal Stack CLI](https://github.com/metal-stack/metalctl) |
||||||
|
- [Moby (former Docker)](https://github.com/moby/moby) |
||||||
|
- [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack) |
||||||
|
- [OpenShift](https://www.openshift.com/) |
||||||
|
- [Pouch](https://github.com/alibaba/pouch) |
||||||
|
- [ProjectAtomic (enterprise)](http://www.projectatomic.io/) |
||||||
|
- [Prototool](https://github.com/uber/prototool) |
||||||
|
- [Rclone](https://rclone.org/) |
||||||
|
- [etcd](https://github.com/coreos/etcd) |
||||||
|
- [nehm](https://github.com/bogem/nehm) |
||||||
|
- [rkt](https://github.com/coreos/rkt) |
@ -0,0 +1,429 @@ |
|||||||
|
# Generating shell completions |
||||||
|
|
||||||
|
Cobra can generate shell completions for multiple shells. |
||||||
|
The currently supported shells are: |
||||||
|
- Bash |
||||||
|
- Zsh |
||||||
|
- Fish |
||||||
|
- PowerShell |
||||||
|
|
||||||
|
If you are using the generator you can create a completion command by running |
||||||
|
|
||||||
|
```bash |
||||||
|
cobra add completion |
||||||
|
``` |
||||||
|
and then modifying the generated `cmd/completion.go` file to look something like this |
||||||
|
(writing the shell script to stdout allows the most flexible use): |
||||||
|
|
||||||
|
```go |
||||||
|
var completionCmd = &cobra.Command{ |
||||||
|
Use: "completion [bash|zsh|fish|powershell]", |
||||||
|
Short: "Generate completion script", |
||||||
|
Long: `To load completions: |
||||||
|
|
||||||
|
Bash: |
||||||
|
|
||||||
|
$ source <(yourprogram completion bash) |
||||||
|
|
||||||
|
# To load completions for each session, execute once: |
||||||
|
Linux: |
||||||
|
$ yourprogram completion bash > /etc/bash_completion.d/yourprogram |
||||||
|
MacOS: |
||||||
|
$ yourprogram completion bash > /usr/local/etc/bash_completion.d/yourprogram |
||||||
|
|
||||||
|
Zsh: |
||||||
|
|
||||||
|
$ source <(yourprogram completion zsh) |
||||||
|
|
||||||
|
# To load completions for each session, execute once: |
||||||
|
$ yourprogram completion zsh > "${fpath[1]}/_yourprogram" |
||||||
|
|
||||||
|
Fish: |
||||||
|
|
||||||
|
$ yourprogram completion fish | source |
||||||
|
|
||||||
|
# To load completions for each session, execute once: |
||||||
|
$ yourprogram completion fish > ~/.config/fish/completions/yourprogram.fish |
||||||
|
`, |
||||||
|
DisableFlagsInUseLine: true, |
||||||
|
ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, |
||||||
|
Args: cobra.ExactValidArgs(1), |
||||||
|
Run: func(cmd *cobra.Command, args []string) { |
||||||
|
switch args[0] { |
||||||
|
case "bash": |
||||||
|
cmd.Root().GenBashCompletion(os.Stdout) |
||||||
|
case "zsh": |
||||||
|
cmd.Root().GenZshCompletion(os.Stdout) |
||||||
|
case "fish": |
||||||
|
cmd.Root().GenFishCompletion(os.Stdout, true) |
||||||
|
case "powershell": |
||||||
|
cmd.Root().GenPowerShellCompletion(os.Stdout) |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
**Note:** The cobra generator may include messages printed to stdout for example if the config file is loaded, this will break the auto complete script so must be removed. |
||||||
|
|
||||||
|
# Customizing completions |
||||||
|
|
||||||
|
The generated completion scripts will automatically handle completing commands and flags. However, you can make your completions much more powerful by providing information to complete your program's nouns and flag values. |
||||||
|
|
||||||
|
## Completion of nouns |
||||||
|
|
||||||
|
### Static completion of nouns |
||||||
|
|
||||||
|
Cobra allows you to provide a pre-defined list of completion choices for your nouns using the `ValidArgs` field. |
||||||
|
For example, if you want `kubectl get [tab][tab]` to show a list of valid "nouns" you have to set them. |
||||||
|
Some simplified code from `kubectl get` looks like: |
||||||
|
|
||||||
|
```go |
||||||
|
validArgs []string = { "pod", "node", "service", "replicationcontroller" } |
||||||
|
|
||||||
|
cmd := &cobra.Command{ |
||||||
|
Use: "get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)", |
||||||
|
Short: "Display one or many resources", |
||||||
|
Long: get_long, |
||||||
|
Example: get_example, |
||||||
|
Run: func(cmd *cobra.Command, args []string) { |
||||||
|
err := RunGet(f, out, cmd, args) |
||||||
|
util.CheckErr(err) |
||||||
|
}, |
||||||
|
ValidArgs: validArgs, |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Notice we put the `ValidArgs` field on the `get` sub-command. Doing so will give results like: |
||||||
|
|
||||||
|
```bash |
||||||
|
$ kubectl get [tab][tab] |
||||||
|
node pod replicationcontroller service |
||||||
|
``` |
||||||
|
|
||||||
|
#### Aliases for nouns |
||||||
|
|
||||||
|
If your nouns have aliases, you can define them alongside `ValidArgs` using `ArgAliases`: |
||||||
|
|
||||||
|
```go |
||||||
|
argAliases []string = { "pods", "nodes", "services", "svc", "replicationcontrollers", "rc" } |
||||||
|
|
||||||
|
cmd := &cobra.Command{ |
||||||
|
... |
||||||
|
ValidArgs: validArgs, |
||||||
|
ArgAliases: argAliases |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
The aliases are not shown to the user on tab completion, but they are accepted as valid nouns by |
||||||
|
the completion algorithm if entered manually, e.g. in: |
||||||
|
|
||||||
|
```bash |
||||||
|
$ kubectl get rc [tab][tab] |
||||||
|
backend frontend database |
||||||
|
``` |
||||||
|
|
||||||
|
Note that without declaring `rc` as an alias, the completion algorithm would not know to show the list of |
||||||
|
replication controllers following `rc`. |
||||||
|
|
||||||
|
### Dynamic completion of nouns |
||||||
|
|
||||||
|
In some cases it is not possible to provide a list of completions in advance. Instead, the list of completions must be determined at execution-time. In a similar fashion as for static completions, you can use the `ValidArgsFunction` field to provide a Go function that Cobra will execute when it needs the list of completion choices for the nouns of a command. Note that either `ValidArgs` or `ValidArgsFunction` can be used for a single cobra command, but not both. |
||||||
|
Simplified code from `helm status` looks like: |
||||||
|
|
||||||
|
```go |
||||||
|
cmd := &cobra.Command{ |
||||||
|
Use: "status RELEASE_NAME", |
||||||
|
Short: "Display the status of the named release", |
||||||
|
Long: status_long, |
||||||
|
RunE: func(cmd *cobra.Command, args []string) { |
||||||
|
RunGet(args[0]) |
||||||
|
}, |
||||||
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { |
||||||
|
if len(args) != 0 { |
||||||
|
return nil, cobra.ShellCompDirectiveNoFileComp |
||||||
|
} |
||||||
|
return getReleasesFromCluster(toComplete), cobra.ShellCompDirectiveNoFileComp |
||||||
|
}, |
||||||
|
} |
||||||
|
``` |
||||||
|
Where `getReleasesFromCluster()` is a Go function that obtains the list of current Helm releases running on the Kubernetes cluster. |
||||||
|
Notice we put the `ValidArgsFunction` on the `status` sub-command. Let's assume the Helm releases on the cluster are: `harbor`, `notary`, `rook` and `thanos` then this dynamic completion will give results like: |
||||||
|
|
||||||
|
```bash |
||||||
|
$ helm status [tab][tab] |
||||||
|
harbor notary rook thanos |
||||||
|
``` |
||||||
|
You may have noticed the use of `cobra.ShellCompDirective`. These directives are bit fields allowing to control some shell completion behaviors for your particular completion. You can combine them with the bit-or operator such as `cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp` |
||||||
|
```go |
||||||
|
// Indicates that the shell will perform its default behavior after completions |
||||||
|
// have been provided (this implies none of the other directives). |
||||||
|
ShellCompDirectiveDefault |
||||||
|
|
||||||
|
// Indicates an error occurred and completions should be ignored. |
||||||
|
ShellCompDirectiveError |
||||||
|
|
||||||
|
// Indicates that the shell should not add a space after the completion, |
||||||
|
// even if there is a single completion provided. |
||||||
|
ShellCompDirectiveNoSpace |
||||||
|
|
||||||
|
// Indicates that the shell should not provide file completion even when |
||||||
|
// no completion is provided. |
||||||
|
ShellCompDirectiveNoFileComp |
||||||
|
|
||||||
|
// Indicates that the returned completions should be used as file extension filters. |
||||||
|
// For example, to complete only files of the form *.json or *.yaml: |
||||||
|
// return []string{"yaml", "json"}, ShellCompDirectiveFilterFileExt |
||||||
|
// For flags, using MarkFlagFilename() and MarkPersistentFlagFilename() |
||||||
|
// is a shortcut to using this directive explicitly. |
||||||
|
// |
||||||
|
ShellCompDirectiveFilterFileExt |
||||||
|
|
||||||
|
// Indicates that only directory names should be provided in file completion. |
||||||
|
// For example: |
||||||
|
// return nil, ShellCompDirectiveFilterDirs |
||||||
|
// For flags, using MarkFlagDirname() is a shortcut to using this directive explicitly. |
||||||
|
// |
||||||
|
// To request directory names within another directory, the returned completions |
||||||
|
// should specify a single directory name within which to search. For example, |
||||||
|
// to complete directories within "themes/": |
||||||
|
// return []string{"themes"}, ShellCompDirectiveFilterDirs |
||||||
|
// |
||||||
|
ShellCompDirectiveFilterDirs |
||||||
|
``` |
||||||
|
|
||||||
|
***Note***: When using the `ValidArgsFunction`, Cobra will call your registered function after having parsed all flags and arguments provided in the command-line. You therefore don't need to do this parsing yourself. For example, when a user calls `helm status --namespace my-rook-ns [tab][tab]`, Cobra will call your registered `ValidArgsFunction` after having parsed the `--namespace` flag, as it would have done when calling the `RunE` function. |
||||||
|
|
||||||
|
#### Debugging |
||||||
|
|
||||||
|
Cobra achieves dynamic completion through the use of a hidden command called by the completion script. To debug your Go completion code, you can call this hidden command directly: |
||||||
|
```bash |
||||||
|
$ helm __complete status har<ENTER> |
||||||
|
harbor |
||||||
|
:4 |
||||||
|
Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr |
||||||
|
``` |
||||||
|
***Important:*** If the noun to complete is empty (when the user has not yet typed any letters of that noun), you must pass an empty parameter to the `__complete` command: |
||||||
|
```bash |
||||||
|
$ helm __complete status ""<ENTER> |
||||||
|
harbor |
||||||
|
notary |
||||||
|
rook |
||||||
|
thanos |
||||||
|
:4 |
||||||
|
Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr |
||||||
|
``` |
||||||
|
Calling the `__complete` command directly allows you to run the Go debugger to troubleshoot your code. You can also add printouts to your code; Cobra provides the following functions to use for printouts in Go completion code: |
||||||
|
```go |
||||||
|
// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE |
||||||
|
// is set to a file path) and optionally prints to stderr. |
||||||
|
cobra.CompDebug(msg string, printToStdErr bool) { |
||||||
|
cobra.CompDebugln(msg string, printToStdErr bool) |
||||||
|
|
||||||
|
// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE |
||||||
|
// is set to a file path) and to stderr. |
||||||
|
cobra.CompError(msg string) |
||||||
|
cobra.CompErrorln(msg string) |
||||||
|
``` |
||||||
|
***Important:*** You should **not** leave traces that print directly to stdout in your completion code as they will be interpreted as completion choices by the completion script. Instead, use the cobra-provided debugging traces functions mentioned above. |
||||||
|
|
||||||
|
## Completions for flags |
||||||
|
|
||||||
|
### Mark flags as required |
||||||
|
|
||||||
|
Most of the time completions will only show sub-commands. But if a flag is required to make a sub-command work, you probably want it to show up when the user types [tab][tab]. You can mark a flag as 'Required' like so: |
||||||
|
|
||||||
|
```go |
||||||
|
cmd.MarkFlagRequired("pod") |
||||||
|
cmd.MarkFlagRequired("container") |
||||||
|
``` |
||||||
|
|
||||||
|
and you'll get something like |
||||||
|
|
||||||
|
```bash |
||||||
|
$ kubectl exec [tab][tab] |
||||||
|
-c --container= -p --pod= |
||||||
|
``` |
||||||
|
|
||||||
|
### Specify dynamic flag completion |
||||||
|
|
||||||
|
As for nouns, Cobra provides a way of defining dynamic completion of flags. To provide a Go function that Cobra will execute when it needs the list of completion choices for a flag, you must register the function using the `command.RegisterFlagCompletionFunc()` function. |
||||||
|
|
||||||
|
```go |
||||||
|
flagName := "output" |
||||||
|
cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { |
||||||
|
return []string{"json", "table", "yaml"}, cobra.ShellCompDirectiveDefault |
||||||
|
}) |
||||||
|
``` |
||||||
|
Notice that calling `RegisterFlagCompletionFunc()` is done through the `command` with which the flag is associated. In our example this dynamic completion will give results like so: |
||||||
|
|
||||||
|
```bash |
||||||
|
$ helm status --output [tab][tab] |
||||||
|
json table yaml |
||||||
|
``` |
||||||
|
|
||||||
|
#### Debugging |
||||||
|
|
||||||
|
You can also easily debug your Go completion code for flags: |
||||||
|
```bash |
||||||
|
$ helm __complete status --output "" |
||||||
|
json |
||||||
|
table |
||||||
|
yaml |
||||||
|
:4 |
||||||
|
Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr |
||||||
|
``` |
||||||
|
***Important:*** You should **not** leave traces that print to stdout in your completion code as they will be interpreted as completion choices by the completion script. Instead, use the cobra-provided debugging traces functions mentioned further above. |
||||||
|
|
||||||
|
### Specify valid filename extensions for flags that take a filename |
||||||
|
|
||||||
|
To limit completions of flag values to file names with certain extensions you can either use the different `MarkFlagFilename()` functions or a combination of `RegisterFlagCompletionFunc()` and `ShellCompDirectiveFilterFileExt`, like so: |
||||||
|
```go |
||||||
|
flagName := "output" |
||||||
|
cmd.MarkFlagFilename(flagName, "yaml", "json") |
||||||
|
``` |
||||||
|
or |
||||||
|
```go |
||||||
|
flagName := "output" |
||||||
|
cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { |
||||||
|
return []string{"yaml", "json"}, ShellCompDirectiveFilterFileExt}) |
||||||
|
``` |
||||||
|
|
||||||
|
### Limit flag completions to directory names |
||||||
|
|
||||||
|
To limit completions of flag values to directory names you can either use the `MarkFlagDirname()` functions or a combination of `RegisterFlagCompletionFunc()` and `ShellCompDirectiveFilterDirs`, like so: |
||||||
|
```go |
||||||
|
flagName := "output" |
||||||
|
cmd.MarkFlagDirname(flagName) |
||||||
|
``` |
||||||
|
or |
||||||
|
```go |
||||||
|
flagName := "output" |
||||||
|
cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { |
||||||
|
return nil, cobra.ShellCompDirectiveFilterDirs |
||||||
|
}) |
||||||
|
``` |
||||||
|
To limit completions of flag values to directory names *within another directory* you can use a combination of `RegisterFlagCompletionFunc()` and `ShellCompDirectiveFilterDirs` like so: |
||||||
|
```go |
||||||
|
flagName := "output" |
||||||
|
cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { |
||||||
|
return []string{"themes"}, cobra.ShellCompDirectiveFilterDirs |
||||||
|
}) |
||||||
|
``` |
||||||
|
### Descriptions for completions |
||||||
|
|
||||||
|
Both `zsh` and `fish` allow for descriptions to annotate completion choices. For commands and flags, Cobra will provide the descriptions automatically, based on usage information. For example, using zsh: |
||||||
|
``` |
||||||
|
$ helm s[tab] |
||||||
|
search -- search for a keyword in charts |
||||||
|
show -- show information of a chart |
||||||
|
status -- displays the status of the named release |
||||||
|
``` |
||||||
|
while using fish: |
||||||
|
``` |
||||||
|
$ helm s[tab] |
||||||
|
search (search for a keyword in charts) show (show information of a chart) status (displays the status of the named release) |
||||||
|
``` |
||||||
|
|
||||||
|
Cobra allows you to add annotations to your own completions. Simply add the annotation text after each completion, following a `\t` separator. This technique applies to completions returned by `ValidArgs`, `ValidArgsFunction` and `RegisterFlagCompletionFunc()`. For example: |
||||||
|
```go |
||||||
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { |
||||||
|
return []string{"harbor\tAn image registry", "thanos\tLong-term metrics"}, cobra.ShellCompDirectiveNoFileComp |
||||||
|
}} |
||||||
|
``` |
||||||
|
or |
||||||
|
```go |
||||||
|
ValidArgs: []string{"bash\tCompletions for bash", "zsh\tCompletions for zsh"} |
||||||
|
``` |
||||||
|
## Bash completions |
||||||
|
|
||||||
|
### Dependencies |
||||||
|
|
||||||
|
The bash completion script generated by Cobra requires the `bash_completion` package. You should update the help text of your completion command to show how to install the `bash_completion` package ([Kubectl docs](https://kubernetes.io/docs/tasks/tools/install-kubectl/#enabling-shell-autocompletion)) |
||||||
|
|
||||||
|
### Aliases |
||||||
|
|
||||||
|
You can also configure `bash` aliases for your program and they will also support completions. |
||||||
|
|
||||||
|
```bash |
||||||
|
alias aliasname=origcommand |
||||||
|
complete -o default -F __start_origcommand aliasname |
||||||
|
|
||||||
|
# and now when you run `aliasname` completion will make |
||||||
|
# suggestions as it did for `origcommand`. |
||||||
|
|
||||||
|
$ aliasname <tab><tab> |
||||||
|
completion firstcommand secondcommand |
||||||
|
``` |
||||||
|
### Bash legacy dynamic completions |
||||||
|
|
||||||
|
For backwards-compatibility, Cobra still supports its bash legacy dynamic completion solution. |
||||||
|
Please refer to [Bash Completions](bash_completions.md) for details. |
||||||
|
|
||||||
|
## Zsh completions |
||||||
|
|
||||||
|
Cobra supports native Zsh completion generated from the root `cobra.Command`. |
||||||
|
The generated completion script should be put somewhere in your `$fpath` and be named |
||||||
|
`_<yourProgram>`. |
||||||
|
|
||||||
|
Zsh supports descriptions for completions. Cobra will provide the description automatically, |
||||||
|
based on usage information. Cobra provides a way to completely disable such descriptions by |
||||||
|
using `GenZshCompletionNoDesc()` or `GenZshCompletionFileNoDesc()`. You can choose to make |
||||||
|
this a configurable option to your users. |
||||||
|
``` |
||||||
|
# With descriptions |
||||||
|
$ helm s[tab] |
||||||
|
search -- search for a keyword in charts |
||||||
|
show -- show information of a chart |
||||||
|
status -- displays the status of the named release |
||||||
|
|
||||||
|
# Without descriptions |
||||||
|
$ helm s[tab] |
||||||
|
search show status |
||||||
|
``` |
||||||
|
*Note*: Because of backwards-compatibility requirements, we were forced to have a different API to disable completion descriptions between `Zsh` and `Fish`. |
||||||
|
|
||||||
|
### Limitations |
||||||
|
|
||||||
|
* Custom completions implemented in Bash scripting (legacy) are not supported and will be ignored for `zsh` (including the use of the `BashCompCustom` flag annotation). |
||||||
|
* You should instead use `ValidArgsFunction` and `RegisterFlagCompletionFunc()` which are portable to the different shells (`bash`, `zsh`, `fish`). |
||||||
|
* The function `MarkFlagCustom()` is not supported and will be ignored for `zsh`. |
||||||
|
* You should instead use `RegisterFlagCompletionFunc()`. |
||||||
|
|
||||||
|
### Zsh completions standardization |
||||||
|
|
||||||
|
Cobra 1.1 standardized its zsh completion support to align it with its other shell completions. Although the API was kept backwards-compatible, some small changes in behavior were introduced. |
||||||
|
Please refer to [Zsh Completions](zsh_completions.md) for details. |
||||||
|
|
||||||
|
## Fish completions |
||||||
|
|
||||||
|
Cobra supports native Fish completions generated from the root `cobra.Command`. You can use the `command.GenFishCompletion()` or `command.GenFishCompletionFile()` functions. You must provide these functions with a parameter indicating if the completions should be annotated with a description; Cobra will provide the description automatically based on usage information. You can choose to make this option configurable by your users. |
||||||
|
``` |
||||||
|
# With descriptions |
||||||
|
$ helm s[tab] |
||||||
|
search (search for a keyword in charts) show (show information of a chart) status (displays the status of the named release) |
||||||
|
|
||||||
|
# Without descriptions |
||||||
|
$ helm s[tab] |
||||||
|
search show status |
||||||
|
``` |
||||||
|
*Note*: Because of backwards-compatibility requirements, we were forced to have a different API to disable completion descriptions between `Zsh` and `Fish`. |
||||||
|
|
||||||
|
### Limitations |
||||||
|
|
||||||
|
* Custom completions implemented in Bash scripting (legacy) are not supported and will be ignored for `fish` (including the use of the `BashCompCustom` flag annotation). |
||||||
|
* You should instead use `ValidArgsFunction` and `RegisterFlagCompletionFunc()` which are portable to the different shells (`bash`, `zsh`, `fish`). |
||||||
|
* The function `MarkFlagCustom()` is not supported and will be ignored for `fish`. |
||||||
|
* You should instead use `RegisterFlagCompletionFunc()`. |
||||||
|
* The following flag completion annotations are not supported and will be ignored for `fish`: |
||||||
|
* `BashCompFilenameExt` (filtering by file extension) |
||||||
|
* `BashCompSubdirsInDir` (filtering by directory) |
||||||
|
* The functions corresponding to the above annotations are consequently not supported and will be ignored for `fish`: |
||||||
|
* `MarkFlagFilename()` and `MarkPersistentFlagFilename()` (filtering by file extension) |
||||||
|
* `MarkFlagDirname()` and `MarkPersistentFlagDirname()` (filtering by directory) |
||||||
|
* Similarly, the following completion directives are not supported and will be ignored for `fish`: |
||||||
|
* `ShellCompDirectiveFilterFileExt` (filtering by file extension) |
||||||
|
* `ShellCompDirectiveFilterDirs` (filtering by directory) |
||||||
|
|
||||||
|
## PowerShell completions |
||||||
|
|
||||||
|
Please refer to [PowerShell Completions](powershell_completions.md) for details. |
@ -1,336 +1,235 @@ |
|||||||
package cobra |
package cobra |
||||||
|
|
||||||
import ( |
import ( |
||||||
"encoding/json" |
"bytes" |
||||||
"fmt" |
"fmt" |
||||||
"io" |
"io" |
||||||
"os" |
"os" |
||||||
"sort" |
|
||||||
"strings" |
|
||||||
"text/template" |
|
||||||
|
|
||||||
"github.com/spf13/pflag" |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
zshCompArgumentAnnotation = "cobra_annotations_zsh_completion_argument_annotation" |
|
||||||
zshCompArgumentFilenameComp = "cobra_annotations_zsh_completion_argument_file_completion" |
|
||||||
zshCompArgumentWordComp = "cobra_annotations_zsh_completion_argument_word_completion" |
|
||||||
zshCompDirname = "cobra_annotations_zsh_dirname" |
|
||||||
) |
) |
||||||
|
|
||||||
var ( |
// GenZshCompletionFile generates zsh completion file including descriptions.
|
||||||
zshCompFuncMap = template.FuncMap{ |
|
||||||
"genZshFuncName": zshCompGenFuncName, |
|
||||||
"extractFlags": zshCompExtractFlag, |
|
||||||
"genFlagEntryForZshArguments": zshCompGenFlagEntryForArguments, |
|
||||||
"extractArgsCompletions": zshCompExtractArgumentCompletionHintsForRendering, |
|
||||||
} |
|
||||||
zshCompletionText = ` |
|
||||||
{{/* should accept Command (that contains subcommands) as parameter */}} |
|
||||||
{{define "argumentsC" -}} |
|
||||||
{{ $cmdPath := genZshFuncName .}} |
|
||||||
function {{$cmdPath}} { |
|
||||||
local -a commands |
|
||||||
|
|
||||||
_arguments -C \{{- range extractFlags .}} |
|
||||||
{{genFlagEntryForZshArguments .}} \{{- end}} |
|
||||||
"1: :->cmnds" \
|
|
||||||
"*::arg:->args" |
|
||||||
|
|
||||||
case $state in |
|
||||||
cmnds) |
|
||||||
commands=({{range .Commands}}{{if not .Hidden}} |
|
||||||
"{{.Name}}:{{.Short}}"{{end}}{{end}} |
|
||||||
) |
|
||||||
_describe "command" commands |
|
||||||
;; |
|
||||||
esac |
|
||||||
|
|
||||||
case "$words[1]" in {{- range .Commands}}{{if not .Hidden}} |
|
||||||
{{.Name}}) |
|
||||||
{{$cmdPath}}_{{.Name}} |
|
||||||
;;{{end}}{{end}} |
|
||||||
esac |
|
||||||
} |
|
||||||
{{range .Commands}}{{if not .Hidden}} |
|
||||||
{{template "selectCmdTemplate" .}} |
|
||||||
{{- end}}{{end}} |
|
||||||
{{- end}} |
|
||||||
|
|
||||||
{{/* should accept Command without subcommands as parameter */}} |
|
||||||
{{define "arguments" -}} |
|
||||||
function {{genZshFuncName .}} { |
|
||||||
{{" _arguments"}}{{range extractFlags .}} \
|
|
||||||
{{genFlagEntryForZshArguments . -}} |
|
||||||
{{end}}{{range extractArgsCompletions .}} \
|
|
||||||
{{.}}{{end}} |
|
||||||
} |
|
||||||
{{end}} |
|
||||||
|
|
||||||
{{/* dispatcher for commands with or without subcommands */}} |
|
||||||
{{define "selectCmdTemplate" -}} |
|
||||||
{{if .Hidden}}{{/* ignore hidden*/}}{{else -}} |
|
||||||
{{if .Commands}}{{template "argumentsC" .}}{{else}}{{template "arguments" .}}{{end}} |
|
||||||
{{- end}} |
|
||||||
{{- end}} |
|
||||||
|
|
||||||
{{/* template entry point */}} |
|
||||||
{{define "Main" -}} |
|
||||||
#compdef _{{.Name}} {{.Name}} |
|
||||||
|
|
||||||
{{template "selectCmdTemplate" .}} |
|
||||||
{{end}} |
|
||||||
` |
|
||||||
) |
|
||||||
|
|
||||||
// zshCompArgsAnnotation is used to encode/decode zsh completion for
|
|
||||||
// arguments to/from Command.Annotations.
|
|
||||||
type zshCompArgsAnnotation map[int]zshCompArgHint |
|
||||||
|
|
||||||
type zshCompArgHint struct { |
|
||||||
// Indicates the type of the completion to use. One of:
|
|
||||||
// zshCompArgumentFilenameComp or zshCompArgumentWordComp
|
|
||||||
Tipe string `json:"type"` |
|
||||||
|
|
||||||
// A value for the type above (globs for file completion or words)
|
|
||||||
Options []string `json:"options"` |
|
||||||
} |
|
||||||
|
|
||||||
// GenZshCompletionFile generates zsh completion file.
|
|
||||||
func (c *Command) GenZshCompletionFile(filename string) error { |
func (c *Command) GenZshCompletionFile(filename string) error { |
||||||
outFile, err := os.Create(filename) |
return c.genZshCompletionFile(filename, true) |
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
defer outFile.Close() |
|
||||||
|
|
||||||
return c.GenZshCompletion(outFile) |
|
||||||
} |
} |
||||||
|
|
||||||
// GenZshCompletion generates a zsh completion file and writes to the passed
|
// GenZshCompletion generates zsh completion file including descriptions
|
||||||
// writer. The completion always run on the root command regardless of the
|
// and writes it to the passed writer.
|
||||||
// command it was called from.
|
|
||||||
func (c *Command) GenZshCompletion(w io.Writer) error { |
func (c *Command) GenZshCompletion(w io.Writer) error { |
||||||
tmpl, err := template.New("Main").Funcs(zshCompFuncMap).Parse(zshCompletionText) |
return c.genZshCompletion(w, true) |
||||||
if err != nil { |
|
||||||
return fmt.Errorf("error creating zsh completion template: %v", err) |
|
||||||
} |
|
||||||
return tmpl.Execute(w, c.Root()) |
|
||||||
} |
|
||||||
|
|
||||||
// MarkZshCompPositionalArgumentFile marks the specified argument (first
|
|
||||||
// argument is 1) as completed by file selection. patterns (e.g. "*.txt") are
|
|
||||||
// optional - if not provided the completion will search for all files.
|
|
||||||
func (c *Command) MarkZshCompPositionalArgumentFile(argPosition int, patterns ...string) error { |
|
||||||
if argPosition < 1 { |
|
||||||
return fmt.Errorf("Invalid argument position (%d)", argPosition) |
|
||||||
} |
|
||||||
annotation, err := c.zshCompGetArgsAnnotations() |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
if c.zshcompArgsAnnotationnIsDuplicatePosition(annotation, argPosition) { |
|
||||||
return fmt.Errorf("Duplicate annotation for positional argument at index %d", argPosition) |
|
||||||
} |
|
||||||
annotation[argPosition] = zshCompArgHint{ |
|
||||||
Tipe: zshCompArgumentFilenameComp, |
|
||||||
Options: patterns, |
|
||||||
} |
|
||||||
return c.zshCompSetArgsAnnotations(annotation) |
|
||||||
} |
|
||||||
|
|
||||||
// MarkZshCompPositionalArgumentWords marks the specified positional argument
|
|
||||||
// (first argument is 1) as completed by the provided words. At east one word
|
|
||||||
// must be provided, spaces within words will be offered completion with
|
|
||||||
// "word\ word".
|
|
||||||
func (c *Command) MarkZshCompPositionalArgumentWords(argPosition int, words ...string) error { |
|
||||||
if argPosition < 1 { |
|
||||||
return fmt.Errorf("Invalid argument position (%d)", argPosition) |
|
||||||
} |
|
||||||
if len(words) == 0 { |
|
||||||
return fmt.Errorf("Trying to set empty word list for positional argument %d", argPosition) |
|
||||||
} |
|
||||||
annotation, err := c.zshCompGetArgsAnnotations() |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
if c.zshcompArgsAnnotationnIsDuplicatePosition(annotation, argPosition) { |
|
||||||
return fmt.Errorf("Duplicate annotation for positional argument at index %d", argPosition) |
|
||||||
} |
|
||||||
annotation[argPosition] = zshCompArgHint{ |
|
||||||
Tipe: zshCompArgumentWordComp, |
|
||||||
Options: words, |
|
||||||
} |
|
||||||
return c.zshCompSetArgsAnnotations(annotation) |
|
||||||
} |
|
||||||
|
|
||||||
func zshCompExtractArgumentCompletionHintsForRendering(c *Command) ([]string, error) { |
|
||||||
var result []string |
|
||||||
annotation, err := c.zshCompGetArgsAnnotations() |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
for k, v := range annotation { |
|
||||||
s, err := zshCompRenderZshCompArgHint(k, v) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
result = append(result, s) |
|
||||||
} |
|
||||||
if len(c.ValidArgs) > 0 { |
|
||||||
if _, positionOneExists := annotation[1]; !positionOneExists { |
|
||||||
s, err := zshCompRenderZshCompArgHint(1, zshCompArgHint{ |
|
||||||
Tipe: zshCompArgumentWordComp, |
|
||||||
Options: c.ValidArgs, |
|
||||||
}) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
result = append(result, s) |
|
||||||
} |
|
||||||
} |
|
||||||
sort.Strings(result) |
|
||||||
return result, nil |
|
||||||
} |
} |
||||||
|
|
||||||
func zshCompRenderZshCompArgHint(i int, z zshCompArgHint) (string, error) { |
// GenZshCompletionFileNoDesc generates zsh completion file without descriptions.
|
||||||
switch t := z.Tipe; t { |
func (c *Command) GenZshCompletionFileNoDesc(filename string) error { |
||||||
case zshCompArgumentFilenameComp: |
return c.genZshCompletionFile(filename, false) |
||||||
var globs []string |
|
||||||
for _, g := range z.Options { |
|
||||||
globs = append(globs, fmt.Sprintf(`-g "%s"`, g)) |
|
||||||
} |
|
||||||
return fmt.Sprintf(`'%d: :_files %s'`, i, strings.Join(globs, " ")), nil |
|
||||||
case zshCompArgumentWordComp: |
|
||||||
var words []string |
|
||||||
for _, w := range z.Options { |
|
||||||
words = append(words, fmt.Sprintf("%q", w)) |
|
||||||
} |
|
||||||
return fmt.Sprintf(`'%d: :(%s)'`, i, strings.Join(words, " ")), nil |
|
||||||
default: |
|
||||||
return "", fmt.Errorf("Invalid zsh argument completion annotation: %s", t) |
|
||||||
} |
|
||||||
} |
} |
||||||
|
|
||||||
func (c *Command) zshcompArgsAnnotationnIsDuplicatePosition(annotation zshCompArgsAnnotation, position int) bool { |
// GenZshCompletionNoDesc generates zsh completion file without descriptions
|
||||||
_, dup := annotation[position] |
// and writes it to the passed writer.
|
||||||
return dup |
func (c *Command) GenZshCompletionNoDesc(w io.Writer) error { |
||||||
|
return c.genZshCompletion(w, false) |
||||||
} |
} |
||||||
|
|
||||||
func (c *Command) zshCompGetArgsAnnotations() (zshCompArgsAnnotation, error) { |
// MarkZshCompPositionalArgumentFile only worked for zsh and its behavior was
|
||||||
annotation := make(zshCompArgsAnnotation) |
// not consistent with Bash completion. It has therefore been disabled.
|
||||||
annotationString, ok := c.Annotations[zshCompArgumentAnnotation] |
// Instead, when no other completion is specified, file completion is done by
|
||||||
if !ok { |
// default for every argument. One can disable file completion on a per-argument
|
||||||
return annotation, nil |
// basis by using ValidArgsFunction and ShellCompDirectiveNoFileComp.
|
||||||
} |
// To achieve file extension filtering, one can use ValidArgsFunction and
|
||||||
err := json.Unmarshal([]byte(annotationString), &annotation) |
// ShellCompDirectiveFilterFileExt.
|
||||||
if err != nil { |
//
|
||||||
return annotation, fmt.Errorf("Error unmarshaling zsh argument annotation: %v", err) |
// Deprecated
|
||||||
} |
func (c *Command) MarkZshCompPositionalArgumentFile(argPosition int, patterns ...string) error { |
||||||
return annotation, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (c *Command) zshCompSetArgsAnnotations(annotation zshCompArgsAnnotation) error { |
|
||||||
jsn, err := json.Marshal(annotation) |
|
||||||
if err != nil { |
|
||||||
return fmt.Errorf("Error marshaling zsh argument annotation: %v", err) |
|
||||||
} |
|
||||||
if c.Annotations == nil { |
|
||||||
c.Annotations = make(map[string]string) |
|
||||||
} |
|
||||||
c.Annotations[zshCompArgumentAnnotation] = string(jsn) |
|
||||||
return nil |
return nil |
||||||
} |
} |
||||||
|
|
||||||
func zshCompGenFuncName(c *Command) string { |
// MarkZshCompPositionalArgumentWords only worked for zsh. It has therefore
|
||||||
if c.HasParent() { |
// been disabled.
|
||||||
return zshCompGenFuncName(c.Parent()) + "_" + c.Name() |
// To achieve the same behavior across all shells, one can use
|
||||||
} |
// ValidArgs (for the first argument only) or ValidArgsFunction for
|
||||||
return "_" + c.Name() |
// any argument (can include the first one also).
|
||||||
} |
//
|
||||||
|
// Deprecated
|
||||||
func zshCompExtractFlag(c *Command) []*pflag.Flag { |
func (c *Command) MarkZshCompPositionalArgumentWords(argPosition int, words ...string) error { |
||||||
var flags []*pflag.Flag |
return nil |
||||||
c.LocalFlags().VisitAll(func(f *pflag.Flag) { |
|
||||||
if !f.Hidden { |
|
||||||
flags = append(flags, f) |
|
||||||
} |
|
||||||
}) |
|
||||||
c.InheritedFlags().VisitAll(func(f *pflag.Flag) { |
|
||||||
if !f.Hidden { |
|
||||||
flags = append(flags, f) |
|
||||||
} |
|
||||||
}) |
|
||||||
return flags |
|
||||||
} |
|
||||||
|
|
||||||
// zshCompGenFlagEntryForArguments returns an entry that matches _arguments
|
|
||||||
// zsh-completion parameters. It's too complicated to generate in a template.
|
|
||||||
func zshCompGenFlagEntryForArguments(f *pflag.Flag) string { |
|
||||||
if f.Name == "" || f.Shorthand == "" { |
|
||||||
return zshCompGenFlagEntryForSingleOptionFlag(f) |
|
||||||
} |
|
||||||
return zshCompGenFlagEntryForMultiOptionFlag(f) |
|
||||||
} |
|
||||||
|
|
||||||
func zshCompGenFlagEntryForSingleOptionFlag(f *pflag.Flag) string { |
|
||||||
var option, multiMark, extras string |
|
||||||
|
|
||||||
if zshCompFlagCouldBeSpecifiedMoreThenOnce(f) { |
|
||||||
multiMark = "*" |
|
||||||
} |
|
||||||
|
|
||||||
option = "--" + f.Name |
|
||||||
if option == "--" { |
|
||||||
option = "-" + f.Shorthand |
|
||||||
} |
|
||||||
extras = zshCompGenFlagEntryExtras(f) |
|
||||||
|
|
||||||
return fmt.Sprintf(`'%s%s[%s]%s'`, multiMark, option, zshCompQuoteFlagDescription(f.Usage), extras) |
|
||||||
} |
|
||||||
|
|
||||||
func zshCompGenFlagEntryForMultiOptionFlag(f *pflag.Flag) string { |
|
||||||
var options, parenMultiMark, curlyMultiMark, extras string |
|
||||||
|
|
||||||
if zshCompFlagCouldBeSpecifiedMoreThenOnce(f) { |
|
||||||
parenMultiMark = "*" |
|
||||||
curlyMultiMark = "\\*" |
|
||||||
} |
|
||||||
|
|
||||||
options = fmt.Sprintf(`'(%s-%s %s--%s)'{%s-%s,%s--%s}`, |
|
||||||
parenMultiMark, f.Shorthand, parenMultiMark, f.Name, curlyMultiMark, f.Shorthand, curlyMultiMark, f.Name) |
|
||||||
extras = zshCompGenFlagEntryExtras(f) |
|
||||||
|
|
||||||
return fmt.Sprintf(`%s'[%s]%s'`, options, zshCompQuoteFlagDescription(f.Usage), extras) |
|
||||||
} |
} |
||||||
|
|
||||||
func zshCompGenFlagEntryExtras(f *pflag.Flag) string { |
func (c *Command) genZshCompletionFile(filename string, includeDesc bool) error { |
||||||
if f.NoOptDefVal != "" { |
outFile, err := os.Create(filename) |
||||||
return "" |
if err != nil { |
||||||
} |
return err |
||||||
|
|
||||||
extras := ":" // allow options for flag (even without assistance)
|
|
||||||
for key, values := range f.Annotations { |
|
||||||
switch key { |
|
||||||
case zshCompDirname: |
|
||||||
extras = fmt.Sprintf(":filename:_files -g %q", values[0]) |
|
||||||
case BashCompFilenameExt: |
|
||||||
extras = ":filename:_files" |
|
||||||
for _, pattern := range values { |
|
||||||
extras = extras + fmt.Sprintf(` -g "%s"`, pattern) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
} |
||||||
|
defer outFile.Close() |
||||||
|
|
||||||
return extras |
return c.genZshCompletion(outFile, includeDesc) |
||||||
} |
} |
||||||
|
|
||||||
func zshCompFlagCouldBeSpecifiedMoreThenOnce(f *pflag.Flag) bool { |
func (c *Command) genZshCompletion(w io.Writer, includeDesc bool) error { |
||||||
return strings.Contains(f.Value.Type(), "Slice") || |
buf := new(bytes.Buffer) |
||||||
strings.Contains(f.Value.Type(), "Array") |
genZshComp(buf, c.Name(), includeDesc) |
||||||
} |
_, err := buf.WriteTo(w) |
||||||
|
return err |
||||||
func zshCompQuoteFlagDescription(s string) string { |
} |
||||||
return strings.Replace(s, "'", `'\''`, -1) |
|
||||||
|
func genZshComp(buf *bytes.Buffer, name string, includeDesc bool) { |
||||||
|
compCmd := ShellCompRequestCmd |
||||||
|
if !includeDesc { |
||||||
|
compCmd = ShellCompNoDescRequestCmd |
||||||
|
} |
||||||
|
buf.WriteString(fmt.Sprintf(`#compdef _%[1]s %[1]s |
||||||
|
|
||||||
|
# zsh completion for %-36[1]s -*- shell-script -*- |
||||||
|
|
||||||
|
__%[1]s_debug() |
||||||
|
{ |
||||||
|
local file="$BASH_COMP_DEBUG_FILE" |
||||||
|
if [[ -n ${file} ]]; then |
||||||
|
echo "$*" >> "${file}" |
||||||
|
fi |
||||||
|
} |
||||||
|
|
||||||
|
_%[1]s() |
||||||
|
{ |
||||||
|
local shellCompDirectiveError=%[3]d |
||||||
|
local shellCompDirectiveNoSpace=%[4]d |
||||||
|
local shellCompDirectiveNoFileComp=%[5]d |
||||||
|
local shellCompDirectiveFilterFileExt=%[6]d |
||||||
|
local shellCompDirectiveFilterDirs=%[7]d |
||||||
|
|
||||||
|
local lastParam lastChar flagPrefix requestComp out directive compCount comp lastComp |
||||||
|
local -a completions |
||||||
|
|
||||||
|
__%[1]s_debug "\n========= starting completion logic ==========" |
||||||
|
__%[1]s_debug "CURRENT: ${CURRENT}, words[*]: ${words[*]}" |
||||||
|
|
||||||
|
# The user could have moved the cursor backwards on the command-line. |
||||||
|
# We need to trigger completion from the $CURRENT location, so we need |
||||||
|
# to truncate the command-line ($words) up to the $CURRENT location. |
||||||
|
# (We cannot use $CURSOR as its value does not work when a command is an alias.) |
||||||
|
words=("${=words[1,CURRENT]}") |
||||||
|
__%[1]s_debug "Truncated words[*]: ${words[*]}," |
||||||
|
|
||||||
|
lastParam=${words[-1]} |
||||||
|
lastChar=${lastParam[-1]} |
||||||
|
__%[1]s_debug "lastParam: ${lastParam}, lastChar: ${lastChar}" |
||||||
|
|
||||||
|
# For zsh, when completing a flag with an = (e.g., %[1]s -n=<TAB>) |
||||||
|
# completions must be prefixed with the flag |
||||||
|
setopt local_options BASH_REMATCH |
||||||
|
if [[ "${lastParam}" =~ '-.*=' ]]; then |
||||||
|
# We are dealing with a flag with an = |
||||||
|
flagPrefix="-P ${BASH_REMATCH}" |
||||||
|
fi |
||||||
|
|
||||||
|
# Prepare the command to obtain completions |
||||||
|
requestComp="${words[1]} %[2]s ${words[2,-1]}" |
||||||
|
if [ "${lastChar}" = "" ]; then |
||||||
|
# If the last parameter is complete (there is a space following it) |
||||||
|
# We add an extra empty parameter so we can indicate this to the go completion code. |
||||||
|
__%[1]s_debug "Adding extra empty parameter" |
||||||
|
requestComp="${requestComp} \"\"" |
||||||
|
fi |
||||||
|
|
||||||
|
__%[1]s_debug "About to call: eval ${requestComp}" |
||||||
|
|
||||||
|
# Use eval to handle any environment variables and such |
||||||
|
out=$(eval ${requestComp} 2>/dev/null) |
||||||
|
__%[1]s_debug "completion output: ${out}" |
||||||
|
|
||||||
|
# Extract the directive integer following a : from the last line |
||||||
|
local lastLine |
||||||
|
while IFS='\n' read -r line; do |
||||||
|
lastLine=${line} |
||||||
|
done < <(printf "%%s\n" "${out[@]}") |
||||||
|
__%[1]s_debug "last line: ${lastLine}" |
||||||
|
|
||||||
|
if [ "${lastLine[1]}" = : ]; then |
||||||
|
directive=${lastLine[2,-1]} |
||||||
|
# Remove the directive including the : and the newline |
||||||
|
local suffix |
||||||
|
(( suffix=${#lastLine}+2)) |
||||||
|
out=${out[1,-$suffix]} |
||||||
|
else |
||||||
|
# There is no directive specified. Leave $out as is. |
||||||
|
__%[1]s_debug "No directive found. Setting do default" |
||||||
|
directive=0 |
||||||
|
fi |
||||||
|
|
||||||
|
__%[1]s_debug "directive: ${directive}" |
||||||
|
__%[1]s_debug "completions: ${out}" |
||||||
|
__%[1]s_debug "flagPrefix: ${flagPrefix}" |
||||||
|
|
||||||
|
if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then |
||||||
|
__%[1]s_debug "Completion received error. Ignoring completions." |
||||||
|
return |
||||||
|
fi |
||||||
|
|
||||||
|
compCount=0 |
||||||
|
while IFS='\n' read -r comp; do |
||||||
|
if [ -n "$comp" ]; then |
||||||
|
# If requested, completions are returned with a description. |
||||||
|
# The description is preceded by a TAB character. |
||||||
|
# For zsh's _describe, we need to use a : instead of a TAB. |
||||||
|
# We first need to escape any : as part of the completion itself. |
||||||
|
comp=${comp//:/\\:}
|
||||||
|
|
||||||
|
local tab=$(printf '\t') |
||||||
|
comp=${comp//$tab/:}
|
||||||
|
|
||||||
|
((compCount++)) |
||||||
|
__%[1]s_debug "Adding completion: ${comp}" |
||||||
|
completions+=${comp} |
||||||
|
lastComp=$comp |
||||||
|
fi |
||||||
|
done < <(printf "%%s\n" "${out[@]}") |
||||||
|
|
||||||
|
if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then |
||||||
|
# File extension filtering |
||||||
|
local filteringCmd |
||||||
|
filteringCmd='_files' |
||||||
|
for filter in ${completions[@]}; do |
||||||
|
if [ ${filter[1]} != '*' ]; then |
||||||
|
# zsh requires a glob pattern to do file filtering |
||||||
|
filter="\*.$filter" |
||||||
|
fi |
||||||
|
filteringCmd+=" -g $filter" |
||||||
|
done |
||||||
|
filteringCmd+=" ${flagPrefix}" |
||||||
|
|
||||||
|
__%[1]s_debug "File filtering command: $filteringCmd" |
||||||
|
_arguments '*:filename:'"$filteringCmd" |
||||||
|
elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then |
||||||
|
# File completion for directories only |
||||||
|
local subDir |
||||||
|
subdir="${completions[1]}" |
||||||
|
if [ -n "$subdir" ]; then |
||||||
|
__%[1]s_debug "Listing directories in $subdir" |
||||||
|
pushd "${subdir}" >/dev/null 2>&1 |
||||||
|
else |
||||||
|
__%[1]s_debug "Listing directories in ." |
||||||
|
fi |
||||||
|
|
||||||
|
_arguments '*:dirname:_files -/'" ${flagPrefix}" |
||||||
|
if [ -n "$subdir" ]; then |
||||||
|
popd >/dev/null 2>&1 |
||||||
|
fi |
||||||
|
elif [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ] && [ ${compCount} -eq 1 ]; then |
||||||
|
__%[1]s_debug "Activating nospace." |
||||||
|
# We can use compadd here as there is no description when |
||||||
|
# there is only one completion. |
||||||
|
compadd -S '' "${lastComp}" |
||||||
|
elif [ ${compCount} -eq 0 ]; then |
||||||
|
if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then |
||||||
|
__%[1]s_debug "deactivating file completion" |
||||||
|
else |
||||||
|
# Perform file completion |
||||||
|
__%[1]s_debug "activating file completion" |
||||||
|
_arguments '*:filename:_files'" ${flagPrefix}" |
||||||
|
fi |
||||||
|
else |
||||||
|
_describe "completions" completions $(echo $flagPrefix) |
||||||
|
fi |
||||||
|
} |
||||||
|
`, name, compCmd, |
||||||
|
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, |
||||||
|
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) |
||||||
} |
} |
||||||
|
@ -1,39 +1,47 @@ |
|||||||
## Generating Zsh Completion for your cobra.Command |
## Generating Zsh Completion For Your cobra.Command |
||||||
|
|
||||||
Cobra supports native Zsh completion generated from the root `cobra.Command`. |
Please refer to [Shell Completions](shell_completions.md) for details. |
||||||
The generated completion script should be put somewhere in your `$fpath` named |
|
||||||
`_<YOUR COMMAND>`. |
## Zsh completions standardization |
||||||
|
|
||||||
### What's Supported |
Cobra 1.1 standardized its zsh completion support to align it with its other shell completions. Although the API was kept backwards-compatible, some small changes in behavior were introduced. |
||||||
|
|
||||||
* Completion for all non-hidden subcommands using their `.Short` description. |
### Deprecation summary |
||||||
* Completion for all non-hidden flags using the following rules: |
|
||||||
* Filename completion works by marking the flag with `cmd.MarkFlagFilename...` |
See further below for more details on these deprecations. |
||||||
family of commands. |
|
||||||
* The requirement for argument to the flag is decided by the `.NoOptDefVal` |
* `cmd.MarkZshCompPositionalArgumentFile(pos, []string{})` is no longer needed. It is therefore **deprecated** and silently ignored. |
||||||
flag value - if it's empty then completion will expect an argument. |
* `cmd.MarkZshCompPositionalArgumentFile(pos, glob[])` is **deprecated** and silently ignored. |
||||||
* Flags of one of the various `*Array` and `*Slice` types supports multiple |
* Instead use `ValidArgsFunction` with `ShellCompDirectiveFilterFileExt`. |
||||||
specifications (with or without argument depending on the specific type). |
* `cmd.MarkZshCompPositionalArgumentWords()` is **deprecated** and silently ignored. |
||||||
* Completion of positional arguments using the following rules: |
* Instead use `ValidArgsFunction`. |
||||||
* Argument position for all options below starts at `1`. If argument position |
|
||||||
`0` is requested it will raise an error. |
### Behavioral changes |
||||||
* Use `command.MarkZshCompPositionalArgumentFile` to complete filenames. Glob |
|
||||||
patterns (e.g. `"*.log"`) are optional - if not specified it will offer to |
**Noun completion** |
||||||
complete all file types. |
|Old behavior|New behavior| |
||||||
* Use `command.MarkZshCompPositionalArgumentWords` to offer specific words for |
|---|---| |
||||||
completion. At least one word is required. |
|No file completion by default (opposite of bash)|File completion by default; use `ValidArgsFunction` with `ShellCompDirectiveNoFileComp` to turn off file completion on a per-argument basis| |
||||||
* It's possible to specify completion for some arguments and leave some |
`cmd.MarkZshCompPositionalArgumentFile(pos, []string{})` used to turn on file completion on a per-argument position basis|File completion for all arguments by default; `cmd.MarkZshCompPositionalArgumentFile()` is **deprecated** and silently ignored| |
||||||
unspecified (e.g. offer words for second argument but nothing for first |
|`cmd.MarkZshCompPositionalArgumentFile(pos, glob[])` used to turn on file completion **with glob filtering** on a per-argument position basis (zsh-specific)|`cmd.MarkZshCompPositionalArgumentFile()` is **deprecated** and silently ignored; use `ValidArgsFunction` with `ShellCompDirectiveFilterFileExt` for file **extension** filtering (not full glob filtering)| |
||||||
argument). This will cause no completion for first argument but words |
|`cmd.MarkZshCompPositionalArgumentWords(pos, words[])` used to provide completion choices on a per-argument position basis (zsh-specific)|`cmd.MarkZshCompPositionalArgumentWords()` is **deprecated** and silently ignored; use `ValidArgsFunction` to achieve the same behavior| |
||||||
completion for second argument. |
|
||||||
* If no argument completion was specified for 1st argument (but optionally was |
**Flag-value completion** |
||||||
specified for 2nd) and the command has `ValidArgs` it will be used as |
|
||||||
completion options for 1st argument. |
|Old behavior|New behavior| |
||||||
* Argument completions only offered for commands with no subcommands. |
|---|---| |
||||||
|
|No file completion by default (opposite of bash)|File completion by default; use `RegisterFlagCompletionFunc()` with `ShellCompDirectiveNoFileComp` to turn off file completion| |
||||||
### What's not yet Supported |
|`cmd.MarkFlagFilename(flag, []string{})` and similar used to turn on file completion|File completion by default; `cmd.MarkFlagFilename(flag, []string{})` no longer needed in this context and silently ignored| |
||||||
|
|`cmd.MarkFlagFilename(flag, glob[])` used to turn on file completion **with glob filtering** (syntax of `[]string{"*.yaml", "*.yml"}` incompatible with bash)|Will continue to work, however, support for bash syntax is added and should be used instead so as to work for all shells (`[]string{"yaml", "yml"}`)| |
||||||
* Custom completion scripts are not supported yet (We should probably create zsh |
|`cmd.MarkFlagDirname(flag)` only completes directories (zsh-specific)|Has been added for all shells| |
||||||
specific one, doesn't make sense to re-use the bash one as the functions will |
|Completion of a flag name does not repeat, unless flag is of type `*Array` or `*Slice` (not supported by bash)|Retained for `zsh` and added to `fish`| |
||||||
be different). |
|Completion of a flag name does not provide the `=` form (unlike bash)|Retained for `zsh` and added to `fish`| |
||||||
* Whatever other feature you're looking for and doesn't exist :) |
|
||||||
|
**Improvements** |
||||||
|
|
||||||
|
* Custom completion support (`ValidArgsFunction` and `RegisterFlagCompletionFunc()`) |
||||||
|
* File completion by default if no other completions found |
||||||
|
* Handling of required flags |
||||||
|
* File extension filtering no longer mutually exclusive with bash usage |
||||||
|
* Completion of directory names *within* another directory |
||||||
|
* Support for `=` form of flags |
||||||
|
Loading…
Reference in new issue