mirror of https://github.com/docker/cli.git
vendor: github.com/spf13/cobra v1.1.1
full diff: https://github.com/spf13/cobra/compare/v1.0.0...v1.1.1 Notable changes: - Extend Go completions and revamp zsh comp - Add completion for help command - Complete subcommands when TraverseChildren is set - Fix stderr printing functions - fix: fish output redirection - fix manpage building with new go-md2man Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
parent
32861cb7aa
commit
74d45bbf61
|
@ -64,7 +64,7 @@ github.com/prometheus/procfs 46159f73e74d1cb8dc223deef9b2
|
||||||
github.com/russross/blackfriday/v2 d3b5b032dc8e8927d31a5071b56e14c89f045135 # v2.0.1
|
github.com/russross/blackfriday/v2 d3b5b032dc8e8927d31a5071b56e14c89f045135 # v2.0.1
|
||||||
github.com/shurcooL/sanitized_anchor_name 7bfe4c7ecddb3666a94b053b422cdd8f5aaa3615 # v1.0.0
|
github.com/shurcooL/sanitized_anchor_name 7bfe4c7ecddb3666a94b053b422cdd8f5aaa3615 # v1.0.0
|
||||||
github.com/sirupsen/logrus 6699a89a232f3db797f2e280639854bbc4b89725 # v1.7.0
|
github.com/sirupsen/logrus 6699a89a232f3db797f2e280639854bbc4b89725 # v1.7.0
|
||||||
github.com/spf13/cobra a684a6d7f5e37385d954dd3b5a14fc6912c6ab9d # v1.0.0
|
github.com/spf13/cobra 86f8bfd7fef868a174e1b606783bd7f5c82ddf8f # v1.1.1
|
||||||
github.com/spf13/pflag 2e9d26c8c37aae03e3f9d4e90b7116f5accb7cab # v1.0.5
|
github.com/spf13/pflag 2e9d26c8c37aae03e3f9d4e90b7116f5accb7cab # v1.0.5
|
||||||
github.com/theupdateframework/notary d6e1431feb32348e0650bf7551ac5cffd01d857b # v0.6.1
|
github.com/theupdateframework/notary d6e1431feb32348e0650bf7551ac5cffd01d857b # v0.6.1
|
||||||
github.com/tonistiigi/fsutil ae3a8d753069d0f76fbee396457e8b6cfd7cb8c3
|
github.com/tonistiigi/fsutil ae3a8d753069d0f76fbee396457e8b6cfd7cb8c3
|
||||||
|
|
|
@ -2,35 +2,14 @@
|
||||||
|
|
||||||
Cobra is both a library for creating powerful modern CLI applications as well as a program to generate applications and command files.
|
Cobra is both a library for creating powerful modern CLI applications as well as a program to generate applications and command files.
|
||||||
|
|
||||||
Many of the most widely used Go projects are built using Cobra, such as:
|
Cobra is used in many Go projects such as [Kubernetes](http://kubernetes.io/),
|
||||||
[Kubernetes](http://kubernetes.io/),
|
[Hugo](https://gohugo.io), and [Github CLI](https://github.com/cli/cli) to
|
||||||
[Hugo](http://gohugo.io),
|
name a few. [This list](./projects_using_cobra.md) contains a more extensive list of projects using Cobra.
|
||||||
[rkt](https://github.com/coreos/rkt),
|
|
||||||
[etcd](https://github.com/coreos/etcd),
|
|
||||||
[Moby (former Docker)](https://github.com/moby/moby),
|
|
||||||
[Docker (distribution)](https://github.com/docker/distribution),
|
|
||||||
[OpenShift](https://www.openshift.com/),
|
|
||||||
[Delve](https://github.com/derekparker/delve),
|
|
||||||
[GopherJS](http://www.gopherjs.org/),
|
|
||||||
[CockroachDB](http://www.cockroachlabs.com/),
|
|
||||||
[Bleve](http://www.blevesearch.com/),
|
|
||||||
[ProjectAtomic (enterprise)](http://www.projectatomic.io/),
|
|
||||||
[Giant Swarm's gsctl](https://github.com/giantswarm/gsctl),
|
|
||||||
[Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack),
|
|
||||||
[rclone](http://rclone.org/),
|
|
||||||
[nehm](https://github.com/bogem/nehm),
|
|
||||||
[Pouch](https://github.com/alibaba/pouch),
|
|
||||||
[Istio](https://istio.io),
|
|
||||||
[Prototool](https://github.com/uber/prototool),
|
|
||||||
[mattermost-server](https://github.com/mattermost/mattermost-server),
|
|
||||||
[Gardener](https://github.com/gardener/gardenctl),
|
|
||||||
[Linkerd](https://linkerd.io/),
|
|
||||||
[Github CLI](https://github.com/cli/cli)
|
|
||||||
etc.
|
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/spf13/cobra.svg "Travis CI status")](https://travis-ci.org/spf13/cobra)
|
[![Build Status](https://travis-ci.org/spf13/cobra.svg "Travis CI status")](https://travis-ci.org/spf13/cobra)
|
||||||
[![GoDoc](https://godoc.org/github.com/spf13/cobra?status.svg)](https://godoc.org/github.com/spf13/cobra)
|
[![GoDoc](https://godoc.org/github.com/spf13/cobra?status.svg)](https://godoc.org/github.com/spf13/cobra)
|
||||||
[![Go Report Card](https://goreportcard.com/badge/github.com/spf13/cobra)](https://goreportcard.com/report/github.com/spf13/cobra)
|
[![Go Report Card](https://goreportcard.com/badge/github.com/spf13/cobra)](https://goreportcard.com/report/github.com/spf13/cobra)
|
||||||
|
[![Slack](https://img.shields.io/badge/Slack-cobra-brightgreen)](https://gophers.slack.com/archives/CD3LP1199)
|
||||||
|
|
||||||
# Table of Contents
|
# Table of Contents
|
||||||
|
|
||||||
|
@ -50,9 +29,8 @@ etc.
|
||||||
* [PreRun and PostRun Hooks](#prerun-and-postrun-hooks)
|
* [PreRun and PostRun Hooks](#prerun-and-postrun-hooks)
|
||||||
* [Suggestions when "unknown command" happens](#suggestions-when-unknown-command-happens)
|
* [Suggestions when "unknown command" happens](#suggestions-when-unknown-command-happens)
|
||||||
* [Generating documentation for your command](#generating-documentation-for-your-command)
|
* [Generating documentation for your command](#generating-documentation-for-your-command)
|
||||||
* [Generating bash completions](#generating-bash-completions)
|
* [Generating shell completions](#generating-shell-completions)
|
||||||
* [Generating zsh completions](#generating-zsh-completions)
|
- [Contributing](CONTRIBUTING.md)
|
||||||
- [Contributing](#contributing)
|
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
|
|
||||||
# Overview
|
# Overview
|
||||||
|
@ -72,7 +50,7 @@ Cobra provides:
|
||||||
* Intelligent suggestions (`app srver`... did you mean `app server`?)
|
* Intelligent suggestions (`app srver`... did you mean `app server`?)
|
||||||
* Automatic help generation for commands and flags
|
* Automatic help generation for commands and flags
|
||||||
* Automatic help flag recognition of `-h`, `--help`, etc.
|
* Automatic help flag recognition of `-h`, `--help`, etc.
|
||||||
* Automatically generated bash autocomplete for your application
|
* Automatically generated shell autocomplete for your application (bash, zsh, fish, powershell)
|
||||||
* Automatically generated man pages for your application
|
* Automatically generated man pages for your application
|
||||||
* Command aliases so you can change things without breaking them
|
* Command aliases so you can change things without breaking them
|
||||||
* The flexibility to define your own help, usage, etc.
|
* The flexibility to define your own help, usage, etc.
|
||||||
|
@ -130,7 +108,7 @@ Using Cobra is easy. First, use `go get` to install the latest version
|
||||||
of the library. This command will install the `cobra` generator executable
|
of the library. This command will install the `cobra` generator executable
|
||||||
along with the library and its dependencies:
|
along with the library and its dependencies:
|
||||||
|
|
||||||
go get -u github.com/spf13/cobra/cobra
|
go get -u github.com/spf13/cobra
|
||||||
|
|
||||||
Next, include Cobra in your application:
|
Next, include Cobra in your application:
|
||||||
|
|
||||||
|
@ -199,7 +177,7 @@ var rootCmd = &cobra.Command{
|
||||||
|
|
||||||
func Execute() {
|
func Execute() {
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -335,6 +313,37 @@ var versionCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Returning and handling errors
|
||||||
|
|
||||||
|
If you wish to return an error to the caller of a command, `RunE` can be used.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(tryCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var tryCmd = &cobra.Command{
|
||||||
|
Use: "try",
|
||||||
|
Short: "Try and possibly fail at something",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if err := someFunc(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The error can then be caught at the execute function call.
|
||||||
|
|
||||||
## Working with Flags
|
## Working with Flags
|
||||||
|
|
||||||
Flags provide modifiers to control how the action command operates.
|
Flags provide modifiers to control how the action command operates.
|
||||||
|
@ -410,6 +419,12 @@ rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)")
|
||||||
rootCmd.MarkFlagRequired("region")
|
rootCmd.MarkFlagRequired("region")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or, for persistent flags:
|
||||||
|
```go
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&Region, "region", "r", "", "AWS region (required)")
|
||||||
|
rootCmd.MarkPersistentFlagRequired("region")
|
||||||
|
```
|
||||||
|
|
||||||
## Positional and Custom Arguments
|
## Positional and Custom Arguments
|
||||||
|
|
||||||
Validation of positional arguments can be specified using the `Args` field
|
Validation of positional arguments can be specified using the `Args` field
|
||||||
|
@ -740,30 +755,11 @@ Run 'kubectl help' for usage.
|
||||||
|
|
||||||
## Generating documentation for your command
|
## Generating documentation for your command
|
||||||
|
|
||||||
Cobra can generate documentation based on subcommands, flags, etc. in the following formats:
|
Cobra can generate documentation based on subcommands, flags, etc. Read more about it in the [docs generation documentation](doc/README.md).
|
||||||
|
|
||||||
- [Markdown](doc/md_docs.md)
|
## Generating shell completions
|
||||||
- [ReStructured Text](doc/rest_docs.md)
|
|
||||||
- [Man Page](doc/man_docs.md)
|
|
||||||
|
|
||||||
## Generating bash completions
|
Cobra can generate a shell-completion file for the following shells: Bash, Zsh, Fish, Powershell. If you add more information to your commands, these completions can be amazingly powerful and flexible. Read more about it in [Shell Completions](shell_completions.md).
|
||||||
|
|
||||||
Cobra can generate a bash-completion file. If you add more information to your command, these completions can be amazingly powerful and flexible. Read more about it in [Bash Completions](bash_completions.md).
|
|
||||||
|
|
||||||
## Generating zsh completions
|
|
||||||
|
|
||||||
Cobra can generate zsh-completion file. Read more about it in
|
|
||||||
[Zsh Completions](zsh_completions.md).
|
|
||||||
|
|
||||||
# Contributing
|
|
||||||
|
|
||||||
1. Fork it
|
|
||||||
2. Download your fork to your PC (`git clone https://github.com/your_username/cobra && cd cobra`)
|
|
||||||
3. Create your feature branch (`git checkout -b my-new-feature`)
|
|
||||||
4. Make changes and add them (`git add .`)
|
|
||||||
5. Commit your changes (`git commit -m 'Add some feature'`)
|
|
||||||
6. Push to the branch (`git push origin my-new-feature`)
|
|
||||||
7. Create new pull request
|
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,12 @@ __%[1]s_handle_go_custom_completion()
|
||||||
{
|
{
|
||||||
__%[1]s_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}"
|
__%[1]s_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}"
|
||||||
|
|
||||||
|
local shellCompDirectiveError=%[3]d
|
||||||
|
local shellCompDirectiveNoSpace=%[4]d
|
||||||
|
local shellCompDirectiveNoFileComp=%[5]d
|
||||||
|
local shellCompDirectiveFilterFileExt=%[6]d
|
||||||
|
local shellCompDirectiveFilterDirs=%[7]d
|
||||||
|
|
||||||
local out requestComp lastParam lastChar comp directive args
|
local out requestComp lastParam lastChar comp directive args
|
||||||
|
|
||||||
# Prepare the command to request completions for the program.
|
# Prepare the command to request completions for the program.
|
||||||
|
@ -95,24 +101,50 @@ __%[1]s_handle_go_custom_completion()
|
||||||
__%[1]s_debug "${FUNCNAME[0]}: the completion directive is: ${directive}"
|
__%[1]s_debug "${FUNCNAME[0]}: the completion directive is: ${directive}"
|
||||||
__%[1]s_debug "${FUNCNAME[0]}: the completions are: ${out[*]}"
|
__%[1]s_debug "${FUNCNAME[0]}: the completions are: ${out[*]}"
|
||||||
|
|
||||||
if [ $((directive & %[3]d)) -ne 0 ]; then
|
if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
|
||||||
# Error code. No completion.
|
# Error code. No completion.
|
||||||
__%[1]s_debug "${FUNCNAME[0]}: received error from custom completion go code"
|
__%[1]s_debug "${FUNCNAME[0]}: received error from custom completion go code"
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
if [ $((directive & %[4]d)) -ne 0 ]; then
|
if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
|
||||||
if [[ $(type -t compopt) = "builtin" ]]; then
|
if [[ $(type -t compopt) = "builtin" ]]; then
|
||||||
__%[1]s_debug "${FUNCNAME[0]}: activating no space"
|
__%[1]s_debug "${FUNCNAME[0]}: activating no space"
|
||||||
compopt -o nospace
|
compopt -o nospace
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [ $((directive & %[5]d)) -ne 0 ]; then
|
if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
|
||||||
if [[ $(type -t compopt) = "builtin" ]]; then
|
if [[ $(type -t compopt) = "builtin" ]]; then
|
||||||
__%[1]s_debug "${FUNCNAME[0]}: activating no file completion"
|
__%[1]s_debug "${FUNCNAME[0]}: activating no file completion"
|
||||||
compopt +o default
|
compopt +o default
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
|
||||||
|
# File extension filtering
|
||||||
|
local fullFilter filter filteringCmd
|
||||||
|
# Do not use quotes around the $out variable or else newline
|
||||||
|
# characters will be kept.
|
||||||
|
for filter in ${out[*]}; do
|
||||||
|
fullFilter+="$filter|"
|
||||||
|
done
|
||||||
|
|
||||||
|
filteringCmd="_filedir $fullFilter"
|
||||||
|
__%[1]s_debug "File filtering command: $filteringCmd"
|
||||||
|
$filteringCmd
|
||||||
|
elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
|
||||||
|
# File completion for directories only
|
||||||
|
local subDir
|
||||||
|
# Use printf to strip any trailing newline
|
||||||
|
subdir=$(printf "%%s" "${out[0]}")
|
||||||
|
if [ -n "$subdir" ]; then
|
||||||
|
__%[1]s_debug "Listing directories in $subdir"
|
||||||
|
__%[1]s_handle_subdirs_in_dir_flag "$subdir"
|
||||||
|
else
|
||||||
|
__%[1]s_debug "Listing directories in ."
|
||||||
|
_filedir -d
|
||||||
|
fi
|
||||||
|
else
|
||||||
while IFS='' read -r comp; do
|
while IFS='' read -r comp; do
|
||||||
COMPREPLY+=("$comp")
|
COMPREPLY+=("$comp")
|
||||||
done < <(compgen -W "${out[*]}" -- "$cur")
|
done < <(compgen -W "${out[*]}" -- "$cur")
|
||||||
|
@ -181,10 +213,9 @@ __%[1]s_handle_reply()
|
||||||
local completions
|
local completions
|
||||||
completions=("${commands[@]}")
|
completions=("${commands[@]}")
|
||||||
if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then
|
if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then
|
||||||
completions=("${must_have_one_noun[@]}")
|
completions+=("${must_have_one_noun[@]}")
|
||||||
elif [[ -n "${has_completion_function}" ]]; then
|
elif [[ -n "${has_completion_function}" ]]; then
|
||||||
# if a go completion function is provided, defer to that function
|
# if a go completion function is provided, defer to that function
|
||||||
completions=()
|
|
||||||
__%[1]s_handle_go_custom_completion
|
__%[1]s_handle_go_custom_completion
|
||||||
fi
|
fi
|
||||||
if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then
|
if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then
|
||||||
|
@ -344,7 +375,9 @@ __%[1]s_handle_word()
|
||||||
__%[1]s_handle_word
|
__%[1]s_handle_word
|
||||||
}
|
}
|
||||||
|
|
||||||
`, name, ShellCompNoDescRequestCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp))
|
`, name, ShellCompNoDescRequestCmd,
|
||||||
|
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
||||||
|
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
|
||||||
}
|
}
|
||||||
|
|
||||||
func writePostscript(buf *bytes.Buffer, name string) {
|
func writePostscript(buf *bytes.Buffer, name string) {
|
||||||
|
@ -390,7 +423,7 @@ fi
|
||||||
func writeCommands(buf *bytes.Buffer, cmd *Command) {
|
func writeCommands(buf *bytes.Buffer, cmd *Command) {
|
||||||
buf.WriteString(" commands=()\n")
|
buf.WriteString(" commands=()\n")
|
||||||
for _, c := range cmd.Commands() {
|
for _, c := range cmd.Commands() {
|
||||||
if !c.IsAvailableCommand() || c == cmd.helpCommand {
|
if !c.IsAvailableCommand() && c != cmd.helpCommand {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
buf.WriteString(fmt.Sprintf(" commands+=(%q)\n", c.Name()))
|
buf.WriteString(fmt.Sprintf(" commands+=(%q)\n", c.Name()))
|
||||||
|
@ -462,12 +495,14 @@ func writeFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) {
|
||||||
|
|
||||||
func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) {
|
func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) {
|
||||||
name := flag.Name
|
name := flag.Name
|
||||||
format := " local_nonpersistent_flags+=(\"--%s"
|
format := " local_nonpersistent_flags+=(\"--%[1]s\")\n"
|
||||||
if len(flag.NoOptDefVal) == 0 {
|
if len(flag.NoOptDefVal) == 0 {
|
||||||
format += "="
|
format += " local_nonpersistent_flags+=(\"--%[1]s=\")\n"
|
||||||
}
|
}
|
||||||
format += "\")\n"
|
|
||||||
buf.WriteString(fmt.Sprintf(format, name))
|
buf.WriteString(fmt.Sprintf(format, name))
|
||||||
|
if len(flag.Shorthand) > 0 {
|
||||||
|
buf.WriteString(fmt.Sprintf(" local_nonpersistent_flags+=(\"-%s\")\n", flag.Shorthand))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup annotations for go completions for registered flags
|
// Setup annotations for go completions for registered flags
|
||||||
|
@ -502,7 +537,9 @@ func writeFlags(buf *bytes.Buffer, cmd *Command) {
|
||||||
if len(flag.Shorthand) > 0 {
|
if len(flag.Shorthand) > 0 {
|
||||||
writeShortFlag(buf, flag, cmd)
|
writeShortFlag(buf, flag, cmd)
|
||||||
}
|
}
|
||||||
if localNonPersistentFlags.Lookup(flag.Name) != nil {
|
// localNonPersistentFlags are used to stop the completion of subcommands when one is set
|
||||||
|
// if TraverseChildren is true we should allow to complete subcommands
|
||||||
|
if localNonPersistentFlags.Lookup(flag.Name) != nil && !cmd.Root().TraverseChildren {
|
||||||
writeLocalNonPersistentFlag(buf, flag)
|
writeLocalNonPersistentFlag(buf, flag)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -583,7 +620,7 @@ func writeArgAliases(buf *bytes.Buffer, cmd *Command) {
|
||||||
|
|
||||||
func gen(buf *bytes.Buffer, cmd *Command) {
|
func gen(buf *bytes.Buffer, cmd *Command) {
|
||||||
for _, c := range cmd.Commands() {
|
for _, c := range cmd.Commands() {
|
||||||
if !c.IsAvailableCommand() || c == cmd.helpCommand {
|
if !c.IsAvailableCommand() && c != cmd.helpCommand {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
gen(buf, c)
|
gen(buf, c)
|
||||||
|
|
|
@ -37,6 +37,14 @@ type FParseErrWhitelist flag.ParseErrorsWhitelist
|
||||||
// definition to ensure usability.
|
// definition to ensure usability.
|
||||||
type Command struct {
|
type Command struct {
|
||||||
// Use is the one-line usage message.
|
// Use is the one-line usage message.
|
||||||
|
// Recommended syntax is as follow:
|
||||||
|
// [ ] identifies an optional argument. Arguments that are not enclosed in brackets are required.
|
||||||
|
// ... indicates that you can specify multiple values for the previous argument.
|
||||||
|
// | indicates mutually exclusive information. You can use the argument to the left of the separator or the
|
||||||
|
// argument to the right of the separator. You cannot use both arguments in a single use of the command.
|
||||||
|
// { } delimits a set of mutually exclusive arguments when one of the arguments is required. If the arguments are
|
||||||
|
// optional, they are enclosed in brackets ([ ]).
|
||||||
|
// Example: add [-F file | -D dir]... [-f format] profile
|
||||||
Use string
|
Use string
|
||||||
|
|
||||||
// Aliases is an array of aliases that can be used instead of the first word in Use.
|
// Aliases is an array of aliases that can be used instead of the first word in Use.
|
||||||
|
@ -359,7 +367,7 @@ func (c *Command) UsageFunc() (f func(*Command) error) {
|
||||||
c.mergePersistentFlags()
|
c.mergePersistentFlags()
|
||||||
err := tmpl(c.OutOrStderr(), c.UsageTemplate(), c)
|
err := tmpl(c.OutOrStderr(), c.UsageTemplate(), c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Println(err)
|
c.PrintErrln(err)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -387,7 +395,7 @@ func (c *Command) HelpFunc() func(*Command, []string) {
|
||||||
// See https://github.com/spf13/cobra/issues/1002
|
// See https://github.com/spf13/cobra/issues/1002
|
||||||
err := tmpl(c.OutOrStdout(), c.HelpTemplate(), c)
|
err := tmpl(c.OutOrStdout(), c.HelpTemplate(), c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Println(err)
|
c.PrintErrln(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -930,8 +938,8 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
|
||||||
c = cmd
|
c = cmd
|
||||||
}
|
}
|
||||||
if !c.SilenceErrors {
|
if !c.SilenceErrors {
|
||||||
c.Println("Error:", err.Error())
|
c.PrintErrln("Error:", err.Error())
|
||||||
c.Printf("Run '%v --help' for usage.\n", c.CommandPath())
|
c.PrintErrf("Run '%v --help' for usage.\n", c.CommandPath())
|
||||||
}
|
}
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
@ -959,7 +967,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
|
||||||
// If root command has SilentErrors flagged,
|
// If root command has SilentErrors flagged,
|
||||||
// all subcommands should respect it
|
// all subcommands should respect it
|
||||||
if !cmd.SilenceErrors && !c.SilenceErrors {
|
if !cmd.SilenceErrors && !c.SilenceErrors {
|
||||||
c.Println("Error:", err.Error())
|
c.PrintErrln("Error:", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// If root command has SilentUsage flagged,
|
// If root command has SilentUsage flagged,
|
||||||
|
@ -979,6 +987,10 @@ func (c *Command) ValidateArgs(args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Command) validateRequiredFlags() error {
|
func (c *Command) validateRequiredFlags() error {
|
||||||
|
if c.DisableFlagParsing {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
flags := c.Flags()
|
flags := c.Flags()
|
||||||
missingFlagNames := []string{}
|
missingFlagNames := []string{}
|
||||||
flags.VisitAll(func(pflag *flag.Flag) {
|
flags.VisitAll(func(pflag *flag.Flag) {
|
||||||
|
@ -1052,7 +1064,25 @@ func (c *Command) InitDefaultHelpCmd() {
|
||||||
Short: "Help about any command",
|
Short: "Help about any command",
|
||||||
Long: `Help provides help for any command in the application.
|
Long: `Help provides help for any command in the application.
|
||||||
Simply type ` + c.Name() + ` help [path to command] for full details.`,
|
Simply type ` + c.Name() + ` help [path to command] for full details.`,
|
||||||
|
ValidArgsFunction: func(c *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
var completions []string
|
||||||
|
cmd, _, e := c.Root().Find(args)
|
||||||
|
if e != nil {
|
||||||
|
return nil, ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
if cmd == nil {
|
||||||
|
// Root help command.
|
||||||
|
cmd = c.Root()
|
||||||
|
}
|
||||||
|
for _, subCmd := range cmd.Commands() {
|
||||||
|
if subCmd.IsAvailableCommand() || subCmd == cmd.helpCommand {
|
||||||
|
if strings.HasPrefix(subCmd.Name(), toComplete) {
|
||||||
|
completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return completions, ShellCompDirectiveNoFileComp
|
||||||
|
},
|
||||||
Run: func(c *Command, args []string) {
|
Run: func(c *Command, args []string) {
|
||||||
cmd, _, e := c.Root().Find(args)
|
cmd, _, e := c.Root().Find(args)
|
||||||
if cmd == nil || e != nil {
|
if cmd == nil || e != nil {
|
||||||
|
@ -1179,12 +1209,12 @@ func (c *Command) PrintErr(i ...interface{}) {
|
||||||
|
|
||||||
// PrintErrln is a convenience method to Println to the defined Err output, fallback to Stderr if not set.
|
// PrintErrln is a convenience method to Println to the defined Err output, fallback to Stderr if not set.
|
||||||
func (c *Command) PrintErrln(i ...interface{}) {
|
func (c *Command) PrintErrln(i ...interface{}) {
|
||||||
c.Print(fmt.Sprintln(i...))
|
c.PrintErr(fmt.Sprintln(i...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrintErrf is a convenience method to Printf to the defined Err output, fallback to Stderr if not set.
|
// PrintErrf is a convenience method to Printf to the defined Err output, fallback to Stderr if not set.
|
||||||
func (c *Command) PrintErrf(format string, i ...interface{}) {
|
func (c *Command) PrintErrf(format string, i ...interface{}) {
|
||||||
c.Print(fmt.Sprintf(format, i...))
|
c.PrintErr(fmt.Sprintf(format, i...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommandPath returns the full path to this command.
|
// CommandPath returns the full path to this command.
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package cobra
|
package cobra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -38,8 +37,29 @@ const (
|
||||||
// This currently does not work for zsh or bash < 4
|
// This currently does not work for zsh or bash < 4
|
||||||
ShellCompDirectiveNoFileComp
|
ShellCompDirectiveNoFileComp
|
||||||
|
|
||||||
|
// ShellCompDirectiveFilterFileExt indicates that the provided completions
|
||||||
|
// should be used as file extension filters.
|
||||||
|
// For flags, using Command.MarkFlagFilename() and Command.MarkPersistentFlagFilename()
|
||||||
|
// is a shortcut to using this directive explicitly. The BashCompFilenameExt
|
||||||
|
// annotation can also be used to obtain the same behavior for flags.
|
||||||
|
ShellCompDirectiveFilterFileExt
|
||||||
|
|
||||||
|
// ShellCompDirectiveFilterDirs indicates that only directory names should
|
||||||
|
// be provided in file completion. To request directory names within another
|
||||||
|
// directory, the returned completions should specify the directory within
|
||||||
|
// which to search. The BashCompSubdirsInDir annotation can be used to
|
||||||
|
// obtain the same behavior but only for flags.
|
||||||
|
ShellCompDirectiveFilterDirs
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
// All directives using iota should be above this one.
|
||||||
|
// For internal use.
|
||||||
|
shellCompDirectiveMaxValue
|
||||||
|
|
||||||
// ShellCompDirectiveDefault indicates to let the shell perform its default
|
// ShellCompDirectiveDefault indicates to let the shell perform its default
|
||||||
// behavior after completions have been provided.
|
// behavior after completions have been provided.
|
||||||
|
// This one must be last to avoid messing up the iota count.
|
||||||
ShellCompDirectiveDefault ShellCompDirective = 0
|
ShellCompDirectiveDefault ShellCompDirective = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -68,11 +88,17 @@ func (d ShellCompDirective) string() string {
|
||||||
if d&ShellCompDirectiveNoFileComp != 0 {
|
if d&ShellCompDirectiveNoFileComp != 0 {
|
||||||
directives = append(directives, "ShellCompDirectiveNoFileComp")
|
directives = append(directives, "ShellCompDirectiveNoFileComp")
|
||||||
}
|
}
|
||||||
|
if d&ShellCompDirectiveFilterFileExt != 0 {
|
||||||
|
directives = append(directives, "ShellCompDirectiveFilterFileExt")
|
||||||
|
}
|
||||||
|
if d&ShellCompDirectiveFilterDirs != 0 {
|
||||||
|
directives = append(directives, "ShellCompDirectiveFilterDirs")
|
||||||
|
}
|
||||||
if len(directives) == 0 {
|
if len(directives) == 0 {
|
||||||
directives = append(directives, "ShellCompDirectiveDefault")
|
directives = append(directives, "ShellCompDirectiveDefault")
|
||||||
}
|
}
|
||||||
|
|
||||||
if d > ShellCompDirectiveError+ShellCompDirectiveNoSpace+ShellCompDirectiveNoFileComp {
|
if d >= shellCompDirectiveMaxValue {
|
||||||
return fmt.Sprintf("ERROR: unexpected ShellCompDirective value: %d", d)
|
return fmt.Sprintf("ERROR: unexpected ShellCompDirective value: %d", d)
|
||||||
}
|
}
|
||||||
return strings.Join(directives, ", ")
|
return strings.Join(directives, ", ")
|
||||||
|
@ -105,11 +131,25 @@ func (c *Command) initCompleteCmd(args []string) {
|
||||||
// Remove any description that may be included following a tab character.
|
// Remove any description that may be included following a tab character.
|
||||||
comp = strings.Split(comp, "\t")[0]
|
comp = strings.Split(comp, "\t")[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure we only write the first line to the output.
|
||||||
|
// This is needed if a description contains a linebreak.
|
||||||
|
// Otherwise the shell scripts will interpret the other lines as new flags
|
||||||
|
// and could therefore provide a wrong completion.
|
||||||
|
comp = strings.Split(comp, "\n")[0]
|
||||||
|
|
||||||
|
// Finally trim the completion. This is especially important to get rid
|
||||||
|
// of a trailing tab when there are no description following it.
|
||||||
|
// For example, a sub-command without a description should not be completed
|
||||||
|
// with a tab at the end (or else zsh will show a -- following it
|
||||||
|
// although there is no description).
|
||||||
|
comp = strings.TrimSpace(comp)
|
||||||
|
|
||||||
// Print each possible completion to stdout for the completion script to consume.
|
// Print each possible completion to stdout for the completion script to consume.
|
||||||
fmt.Fprintln(finalCmd.OutOrStdout(), comp)
|
fmt.Fprintln(finalCmd.OutOrStdout(), comp)
|
||||||
}
|
}
|
||||||
|
|
||||||
if directive > ShellCompDirectiveError+ShellCompDirectiveNoSpace+ShellCompDirectiveNoFileComp {
|
if directive >= shellCompDirectiveMaxValue {
|
||||||
directive = ShellCompDirectiveDefault
|
directive = ShellCompDirectiveDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,82 +176,104 @@ func (c *Command) initCompleteCmd(args []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDirective, error) {
|
func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDirective, error) {
|
||||||
var completions []string
|
|
||||||
|
|
||||||
// The last argument, which is not completely typed by the user,
|
// The last argument, which is not completely typed by the user,
|
||||||
// should not be part of the list of arguments
|
// should not be part of the list of arguments
|
||||||
toComplete := args[len(args)-1]
|
toComplete := args[len(args)-1]
|
||||||
trimmedArgs := args[:len(args)-1]
|
trimmedArgs := args[:len(args)-1]
|
||||||
|
|
||||||
|
var finalCmd *Command
|
||||||
|
var finalArgs []string
|
||||||
|
var err error
|
||||||
// Find the real command for which completion must be performed
|
// Find the real command for which completion must be performed
|
||||||
finalCmd, finalArgs, err := c.Root().Find(trimmedArgs)
|
// check if we need to traverse here to parse local flags on parent commands
|
||||||
|
if c.Root().TraverseChildren {
|
||||||
|
finalCmd, finalArgs, err = c.Root().Traverse(trimmedArgs)
|
||||||
|
} else {
|
||||||
|
finalCmd, finalArgs, err = c.Root().Find(trimmedArgs)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Unable to find the real command. E.g., <program> someInvalidCmd <TAB>
|
// Unable to find the real command. E.g., <program> someInvalidCmd <TAB>
|
||||||
return c, completions, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs)
|
return c, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we are doing flag value completion before parsing the flags.
|
||||||
|
// This is important because if we are completing a flag value, we need to also
|
||||||
|
// remove the flag name argument from the list of finalArgs or else the parsing
|
||||||
|
// could fail due to an invalid value (incomplete) for the flag.
|
||||||
|
flag, finalArgs, toComplete, err := checkIfFlagCompletion(finalCmd, finalArgs, toComplete)
|
||||||
|
if err != nil {
|
||||||
|
// Error while attempting to parse flags
|
||||||
|
return finalCmd, []string{}, ShellCompDirectiveDefault, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the flags early so we can check if required flags are set
|
||||||
|
if err = finalCmd.ParseFlags(finalArgs); err != nil {
|
||||||
|
return finalCmd, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if flag != nil {
|
||||||
|
// Check if we are completing a flag value subject to annotations
|
||||||
|
if validExts, present := flag.Annotations[BashCompFilenameExt]; present {
|
||||||
|
if len(validExts) != 0 {
|
||||||
|
// File completion filtered by extensions
|
||||||
|
return finalCmd, validExts, ShellCompDirectiveFilterFileExt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The annotation requests simple file completion. There is no reason to do
|
||||||
|
// that since it is the default behavior anyway. Let's ignore this annotation
|
||||||
|
// in case the program also registered a completion function for this flag.
|
||||||
|
// Even though it is a mistake on the program's side, let's be nice when we can.
|
||||||
|
}
|
||||||
|
|
||||||
|
if subDir, present := flag.Annotations[BashCompSubdirsInDir]; present {
|
||||||
|
if len(subDir) == 1 {
|
||||||
|
// Directory completion from within a directory
|
||||||
|
return finalCmd, subDir, ShellCompDirectiveFilterDirs, nil
|
||||||
|
}
|
||||||
|
// Directory completion
|
||||||
|
return finalCmd, []string{}, ShellCompDirectiveFilterDirs, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When doing completion of a flag name, as soon as an argument starts with
|
// When doing completion of a flag name, as soon as an argument starts with
|
||||||
// a '-' we know it is a flag. We cannot use isFlagArg() here as it requires
|
// a '-' we know it is a flag. We cannot use isFlagArg() here as it requires
|
||||||
// the flag to be complete
|
// the flag name to be complete
|
||||||
if len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") {
|
if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") {
|
||||||
// We are completing a flag name
|
var completions []string
|
||||||
finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
|
|
||||||
completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
|
|
||||||
})
|
|
||||||
finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
|
|
||||||
completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
|
|
||||||
})
|
|
||||||
|
|
||||||
directive := ShellCompDirectiveDefault
|
// First check for required flags
|
||||||
if len(completions) > 0 {
|
completions = completeRequireFlags(finalCmd, toComplete)
|
||||||
if strings.HasSuffix(completions[0], "=") {
|
|
||||||
directive = ShellCompDirectiveNoSpace
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return finalCmd, completions, directive, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var flag *pflag.Flag
|
// If we have not found any required flags, only then can we show regular flags
|
||||||
if !finalCmd.DisableFlagParsing {
|
if len(completions) == 0 {
|
||||||
// We only do flag completion if we are allowed to parse flags
|
doCompleteFlags := func(flag *pflag.Flag) {
|
||||||
// This is important for commands which have requested to do their own flag completion.
|
if !flag.Changed ||
|
||||||
flag, finalArgs, toComplete, err = checkIfFlagCompletion(finalCmd, finalArgs, toComplete)
|
strings.Contains(flag.Value.Type(), "Slice") ||
|
||||||
if err != nil {
|
strings.Contains(flag.Value.Type(), "Array") {
|
||||||
// Error while attempting to parse flags
|
// If the flag is not already present, or if it can be specified multiple times (Array or Slice)
|
||||||
return finalCmd, completions, ShellCompDirectiveDefault, err
|
// we suggest it as a completion
|
||||||
}
|
completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
|
||||||
}
|
|
||||||
|
|
||||||
if flag == nil {
|
|
||||||
// Complete subcommand names
|
|
||||||
for _, subCmd := range finalCmd.Commands() {
|
|
||||||
if subCmd.IsAvailableCommand() && strings.HasPrefix(subCmd.Name(), toComplete) {
|
|
||||||
completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(finalCmd.ValidArgs) > 0 {
|
|
||||||
// Always complete ValidArgs, even if we are completing a subcommand name.
|
|
||||||
// This is for commands that have both subcommands and ValidArgs.
|
|
||||||
for _, validArg := range finalCmd.ValidArgs {
|
|
||||||
if strings.HasPrefix(validArg, toComplete) {
|
|
||||||
completions = append(completions, validArg)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are ValidArgs specified (even if they don't match), we stop completion.
|
// We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands
|
||||||
// Only one of ValidArgs or ValidArgsFunction can be used for a single command.
|
// that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and
|
||||||
return finalCmd, completions, ShellCompDirectiveNoFileComp, nil
|
// non-inherited flags.
|
||||||
|
finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
|
||||||
|
doCompleteFlags(flag)
|
||||||
|
})
|
||||||
|
finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
|
||||||
|
doCompleteFlags(flag)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always let the logic continue so as to add any ValidArgsFunction completions,
|
directive := ShellCompDirectiveNoFileComp
|
||||||
// even if we already found sub-commands.
|
if len(completions) == 1 && strings.HasSuffix(completions[0], "=") {
|
||||||
// This is for commands that have subcommands but also specify a ValidArgsFunction.
|
// If there is a single completion, the shell usually adds a space
|
||||||
}
|
// after the completion. We don't want that if the flag ends with an =
|
||||||
|
directive = ShellCompDirectiveNoSpace
|
||||||
// Parse the flags and extract the arguments to prepare for calling the completion function
|
}
|
||||||
if err = finalCmd.ParseFlags(finalArgs); err != nil {
|
return finalCmd, completions, directive, nil
|
||||||
return finalCmd, completions, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We only remove the flags from the arguments if DisableFlagParsing is not set.
|
// We only remove the flags from the arguments if DisableFlagParsing is not set.
|
||||||
|
@ -220,6 +282,73 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
||||||
finalArgs = finalCmd.Flags().Args()
|
finalArgs = finalCmd.Flags().Args()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var completions []string
|
||||||
|
directive := ShellCompDirectiveDefault
|
||||||
|
if flag == nil {
|
||||||
|
foundLocalNonPersistentFlag := false
|
||||||
|
// If TraverseChildren is true on the root command we don't check for
|
||||||
|
// local flags because we can use a local flag on a parent command
|
||||||
|
if !finalCmd.Root().TraverseChildren {
|
||||||
|
// Check if there are any local, non-persistent flags on the command-line
|
||||||
|
localNonPersistentFlags := finalCmd.LocalNonPersistentFlags()
|
||||||
|
finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
|
||||||
|
if localNonPersistentFlags.Lookup(flag.Name) != nil && flag.Changed {
|
||||||
|
foundLocalNonPersistentFlag = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete subcommand names, including the help command
|
||||||
|
if len(finalArgs) == 0 && !foundLocalNonPersistentFlag {
|
||||||
|
// We only complete sub-commands if:
|
||||||
|
// - there are no arguments on the command-line and
|
||||||
|
// - there are no local, non-peristent flag on the command-line or TraverseChildren is true
|
||||||
|
for _, subCmd := range finalCmd.Commands() {
|
||||||
|
if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand {
|
||||||
|
if strings.HasPrefix(subCmd.Name(), toComplete) {
|
||||||
|
completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
|
||||||
|
}
|
||||||
|
directive = ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete required flags even without the '-' prefix
|
||||||
|
completions = append(completions, completeRequireFlags(finalCmd, toComplete)...)
|
||||||
|
|
||||||
|
// Always complete ValidArgs, even if we are completing a subcommand name.
|
||||||
|
// This is for commands that have both subcommands and ValidArgs.
|
||||||
|
if len(finalCmd.ValidArgs) > 0 {
|
||||||
|
if len(finalArgs) == 0 {
|
||||||
|
// ValidArgs are only for the first argument
|
||||||
|
for _, validArg := range finalCmd.ValidArgs {
|
||||||
|
if strings.HasPrefix(validArg, toComplete) {
|
||||||
|
completions = append(completions, validArg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
directive = ShellCompDirectiveNoFileComp
|
||||||
|
|
||||||
|
// If no completions were found within commands or ValidArgs,
|
||||||
|
// see if there are any ArgAliases that should be completed.
|
||||||
|
if len(completions) == 0 {
|
||||||
|
for _, argAlias := range finalCmd.ArgAliases {
|
||||||
|
if strings.HasPrefix(argAlias, toComplete) {
|
||||||
|
completions = append(completions, argAlias)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are ValidArgs specified (even if they don't match), we stop completion.
|
||||||
|
// Only one of ValidArgs or ValidArgsFunction can be used for a single command.
|
||||||
|
return finalCmd, completions, directive, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let the logic continue so as to add any ValidArgsFunction completions,
|
||||||
|
// even if we already found sub-commands.
|
||||||
|
// This is for commands that have subcommands but also specify a ValidArgsFunction.
|
||||||
|
}
|
||||||
|
|
||||||
// Find the completion function for the flag or command
|
// Find the completion function for the flag or command
|
||||||
var completionFn func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
|
var completionFn func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
|
||||||
if flag != nil {
|
if flag != nil {
|
||||||
|
@ -227,14 +356,14 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
||||||
} else {
|
} else {
|
||||||
completionFn = finalCmd.ValidArgsFunction
|
completionFn = finalCmd.ValidArgsFunction
|
||||||
}
|
}
|
||||||
if completionFn == nil {
|
if completionFn != nil {
|
||||||
// Go custom completion not supported/needed for this flag or command
|
// Go custom completion defined for this flag or command.
|
||||||
return finalCmd, completions, ShellCompDirectiveDefault, nil
|
// Call the registered completion function to get the completions.
|
||||||
|
var comps []string
|
||||||
|
comps, directive = completionFn(finalCmd, finalArgs, toComplete)
|
||||||
|
completions = append(completions, comps...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the registered completion function to get the completions
|
|
||||||
comps, directive := completionFn(finalCmd, finalArgs, toComplete)
|
|
||||||
completions = append(completions, comps...)
|
|
||||||
return finalCmd, completions, directive, nil
|
return finalCmd, completions, directive, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,11 +378,18 @@ func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
|
||||||
// Flag without the =
|
// Flag without the =
|
||||||
completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
|
completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
|
||||||
|
|
||||||
if len(flag.NoOptDefVal) == 0 {
|
// Why suggest both long forms: --flag and --flag= ?
|
||||||
// Flag requires a value, so it can be suffixed with =
|
// This forces the user to *always* have to type either an = or a space after the flag name.
|
||||||
flagName += "="
|
// Let's be nice and avoid making users have to do that.
|
||||||
completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
|
// Since boolean flags and shortname flags don't show the = form, let's go that route and never show it.
|
||||||
}
|
// The = form will still work, we just won't suggest it.
|
||||||
|
// This also makes the list of suggested flags shorter as we avoid all the = forms.
|
||||||
|
//
|
||||||
|
// if len(flag.NoOptDefVal) == 0 {
|
||||||
|
// // Flag requires a value, so it can be suffixed with =
|
||||||
|
// flagName += "="
|
||||||
|
// completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
flagName = "-" + flag.Shorthand
|
flagName = "-" + flag.Shorthand
|
||||||
|
@ -264,17 +400,54 @@ func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
|
||||||
return completions
|
return completions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func completeRequireFlags(finalCmd *Command, toComplete string) []string {
|
||||||
|
var completions []string
|
||||||
|
|
||||||
|
doCompleteRequiredFlags := func(flag *pflag.Flag) {
|
||||||
|
if _, present := flag.Annotations[BashCompOneRequiredFlag]; present {
|
||||||
|
if !flag.Changed {
|
||||||
|
// If the flag is not already present, we suggest it as a completion
|
||||||
|
completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands
|
||||||
|
// that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and
|
||||||
|
// non-inherited flags.
|
||||||
|
finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
|
||||||
|
doCompleteRequiredFlags(flag)
|
||||||
|
})
|
||||||
|
finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
|
||||||
|
doCompleteRequiredFlags(flag)
|
||||||
|
})
|
||||||
|
|
||||||
|
return completions
|
||||||
|
}
|
||||||
|
|
||||||
func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) {
|
func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) {
|
||||||
|
if finalCmd.DisableFlagParsing {
|
||||||
|
// We only do flag completion if we are allowed to parse flags
|
||||||
|
// This is important for commands which have requested to do their own flag completion.
|
||||||
|
return nil, args, lastArg, nil
|
||||||
|
}
|
||||||
|
|
||||||
var flagName string
|
var flagName string
|
||||||
trimmedArgs := args
|
trimmedArgs := args
|
||||||
flagWithEqual := false
|
flagWithEqual := false
|
||||||
if isFlagArg(lastArg) {
|
|
||||||
|
// When doing completion of a flag name, as soon as an argument starts with
|
||||||
|
// a '-' we know it is a flag. We cannot use isFlagArg() here as that function
|
||||||
|
// requires the flag name to be complete
|
||||||
|
if len(lastArg) > 0 && lastArg[0] == '-' {
|
||||||
if index := strings.Index(lastArg, "="); index >= 0 {
|
if index := strings.Index(lastArg, "="); index >= 0 {
|
||||||
|
// Flag with an =
|
||||||
flagName = strings.TrimLeft(lastArg[:index], "-")
|
flagName = strings.TrimLeft(lastArg[:index], "-")
|
||||||
lastArg = lastArg[index+1:]
|
lastArg = lastArg[index+1:]
|
||||||
flagWithEqual = true
|
flagWithEqual = true
|
||||||
} else {
|
} else {
|
||||||
return nil, nil, "", errors.New("Unexpected completion request for flag")
|
// Normal flag completion
|
||||||
|
return nil, args, lastArg, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Documentation generation
|
||||||
|
|
||||||
|
- [Man page docs](./man_docs.md)
|
||||||
|
- [Markdown docs](./md_docs.md)
|
||||||
|
- [Rest docs](./rest_docs.md)
|
||||||
|
- [Yaml docs](./yaml_docs.md)
|
||||||
|
|
||||||
|
## Options
|
||||||
|
### `DisableAutoGenTag`
|
||||||
|
You may set `cmd.DisableAutoGenTag = true`
|
||||||
|
to _entirely_ remove the auto generated string "Auto generated by spf13/cobra..."
|
||||||
|
from any documentation source.
|
|
@ -105,7 +105,7 @@ func GenMan(cmd *cobra.Command, header *GenManHeader, w io.Writer) error {
|
||||||
if header == nil {
|
if header == nil {
|
||||||
header = &GenManHeader{}
|
header = &GenManHeader{}
|
||||||
}
|
}
|
||||||
if err := fillHeader(header, cmd.CommandPath()); err != nil {
|
if err := fillHeader(header, cmd.CommandPath(), cmd.DisableAutoGenTag); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ func GenMan(cmd *cobra.Command, header *GenManHeader, w io.Writer) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func fillHeader(header *GenManHeader, name string) error {
|
func fillHeader(header *GenManHeader, name string, disableAutoGen bool) error {
|
||||||
if header.Title == "" {
|
if header.Title == "" {
|
||||||
header.Title = strings.ToUpper(strings.Replace(name, " ", "\\-", -1))
|
header.Title = strings.ToUpper(strings.Replace(name, " ", "\\-", -1))
|
||||||
}
|
}
|
||||||
|
@ -133,7 +133,7 @@ func fillHeader(header *GenManHeader, name string) error {
|
||||||
header.Date = &now
|
header.Date = &now
|
||||||
}
|
}
|
||||||
header.date = (*header.Date).Format("Jan 2006")
|
header.date = (*header.Date).Format("Jan 2006")
|
||||||
if header.Source == "" {
|
if header.Source == "" && !disableAutoGen {
|
||||||
header.Source = "Auto generated by spf13/cobra"
|
header.Source = "Auto generated by spf13/cobra"
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -145,9 +145,7 @@ func manPreamble(buf *bytes.Buffer, header *GenManHeader, cmd *cobra.Command, da
|
||||||
description = cmd.Short
|
description = cmd.Short
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.WriteString(fmt.Sprintf(`%% %s(%s)%s
|
buf.WriteString(fmt.Sprintf(`%% "%s" "%s" "%s" "%s" "%s"
|
||||||
%% %s
|
|
||||||
%% %s
|
|
||||||
# NAME
|
# NAME
|
||||||
`, header.Title, header.Section, header.date, header.Source, header.Manual))
|
`, header.Title, header.Section, header.date, header.Source, header.Manual))
|
||||||
buf.WriteString(fmt.Sprintf("%s \\- %s\n\n", dashedName, cmd.Short))
|
buf.WriteString(fmt.Sprintf("%s \\- %s\n\n", dashedName, cmd.Short))
|
||||||
|
|
|
@ -58,16 +58,12 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string)
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
name := cmd.CommandPath()
|
name := cmd.CommandPath()
|
||||||
|
|
||||||
short := cmd.Short
|
|
||||||
long := cmd.Long
|
|
||||||
if len(long) == 0 {
|
|
||||||
long = short
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteString("## " + name + "\n\n")
|
buf.WriteString("## " + name + "\n\n")
|
||||||
buf.WriteString(short + "\n\n")
|
buf.WriteString(cmd.Short + "\n\n")
|
||||||
buf.WriteString("### Synopsis\n\n")
|
if len(cmd.Long) > 0 {
|
||||||
buf.WriteString(long + "\n\n")
|
buf.WriteString("### Synopsis\n\n")
|
||||||
|
buf.WriteString(cmd.Long + "\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
if cmd.Runnable() {
|
if cmd.Runnable() {
|
||||||
buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.UseLine()))
|
buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.UseLine()))
|
||||||
|
|
|
@ -20,7 +20,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Test to see if we have a reason to print See Also information in docs
|
// Test to see if we have a reason to print See Also information in docs
|
||||||
// Basically this is a test for a parent commend or a subcommand which is
|
// Basically this is a test for a parent command or a subcommand which is
|
||||||
// both not deprecated and not the autogenerated help command.
|
// both not deprecated and not the autogenerated help command.
|
||||||
func hasSeeAlso(cmd *cobra.Command) bool {
|
func hasSeeAlso(cmd *cobra.Command) bool {
|
||||||
if cmd.HasParent() {
|
if cmd.HasParent() {
|
||||||
|
|
|
@ -37,6 +37,7 @@ type cmdDoc struct {
|
||||||
Name string
|
Name string
|
||||||
Synopsis string `yaml:",omitempty"`
|
Synopsis string `yaml:",omitempty"`
|
||||||
Description string `yaml:",omitempty"`
|
Description string `yaml:",omitempty"`
|
||||||
|
Usage string `yaml:",omitempty"`
|
||||||
Options []cmdOption `yaml:",omitempty"`
|
Options []cmdOption `yaml:",omitempty"`
|
||||||
InheritedOptions []cmdOption `yaml:"inherited_options,omitempty"`
|
InheritedOptions []cmdOption `yaml:"inherited_options,omitempty"`
|
||||||
Example string `yaml:",omitempty"`
|
Example string `yaml:",omitempty"`
|
||||||
|
@ -98,6 +99,10 @@ func GenYamlCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) str
|
||||||
yamlDoc.Synopsis = forceMultiLine(cmd.Short)
|
yamlDoc.Synopsis = forceMultiLine(cmd.Short)
|
||||||
yamlDoc.Description = forceMultiLine(cmd.Long)
|
yamlDoc.Description = forceMultiLine(cmd.Long)
|
||||||
|
|
||||||
|
if cmd.Runnable() {
|
||||||
|
yamlDoc.Usage = cmd.UseLine()
|
||||||
|
}
|
||||||
|
|
||||||
if len(cmd.Example) > 0 {
|
if len(cmd.Example) > 0 {
|
||||||
yamlDoc.Example = cmd.Example
|
yamlDoc.Example = cmd.Example
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,15 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func genFishComp(buf *bytes.Buffer, name string, includeDesc bool) {
|
func genFishComp(buf *bytes.Buffer, name string, includeDesc bool) {
|
||||||
|
// Variables should not contain a '-' or ':' character
|
||||||
|
nameForVar := name
|
||||||
|
nameForVar = strings.Replace(nameForVar, "-", "_", -1)
|
||||||
|
nameForVar = strings.Replace(nameForVar, ":", "_", -1)
|
||||||
|
|
||||||
compCmd := ShellCompRequestCmd
|
compCmd := ShellCompRequestCmd
|
||||||
if !includeDesc {
|
if !includeDesc {
|
||||||
compCmd = ShellCompNoDescRequestCmd
|
compCmd = ShellCompNoDescRequestCmd
|
||||||
|
@ -37,7 +43,13 @@ function __%[1]s_perform_completion
|
||||||
end
|
end
|
||||||
__%[1]s_debug "emptyArg: $emptyArg"
|
__%[1]s_debug "emptyArg: $emptyArg"
|
||||||
|
|
||||||
set requestComp "$args[1] %[2]s $args[2..-1] $emptyArg"
|
if not type -q "$args[1]"
|
||||||
|
# This can happen when "complete --do-complete %[2]s" is called when running this script.
|
||||||
|
__%[1]s_debug "Cannot find $args[1]. No completions."
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
set requestComp "$args[1] %[3]s $args[2..-1] $emptyArg"
|
||||||
__%[1]s_debug "Calling $requestComp"
|
__%[1]s_debug "Calling $requestComp"
|
||||||
|
|
||||||
set results (eval $requestComp 2> /dev/null)
|
set results (eval $requestComp 2> /dev/null)
|
||||||
|
@ -71,7 +83,8 @@ function __%[1]s_prepare_completions
|
||||||
|
|
||||||
# Check if the command-line is already provided. This is useful for testing.
|
# Check if the command-line is already provided. This is useful for testing.
|
||||||
if not set --query __%[1]s_comp_commandLine
|
if not set --query __%[1]s_comp_commandLine
|
||||||
set __%[1]s_comp_commandLine (commandline)
|
# Use the -c flag to allow for completion in the middle of the line
|
||||||
|
set __%[1]s_comp_commandLine (commandline -c)
|
||||||
end
|
end
|
||||||
__%[1]s_debug "commandLine is: $__%[1]s_comp_commandLine"
|
__%[1]s_debug "commandLine is: $__%[1]s_comp_commandLine"
|
||||||
|
|
||||||
|
@ -83,7 +96,7 @@ function __%[1]s_prepare_completions
|
||||||
__%[1]s_debug "No completion, probably due to a failure"
|
__%[1]s_debug "No completion, probably due to a failure"
|
||||||
# Might as well do file completion, in case it helps
|
# Might as well do file completion, in case it helps
|
||||||
set --global __%[1]s_comp_do_file_comp 1
|
set --global __%[1]s_comp_do_file_comp 1
|
||||||
return 0
|
return 1
|
||||||
end
|
end
|
||||||
|
|
||||||
set directive (string sub --start 2 $results[-1])
|
set directive (string sub --start 2 $results[-1])
|
||||||
|
@ -92,20 +105,35 @@ function __%[1]s_prepare_completions
|
||||||
__%[1]s_debug "Completions are: $__%[1]s_comp_results"
|
__%[1]s_debug "Completions are: $__%[1]s_comp_results"
|
||||||
__%[1]s_debug "Directive is: $directive"
|
__%[1]s_debug "Directive is: $directive"
|
||||||
|
|
||||||
|
set shellCompDirectiveError %[4]d
|
||||||
|
set shellCompDirectiveNoSpace %[5]d
|
||||||
|
set shellCompDirectiveNoFileComp %[6]d
|
||||||
|
set shellCompDirectiveFilterFileExt %[7]d
|
||||||
|
set shellCompDirectiveFilterDirs %[8]d
|
||||||
|
|
||||||
if test -z "$directive"
|
if test -z "$directive"
|
||||||
set directive 0
|
set directive 0
|
||||||
end
|
end
|
||||||
|
|
||||||
set compErr (math (math --scale 0 $directive / %[3]d) %% 2)
|
set compErr (math (math --scale 0 $directive / $shellCompDirectiveError) %% 2)
|
||||||
if test $compErr -eq 1
|
if test $compErr -eq 1
|
||||||
__%[1]s_debug "Received error directive: aborting."
|
__%[1]s_debug "Received error directive: aborting."
|
||||||
# Might as well do file completion, in case it helps
|
# Might as well do file completion, in case it helps
|
||||||
set --global __%[1]s_comp_do_file_comp 1
|
set --global __%[1]s_comp_do_file_comp 1
|
||||||
return 0
|
return 1
|
||||||
end
|
end
|
||||||
|
|
||||||
set nospace (math (math --scale 0 $directive / %[4]d) %% 2)
|
set filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) %% 2)
|
||||||
set nofiles (math (math --scale 0 $directive / %[5]d) %% 2)
|
set dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) %% 2)
|
||||||
|
if test $filefilter -eq 1; or test $dirfilter -eq 1
|
||||||
|
__%[1]s_debug "File extension filtering or directory filtering not supported"
|
||||||
|
# Do full file completion instead
|
||||||
|
set --global __%[1]s_comp_do_file_comp 1
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
set nospace (math (math --scale 0 $directive / $shellCompDirectiveNoSpace) %% 2)
|
||||||
|
set nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) %% 2)
|
||||||
|
|
||||||
__%[1]s_debug "nospace: $nospace, nofiles: $nofiles"
|
__%[1]s_debug "nospace: $nospace, nofiles: $nofiles"
|
||||||
|
|
||||||
|
@ -132,24 +160,31 @@ function __%[1]s_prepare_completions
|
||||||
return (not set --query __%[1]s_comp_do_file_comp)
|
return (not set --query __%[1]s_comp_do_file_comp)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Remove any pre-existing completions for the program since we will be handling all of them
|
# Since Fish completions are only loaded once the user triggers them, we trigger them ourselves
|
||||||
# TODO this cleanup is not sufficient. Fish completions are only loaded once the user triggers
|
# so we can properly delete any completions provided by another script.
|
||||||
# them, so the below deletion will not work as it is run too early. What else can we do?
|
# The space after the the program name is essential to trigger completion for the program
|
||||||
complete -c %[1]s -e
|
# and not completion of the program name itself.
|
||||||
|
complete --do-complete "%[2]s " > /dev/null 2>&1
|
||||||
|
# Using '> /dev/null 2>&1' since '&>' is not supported in older versions of fish.
|
||||||
|
|
||||||
|
# Remove any pre-existing completions for the program since we will be handling all of them.
|
||||||
|
complete -c %[2]s -e
|
||||||
|
|
||||||
# The order in which the below two lines are defined is very important so that __%[1]s_prepare_completions
|
# The order in which the below two lines are defined is very important so that __%[1]s_prepare_completions
|
||||||
# is called first. It is __%[1]s_prepare_completions that sets up the __%[1]s_comp_do_file_comp variable.
|
# is called first. It is __%[1]s_prepare_completions that sets up the __%[1]s_comp_do_file_comp variable.
|
||||||
#
|
#
|
||||||
# This completion will be run second as complete commands are added FILO.
|
# This completion will be run second as complete commands are added FILO.
|
||||||
# It triggers file completion choices when __%[1]s_comp_do_file_comp is set.
|
# It triggers file completion choices when __%[1]s_comp_do_file_comp is set.
|
||||||
complete -c %[1]s -n 'set --query __%[1]s_comp_do_file_comp'
|
complete -c %[2]s -n 'set --query __%[1]s_comp_do_file_comp'
|
||||||
|
|
||||||
# This completion will be run first as complete commands are added FILO.
|
# This completion will be run first as complete commands are added FILO.
|
||||||
# The call to __%[1]s_prepare_completions will setup both __%[1]s_comp_results abd __%[1]s_comp_do_file_comp.
|
# The call to __%[1]s_prepare_completions will setup both __%[1]s_comp_results and __%[1]s_comp_do_file_comp.
|
||||||
# It provides the program's completion choices.
|
# It provides the program's completion choices.
|
||||||
complete -c %[1]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results'
|
complete -c %[2]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results'
|
||||||
|
|
||||||
`, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp))
|
`, nameForVar, name, compCmd,
|
||||||
|
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
||||||
|
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenFishCompletion generates fish completion file and writes to the passed writer.
|
// GenFishCompletion generates fish completion file and writes to the passed writer.
|
||||||
|
|
|
@ -6,7 +6,7 @@ require (
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0
|
github.com/cpuguy83/go-md2man/v2 v2.0.0
|
||||||
github.com/inconshreveable/mousetrap v1.0.0
|
github.com/inconshreveable/mousetrap v1.0.0
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/spf13/pflag v1.0.3
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/spf13/viper v1.4.0
|
github.com/spf13/viper v1.7.0
|
||||||
gopkg.in/yaml.v2 v2.2.2
|
gopkg.in/yaml.v2 v2.2.8
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,82 +4,81 @@ import (
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists,
|
// MarkFlagRequired instructs the various shell completion implementations to
|
||||||
|
// prioritize the named flag when performing completion,
|
||||||
// and causes your command to report an error if invoked without the flag.
|
// and causes your command to report an error if invoked without the flag.
|
||||||
func (c *Command) MarkFlagRequired(name string) error {
|
func (c *Command) MarkFlagRequired(name string) error {
|
||||||
return MarkFlagRequired(c.Flags(), name)
|
return MarkFlagRequired(c.Flags(), name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag if it exists,
|
// MarkPersistentFlagRequired instructs the various shell completion implementations to
|
||||||
|
// prioritize the named persistent flag when performing completion,
|
||||||
// and causes your command to report an error if invoked without the flag.
|
// and causes your command to report an error if invoked without the flag.
|
||||||
func (c *Command) MarkPersistentFlagRequired(name string) error {
|
func (c *Command) MarkPersistentFlagRequired(name string) error {
|
||||||
return MarkFlagRequired(c.PersistentFlags(), name)
|
return MarkFlagRequired(c.PersistentFlags(), name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists,
|
// MarkFlagRequired instructs the various shell completion implementations to
|
||||||
|
// prioritize the named flag when performing completion,
|
||||||
// and causes your command to report an error if invoked without the flag.
|
// and causes your command to report an error if invoked without the flag.
|
||||||
func MarkFlagRequired(flags *pflag.FlagSet, name string) error {
|
func MarkFlagRequired(flags *pflag.FlagSet, name string) error {
|
||||||
return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"})
|
return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists.
|
// MarkFlagFilename instructs the various shell completion implementations to
|
||||||
// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
|
// limit completions for the named flag to the specified file extensions.
|
||||||
func (c *Command) MarkFlagFilename(name string, extensions ...string) error {
|
func (c *Command) MarkFlagFilename(name string, extensions ...string) error {
|
||||||
return MarkFlagFilename(c.Flags(), name, extensions...)
|
return MarkFlagFilename(c.Flags(), name, extensions...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists.
|
// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists.
|
||||||
// Generated bash autocompletion will call the bash function f for the flag.
|
// The bash completion script will call the bash function f for the flag.
|
||||||
|
//
|
||||||
|
// This will only work for bash completion.
|
||||||
|
// It is recommended to instead use c.RegisterFlagCompletionFunc(...) which allows
|
||||||
|
// to register a Go function which will work across all shells.
|
||||||
func (c *Command) MarkFlagCustom(name string, f string) error {
|
func (c *Command) MarkFlagCustom(name string, f string) error {
|
||||||
return MarkFlagCustom(c.Flags(), name, f)
|
return MarkFlagCustom(c.Flags(), name, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkPersistentFlagFilename instructs the various shell completion
|
// MarkPersistentFlagFilename instructs the various shell completion
|
||||||
// implementations to limit completions for this persistent flag to the
|
// implementations to limit completions for the named persistent flag to the
|
||||||
// specified extensions (patterns).
|
// specified file extensions.
|
||||||
//
|
|
||||||
// Shell Completion compatibility matrix: bash, zsh
|
|
||||||
func (c *Command) MarkPersistentFlagFilename(name string, extensions ...string) error {
|
func (c *Command) MarkPersistentFlagFilename(name string, extensions ...string) error {
|
||||||
return MarkFlagFilename(c.PersistentFlags(), name, extensions...)
|
return MarkFlagFilename(c.PersistentFlags(), name, extensions...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkFlagFilename instructs the various shell completion implementations to
|
// MarkFlagFilename instructs the various shell completion implementations to
|
||||||
// limit completions for this flag to the specified extensions (patterns).
|
// limit completions for the named flag to the specified file extensions.
|
||||||
//
|
|
||||||
// Shell Completion compatibility matrix: bash, zsh
|
|
||||||
func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error {
|
func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error {
|
||||||
return flags.SetAnnotation(name, BashCompFilenameExt, extensions)
|
return flags.SetAnnotation(name, BashCompFilenameExt, extensions)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkFlagCustom instructs the various shell completion implementations to
|
// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists.
|
||||||
// limit completions for this flag to the specified extensions (patterns).
|
// The bash completion script will call the bash function f for the flag.
|
||||||
//
|
//
|
||||||
// Shell Completion compatibility matrix: bash, zsh
|
// This will only work for bash completion.
|
||||||
|
// It is recommended to instead use c.RegisterFlagCompletionFunc(...) which allows
|
||||||
|
// to register a Go function which will work across all shells.
|
||||||
func MarkFlagCustom(flags *pflag.FlagSet, name string, f string) error {
|
func MarkFlagCustom(flags *pflag.FlagSet, name string, f string) error {
|
||||||
return flags.SetAnnotation(name, BashCompCustom, []string{f})
|
return flags.SetAnnotation(name, BashCompCustom, []string{f})
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkFlagDirname instructs the various shell completion implementations to
|
// MarkFlagDirname instructs the various shell completion implementations to
|
||||||
// complete only directories with this named flag.
|
// limit completions for the named flag to directory names.
|
||||||
//
|
|
||||||
// Shell Completion compatibility matrix: zsh
|
|
||||||
func (c *Command) MarkFlagDirname(name string) error {
|
func (c *Command) MarkFlagDirname(name string) error {
|
||||||
return MarkFlagDirname(c.Flags(), name)
|
return MarkFlagDirname(c.Flags(), name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkPersistentFlagDirname instructs the various shell completion
|
// MarkPersistentFlagDirname instructs the various shell completion
|
||||||
// implementations to complete only directories with this persistent named flag.
|
// implementations to limit completions for the named persistent flag to
|
||||||
//
|
// directory names.
|
||||||
// Shell Completion compatibility matrix: zsh
|
|
||||||
func (c *Command) MarkPersistentFlagDirname(name string) error {
|
func (c *Command) MarkPersistentFlagDirname(name string) error {
|
||||||
return MarkFlagDirname(c.PersistentFlags(), name)
|
return MarkFlagDirname(c.PersistentFlags(), name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkFlagDirname instructs the various shell completion implementations to
|
// MarkFlagDirname instructs the various shell completion implementations to
|
||||||
// complete only directories with this specified flag.
|
// limit completions for the named flag to directory names.
|
||||||
//
|
|
||||||
// Shell Completion compatibility matrix: zsh
|
|
||||||
func MarkFlagDirname(flags *pflag.FlagSet, name string) error {
|
func MarkFlagDirname(flags *pflag.FlagSet, name string) error {
|
||||||
zshPattern := "-(/)"
|
return flags.SetAnnotation(name, BashCompSubdirsInDir, []string{})
|
||||||
return flags.SetAnnotation(name, zshCompDirname, []string{zshPattern})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,336 +1,240 @@
|
||||||
package cobra
|
package cobra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
// GenZshCompletionFile generates zsh completion file including descriptions.
|
||||||
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 (
|
|
||||||
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 {
|
||||||
|
return c.genZshCompletionFile(filename, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenZshCompletion generates zsh completion file including descriptions
|
||||||
|
// and writes it to the passed writer.
|
||||||
|
func (c *Command) GenZshCompletion(w io.Writer) error {
|
||||||
|
return c.genZshCompletion(w, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenZshCompletionFileNoDesc generates zsh completion file without descriptions.
|
||||||
|
func (c *Command) GenZshCompletionFileNoDesc(filename string) error {
|
||||||
|
return c.genZshCompletionFile(filename, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenZshCompletionNoDesc generates zsh completion file without descriptions
|
||||||
|
// and writes it to the passed writer.
|
||||||
|
func (c *Command) GenZshCompletionNoDesc(w io.Writer) error {
|
||||||
|
return c.genZshCompletion(w, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkZshCompPositionalArgumentFile only worked for zsh and its behavior was
|
||||||
|
// not consistent with Bash completion. It has therefore been disabled.
|
||||||
|
// Instead, when no other completion is specified, file completion is done by
|
||||||
|
// default for every argument. One can disable file completion on a per-argument
|
||||||
|
// basis by using ValidArgsFunction and ShellCompDirectiveNoFileComp.
|
||||||
|
// To achieve file extension filtering, one can use ValidArgsFunction and
|
||||||
|
// ShellCompDirectiveFilterFileExt.
|
||||||
|
//
|
||||||
|
// Deprecated
|
||||||
|
func (c *Command) MarkZshCompPositionalArgumentFile(argPosition int, patterns ...string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkZshCompPositionalArgumentWords only worked for zsh. It has therefore
|
||||||
|
// been disabled.
|
||||||
|
// To achieve the same behavior across all shells, one can use
|
||||||
|
// ValidArgs (for the first argument only) or ValidArgsFunction for
|
||||||
|
// any argument (can include the first one also).
|
||||||
|
//
|
||||||
|
// Deprecated
|
||||||
|
func (c *Command) MarkZshCompPositionalArgumentWords(argPosition int, words ...string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) genZshCompletionFile(filename string, includeDesc bool) error {
|
||||||
outFile, err := os.Create(filename)
|
outFile, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer outFile.Close()
|
defer outFile.Close()
|
||||||
|
|
||||||
return c.GenZshCompletion(outFile)
|
return c.genZshCompletion(outFile, includeDesc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenZshCompletion generates a zsh completion file and writes to the passed
|
func (c *Command) genZshCompletion(w io.Writer, includeDesc bool) error {
|
||||||
// writer. The completion always run on the root command regardless of the
|
buf := new(bytes.Buffer)
|
||||||
// command it was called from.
|
genZshComp(buf, c.Name(), includeDesc)
|
||||||
func (c *Command) GenZshCompletion(w io.Writer) error {
|
_, err := buf.WriteTo(w)
|
||||||
tmpl, err := template.New("Main").Funcs(zshCompFuncMap).Parse(zshCompletionText)
|
return err
|
||||||
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
|
func genZshComp(buf *bytes.Buffer, name string, includeDesc bool) {
|
||||||
// argument is 1) as completed by file selection. patterns (e.g. "*.txt") are
|
compCmd := ShellCompRequestCmd
|
||||||
// optional - if not provided the completion will search for all files.
|
if !includeDesc {
|
||||||
func (c *Command) MarkZshCompPositionalArgumentFile(argPosition int, patterns ...string) error {
|
compCmd = ShellCompNoDescRequestCmd
|
||||||
if argPosition < 1 {
|
|
||||||
return fmt.Errorf("Invalid argument position (%d)", argPosition)
|
|
||||||
}
|
}
|
||||||
annotation, err := c.zshCompGetArgsAnnotations()
|
buf.WriteString(fmt.Sprintf(`#compdef _%[1]s %[1]s
|
||||||
if err != nil {
|
|
||||||
return err
|
# zsh completion for %-36[1]s -*- shell-script -*-
|
||||||
}
|
|
||||||
if c.zshcompArgsAnnotationnIsDuplicatePosition(annotation, argPosition) {
|
__%[1]s_debug()
|
||||||
return fmt.Errorf("Duplicate annotation for positional argument at index %d", argPosition)
|
{
|
||||||
}
|
local file="$BASH_COMP_DEBUG_FILE"
|
||||||
annotation[argPosition] = zshCompArgHint{
|
if [[ -n ${file} ]]; then
|
||||||
Tipe: zshCompArgumentFilenameComp,
|
echo "$*" >> "${file}"
|
||||||
Options: patterns,
|
fi
|
||||||
}
|
|
||||||
return c.zshCompSetArgsAnnotations(annotation)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkZshCompPositionalArgumentWords marks the specified positional argument
|
_%[1]s()
|
||||||
// (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
|
local shellCompDirectiveError=%[3]d
|
||||||
// "word\ word".
|
local shellCompDirectiveNoSpace=%[4]d
|
||||||
func (c *Command) MarkZshCompPositionalArgumentWords(argPosition int, words ...string) error {
|
local shellCompDirectiveNoFileComp=%[5]d
|
||||||
if argPosition < 1 {
|
local shellCompDirectiveFilterFileExt=%[6]d
|
||||||
return fmt.Errorf("Invalid argument position (%d)", argPosition)
|
local shellCompDirectiveFilterDirs=%[7]d
|
||||||
}
|
|
||||||
if len(words) == 0 {
|
local lastParam lastChar flagPrefix requestComp out directive compCount comp lastComp
|
||||||
return fmt.Errorf("Trying to set empty word list for positional argument %d", argPosition)
|
local -a completions
|
||||||
}
|
|
||||||
annotation, err := c.zshCompGetArgsAnnotations()
|
__%[1]s_debug "\n========= starting completion logic =========="
|
||||||
if err != nil {
|
__%[1]s_debug "CURRENT: ${CURRENT}, words[*]: ${words[*]}"
|
||||||
return err
|
|
||||||
}
|
# The user could have moved the cursor backwards on the command-line.
|
||||||
if c.zshcompArgsAnnotationnIsDuplicatePosition(annotation, argPosition) {
|
# We need to trigger completion from the $CURRENT location, so we need
|
||||||
return fmt.Errorf("Duplicate annotation for positional argument at index %d", argPosition)
|
# 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.)
|
||||||
annotation[argPosition] = zshCompArgHint{
|
words=("${=words[1,CURRENT]}")
|
||||||
Tipe: zshCompArgumentWordComp,
|
__%[1]s_debug "Truncated words[*]: ${words[*]},"
|
||||||
Options: words,
|
|
||||||
}
|
lastParam=${words[-1]}
|
||||||
return c.zshCompSetArgsAnnotations(annotation)
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
func zshCompExtractArgumentCompletionHintsForRendering(c *Command) ([]string, error) {
|
# don't run the completion function when being source-ed or eval-ed
|
||||||
var result []string
|
if [ "$funcstack[1]" = "_%[1]s" ]; then
|
||||||
annotation, err := c.zshCompGetArgsAnnotations()
|
_%[1]s
|
||||||
if err != nil {
|
fi
|
||||||
return nil, err
|
`, name, compCmd,
|
||||||
}
|
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
|
||||||
for k, v := range annotation {
|
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
|
||||||
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) {
|
|
||||||
switch t := z.Tipe; t {
|
|
||||||
case zshCompArgumentFilenameComp:
|
|
||||||
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 {
|
|
||||||
_, dup := annotation[position]
|
|
||||||
return dup
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Command) zshCompGetArgsAnnotations() (zshCompArgsAnnotation, error) {
|
|
||||||
annotation := make(zshCompArgsAnnotation)
|
|
||||||
annotationString, ok := c.Annotations[zshCompArgumentAnnotation]
|
|
||||||
if !ok {
|
|
||||||
return annotation, nil
|
|
||||||
}
|
|
||||||
err := json.Unmarshal([]byte(annotationString), &annotation)
|
|
||||||
if err != nil {
|
|
||||||
return annotation, fmt.Errorf("Error unmarshaling zsh argument annotation: %v", err)
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func zshCompGenFuncName(c *Command) string {
|
|
||||||
if c.HasParent() {
|
|
||||||
return zshCompGenFuncName(c.Parent()) + "_" + c.Name()
|
|
||||||
}
|
|
||||||
return "_" + c.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
func zshCompExtractFlag(c *Command) []*pflag.Flag {
|
|
||||||
var flags []*pflag.Flag
|
|
||||||
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 {
|
|
||||||
if f.NoOptDefVal != "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return extras
|
|
||||||
}
|
|
||||||
|
|
||||||
func zshCompFlagCouldBeSpecifiedMoreThenOnce(f *pflag.Flag) bool {
|
|
||||||
return strings.Contains(f.Value.Type(), "Slice") ||
|
|
||||||
strings.Contains(f.Value.Type(), "Array")
|
|
||||||
}
|
|
||||||
|
|
||||||
func zshCompQuoteFlagDescription(s string) string {
|
|
||||||
return strings.Replace(s, "'", `'\''`, -1)
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue