Browse Source

Merge pull request #41554 from thaJeztah/bump_cobra

vendor: github.com/spf13/cobra v1.1.1
Brian Goff 4 years ago
parent
commit
e11cabe558

+ 1 - 1
vendor.conf

@@ -167,7 +167,7 @@ github.com/grpc-ecosystem/go-grpc-prometheus        c225b8c3b01faf2899099b768856
 github.com/cespare/xxhash/v2                        d7df74196a9e781ede915320c11c378c1b2f3a1f # v2.1.1
 
 # cli
-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/inconshreveable/mousetrap                76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 # v1.0.0
 github.com/morikuni/aec                             39771216ff4c63d11f5e604076f9c45e8be1067b # v1.0.0

+ 49 - 53
vendor/github.com/spf13/cobra/README.md

@@ -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.
 
-Many of the most widely used Go projects are built using Cobra, such as:
-[Kubernetes](http://kubernetes.io/),
-[Hugo](http://gohugo.io),
-[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.
+Cobra is used in many Go projects such as [Kubernetes](http://kubernetes.io/),
+[Hugo](https://gohugo.io), and [Github CLI](https://github.com/cli/cli) to
+name a few. [This list](./projects_using_cobra.md) contains a more extensive list of projects using 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)
 [![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
 
@@ -50,9 +29,8 @@ etc.
   * [PreRun and PostRun Hooks](#prerun-and-postrun-hooks)
   * [Suggestions when "unknown command" happens](#suggestions-when-unknown-command-happens)
   * [Generating documentation for your command](#generating-documentation-for-your-command)
-  * [Generating bash completions](#generating-bash-completions)
-  * [Generating zsh completions](#generating-zsh-completions)
-- [Contributing](#contributing)
+  * [Generating shell completions](#generating-shell-completions)
+- [Contributing](CONTRIBUTING.md)
 - [License](#license)
 
 # Overview
@@ -72,7 +50,7 @@ Cobra provides:
 * Intelligent suggestions (`app srver`... did you mean `app server`?)
 * Automatic help generation for commands and flags
 * 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
 * Command aliases so you can change things without breaking them
 * 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
 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:
 
@@ -199,7 +177,7 @@ var rootCmd = &cobra.Command{
 
 func Execute() {
   if err := rootCmd.Execute(); err != nil {
-    fmt.Println(err)
+    fmt.Fprintln(os.Stderr, err)
     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
 
 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")
 ```
 
+Or, for persistent flags:
+```go
+rootCmd.PersistentFlags().StringVarP(&Region, "region", "r", "", "AWS region (required)")
+rootCmd.MarkPersistentFlagRequired("region")
+```
+
 ## Positional and Custom Arguments
 
 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
 
-Cobra can generate documentation based on subcommands, flags, etc. in the following formats:
-
-- [Markdown](doc/md_docs.md)
-- [ReStructured Text](doc/rest_docs.md)
-- [Man Page](doc/man_docs.md)
-
-## Generating bash completions
-
-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).
+Cobra can generate documentation based on subcommands, flags, etc. Read more about it in the [docs generation documentation](doc/README.md).
 
-# Contributing
+## Generating shell completions
 
-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
+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).
 
 # License
 

+ 49 - 12
vendor/github.com/spf13/cobra/bash_completions.go

@@ -62,6 +62,12 @@ __%[1]s_handle_go_custom_completion()
 {
     __%[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
 
     # 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 completions are: ${out[*]}"
 
-    if [ $((directive & %[3]d)) -ne 0 ]; then
+    if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
         # Error code.  No completion.
         __%[1]s_debug "${FUNCNAME[0]}: received error from custom completion go code"
         return
     else
-        if [ $((directive & %[4]d)) -ne 0 ]; then
+        if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
             if [[ $(type -t compopt) = "builtin" ]]; then
                 __%[1]s_debug "${FUNCNAME[0]}: activating no space"
                 compopt -o nospace
             fi
         fi
-        if [ $((directive & %[5]d)) -ne 0 ]; then
+        if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
             if [[ $(type -t compopt) = "builtin" ]]; then
                 __%[1]s_debug "${FUNCNAME[0]}: activating no file completion"
                 compopt +o default
             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
             COMPREPLY+=("$comp")
         done < <(compgen -W "${out[*]}" -- "$cur")
@@ -181,10 +213,9 @@ __%[1]s_handle_reply()
     local completions
     completions=("${commands[@]}")
     if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then
-        completions=("${must_have_one_noun[@]}")
+        completions+=("${must_have_one_noun[@]}")
     elif [[ -n "${has_completion_function}" ]]; then
         # if a go completion function is provided, defer to that function
-        completions=()
         __%[1]s_handle_go_custom_completion
     fi
     if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then
@@ -344,7 +375,9 @@ __%[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) {
@@ -390,7 +423,7 @@ fi
 func writeCommands(buf *bytes.Buffer, cmd *Command) {
 	buf.WriteString("    commands=()\n")
 	for _, c := range cmd.Commands() {
-		if !c.IsAvailableCommand() || c == cmd.helpCommand {
+		if !c.IsAvailableCommand() && c != cmd.helpCommand {
 			continue
 		}
 		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) {
 	name := flag.Name
-	format := "    local_nonpersistent_flags+=(\"--%s"
+	format := "    local_nonpersistent_flags+=(\"--%[1]s\")\n"
 	if len(flag.NoOptDefVal) == 0 {
-		format += "="
+		format += "    local_nonpersistent_flags+=(\"--%[1]s=\")\n"
 	}
-	format += "\")\n"
 	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
@@ -502,7 +537,9 @@ func writeFlags(buf *bytes.Buffer, cmd *Command) {
 		if len(flag.Shorthand) > 0 {
 			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)
 		}
 	})
@@ -583,7 +620,7 @@ func writeArgAliases(buf *bytes.Buffer, cmd *Command) {
 
 func gen(buf *bytes.Buffer, cmd *Command) {
 	for _, c := range cmd.Commands() {
-		if !c.IsAvailableCommand() || c == cmd.helpCommand {
+		if !c.IsAvailableCommand() && c != cmd.helpCommand {
 			continue
 		}
 		gen(buf, c)

+ 38 - 8
vendor/github.com/spf13/cobra/command.go

@@ -37,6 +37,14 @@ type FParseErrWhitelist flag.ParseErrorsWhitelist
 // definition to ensure usability.
 type Command struct {
 	// 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
 
 	// 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()
 		err := tmpl(c.OutOrStderr(), c.UsageTemplate(), c)
 		if err != nil {
-			c.Println(err)
+			c.PrintErrln(err)
 		}
 		return err
 	}
@@ -387,7 +395,7 @@ func (c *Command) HelpFunc() func(*Command, []string) {
 		// See https://github.com/spf13/cobra/issues/1002
 		err := tmpl(c.OutOrStdout(), c.HelpTemplate(), c)
 		if err != nil {
-			c.Println(err)
+			c.PrintErrln(err)
 		}
 	}
 }
@@ -930,8 +938,8 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
 			c = cmd
 		}
 		if !c.SilenceErrors {
-			c.Println("Error:", err.Error())
-			c.Printf("Run '%v --help' for usage.\n", c.CommandPath())
+			c.PrintErrln("Error:", err.Error())
+			c.PrintErrf("Run '%v --help' for usage.\n", c.CommandPath())
 		}
 		return c, err
 	}
@@ -959,7 +967,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
 		// If root command has SilentErrors flagged,
 		// all subcommands should respect it
 		if !cmd.SilenceErrors && !c.SilenceErrors {
-			c.Println("Error:", err.Error())
+			c.PrintErrln("Error:", err.Error())
 		}
 
 		// If root command has SilentUsage flagged,
@@ -979,6 +987,10 @@ func (c *Command) ValidateArgs(args []string) error {
 }
 
 func (c *Command) validateRequiredFlags() error {
+	if c.DisableFlagParsing {
+		return nil
+	}
+
 	flags := c.Flags()
 	missingFlagNames := []string{}
 	flags.VisitAll(func(pflag *flag.Flag) {
@@ -1052,7 +1064,25 @@ func (c *Command) InitDefaultHelpCmd() {
 			Short: "Help about any command",
 			Long: `Help provides help for any command in the application.
 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) {
 				cmd, _, e := c.Root().Find(args)
 				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.
 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.
 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.

+ 237 - 64
vendor/github.com/spf13/cobra/custom_completions.go

@@ -1,7 +1,6 @@
 package cobra
 
 import (
-	"errors"
 	"fmt"
 	"os"
 	"strings"
@@ -38,8 +37,29 @@ const (
 	// This currently does not work for zsh or bash < 4
 	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
 	// behavior after completions have been provided.
+	// This one must be last to avoid messing up the iota count.
 	ShellCompDirectiveDefault ShellCompDirective = 0
 )
 
@@ -68,11 +88,17 @@ func (d ShellCompDirective) string() string {
 	if d&ShellCompDirectiveNoFileComp != 0 {
 		directives = append(directives, "ShellCompDirectiveNoFileComp")
 	}
+	if d&ShellCompDirectiveFilterFileExt != 0 {
+		directives = append(directives, "ShellCompDirectiveFilterFileExt")
+	}
+	if d&ShellCompDirectiveFilterDirs != 0 {
+		directives = append(directives, "ShellCompDirectiveFilterDirs")
+	}
 	if len(directives) == 0 {
 		directives = append(directives, "ShellCompDirectiveDefault")
 	}
 
-	if d > ShellCompDirectiveError+ShellCompDirectiveNoSpace+ShellCompDirectiveNoFileComp {
+	if d >= shellCompDirectiveMaxValue {
 		return fmt.Sprintf("ERROR: unexpected ShellCompDirective value: %d", d)
 	}
 	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.
 					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.
 				fmt.Fprintln(finalCmd.OutOrStdout(), comp)
 			}
 
-			if directive > ShellCompDirectiveError+ShellCompDirectiveNoSpace+ShellCompDirectiveNoFileComp {
+			if directive >= shellCompDirectiveMaxValue {
 				directive = ShellCompDirectiveDefault
 			}
 
@@ -136,90 +176,179 @@ func (c *Command) initCompleteCmd(args []string) {
 }
 
 func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDirective, error) {
-	var completions []string
-
 	// The last argument, which is not completely typed by the user,
 	// should not be part of the list of arguments
 	toComplete := 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
-	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 {
 		// 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
 	// a '-' we know it is a flag.  We cannot use isFlagArg() here as it requires
-	// the flag to be complete
-	if len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") {
-		// We are completing a flag name
-		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
-		if len(completions) > 0 {
-			if strings.HasSuffix(completions[0], "=") {
-				directive = ShellCompDirectiveNoSpace
+	// the flag name to be complete
+	if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") {
+		var completions []string
+
+		// First check for required flags
+		completions = completeRequireFlags(finalCmd, toComplete)
+
+		// If we have not found any required flags, only then can we show regular flags
+		if len(completions) == 0 {
+			doCompleteFlags := func(flag *pflag.Flag) {
+				if !flag.Changed ||
+					strings.Contains(flag.Value.Type(), "Slice") ||
+					strings.Contains(flag.Value.Type(), "Array") {
+					// If the flag is not already present, or if it can be specified multiple times (Array or Slice)
+					// 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) {
+				doCompleteFlags(flag)
+			})
+			finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
+				doCompleteFlags(flag)
+			})
+		}
+
+		directive := ShellCompDirectiveNoFileComp
+		if len(completions) == 1 && strings.HasSuffix(completions[0], "=") {
+			// 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
 		}
 		return finalCmd, completions, directive, nil
 	}
 
-	var flag *pflag.Flag
+	// We only remove the flags from the arguments if DisableFlagParsing is not set.
+	// This is important for commands which have requested to do their own flag completion.
 	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.
-		flag, finalArgs, toComplete, err = checkIfFlagCompletion(finalCmd, finalArgs, toComplete)
-		if err != nil {
-			// Error while attempting to parse flags
-			return finalCmd, completions, ShellCompDirectiveDefault, err
-		}
+		finalArgs = finalCmd.Flags().Args()
 	}
 
+	var completions []string
+	directive := ShellCompDirectiveDefault
 	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))
+		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 {
-			// 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 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, ShellCompDirectiveNoFileComp, nil
+			return finalCmd, completions, directive, nil
 		}
 
-		// Always let the logic continue so as to add any ValidArgsFunction completions,
+		// 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.
 	}
 
-	// Parse the flags and extract the arguments to prepare for calling the completion function
-	if err = finalCmd.ParseFlags(finalArgs); err != 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.
-	// This is important for commands which have requested to do their own flag completion.
-	if !finalCmd.DisableFlagParsing {
-		finalArgs = finalCmd.Flags().Args()
-	}
-
 	// Find the completion function for the flag or command
 	var completionFn func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
 	if flag != nil {
@@ -227,14 +356,14 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
 	} else {
 		completionFn = finalCmd.ValidArgsFunction
 	}
-	if completionFn == nil {
-		// Go custom completion not supported/needed for this flag or command
-		return finalCmd, completions, ShellCompDirectiveDefault, nil
+	if completionFn != nil {
+		// Go custom completion defined for this flag or command.
+		// 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
 }
 
@@ -249,11 +378,18 @@ func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
 		// Flag without the =
 		completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
 
-		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))
-		}
+		// Why suggest both long forms: --flag and --flag= ?
+		// This forces the user to *always* have to type either an = or a space after the flag name.
+		// Let's be nice and avoid making users have to do that.
+		// 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
@@ -264,17 +400,54 @@ func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
 	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) {
+	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
 	trimmedArgs := args
 	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 {
+			// Flag with an =
 			flagName = strings.TrimLeft(lastArg[:index], "-")
 			lastArg = lastArg[index+1:]
 			flagWithEqual = true
 		} else {
-			return nil, nil, "", errors.New("Unexpected completion request for flag")
+			// Normal flag completion
+			return nil, args, lastArg, nil
 		}
 	}
 

+ 50 - 15
vendor/github.com/spf13/cobra/fish_completions.go

@@ -5,9 +5,15 @@ import (
 	"fmt"
 	"io"
 	"os"
+	"strings"
 )
 
 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
 	if !includeDesc {
 		compCmd = ShellCompNoDescRequestCmd
@@ -37,7 +43,13 @@ function __%[1]s_perform_completion
     end
     __%[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"
 
     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.
     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
     __%[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"
         # Might as well do file completion, in case it helps
         set --global __%[1]s_comp_do_file_comp 1
-        return 0
+        return 1
     end
 
     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 "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"
         set directive 0
     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
         __%[1]s_debug "Received error directive: aborting."
         # Might as well do file completion, in case it helps
         set --global __%[1]s_comp_do_file_comp 1
-        return 0
+        return 1
     end
 
-    set nospace (math (math --scale 0 $directive / %[4]d) %% 2)
-    set nofiles (math (math --scale 0 $directive / %[5]d) %% 2)
+    set filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) %% 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"
 
@@ -132,24 +160,31 @@ function __%[1]s_prepare_completions
     return (not set --query __%[1]s_comp_do_file_comp)
 end
 
-# Remove any pre-existing completions for the program since we will be handling all of them
-# TODO this cleanup is not sufficient.  Fish completions are only loaded once the user triggers
-# them, so the below deletion will not work as it is run too early.  What else can we do?
-complete -c %[1]s -e
+# Since Fish completions are only loaded once the user triggers them, we trigger them ourselves
+# so we can properly delete any completions provided by another script.
+# The space after the the program name is essential to trigger completion for the program
+# 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
 # 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.
 # 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.
-# 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.
-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.

+ 3 - 3
vendor/github.com/spf13/cobra/go.mod

@@ -6,7 +6,7 @@ require (
 	github.com/cpuguy83/go-md2man/v2 v2.0.0
 	github.com/inconshreveable/mousetrap v1.0.0
 	github.com/mitchellh/go-homedir v1.1.0
-	github.com/spf13/pflag v1.0.3
-	github.com/spf13/viper v1.4.0
-	gopkg.in/yaml.v2 v2.2.2
+	github.com/spf13/pflag v1.0.5
+	github.com/spf13/viper v1.7.0
+	gopkg.in/yaml.v2 v2.2.8
 )

+ 26 - 27
vendor/github.com/spf13/cobra/shell_completions.go

@@ -4,82 +4,81 @@ import (
 	"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.
 func (c *Command) MarkFlagRequired(name string) error {
 	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.
 func (c *Command) MarkPersistentFlagRequired(name string) error {
 	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.
 func MarkFlagRequired(flags *pflag.FlagSet, name string) error {
 	return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"})
 }
 
-// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists.
-// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
+// MarkFlagFilename instructs the various shell completion implementations to
+// limit completions for the named flag to the specified file extensions.
 func (c *Command) MarkFlagFilename(name string, extensions ...string) error {
 	return MarkFlagFilename(c.Flags(), name, extensions...)
 }
 
 // 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 {
 	return MarkFlagCustom(c.Flags(), name, f)
 }
 
 // MarkPersistentFlagFilename instructs the various shell completion
-// implementations to limit completions for this persistent flag to the
-// specified extensions (patterns).
-//
-// Shell Completion compatibility matrix: bash, zsh
+// implementations to limit completions for the named persistent flag to the
+// specified file extensions.
 func (c *Command) MarkPersistentFlagFilename(name string, extensions ...string) error {
 	return MarkFlagFilename(c.PersistentFlags(), name, extensions...)
 }
 
 // MarkFlagFilename instructs the various shell completion implementations to
-// limit completions for this flag to the specified extensions (patterns).
-//
-// Shell Completion compatibility matrix: bash, zsh
+// limit completions for the named flag to the specified file extensions.
 func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error {
 	return flags.SetAnnotation(name, BashCompFilenameExt, extensions)
 }
 
-// MarkFlagCustom instructs the various shell completion implementations to
-// limit completions for this flag to the specified extensions (patterns).
+// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists.
+// 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 {
 	return flags.SetAnnotation(name, BashCompCustom, []string{f})
 }
 
 // MarkFlagDirname instructs the various shell completion implementations to
-// complete only directories with this named flag.
-//
-// Shell Completion compatibility matrix: zsh
+// limit completions for the named flag to directory names.
 func (c *Command) MarkFlagDirname(name string) error {
 	return MarkFlagDirname(c.Flags(), name)
 }
 
 // MarkPersistentFlagDirname instructs the various shell completion
-// implementations to complete only directories with this persistent named flag.
-//
-// Shell Completion compatibility matrix: zsh
+// implementations to limit completions for the named persistent flag to
+// directory names.
 func (c *Command) MarkPersistentFlagDirname(name string) error {
 	return MarkFlagDirname(c.PersistentFlags(), name)
 }
 
 // MarkFlagDirname instructs the various shell completion implementations to
-// complete only directories with this specified flag.
-//
-// Shell Completion compatibility matrix: zsh
+// limit completions for the named flag to directory names.
 func MarkFlagDirname(flags *pflag.FlagSet, name string) error {
-	zshPattern := "-(/)"
-	return flags.SetAnnotation(name, zshCompDirname, []string{zshPattern})
+	return flags.SetAnnotation(name, BashCompSubdirsInDir, []string{})
 }

+ 214 - 310
vendor/github.com/spf13/cobra/zsh_completions.go

@@ -1,336 +1,240 @@
 package cobra
 
 import (
-	"encoding/json"
+	"bytes"
 	"fmt"
 	"io"
 	"os"
-	"sort"
-	"strings"
-	"text/template"
-
-	"github.com/spf13/pflag"
-)
-
-const (
-	zshCompArgumentAnnotation   = "cobra_annotations_zsh_completion_argument_annotation"
-	zshCompArgumentFilenameComp = "cobra_annotations_zsh_completion_argument_file_completion"
-	zshCompArgumentWordComp     = "cobra_annotations_zsh_completion_argument_word_completion"
-	zshCompDirname              = "cobra_annotations_zsh_dirname"
 )
 
-var (
-	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.
+// GenZshCompletionFile generates zsh completion file including descriptions.
 func (c *Command) GenZshCompletionFile(filename string) error {
-	outFile, err := os.Create(filename)
-	if err != nil {
-		return err
-	}
-	defer outFile.Close()
-
-	return c.GenZshCompletion(outFile)
+	return c.genZshCompletionFile(filename, true)
 }
 
-// GenZshCompletion generates a zsh completion file and writes to the passed
-// writer. The completion always run on the root command regardless of the
-// command it was called from.
+// GenZshCompletion generates zsh completion file including descriptions
+// and writes it to the passed writer.
 func (c *Command) GenZshCompletion(w io.Writer) error {
-	tmpl, err := template.New("Main").Funcs(zshCompFuncMap).Parse(zshCompletionText)
-	if err != nil {
-		return fmt.Errorf("error creating zsh completion template: %v", err)
-	}
-	return tmpl.Execute(w, c.Root())
-}
-
-// MarkZshCompPositionalArgumentFile marks the specified argument (first
-// argument is 1) as completed by file selection. patterns (e.g. "*.txt") are
-// optional - if not provided the completion will search for all files.
-func (c *Command) MarkZshCompPositionalArgumentFile(argPosition int, patterns ...string) error {
-	if argPosition < 1 {
-		return fmt.Errorf("Invalid argument position (%d)", argPosition)
-	}
-	annotation, err := c.zshCompGetArgsAnnotations()
-	if err != nil {
-		return err
-	}
-	if c.zshcompArgsAnnotationnIsDuplicatePosition(annotation, argPosition) {
-		return fmt.Errorf("Duplicate annotation for positional argument at index %d", argPosition)
-	}
-	annotation[argPosition] = zshCompArgHint{
-		Tipe:    zshCompArgumentFilenameComp,
-		Options: patterns,
-	}
-	return c.zshCompSetArgsAnnotations(annotation)
-}
-
-// MarkZshCompPositionalArgumentWords marks the specified positional argument
-// (first argument is 1) as completed by the provided words. At east one word
-// must be provided, spaces within words will be offered completion with
-// "word\ word".
-func (c *Command) MarkZshCompPositionalArgumentWords(argPosition int, words ...string) error {
-	if argPosition < 1 {
-		return fmt.Errorf("Invalid argument position (%d)", argPosition)
-	}
-	if len(words) == 0 {
-		return fmt.Errorf("Trying to set empty word list for positional argument %d", argPosition)
-	}
-	annotation, err := c.zshCompGetArgsAnnotations()
-	if err != nil {
-		return err
-	}
-	if c.zshcompArgsAnnotationnIsDuplicatePosition(annotation, argPosition) {
-		return fmt.Errorf("Duplicate annotation for positional argument at index %d", argPosition)
-	}
-	annotation[argPosition] = zshCompArgHint{
-		Tipe:    zshCompArgumentWordComp,
-		Options: words,
-	}
-	return c.zshCompSetArgsAnnotations(annotation)
-}
-
-func zshCompExtractArgumentCompletionHintsForRendering(c *Command) ([]string, error) {
-	var result []string
-	annotation, err := c.zshCompGetArgsAnnotations()
-	if err != nil {
-		return nil, err
-	}
-	for k, v := range annotation {
-		s, err := zshCompRenderZshCompArgHint(k, v)
-		if err != nil {
-			return nil, err
-		}
-		result = append(result, s)
-	}
-	if len(c.ValidArgs) > 0 {
-		if _, positionOneExists := annotation[1]; !positionOneExists {
-			s, err := zshCompRenderZshCompArgHint(1, zshCompArgHint{
-				Tipe:    zshCompArgumentWordComp,
-				Options: c.ValidArgs,
-			})
-			if err != nil {
-				return nil, err
-			}
-			result = append(result, s)
-		}
-	}
-	sort.Strings(result)
-	return result, nil
+	return c.genZshCompletion(w, true)
 }
 
-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)
-	}
+// GenZshCompletionFileNoDesc generates zsh completion file without descriptions.
+func (c *Command) GenZshCompletionFileNoDesc(filename string) error {
+	return c.genZshCompletionFile(filename, false)
 }
 
-func (c *Command) zshcompArgsAnnotationnIsDuplicatePosition(annotation zshCompArgsAnnotation, position int) bool {
-	_, dup := annotation[position]
-	return dup
+// 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)
 }
 
-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)
+// 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
 }
 
-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)
+// 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 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)
-			}
-		}
+func (c *Command) genZshCompletionFile(filename string, includeDesc bool) error {
+	outFile, err := os.Create(filename)
+	if err != nil {
+		return err
 	}
+	defer outFile.Close()
 
-	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)
+	return c.genZshCompletion(outFile, includeDesc)
+}
+
+func (c *Command) genZshCompletion(w io.Writer, includeDesc bool) error {
+	buf := new(bytes.Buffer)
+	genZshComp(buf, c.Name(), includeDesc)
+	_, err := buf.WriteTo(w)
+	return err
+}
+
+func genZshComp(buf *bytes.Buffer, name string, includeDesc bool) {
+	compCmd := ShellCompRequestCmd
+	if !includeDesc {
+		compCmd = ShellCompNoDescRequestCmd
+	}
+	buf.WriteString(fmt.Sprintf(`#compdef _%[1]s %[1]s
+
+# zsh completion for %-36[1]s -*- shell-script -*-
+
+__%[1]s_debug()
+{
+    local file="$BASH_COMP_DEBUG_FILE"
+    if [[ -n ${file} ]]; then
+        echo "$*" >> "${file}"
+    fi
+}
+
+_%[1]s()
+{
+    local shellCompDirectiveError=%[3]d
+    local shellCompDirectiveNoSpace=%[4]d
+    local shellCompDirectiveNoFileComp=%[5]d
+    local shellCompDirectiveFilterFileExt=%[6]d
+    local shellCompDirectiveFilterDirs=%[7]d
+
+    local lastParam lastChar flagPrefix requestComp out directive compCount comp lastComp
+    local -a completions
+
+    __%[1]s_debug "\n========= starting completion logic =========="
+    __%[1]s_debug "CURRENT: ${CURRENT}, words[*]: ${words[*]}"
+
+    # The user could have moved the cursor backwards on the command-line.
+    # We need to trigger completion from the $CURRENT location, so we need
+    # to truncate the command-line ($words) up to the $CURRENT location.
+    # (We cannot use $CURSOR as its value does not work when a command is an alias.)
+    words=("${=words[1,CURRENT]}")
+    __%[1]s_debug "Truncated words[*]: ${words[*]},"
+
+    lastParam=${words[-1]}
+    lastChar=${lastParam[-1]}
+    __%[1]s_debug "lastParam: ${lastParam}, lastChar: ${lastChar}"
+
+    # For zsh, when completing a flag with an = (e.g., %[1]s -n=<TAB>)
+    # completions must be prefixed with the flag
+    setopt local_options BASH_REMATCH
+    if [[ "${lastParam}" =~ '-.*=' ]]; then
+        # We are dealing with a flag with an =
+        flagPrefix="-P ${BASH_REMATCH}"
+    fi
+
+    # Prepare the command to obtain completions
+    requestComp="${words[1]} %[2]s ${words[2,-1]}"
+    if [ "${lastChar}" = "" ]; then
+        # If the last parameter is complete (there is a space following it)
+        # We add an extra empty parameter so we can indicate this to the go completion code.
+        __%[1]s_debug "Adding extra empty parameter"
+        requestComp="${requestComp} \"\""
+    fi
+
+    __%[1]s_debug "About to call: eval ${requestComp}"
+
+    # Use eval to handle any environment variables and such
+    out=$(eval ${requestComp} 2>/dev/null)
+    __%[1]s_debug "completion output: ${out}"
+
+    # Extract the directive integer following a : from the last line
+    local lastLine
+    while IFS='\n' read -r line; do
+        lastLine=${line}
+    done < <(printf "%%s\n" "${out[@]}")
+    __%[1]s_debug "last line: ${lastLine}"
+
+    if [ "${lastLine[1]}" = : ]; then
+        directive=${lastLine[2,-1]}
+        # Remove the directive including the : and the newline
+        local suffix
+        (( suffix=${#lastLine}+2))
+        out=${out[1,-$suffix]}
+    else
+        # There is no directive specified.  Leave $out as is.
+        __%[1]s_debug "No directive found.  Setting do default"
+        directive=0
+    fi
+
+    __%[1]s_debug "directive: ${directive}"
+    __%[1]s_debug "completions: ${out}"
+    __%[1]s_debug "flagPrefix: ${flagPrefix}"
+
+    if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
+        __%[1]s_debug "Completion received error. Ignoring completions."
+        return
+    fi
+
+    compCount=0
+    while IFS='\n' read -r comp; do
+        if [ -n "$comp" ]; then
+            # If requested, completions are returned with a description.
+            # The description is preceded by a TAB character.
+            # For zsh's _describe, we need to use a : instead of a TAB.
+            # We first need to escape any : as part of the completion itself.
+            comp=${comp//:/\\:}
+
+            local tab=$(printf '\t')
+            comp=${comp//$tab/:}
+
+            ((compCount++))
+            __%[1]s_debug "Adding completion: ${comp}"
+            completions+=${comp}
+            lastComp=$comp
+        fi
+    done < <(printf "%%s\n" "${out[@]}")
+
+    if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
+        # File extension filtering
+        local filteringCmd
+        filteringCmd='_files'
+        for filter in ${completions[@]}; do
+            if [ ${filter[1]} != '*' ]; then
+                # zsh requires a glob pattern to do file filtering
+                filter="\*.$filter"
+            fi
+            filteringCmd+=" -g $filter"
+        done
+        filteringCmd+=" ${flagPrefix}"
+
+        __%[1]s_debug "File filtering command: $filteringCmd"
+        _arguments '*:filename:'"$filteringCmd"
+    elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
+        # File completion for directories only
+        local subDir
+        subdir="${completions[1]}"
+        if [ -n "$subdir" ]; then
+            __%[1]s_debug "Listing directories in $subdir"
+            pushd "${subdir}" >/dev/null 2>&1
+        else
+            __%[1]s_debug "Listing directories in ."
+        fi
+
+        _arguments '*:dirname:_files -/'" ${flagPrefix}"
+        if [ -n "$subdir" ]; then
+            popd >/dev/null 2>&1
+        fi
+    elif [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ] && [ ${compCount} -eq 1 ]; then
+        __%[1]s_debug "Activating nospace."
+        # We can use compadd here as there is no description when
+        # there is only one completion.
+        compadd -S '' "${lastComp}"
+    elif [ ${compCount} -eq 0 ]; then
+        if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
+            __%[1]s_debug "deactivating file completion"
+        else
+            # Perform file completion
+            __%[1]s_debug "activating file completion"
+            _arguments '*:filename:_files'" ${flagPrefix}"
+        fi
+    else
+        _describe "completions" completions $(echo $flagPrefix)
+    fi
+}
+
+# don't run the completion function when being source-ed or eval-ed
+if [ "$funcstack[1]" = "_%[1]s" ]; then
+	_%[1]s
+fi
+`, name, compCmd,
+		ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
+		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
 }