Browse code

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>

Sebastiaan van Stijn authored on 2020/10/16 05:02:56
Showing 9 changed files
... ...
@@ -161,7 +161,7 @@ github.com/grpc-ecosystem/go-grpc-prometheus        c225b8c3b01faf2899099b768856
161 161
 github.com/cespare/xxhash/v2                        d7df74196a9e781ede915320c11c378c1b2f3a1f # v2.1.1
162 162
 
163 163
 # cli
164
-github.com/spf13/cobra                              a684a6d7f5e37385d954dd3b5a14fc6912c6ab9d # v1.0.0
164
+github.com/spf13/cobra                              86f8bfd7fef868a174e1b606783bd7f5c82ddf8f # v1.1.1
165 165
 github.com/spf13/pflag                              2e9d26c8c37aae03e3f9d4e90b7116f5accb7cab # v1.0.5
166 166
 github.com/inconshreveable/mousetrap                76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 # v1.0.0
167 167
 github.com/morikuni/aec                             39771216ff4c63d11f5e604076f9c45e8be1067b # v1.0.0
... ...
@@ -2,35 +2,14 @@
2 2
 
3 3
 Cobra is both a library for creating powerful modern CLI applications as well as a program to generate applications and command files.
4 4
 
5
-Many of the most widely used Go projects are built using Cobra, such as:
6
-[Kubernetes](http://kubernetes.io/),
7
-[Hugo](http://gohugo.io),
8
-[rkt](https://github.com/coreos/rkt),
9
-[etcd](https://github.com/coreos/etcd),
10
-[Moby (former Docker)](https://github.com/moby/moby),
11
-[Docker (distribution)](https://github.com/docker/distribution),
12
-[OpenShift](https://www.openshift.com/),
13
-[Delve](https://github.com/derekparker/delve),
14
-[GopherJS](http://www.gopherjs.org/),
15
-[CockroachDB](http://www.cockroachlabs.com/),
16
-[Bleve](http://www.blevesearch.com/),
17
-[ProjectAtomic (enterprise)](http://www.projectatomic.io/),
18
-[Giant Swarm's gsctl](https://github.com/giantswarm/gsctl),
19
-[Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack),
20
-[rclone](http://rclone.org/),
21
-[nehm](https://github.com/bogem/nehm),
22
-[Pouch](https://github.com/alibaba/pouch),
23
-[Istio](https://istio.io),
24
-[Prototool](https://github.com/uber/prototool),
25
-[mattermost-server](https://github.com/mattermost/mattermost-server),
26
-[Gardener](https://github.com/gardener/gardenctl),
27
-[Linkerd](https://linkerd.io/),
28
-[Github CLI](https://github.com/cli/cli)
29
-etc.
5
+Cobra is used in many Go projects such as [Kubernetes](http://kubernetes.io/),
6
+[Hugo](https://gohugo.io), and [Github CLI](https://github.com/cli/cli) to
7
+name a few. [This list](./projects_using_cobra.md) contains a more extensive list of projects using Cobra.
30 8
 
31 9
 [![Build Status](https://travis-ci.org/spf13/cobra.svg "Travis CI status")](https://travis-ci.org/spf13/cobra)
32 10
 [![GoDoc](https://godoc.org/github.com/spf13/cobra?status.svg)](https://godoc.org/github.com/spf13/cobra)
33 11
 [![Go Report Card](https://goreportcard.com/badge/github.com/spf13/cobra)](https://goreportcard.com/report/github.com/spf13/cobra)
12
+[![Slack](https://img.shields.io/badge/Slack-cobra-brightgreen)](https://gophers.slack.com/archives/CD3LP1199)
34 13
 
35 14
 # Table of Contents
36 15
 
... ...
@@ -50,9 +29,8 @@ etc.
50 50
   * [PreRun and PostRun Hooks](#prerun-and-postrun-hooks)
51 51
   * [Suggestions when "unknown command" happens](#suggestions-when-unknown-command-happens)
52 52
   * [Generating documentation for your command](#generating-documentation-for-your-command)
53
-  * [Generating bash completions](#generating-bash-completions)
54
-  * [Generating zsh completions](#generating-zsh-completions)
55
-- [Contributing](#contributing)
53
+  * [Generating shell completions](#generating-shell-completions)
54
+- [Contributing](CONTRIBUTING.md)
56 55
 - [License](#license)
57 56
 
58 57
 # Overview
... ...
@@ -72,7 +50,7 @@ Cobra provides:
72 72
 * Intelligent suggestions (`app srver`... did you mean `app server`?)
73 73
 * Automatic help generation for commands and flags
74 74
 * Automatic help flag recognition of `-h`, `--help`, etc.
75
-* Automatically generated bash autocomplete for your application
75
+* Automatically generated shell autocomplete for your application (bash, zsh, fish, powershell)
76 76
 * Automatically generated man pages for your application
77 77
 * Command aliases so you can change things without breaking them
78 78
 * 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
130 130
 of the library. This command will install the `cobra` generator executable
131 131
 along with the library and its dependencies:
132 132
 
133
-    go get -u github.com/spf13/cobra/cobra
133
+    go get -u github.com/spf13/cobra
134 134
 
135 135
 Next, include Cobra in your application:
136 136
 
... ...
@@ -199,7 +177,7 @@ var rootCmd = &cobra.Command{
199 199
 
200 200
 func Execute() {
201 201
   if err := rootCmd.Execute(); err != nil {
202
-    fmt.Println(err)
202
+    fmt.Fprintln(os.Stderr, err)
203 203
     os.Exit(1)
204 204
   }
205 205
 }
... ...
@@ -335,6 +313,37 @@ var versionCmd = &cobra.Command{
335 335
 }
336 336
 ```
337 337
 
338
+### Returning and handling errors
339
+
340
+If you wish to return an error to the caller of a command, `RunE` can be used.
341
+
342
+```go
343
+package cmd
344
+
345
+import (
346
+  "fmt"
347
+
348
+  "github.com/spf13/cobra"
349
+)
350
+
351
+func init() {
352
+  rootCmd.AddCommand(tryCmd)
353
+}
354
+
355
+var tryCmd = &cobra.Command{
356
+  Use:   "try",
357
+  Short: "Try and possibly fail at something",
358
+  RunE: func(cmd *cobra.Command, args []string) error {
359
+    if err := someFunc(); err != nil {
360
+	return err
361
+    }
362
+    return nil
363
+  },
364
+}
365
+```
366
+
367
+The error can then be caught at the execute function call.
368
+
338 369
 ## Working with Flags
339 370
 
340 371
 Flags provide modifiers to control how the action command operates.
... ...
@@ -410,6 +419,12 @@ rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)")
410 410
 rootCmd.MarkFlagRequired("region")
411 411
 ```
412 412
 
413
+Or, for persistent flags:
414
+```go
415
+rootCmd.PersistentFlags().StringVarP(&Region, "region", "r", "", "AWS region (required)")
416
+rootCmd.MarkPersistentFlagRequired("region")
417
+```
418
+
413 419
 ## Positional and Custom Arguments
414 420
 
415 421
 Validation of positional arguments can be specified using the `Args` field
... ...
@@ -740,30 +755,11 @@ Run 'kubectl help' for usage.
740 740
 
741 741
 ## Generating documentation for your command
742 742
 
743
-Cobra can generate documentation based on subcommands, flags, etc. in the following formats:
744
-
745
-- [Markdown](doc/md_docs.md)
746
-- [ReStructured Text](doc/rest_docs.md)
747
-- [Man Page](doc/man_docs.md)
748
-
749
-## Generating bash completions
750
-
751
-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).
752
-
753
-## Generating zsh completions
754
-
755
-Cobra can generate zsh-completion file. Read more about it in
756
-[Zsh Completions](zsh_completions.md).
743
+Cobra can generate documentation based on subcommands, flags, etc. Read more about it in the [docs generation documentation](doc/README.md).
757 744
 
758
-# Contributing
745
+## Generating shell completions
759 746
 
760
-1. Fork it
761
-2. Download your fork to your PC (`git clone https://github.com/your_username/cobra && cd cobra`)
762
-3. Create your feature branch (`git checkout -b my-new-feature`)
763
-4. Make changes and add them (`git add .`)
764
-5. Commit your changes (`git commit -m 'Add some feature'`)
765
-6. Push to the branch (`git push origin my-new-feature`)
766
-7. Create new pull request
747
+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).
767 748
 
768 749
 # License
769 750
 
... ...
@@ -62,6 +62,12 @@ __%[1]s_handle_go_custom_completion()
62 62
 {
63 63
     __%[1]s_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}"
64 64
 
65
+    local shellCompDirectiveError=%[3]d
66
+    local shellCompDirectiveNoSpace=%[4]d
67
+    local shellCompDirectiveNoFileComp=%[5]d
68
+    local shellCompDirectiveFilterFileExt=%[6]d
69
+    local shellCompDirectiveFilterDirs=%[7]d
70
+
65 71
     local out requestComp lastParam lastChar comp directive args
66 72
 
67 73
     # Prepare the command to request completions for the program.
... ...
@@ -95,24 +101,50 @@ __%[1]s_handle_go_custom_completion()
95 95
     __%[1]s_debug "${FUNCNAME[0]}: the completion directive is: ${directive}"
96 96
     __%[1]s_debug "${FUNCNAME[0]}: the completions are: ${out[*]}"
97 97
 
98
-    if [ $((directive & %[3]d)) -ne 0 ]; then
98
+    if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
99 99
         # Error code.  No completion.
100 100
         __%[1]s_debug "${FUNCNAME[0]}: received error from custom completion go code"
101 101
         return
102 102
     else
103
-        if [ $((directive & %[4]d)) -ne 0 ]; then
103
+        if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
104 104
             if [[ $(type -t compopt) = "builtin" ]]; then
105 105
                 __%[1]s_debug "${FUNCNAME[0]}: activating no space"
106 106
                 compopt -o nospace
107 107
             fi
108 108
         fi
109
-        if [ $((directive & %[5]d)) -ne 0 ]; then
109
+        if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
110 110
             if [[ $(type -t compopt) = "builtin" ]]; then
111 111
                 __%[1]s_debug "${FUNCNAME[0]}: activating no file completion"
112 112
                 compopt +o default
113 113
             fi
114 114
         fi
115
+    fi
115 116
 
117
+    if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
118
+        # File extension filtering
119
+        local fullFilter filter filteringCmd
120
+        # Do not use quotes around the $out variable or else newline
121
+        # characters will be kept.
122
+        for filter in ${out[*]}; do
123
+            fullFilter+="$filter|"
124
+        done
125
+
126
+        filteringCmd="_filedir $fullFilter"
127
+        __%[1]s_debug "File filtering command: $filteringCmd"
128
+        $filteringCmd
129
+    elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
130
+        # File completion for directories only
131
+        local subDir
132
+        # Use printf to strip any trailing newline
133
+        subdir=$(printf "%%s" "${out[0]}")
134
+        if [ -n "$subdir" ]; then
135
+            __%[1]s_debug "Listing directories in $subdir"
136
+            __%[1]s_handle_subdirs_in_dir_flag "$subdir"
137
+        else
138
+            __%[1]s_debug "Listing directories in ."
139
+            _filedir -d
140
+        fi
141
+    else
116 142
         while IFS='' read -r comp; do
117 143
             COMPREPLY+=("$comp")
118 144
         done < <(compgen -W "${out[*]}" -- "$cur")
... ...
@@ -181,10 +213,9 @@ __%[1]s_handle_reply()
181 181
     local completions
182 182
     completions=("${commands[@]}")
183 183
     if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then
184
-        completions=("${must_have_one_noun[@]}")
184
+        completions+=("${must_have_one_noun[@]}")
185 185
     elif [[ -n "${has_completion_function}" ]]; then
186 186
         # if a go completion function is provided, defer to that function
187
-        completions=()
188 187
         __%[1]s_handle_go_custom_completion
189 188
     fi
190 189
     if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then
... ...
@@ -344,7 +375,9 @@ __%[1]s_handle_word()
344 344
     __%[1]s_handle_word
345 345
 }
346 346
 
347
-`, name, ShellCompNoDescRequestCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp))
347
+`, name, ShellCompNoDescRequestCmd,
348
+		ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
349
+		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
348 350
 }
349 351
 
350 352
 func writePostscript(buf *bytes.Buffer, name string) {
... ...
@@ -390,7 +423,7 @@ fi
390 390
 func writeCommands(buf *bytes.Buffer, cmd *Command) {
391 391
 	buf.WriteString("    commands=()\n")
392 392
 	for _, c := range cmd.Commands() {
393
-		if !c.IsAvailableCommand() || c == cmd.helpCommand {
393
+		if !c.IsAvailableCommand() && c != cmd.helpCommand {
394 394
 			continue
395 395
 		}
396 396
 		buf.WriteString(fmt.Sprintf("    commands+=(%q)\n", c.Name()))
... ...
@@ -462,12 +495,14 @@ func writeFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) {
462 462
 
463 463
 func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) {
464 464
 	name := flag.Name
465
-	format := "    local_nonpersistent_flags+=(\"--%s"
465
+	format := "    local_nonpersistent_flags+=(\"--%[1]s\")\n"
466 466
 	if len(flag.NoOptDefVal) == 0 {
467
-		format += "="
467
+		format += "    local_nonpersistent_flags+=(\"--%[1]s=\")\n"
468 468
 	}
469
-	format += "\")\n"
470 469
 	buf.WriteString(fmt.Sprintf(format, name))
470
+	if len(flag.Shorthand) > 0 {
471
+		buf.WriteString(fmt.Sprintf("    local_nonpersistent_flags+=(\"-%s\")\n", flag.Shorthand))
472
+	}
471 473
 }
472 474
 
473 475
 // Setup annotations for go completions for registered flags
... ...
@@ -502,7 +537,9 @@ func writeFlags(buf *bytes.Buffer, cmd *Command) {
502 502
 		if len(flag.Shorthand) > 0 {
503 503
 			writeShortFlag(buf, flag, cmd)
504 504
 		}
505
-		if localNonPersistentFlags.Lookup(flag.Name) != nil {
505
+		// localNonPersistentFlags are used to stop the completion of subcommands when one is set
506
+		// if TraverseChildren is true we should allow to complete subcommands
507
+		if localNonPersistentFlags.Lookup(flag.Name) != nil && !cmd.Root().TraverseChildren {
506 508
 			writeLocalNonPersistentFlag(buf, flag)
507 509
 		}
508 510
 	})
... ...
@@ -583,7 +620,7 @@ func writeArgAliases(buf *bytes.Buffer, cmd *Command) {
583 583
 
584 584
 func gen(buf *bytes.Buffer, cmd *Command) {
585 585
 	for _, c := range cmd.Commands() {
586
-		if !c.IsAvailableCommand() || c == cmd.helpCommand {
586
+		if !c.IsAvailableCommand() && c != cmd.helpCommand {
587 587
 			continue
588 588
 		}
589 589
 		gen(buf, c)
... ...
@@ -37,6 +37,14 @@ type FParseErrWhitelist flag.ParseErrorsWhitelist
37 37
 // definition to ensure usability.
38 38
 type Command struct {
39 39
 	// Use is the one-line usage message.
40
+	// Recommended syntax is as follow:
41
+	//   [ ] identifies an optional argument. Arguments that are not enclosed in brackets are required.
42
+	//   ... indicates that you can specify multiple values for the previous argument.
43
+	//   |   indicates mutually exclusive information. You can use the argument to the left of the separator or the
44
+	//       argument to the right of the separator. You cannot use both arguments in a single use of the command.
45
+	//   { } delimits a set of mutually exclusive arguments when one of the arguments is required. If the arguments are
46
+	//       optional, they are enclosed in brackets ([ ]).
47
+	// Example: add [-F file | -D dir]... [-f format] profile
40 48
 	Use string
41 49
 
42 50
 	// 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) {
359 359
 		c.mergePersistentFlags()
360 360
 		err := tmpl(c.OutOrStderr(), c.UsageTemplate(), c)
361 361
 		if err != nil {
362
-			c.Println(err)
362
+			c.PrintErrln(err)
363 363
 		}
364 364
 		return err
365 365
 	}
... ...
@@ -387,7 +395,7 @@ func (c *Command) HelpFunc() func(*Command, []string) {
387 387
 		// See https://github.com/spf13/cobra/issues/1002
388 388
 		err := tmpl(c.OutOrStdout(), c.HelpTemplate(), c)
389 389
 		if err != nil {
390
-			c.Println(err)
390
+			c.PrintErrln(err)
391 391
 		}
392 392
 	}
393 393
 }
... ...
@@ -930,8 +938,8 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
930 930
 			c = cmd
931 931
 		}
932 932
 		if !c.SilenceErrors {
933
-			c.Println("Error:", err.Error())
934
-			c.Printf("Run '%v --help' for usage.\n", c.CommandPath())
933
+			c.PrintErrln("Error:", err.Error())
934
+			c.PrintErrf("Run '%v --help' for usage.\n", c.CommandPath())
935 935
 		}
936 936
 		return c, err
937 937
 	}
... ...
@@ -959,7 +967,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
959 959
 		// If root command has SilentErrors flagged,
960 960
 		// all subcommands should respect it
961 961
 		if !cmd.SilenceErrors && !c.SilenceErrors {
962
-			c.Println("Error:", err.Error())
962
+			c.PrintErrln("Error:", err.Error())
963 963
 		}
964 964
 
965 965
 		// If root command has SilentUsage flagged,
... ...
@@ -979,6 +987,10 @@ func (c *Command) ValidateArgs(args []string) error {
979 979
 }
980 980
 
981 981
 func (c *Command) validateRequiredFlags() error {
982
+	if c.DisableFlagParsing {
983
+		return nil
984
+	}
985
+
982 986
 	flags := c.Flags()
983 987
 	missingFlagNames := []string{}
984 988
 	flags.VisitAll(func(pflag *flag.Flag) {
... ...
@@ -1052,7 +1064,25 @@ func (c *Command) InitDefaultHelpCmd() {
1052 1052
 			Short: "Help about any command",
1053 1053
 			Long: `Help provides help for any command in the application.
1054 1054
 Simply type ` + c.Name() + ` help [path to command] for full details.`,
1055
-
1055
+			ValidArgsFunction: func(c *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
1056
+				var completions []string
1057
+				cmd, _, e := c.Root().Find(args)
1058
+				if e != nil {
1059
+					return nil, ShellCompDirectiveNoFileComp
1060
+				}
1061
+				if cmd == nil {
1062
+					// Root help command.
1063
+					cmd = c.Root()
1064
+				}
1065
+				for _, subCmd := range cmd.Commands() {
1066
+					if subCmd.IsAvailableCommand() || subCmd == cmd.helpCommand {
1067
+						if strings.HasPrefix(subCmd.Name(), toComplete) {
1068
+							completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
1069
+						}
1070
+					}
1071
+				}
1072
+				return completions, ShellCompDirectiveNoFileComp
1073
+			},
1056 1074
 			Run: func(c *Command, args []string) {
1057 1075
 				cmd, _, e := c.Root().Find(args)
1058 1076
 				if cmd == nil || e != nil {
... ...
@@ -1179,12 +1209,12 @@ func (c *Command) PrintErr(i ...interface{}) {
1179 1179
 
1180 1180
 // PrintErrln is a convenience method to Println to the defined Err output, fallback to Stderr if not set.
1181 1181
 func (c *Command) PrintErrln(i ...interface{}) {
1182
-	c.Print(fmt.Sprintln(i...))
1182
+	c.PrintErr(fmt.Sprintln(i...))
1183 1183
 }
1184 1184
 
1185 1185
 // PrintErrf is a convenience method to Printf to the defined Err output, fallback to Stderr if not set.
1186 1186
 func (c *Command) PrintErrf(format string, i ...interface{}) {
1187
-	c.Print(fmt.Sprintf(format, i...))
1187
+	c.PrintErr(fmt.Sprintf(format, i...))
1188 1188
 }
1189 1189
 
1190 1190
 // CommandPath returns the full path to this command.
... ...
@@ -1,7 +1,6 @@
1 1
 package cobra
2 2
 
3 3
 import (
4
-	"errors"
5 4
 	"fmt"
6 5
 	"os"
7 6
 	"strings"
... ...
@@ -38,8 +37,29 @@ const (
38 38
 	// This currently does not work for zsh or bash < 4
39 39
 	ShellCompDirectiveNoFileComp
40 40
 
41
+	// ShellCompDirectiveFilterFileExt indicates that the provided completions
42
+	// should be used as file extension filters.
43
+	// For flags, using Command.MarkFlagFilename() and Command.MarkPersistentFlagFilename()
44
+	// is a shortcut to using this directive explicitly.  The BashCompFilenameExt
45
+	// annotation can also be used to obtain the same behavior for flags.
46
+	ShellCompDirectiveFilterFileExt
47
+
48
+	// ShellCompDirectiveFilterDirs indicates that only directory names should
49
+	// be provided in file completion.  To request directory names within another
50
+	// directory, the returned completions should specify the directory within
51
+	// which to search.  The BashCompSubdirsInDir annotation can be used to
52
+	// obtain the same behavior but only for flags.
53
+	ShellCompDirectiveFilterDirs
54
+
55
+	// ===========================================================================
56
+
57
+	// All directives using iota should be above this one.
58
+	// For internal use.
59
+	shellCompDirectiveMaxValue
60
+
41 61
 	// ShellCompDirectiveDefault indicates to let the shell perform its default
42 62
 	// behavior after completions have been provided.
63
+	// This one must be last to avoid messing up the iota count.
43 64
 	ShellCompDirectiveDefault ShellCompDirective = 0
44 65
 )
45 66
 
... ...
@@ -68,11 +88,17 @@ func (d ShellCompDirective) string() string {
68 68
 	if d&ShellCompDirectiveNoFileComp != 0 {
69 69
 		directives = append(directives, "ShellCompDirectiveNoFileComp")
70 70
 	}
71
+	if d&ShellCompDirectiveFilterFileExt != 0 {
72
+		directives = append(directives, "ShellCompDirectiveFilterFileExt")
73
+	}
74
+	if d&ShellCompDirectiveFilterDirs != 0 {
75
+		directives = append(directives, "ShellCompDirectiveFilterDirs")
76
+	}
71 77
 	if len(directives) == 0 {
72 78
 		directives = append(directives, "ShellCompDirectiveDefault")
73 79
 	}
74 80
 
75
-	if d > ShellCompDirectiveError+ShellCompDirectiveNoSpace+ShellCompDirectiveNoFileComp {
81
+	if d >= shellCompDirectiveMaxValue {
76 82
 		return fmt.Sprintf("ERROR: unexpected ShellCompDirective value: %d", d)
77 83
 	}
78 84
 	return strings.Join(directives, ", ")
... ...
@@ -105,11 +131,25 @@ func (c *Command) initCompleteCmd(args []string) {
105 105
 					// Remove any description that may be included following a tab character.
106 106
 					comp = strings.Split(comp, "\t")[0]
107 107
 				}
108
+
109
+				// Make sure we only write the first line to the output.
110
+				// This is needed if a description contains a linebreak.
111
+				// Otherwise the shell scripts will interpret the other lines as new flags
112
+				// and could therefore provide a wrong completion.
113
+				comp = strings.Split(comp, "\n")[0]
114
+
115
+				// Finally trim the completion.  This is especially important to get rid
116
+				// of a trailing tab when there are no description following it.
117
+				// For example, a sub-command without a description should not be completed
118
+				// with a tab at the end (or else zsh will show a -- following it
119
+				// although there is no description).
120
+				comp = strings.TrimSpace(comp)
121
+
108 122
 				// Print each possible completion to stdout for the completion script to consume.
109 123
 				fmt.Fprintln(finalCmd.OutOrStdout(), comp)
110 124
 			}
111 125
 
112
-			if directive > ShellCompDirectiveError+ShellCompDirectiveNoSpace+ShellCompDirectiveNoFileComp {
126
+			if directive >= shellCompDirectiveMaxValue {
113 127
 				directive = ShellCompDirectiveDefault
114 128
 			}
115 129
 
... ...
@@ -136,90 +176,179 @@ func (c *Command) initCompleteCmd(args []string) {
136 136
 }
137 137
 
138 138
 func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDirective, error) {
139
-	var completions []string
140
-
141 139
 	// The last argument, which is not completely typed by the user,
142 140
 	// should not be part of the list of arguments
143 141
 	toComplete := args[len(args)-1]
144 142
 	trimmedArgs := args[:len(args)-1]
145 143
 
144
+	var finalCmd *Command
145
+	var finalArgs []string
146
+	var err error
146 147
 	// Find the real command for which completion must be performed
147
-	finalCmd, finalArgs, err := c.Root().Find(trimmedArgs)
148
+	// check if we need to traverse here to parse local flags on parent commands
149
+	if c.Root().TraverseChildren {
150
+		finalCmd, finalArgs, err = c.Root().Traverse(trimmedArgs)
151
+	} else {
152
+		finalCmd, finalArgs, err = c.Root().Find(trimmedArgs)
153
+	}
148 154
 	if err != nil {
149 155
 		// Unable to find the real command. E.g., <program> someInvalidCmd <TAB>
150
-		return c, completions, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs)
156
+		return c, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs)
157
+	}
158
+
159
+	// Check if we are doing flag value completion before parsing the flags.
160
+	// This is important because if we are completing a flag value, we need to also
161
+	// remove the flag name argument from the list of finalArgs or else the parsing
162
+	// could fail due to an invalid value (incomplete) for the flag.
163
+	flag, finalArgs, toComplete, err := checkIfFlagCompletion(finalCmd, finalArgs, toComplete)
164
+	if err != nil {
165
+		// Error while attempting to parse flags
166
+		return finalCmd, []string{}, ShellCompDirectiveDefault, err
167
+	}
168
+
169
+	// Parse the flags early so we can check if required flags are set
170
+	if err = finalCmd.ParseFlags(finalArgs); err != nil {
171
+		return finalCmd, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error())
172
+	}
173
+
174
+	if flag != nil {
175
+		// Check if we are completing a flag value subject to annotations
176
+		if validExts, present := flag.Annotations[BashCompFilenameExt]; present {
177
+			if len(validExts) != 0 {
178
+				// File completion filtered by extensions
179
+				return finalCmd, validExts, ShellCompDirectiveFilterFileExt, nil
180
+			}
181
+
182
+			// The annotation requests simple file completion.  There is no reason to do
183
+			// that since it is the default behavior anyway.  Let's ignore this annotation
184
+			// in case the program also registered a completion function for this flag.
185
+			// Even though it is a mistake on the program's side, let's be nice when we can.
186
+		}
187
+
188
+		if subDir, present := flag.Annotations[BashCompSubdirsInDir]; present {
189
+			if len(subDir) == 1 {
190
+				// Directory completion from within a directory
191
+				return finalCmd, subDir, ShellCompDirectiveFilterDirs, nil
192
+			}
193
+			// Directory completion
194
+			return finalCmd, []string{}, ShellCompDirectiveFilterDirs, nil
195
+		}
151 196
 	}
152 197
 
153 198
 	// When doing completion of a flag name, as soon as an argument starts with
154 199
 	// a '-' we know it is a flag.  We cannot use isFlagArg() here as it requires
155
-	// the flag to be complete
156
-	if len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") {
157
-		// We are completing a flag name
158
-		finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
159
-			completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
160
-		})
161
-		finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
162
-			completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
163
-		})
164
-
165
-		directive := ShellCompDirectiveDefault
166
-		if len(completions) > 0 {
167
-			if strings.HasSuffix(completions[0], "=") {
168
-				directive = ShellCompDirectiveNoSpace
200
+	// the flag name to be complete
201
+	if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") {
202
+		var completions []string
203
+
204
+		// First check for required flags
205
+		completions = completeRequireFlags(finalCmd, toComplete)
206
+
207
+		// If we have not found any required flags, only then can we show regular flags
208
+		if len(completions) == 0 {
209
+			doCompleteFlags := func(flag *pflag.Flag) {
210
+				if !flag.Changed ||
211
+					strings.Contains(flag.Value.Type(), "Slice") ||
212
+					strings.Contains(flag.Value.Type(), "Array") {
213
+					// If the flag is not already present, or if it can be specified multiple times (Array or Slice)
214
+					// we suggest it as a completion
215
+					completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
216
+				}
169 217
 			}
218
+
219
+			// We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands
220
+			// that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and
221
+			// non-inherited flags.
222
+			finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
223
+				doCompleteFlags(flag)
224
+			})
225
+			finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
226
+				doCompleteFlags(flag)
227
+			})
228
+		}
229
+
230
+		directive := ShellCompDirectiveNoFileComp
231
+		if len(completions) == 1 && strings.HasSuffix(completions[0], "=") {
232
+			// If there is a single completion, the shell usually adds a space
233
+			// after the completion.  We don't want that if the flag ends with an =
234
+			directive = ShellCompDirectiveNoSpace
170 235
 		}
171 236
 		return finalCmd, completions, directive, nil
172 237
 	}
173 238
 
174
-	var flag *pflag.Flag
239
+	// We only remove the flags from the arguments if DisableFlagParsing is not set.
240
+	// This is important for commands which have requested to do their own flag completion.
175 241
 	if !finalCmd.DisableFlagParsing {
176
-		// We only do flag completion if we are allowed to parse flags
177
-		// This is important for commands which have requested to do their own flag completion.
178
-		flag, finalArgs, toComplete, err = checkIfFlagCompletion(finalCmd, finalArgs, toComplete)
179
-		if err != nil {
180
-			// Error while attempting to parse flags
181
-			return finalCmd, completions, ShellCompDirectiveDefault, err
182
-		}
242
+		finalArgs = finalCmd.Flags().Args()
183 243
 	}
184 244
 
245
+	var completions []string
246
+	directive := ShellCompDirectiveDefault
185 247
 	if flag == nil {
186
-		// Complete subcommand names
187
-		for _, subCmd := range finalCmd.Commands() {
188
-			if subCmd.IsAvailableCommand() && strings.HasPrefix(subCmd.Name(), toComplete) {
189
-				completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
248
+		foundLocalNonPersistentFlag := false
249
+		// If TraverseChildren is true on the root command we don't check for
250
+		// local flags because we can use a local flag on a parent command
251
+		if !finalCmd.Root().TraverseChildren {
252
+			// Check if there are any local, non-persistent flags on the command-line
253
+			localNonPersistentFlags := finalCmd.LocalNonPersistentFlags()
254
+			finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
255
+				if localNonPersistentFlags.Lookup(flag.Name) != nil && flag.Changed {
256
+					foundLocalNonPersistentFlag = true
257
+				}
258
+			})
259
+		}
260
+
261
+		// Complete subcommand names, including the help command
262
+		if len(finalArgs) == 0 && !foundLocalNonPersistentFlag {
263
+			// We only complete sub-commands if:
264
+			// - there are no arguments on the command-line and
265
+			// - there are no local, non-peristent flag on the command-line or TraverseChildren is true
266
+			for _, subCmd := range finalCmd.Commands() {
267
+				if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand {
268
+					if strings.HasPrefix(subCmd.Name(), toComplete) {
269
+						completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
270
+					}
271
+					directive = ShellCompDirectiveNoFileComp
272
+				}
190 273
 			}
191 274
 		}
192 275
 
276
+		// Complete required flags even without the '-' prefix
277
+		completions = append(completions, completeRequireFlags(finalCmd, toComplete)...)
278
+
279
+		// Always complete ValidArgs, even if we are completing a subcommand name.
280
+		// This is for commands that have both subcommands and ValidArgs.
193 281
 		if len(finalCmd.ValidArgs) > 0 {
194
-			// Always complete ValidArgs, even if we are completing a subcommand name.
195
-			// This is for commands that have both subcommands and ValidArgs.
196
-			for _, validArg := range finalCmd.ValidArgs {
197
-				if strings.HasPrefix(validArg, toComplete) {
198
-					completions = append(completions, validArg)
282
+			if len(finalArgs) == 0 {
283
+				// ValidArgs are only for the first argument
284
+				for _, validArg := range finalCmd.ValidArgs {
285
+					if strings.HasPrefix(validArg, toComplete) {
286
+						completions = append(completions, validArg)
287
+					}
288
+				}
289
+				directive = ShellCompDirectiveNoFileComp
290
+
291
+				// If no completions were found within commands or ValidArgs,
292
+				// see if there are any ArgAliases that should be completed.
293
+				if len(completions) == 0 {
294
+					for _, argAlias := range finalCmd.ArgAliases {
295
+						if strings.HasPrefix(argAlias, toComplete) {
296
+							completions = append(completions, argAlias)
297
+						}
298
+					}
199 299
 				}
200 300
 			}
201 301
 
202 302
 			// If there are ValidArgs specified (even if they don't match), we stop completion.
203 303
 			// Only one of ValidArgs or ValidArgsFunction can be used for a single command.
204
-			return finalCmd, completions, ShellCompDirectiveNoFileComp, nil
304
+			return finalCmd, completions, directive, nil
205 305
 		}
206 306
 
207
-		// Always let the logic continue so as to add any ValidArgsFunction completions,
307
+		// Let the logic continue so as to add any ValidArgsFunction completions,
208 308
 		// even if we already found sub-commands.
209 309
 		// This is for commands that have subcommands but also specify a ValidArgsFunction.
210 310
 	}
211 311
 
212
-	// Parse the flags and extract the arguments to prepare for calling the completion function
213
-	if err = finalCmd.ParseFlags(finalArgs); err != nil {
214
-		return finalCmd, completions, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error())
215
-	}
216
-
217
-	// We only remove the flags from the arguments if DisableFlagParsing is not set.
218
-	// This is important for commands which have requested to do their own flag completion.
219
-	if !finalCmd.DisableFlagParsing {
220
-		finalArgs = finalCmd.Flags().Args()
221
-	}
222
-
223 312
 	// Find the completion function for the flag or command
224 313
 	var completionFn func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
225 314
 	if flag != nil {
... ...
@@ -227,14 +356,14 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
227 227
 	} else {
228 228
 		completionFn = finalCmd.ValidArgsFunction
229 229
 	}
230
-	if completionFn == nil {
231
-		// Go custom completion not supported/needed for this flag or command
232
-		return finalCmd, completions, ShellCompDirectiveDefault, nil
230
+	if completionFn != nil {
231
+		// Go custom completion defined for this flag or command.
232
+		// Call the registered completion function to get the completions.
233
+		var comps []string
234
+		comps, directive = completionFn(finalCmd, finalArgs, toComplete)
235
+		completions = append(completions, comps...)
233 236
 	}
234 237
 
235
-	// Call the registered completion function to get the completions
236
-	comps, directive := completionFn(finalCmd, finalArgs, toComplete)
237
-	completions = append(completions, comps...)
238 238
 	return finalCmd, completions, directive, nil
239 239
 }
240 240
 
... ...
@@ -249,11 +378,18 @@ func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
249 249
 		// Flag without the =
250 250
 		completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
251 251
 
252
-		if len(flag.NoOptDefVal) == 0 {
253
-			// Flag requires a value, so it can be suffixed with =
254
-			flagName += "="
255
-			completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
256
-		}
252
+		// Why suggest both long forms: --flag and --flag= ?
253
+		// This forces the user to *always* have to type either an = or a space after the flag name.
254
+		// Let's be nice and avoid making users have to do that.
255
+		// Since boolean flags and shortname flags don't show the = form, let's go that route and never show it.
256
+		// The = form will still work, we just won't suggest it.
257
+		// This also makes the list of suggested flags shorter as we avoid all the = forms.
258
+		//
259
+		// if len(flag.NoOptDefVal) == 0 {
260
+		// 	// Flag requires a value, so it can be suffixed with =
261
+		// 	flagName += "="
262
+		// 	completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
263
+		// }
257 264
 	}
258 265
 
259 266
 	flagName = "-" + flag.Shorthand
... ...
@@ -264,17 +400,54 @@ func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
264 264
 	return completions
265 265
 }
266 266
 
267
+func completeRequireFlags(finalCmd *Command, toComplete string) []string {
268
+	var completions []string
269
+
270
+	doCompleteRequiredFlags := func(flag *pflag.Flag) {
271
+		if _, present := flag.Annotations[BashCompOneRequiredFlag]; present {
272
+			if !flag.Changed {
273
+				// If the flag is not already present, we suggest it as a completion
274
+				completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
275
+			}
276
+		}
277
+	}
278
+
279
+	// We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands
280
+	// that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and
281
+	// non-inherited flags.
282
+	finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
283
+		doCompleteRequiredFlags(flag)
284
+	})
285
+	finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
286
+		doCompleteRequiredFlags(flag)
287
+	})
288
+
289
+	return completions
290
+}
291
+
267 292
 func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) {
293
+	if finalCmd.DisableFlagParsing {
294
+		// We only do flag completion if we are allowed to parse flags
295
+		// This is important for commands which have requested to do their own flag completion.
296
+		return nil, args, lastArg, nil
297
+	}
298
+
268 299
 	var flagName string
269 300
 	trimmedArgs := args
270 301
 	flagWithEqual := false
271
-	if isFlagArg(lastArg) {
302
+
303
+	// When doing completion of a flag name, as soon as an argument starts with
304
+	// a '-' we know it is a flag.  We cannot use isFlagArg() here as that function
305
+	// requires the flag name to be complete
306
+	if len(lastArg) > 0 && lastArg[0] == '-' {
272 307
 		if index := strings.Index(lastArg, "="); index >= 0 {
308
+			// Flag with an =
273 309
 			flagName = strings.TrimLeft(lastArg[:index], "-")
274 310
 			lastArg = lastArg[index+1:]
275 311
 			flagWithEqual = true
276 312
 		} else {
277
-			return nil, nil, "", errors.New("Unexpected completion request for flag")
313
+			// Normal flag completion
314
+			return nil, args, lastArg, nil
278 315
 		}
279 316
 	}
280 317
 
... ...
@@ -5,9 +5,15 @@ import (
5 5
 	"fmt"
6 6
 	"io"
7 7
 	"os"
8
+	"strings"
8 9
 )
9 10
 
10 11
 func genFishComp(buf *bytes.Buffer, name string, includeDesc bool) {
12
+	// Variables should not contain a '-' or ':' character
13
+	nameForVar := name
14
+	nameForVar = strings.Replace(nameForVar, "-", "_", -1)
15
+	nameForVar = strings.Replace(nameForVar, ":", "_", -1)
16
+
11 17
 	compCmd := ShellCompRequestCmd
12 18
 	if !includeDesc {
13 19
 		compCmd = ShellCompNoDescRequestCmd
... ...
@@ -37,7 +43,13 @@ function __%[1]s_perform_completion
37 37
     end
38 38
     __%[1]s_debug "emptyArg: $emptyArg"
39 39
 
40
-    set requestComp "$args[1] %[2]s $args[2..-1] $emptyArg"
40
+    if not type -q "$args[1]"
41
+        # This can happen when "complete --do-complete %[2]s" is called when running this script.
42
+        __%[1]s_debug "Cannot find $args[1]. No completions."
43
+        return
44
+    end
45
+
46
+    set requestComp "$args[1] %[3]s $args[2..-1] $emptyArg"
41 47
     __%[1]s_debug "Calling $requestComp"
42 48
 
43 49
     set results (eval $requestComp 2> /dev/null)
... ...
@@ -71,7 +83,8 @@ function __%[1]s_prepare_completions
71 71
 
72 72
     # Check if the command-line is already provided.  This is useful for testing.
73 73
     if not set --query __%[1]s_comp_commandLine
74
-        set __%[1]s_comp_commandLine (commandline)
74
+        # Use the -c flag to allow for completion in the middle of the line
75
+        set __%[1]s_comp_commandLine (commandline -c)
75 76
     end
76 77
     __%[1]s_debug "commandLine is: $__%[1]s_comp_commandLine"
77 78
 
... ...
@@ -83,7 +96,7 @@ function __%[1]s_prepare_completions
83 83
         __%[1]s_debug "No completion, probably due to a failure"
84 84
         # Might as well do file completion, in case it helps
85 85
         set --global __%[1]s_comp_do_file_comp 1
86
-        return 0
86
+        return 1
87 87
     end
88 88
 
89 89
     set directive (string sub --start 2 $results[-1])
... ...
@@ -92,20 +105,35 @@ function __%[1]s_prepare_completions
92 92
     __%[1]s_debug "Completions are: $__%[1]s_comp_results"
93 93
     __%[1]s_debug "Directive is: $directive"
94 94
 
95
+    set shellCompDirectiveError %[4]d
96
+    set shellCompDirectiveNoSpace %[5]d
97
+    set shellCompDirectiveNoFileComp %[6]d
98
+    set shellCompDirectiveFilterFileExt %[7]d
99
+    set shellCompDirectiveFilterDirs %[8]d
100
+
95 101
     if test -z "$directive"
96 102
         set directive 0
97 103
     end
98 104
 
99
-    set compErr (math (math --scale 0 $directive / %[3]d) %% 2)
105
+    set compErr (math (math --scale 0 $directive / $shellCompDirectiveError) %% 2)
100 106
     if test $compErr -eq 1
101 107
         __%[1]s_debug "Received error directive: aborting."
102 108
         # Might as well do file completion, in case it helps
103 109
         set --global __%[1]s_comp_do_file_comp 1
104
-        return 0
110
+        return 1
105 111
     end
106 112
 
107
-    set nospace (math (math --scale 0 $directive / %[4]d) %% 2)
108
-    set nofiles (math (math --scale 0 $directive / %[5]d) %% 2)
113
+    set filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) %% 2)
114
+    set dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) %% 2)
115
+    if test $filefilter -eq 1; or test $dirfilter -eq 1
116
+        __%[1]s_debug "File extension filtering or directory filtering not supported"
117
+        # Do full file completion instead
118
+        set --global __%[1]s_comp_do_file_comp 1
119
+        return 1
120
+    end
121
+
122
+    set nospace (math (math --scale 0 $directive / $shellCompDirectiveNoSpace) %% 2)
123
+    set nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) %% 2)
109 124
 
110 125
     __%[1]s_debug "nospace: $nospace, nofiles: $nofiles"
111 126
 
... ...
@@ -132,24 +160,31 @@ function __%[1]s_prepare_completions
132 132
     return (not set --query __%[1]s_comp_do_file_comp)
133 133
 end
134 134
 
135
-# Remove any pre-existing completions for the program since we will be handling all of them
136
-# TODO this cleanup is not sufficient.  Fish completions are only loaded once the user triggers
137
-# them, so the below deletion will not work as it is run too early.  What else can we do?
138
-complete -c %[1]s -e
135
+# Since Fish completions are only loaded once the user triggers them, we trigger them ourselves
136
+# so we can properly delete any completions provided by another script.
137
+# The space after the the program name is essential to trigger completion for the program
138
+# and not completion of the program name itself.
139
+complete --do-complete "%[2]s " > /dev/null 2>&1
140
+# Using '> /dev/null 2>&1' since '&>' is not supported in older versions of fish.
141
+
142
+# Remove any pre-existing completions for the program since we will be handling all of them.
143
+complete -c %[2]s -e
139 144
 
140 145
 # The order in which the below two lines are defined is very important so that __%[1]s_prepare_completions
141 146
 # is called first.  It is __%[1]s_prepare_completions that sets up the __%[1]s_comp_do_file_comp variable.
142 147
 #
143 148
 # This completion will be run second as complete commands are added FILO.
144 149
 # It triggers file completion choices when __%[1]s_comp_do_file_comp is set.
145
-complete -c %[1]s -n 'set --query __%[1]s_comp_do_file_comp'
150
+complete -c %[2]s -n 'set --query __%[1]s_comp_do_file_comp'
146 151
 
147 152
 # This completion will be run first as complete commands are added FILO.
148
-# The call to __%[1]s_prepare_completions will setup both __%[1]s_comp_results abd __%[1]s_comp_do_file_comp.
153
+# The call to __%[1]s_prepare_completions will setup both __%[1]s_comp_results and __%[1]s_comp_do_file_comp.
149 154
 # It provides the program's completion choices.
150
-complete -c %[1]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results'
155
+complete -c %[2]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results'
151 156
 
152
-`, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp))
157
+`, nameForVar, name, compCmd,
158
+		ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
159
+		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
153 160
 }
154 161
 
155 162
 // GenFishCompletion generates fish completion file and writes to the passed writer.
... ...
@@ -6,7 +6,7 @@ require (
6 6
 	github.com/cpuguy83/go-md2man/v2 v2.0.0
7 7
 	github.com/inconshreveable/mousetrap v1.0.0
8 8
 	github.com/mitchellh/go-homedir v1.1.0
9
-	github.com/spf13/pflag v1.0.3
10
-	github.com/spf13/viper v1.4.0
11
-	gopkg.in/yaml.v2 v2.2.2
9
+	github.com/spf13/pflag v1.0.5
10
+	github.com/spf13/viper v1.7.0
11
+	gopkg.in/yaml.v2 v2.2.8
12 12
 )
... ...
@@ -4,82 +4,81 @@ import (
4 4
 	"github.com/spf13/pflag"
5 5
 )
6 6
 
7
-// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists,
7
+// MarkFlagRequired instructs the various shell completion implementations to
8
+// prioritize the named flag when performing completion,
8 9
 // and causes your command to report an error if invoked without the flag.
9 10
 func (c *Command) MarkFlagRequired(name string) error {
10 11
 	return MarkFlagRequired(c.Flags(), name)
11 12
 }
12 13
 
13
-// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag if it exists,
14
+// MarkPersistentFlagRequired instructs the various shell completion implementations to
15
+// prioritize the named persistent flag when performing completion,
14 16
 // and causes your command to report an error if invoked without the flag.
15 17
 func (c *Command) MarkPersistentFlagRequired(name string) error {
16 18
 	return MarkFlagRequired(c.PersistentFlags(), name)
17 19
 }
18 20
 
19
-// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists,
21
+// MarkFlagRequired instructs the various shell completion implementations to
22
+// prioritize the named flag when performing completion,
20 23
 // and causes your command to report an error if invoked without the flag.
21 24
 func MarkFlagRequired(flags *pflag.FlagSet, name string) error {
22 25
 	return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"})
23 26
 }
24 27
 
25
-// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists.
26
-// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
28
+// MarkFlagFilename instructs the various shell completion implementations to
29
+// limit completions for the named flag to the specified file extensions.
27 30
 func (c *Command) MarkFlagFilename(name string, extensions ...string) error {
28 31
 	return MarkFlagFilename(c.Flags(), name, extensions...)
29 32
 }
30 33
 
31 34
 // MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists.
32
-// Generated bash autocompletion will call the bash function f for the flag.
35
+// The bash completion script will call the bash function f for the flag.
36
+//
37
+// This will only work for bash completion.
38
+// It is recommended to instead use c.RegisterFlagCompletionFunc(...) which allows
39
+// to register a Go function which will work across all shells.
33 40
 func (c *Command) MarkFlagCustom(name string, f string) error {
34 41
 	return MarkFlagCustom(c.Flags(), name, f)
35 42
 }
36 43
 
37 44
 // MarkPersistentFlagFilename instructs the various shell completion
38
-// implementations to limit completions for this persistent flag to the
39
-// specified extensions (patterns).
40
-//
41
-// Shell Completion compatibility matrix: bash, zsh
45
+// implementations to limit completions for the named persistent flag to the
46
+// specified file extensions.
42 47
 func (c *Command) MarkPersistentFlagFilename(name string, extensions ...string) error {
43 48
 	return MarkFlagFilename(c.PersistentFlags(), name, extensions...)
44 49
 }
45 50
 
46 51
 // MarkFlagFilename instructs the various shell completion implementations to
47
-// limit completions for this flag to the specified extensions (patterns).
48
-//
49
-// Shell Completion compatibility matrix: bash, zsh
52
+// limit completions for the named flag to the specified file extensions.
50 53
 func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error {
51 54
 	return flags.SetAnnotation(name, BashCompFilenameExt, extensions)
52 55
 }
53 56
 
54
-// MarkFlagCustom instructs the various shell completion implementations to
55
-// limit completions for this flag to the specified extensions (patterns).
57
+// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists.
58
+// The bash completion script will call the bash function f for the flag.
56 59
 //
57
-// Shell Completion compatibility matrix: bash, zsh
60
+// This will only work for bash completion.
61
+// It is recommended to instead use c.RegisterFlagCompletionFunc(...) which allows
62
+// to register a Go function which will work across all shells.
58 63
 func MarkFlagCustom(flags *pflag.FlagSet, name string, f string) error {
59 64
 	return flags.SetAnnotation(name, BashCompCustom, []string{f})
60 65
 }
61 66
 
62 67
 // MarkFlagDirname instructs the various shell completion implementations to
63
-// complete only directories with this named flag.
64
-//
65
-// Shell Completion compatibility matrix: zsh
68
+// limit completions for the named flag to directory names.
66 69
 func (c *Command) MarkFlagDirname(name string) error {
67 70
 	return MarkFlagDirname(c.Flags(), name)
68 71
 }
69 72
 
70 73
 // MarkPersistentFlagDirname instructs the various shell completion
71
-// implementations to complete only directories with this persistent named flag.
72
-//
73
-// Shell Completion compatibility matrix: zsh
74
+// implementations to limit completions for the named persistent flag to
75
+// directory names.
74 76
 func (c *Command) MarkPersistentFlagDirname(name string) error {
75 77
 	return MarkFlagDirname(c.PersistentFlags(), name)
76 78
 }
77 79
 
78 80
 // MarkFlagDirname instructs the various shell completion implementations to
79
-// complete only directories with this specified flag.
80
-//
81
-// Shell Completion compatibility matrix: zsh
81
+// limit completions for the named flag to directory names.
82 82
 func MarkFlagDirname(flags *pflag.FlagSet, name string) error {
83
-	zshPattern := "-(/)"
84
-	return flags.SetAnnotation(name, zshCompDirname, []string{zshPattern})
83
+	return flags.SetAnnotation(name, BashCompSubdirsInDir, []string{})
85 84
 }
... ...
@@ -1,336 +1,240 @@
1 1
 package cobra
2 2
 
3 3
 import (
4
-	"encoding/json"
4
+	"bytes"
5 5
 	"fmt"
6 6
 	"io"
7 7
 	"os"
8
-	"sort"
9
-	"strings"
10
-	"text/template"
11
-
12
-	"github.com/spf13/pflag"
13
-)
14
-
15
-const (
16
-	zshCompArgumentAnnotation   = "cobra_annotations_zsh_completion_argument_annotation"
17
-	zshCompArgumentFilenameComp = "cobra_annotations_zsh_completion_argument_file_completion"
18
-	zshCompArgumentWordComp     = "cobra_annotations_zsh_completion_argument_word_completion"
19
-	zshCompDirname              = "cobra_annotations_zsh_dirname"
20 8
 )
21 9
 
22
-var (
23
-	zshCompFuncMap = template.FuncMap{
24
-		"genZshFuncName":              zshCompGenFuncName,
25
-		"extractFlags":                zshCompExtractFlag,
26
-		"genFlagEntryForZshArguments": zshCompGenFlagEntryForArguments,
27
-		"extractArgsCompletions":      zshCompExtractArgumentCompletionHintsForRendering,
28
-	}
29
-	zshCompletionText = `
30
-{{/* should accept Command (that contains subcommands) as parameter */}}
31
-{{define "argumentsC" -}}
32
-{{ $cmdPath := genZshFuncName .}}
33
-function {{$cmdPath}} {
34
-  local -a commands
35
-
36
-  _arguments -C \{{- range extractFlags .}}
37
-    {{genFlagEntryForZshArguments .}} \{{- end}}
38
-    "1: :->cmnds" \
39
-    "*::arg:->args"
40
-
41
-  case $state in
42
-  cmnds)
43
-    commands=({{range .Commands}}{{if not .Hidden}}
44
-      "{{.Name}}:{{.Short}}"{{end}}{{end}}
45
-    )
46
-    _describe "command" commands
47
-    ;;
48
-  esac
49
-
50
-  case "$words[1]" in {{- range .Commands}}{{if not .Hidden}}
51
-  {{.Name}})
52
-    {{$cmdPath}}_{{.Name}}
53
-    ;;{{end}}{{end}}
54
-  esac
55
-}
56
-{{range .Commands}}{{if not .Hidden}}
57
-{{template "selectCmdTemplate" .}}
58
-{{- end}}{{end}}
59
-{{- end}}
60
-
61
-{{/* should accept Command without subcommands as parameter */}}
62
-{{define "arguments" -}}
63
-function {{genZshFuncName .}} {
64
-{{"  _arguments"}}{{range extractFlags .}} \
65
-    {{genFlagEntryForZshArguments . -}}
66
-{{end}}{{range extractArgsCompletions .}} \
67
-    {{.}}{{end}}
68
-}
69
-{{end}}
70
-
71
-{{/* dispatcher for commands with or without subcommands */}}
72
-{{define "selectCmdTemplate" -}}
73
-{{if .Hidden}}{{/* ignore hidden*/}}{{else -}}
74
-{{if .Commands}}{{template "argumentsC" .}}{{else}}{{template "arguments" .}}{{end}}
75
-{{- end}}
76
-{{- end}}
77
-
78
-{{/* template entry point */}}
79
-{{define "Main" -}}
80
-#compdef _{{.Name}} {{.Name}}
81
-
82
-{{template "selectCmdTemplate" .}}
83
-{{end}}
84
-`
85
-)
86
-
87
-// zshCompArgsAnnotation is used to encode/decode zsh completion for
88
-// arguments to/from Command.Annotations.
89
-type zshCompArgsAnnotation map[int]zshCompArgHint
90
-
91
-type zshCompArgHint struct {
92
-	// Indicates the type of the completion to use. One of:
93
-	// zshCompArgumentFilenameComp or zshCompArgumentWordComp
94
-	Tipe string `json:"type"`
95
-
96
-	// A value for the type above (globs for file completion or words)
97
-	Options []string `json:"options"`
98
-}
99
-
100
-// GenZshCompletionFile generates zsh completion file.
10
+// GenZshCompletionFile generates zsh completion file including descriptions.
101 11
 func (c *Command) GenZshCompletionFile(filename string) error {
102
-	outFile, err := os.Create(filename)
103
-	if err != nil {
104
-		return err
105
-	}
106
-	defer outFile.Close()
107
-
108
-	return c.GenZshCompletion(outFile)
12
+	return c.genZshCompletionFile(filename, true)
109 13
 }
110 14
 
111
-// GenZshCompletion generates a zsh completion file and writes to the passed
112
-// writer. The completion always run on the root command regardless of the
113
-// command it was called from.
15
+// GenZshCompletion generates zsh completion file including descriptions
16
+// and writes it to the passed writer.
114 17
 func (c *Command) GenZshCompletion(w io.Writer) error {
115
-	tmpl, err := template.New("Main").Funcs(zshCompFuncMap).Parse(zshCompletionText)
116
-	if err != nil {
117
-		return fmt.Errorf("error creating zsh completion template: %v", err)
118
-	}
119
-	return tmpl.Execute(w, c.Root())
120
-}
121
-
122
-// MarkZshCompPositionalArgumentFile marks the specified argument (first
123
-// argument is 1) as completed by file selection. patterns (e.g. "*.txt") are
124
-// optional - if not provided the completion will search for all files.
125
-func (c *Command) MarkZshCompPositionalArgumentFile(argPosition int, patterns ...string) error {
126
-	if argPosition < 1 {
127
-		return fmt.Errorf("Invalid argument position (%d)", argPosition)
128
-	}
129
-	annotation, err := c.zshCompGetArgsAnnotations()
130
-	if err != nil {
131
-		return err
132
-	}
133
-	if c.zshcompArgsAnnotationnIsDuplicatePosition(annotation, argPosition) {
134
-		return fmt.Errorf("Duplicate annotation for positional argument at index %d", argPosition)
135
-	}
136
-	annotation[argPosition] = zshCompArgHint{
137
-		Tipe:    zshCompArgumentFilenameComp,
138
-		Options: patterns,
139
-	}
140
-	return c.zshCompSetArgsAnnotations(annotation)
141
-}
142
-
143
-// MarkZshCompPositionalArgumentWords marks the specified positional argument
144
-// (first argument is 1) as completed by the provided words. At east one word
145
-// must be provided, spaces within words will be offered completion with
146
-// "word\ word".
147
-func (c *Command) MarkZshCompPositionalArgumentWords(argPosition int, words ...string) error {
148
-	if argPosition < 1 {
149
-		return fmt.Errorf("Invalid argument position (%d)", argPosition)
150
-	}
151
-	if len(words) == 0 {
152
-		return fmt.Errorf("Trying to set empty word list for positional argument %d", argPosition)
153
-	}
154
-	annotation, err := c.zshCompGetArgsAnnotations()
155
-	if err != nil {
156
-		return err
157
-	}
158
-	if c.zshcompArgsAnnotationnIsDuplicatePosition(annotation, argPosition) {
159
-		return fmt.Errorf("Duplicate annotation for positional argument at index %d", argPosition)
160
-	}
161
-	annotation[argPosition] = zshCompArgHint{
162
-		Tipe:    zshCompArgumentWordComp,
163
-		Options: words,
164
-	}
165
-	return c.zshCompSetArgsAnnotations(annotation)
166
-}
167
-
168
-func zshCompExtractArgumentCompletionHintsForRendering(c *Command) ([]string, error) {
169
-	var result []string
170
-	annotation, err := c.zshCompGetArgsAnnotations()
171
-	if err != nil {
172
-		return nil, err
173
-	}
174
-	for k, v := range annotation {
175
-		s, err := zshCompRenderZshCompArgHint(k, v)
176
-		if err != nil {
177
-			return nil, err
178
-		}
179
-		result = append(result, s)
180
-	}
181
-	if len(c.ValidArgs) > 0 {
182
-		if _, positionOneExists := annotation[1]; !positionOneExists {
183
-			s, err := zshCompRenderZshCompArgHint(1, zshCompArgHint{
184
-				Tipe:    zshCompArgumentWordComp,
185
-				Options: c.ValidArgs,
186
-			})
187
-			if err != nil {
188
-				return nil, err
189
-			}
190
-			result = append(result, s)
191
-		}
192
-	}
193
-	sort.Strings(result)
194
-	return result, nil
18
+	return c.genZshCompletion(w, true)
195 19
 }
196 20
 
197
-func zshCompRenderZshCompArgHint(i int, z zshCompArgHint) (string, error) {
198
-	switch t := z.Tipe; t {
199
-	case zshCompArgumentFilenameComp:
200
-		var globs []string
201
-		for _, g := range z.Options {
202
-			globs = append(globs, fmt.Sprintf(`-g "%s"`, g))
203
-		}
204
-		return fmt.Sprintf(`'%d: :_files %s'`, i, strings.Join(globs, " ")), nil
205
-	case zshCompArgumentWordComp:
206
-		var words []string
207
-		for _, w := range z.Options {
208
-			words = append(words, fmt.Sprintf("%q", w))
209
-		}
210
-		return fmt.Sprintf(`'%d: :(%s)'`, i, strings.Join(words, " ")), nil
211
-	default:
212
-		return "", fmt.Errorf("Invalid zsh argument completion annotation: %s", t)
213
-	}
21
+// GenZshCompletionFileNoDesc generates zsh completion file without descriptions.
22
+func (c *Command) GenZshCompletionFileNoDesc(filename string) error {
23
+	return c.genZshCompletionFile(filename, false)
214 24
 }
215 25
 
216
-func (c *Command) zshcompArgsAnnotationnIsDuplicatePosition(annotation zshCompArgsAnnotation, position int) bool {
217
-	_, dup := annotation[position]
218
-	return dup
26
+// GenZshCompletionNoDesc generates zsh completion file without descriptions
27
+// and writes it to the passed writer.
28
+func (c *Command) GenZshCompletionNoDesc(w io.Writer) error {
29
+	return c.genZshCompletion(w, false)
219 30
 }
220 31
 
221
-func (c *Command) zshCompGetArgsAnnotations() (zshCompArgsAnnotation, error) {
222
-	annotation := make(zshCompArgsAnnotation)
223
-	annotationString, ok := c.Annotations[zshCompArgumentAnnotation]
224
-	if !ok {
225
-		return annotation, nil
226
-	}
227
-	err := json.Unmarshal([]byte(annotationString), &annotation)
228
-	if err != nil {
229
-		return annotation, fmt.Errorf("Error unmarshaling zsh argument annotation: %v", err)
230
-	}
231
-	return annotation, nil
232
-}
233
-
234
-func (c *Command) zshCompSetArgsAnnotations(annotation zshCompArgsAnnotation) error {
235
-	jsn, err := json.Marshal(annotation)
236
-	if err != nil {
237
-		return fmt.Errorf("Error marshaling zsh argument annotation: %v", err)
238
-	}
239
-	if c.Annotations == nil {
240
-		c.Annotations = make(map[string]string)
241
-	}
242
-	c.Annotations[zshCompArgumentAnnotation] = string(jsn)
32
+// MarkZshCompPositionalArgumentFile only worked for zsh and its behavior was
33
+// not consistent with Bash completion. It has therefore been disabled.
34
+// Instead, when no other completion is specified, file completion is done by
35
+// default for every argument. One can disable file completion on a per-argument
36
+// basis by using ValidArgsFunction and ShellCompDirectiveNoFileComp.
37
+// To achieve file extension filtering, one can use ValidArgsFunction and
38
+// ShellCompDirectiveFilterFileExt.
39
+//
40
+// Deprecated
41
+func (c *Command) MarkZshCompPositionalArgumentFile(argPosition int, patterns ...string) error {
243 42
 	return nil
244 43
 }
245 44
 
246
-func zshCompGenFuncName(c *Command) string {
247
-	if c.HasParent() {
248
-		return zshCompGenFuncName(c.Parent()) + "_" + c.Name()
249
-	}
250
-	return "_" + c.Name()
251
-}
252
-
253
-func zshCompExtractFlag(c *Command) []*pflag.Flag {
254
-	var flags []*pflag.Flag
255
-	c.LocalFlags().VisitAll(func(f *pflag.Flag) {
256
-		if !f.Hidden {
257
-			flags = append(flags, f)
258
-		}
259
-	})
260
-	c.InheritedFlags().VisitAll(func(f *pflag.Flag) {
261
-		if !f.Hidden {
262
-			flags = append(flags, f)
263
-		}
264
-	})
265
-	return flags
266
-}
267
-
268
-// zshCompGenFlagEntryForArguments returns an entry that matches _arguments
269
-// zsh-completion parameters. It's too complicated to generate in a template.
270
-func zshCompGenFlagEntryForArguments(f *pflag.Flag) string {
271
-	if f.Name == "" || f.Shorthand == "" {
272
-		return zshCompGenFlagEntryForSingleOptionFlag(f)
273
-	}
274
-	return zshCompGenFlagEntryForMultiOptionFlag(f)
275
-}
276
-
277
-func zshCompGenFlagEntryForSingleOptionFlag(f *pflag.Flag) string {
278
-	var option, multiMark, extras string
279
-
280
-	if zshCompFlagCouldBeSpecifiedMoreThenOnce(f) {
281
-		multiMark = "*"
282
-	}
283
-
284
-	option = "--" + f.Name
285
-	if option == "--" {
286
-		option = "-" + f.Shorthand
287
-	}
288
-	extras = zshCompGenFlagEntryExtras(f)
289
-
290
-	return fmt.Sprintf(`'%s%s[%s]%s'`, multiMark, option, zshCompQuoteFlagDescription(f.Usage), extras)
291
-}
292
-
293
-func zshCompGenFlagEntryForMultiOptionFlag(f *pflag.Flag) string {
294
-	var options, parenMultiMark, curlyMultiMark, extras string
295
-
296
-	if zshCompFlagCouldBeSpecifiedMoreThenOnce(f) {
297
-		parenMultiMark = "*"
298
-		curlyMultiMark = "\\*"
299
-	}
300
-
301
-	options = fmt.Sprintf(`'(%s-%s %s--%s)'{%s-%s,%s--%s}`,
302
-		parenMultiMark, f.Shorthand, parenMultiMark, f.Name, curlyMultiMark, f.Shorthand, curlyMultiMark, f.Name)
303
-	extras = zshCompGenFlagEntryExtras(f)
304
-
305
-	return fmt.Sprintf(`%s'[%s]%s'`, options, zshCompQuoteFlagDescription(f.Usage), extras)
45
+// MarkZshCompPositionalArgumentWords only worked for zsh. It has therefore
46
+// been disabled.
47
+// To achieve the same behavior across all shells, one can use
48
+// ValidArgs (for the first argument only) or ValidArgsFunction for
49
+// any argument (can include the first one also).
50
+//
51
+// Deprecated
52
+func (c *Command) MarkZshCompPositionalArgumentWords(argPosition int, words ...string) error {
53
+	return nil
306 54
 }
307 55
 
308
-func zshCompGenFlagEntryExtras(f *pflag.Flag) string {
309
-	if f.NoOptDefVal != "" {
310
-		return ""
311
-	}
312
-
313
-	extras := ":" // allow options for flag (even without assistance)
314
-	for key, values := range f.Annotations {
315
-		switch key {
316
-		case zshCompDirname:
317
-			extras = fmt.Sprintf(":filename:_files -g %q", values[0])
318
-		case BashCompFilenameExt:
319
-			extras = ":filename:_files"
320
-			for _, pattern := range values {
321
-				extras = extras + fmt.Sprintf(` -g "%s"`, pattern)
322
-			}
323
-		}
56
+func (c *Command) genZshCompletionFile(filename string, includeDesc bool) error {
57
+	outFile, err := os.Create(filename)
58
+	if err != nil {
59
+		return err
324 60
 	}
61
+	defer outFile.Close()
325 62
 
326
-	return extras
327
-}
328
-
329
-func zshCompFlagCouldBeSpecifiedMoreThenOnce(f *pflag.Flag) bool {
330
-	return strings.Contains(f.Value.Type(), "Slice") ||
331
-		strings.Contains(f.Value.Type(), "Array")
332
-}
333
-
334
-func zshCompQuoteFlagDescription(s string) string {
335
-	return strings.Replace(s, "'", `'\''`, -1)
63
+	return c.genZshCompletion(outFile, includeDesc)
64
+}
65
+
66
+func (c *Command) genZshCompletion(w io.Writer, includeDesc bool) error {
67
+	buf := new(bytes.Buffer)
68
+	genZshComp(buf, c.Name(), includeDesc)
69
+	_, err := buf.WriteTo(w)
70
+	return err
71
+}
72
+
73
+func genZshComp(buf *bytes.Buffer, name string, includeDesc bool) {
74
+	compCmd := ShellCompRequestCmd
75
+	if !includeDesc {
76
+		compCmd = ShellCompNoDescRequestCmd
77
+	}
78
+	buf.WriteString(fmt.Sprintf(`#compdef _%[1]s %[1]s
79
+
80
+# zsh completion for %-36[1]s -*- shell-script -*-
81
+
82
+__%[1]s_debug()
83
+{
84
+    local file="$BASH_COMP_DEBUG_FILE"
85
+    if [[ -n ${file} ]]; then
86
+        echo "$*" >> "${file}"
87
+    fi
88
+}
89
+
90
+_%[1]s()
91
+{
92
+    local shellCompDirectiveError=%[3]d
93
+    local shellCompDirectiveNoSpace=%[4]d
94
+    local shellCompDirectiveNoFileComp=%[5]d
95
+    local shellCompDirectiveFilterFileExt=%[6]d
96
+    local shellCompDirectiveFilterDirs=%[7]d
97
+
98
+    local lastParam lastChar flagPrefix requestComp out directive compCount comp lastComp
99
+    local -a completions
100
+
101
+    __%[1]s_debug "\n========= starting completion logic =========="
102
+    __%[1]s_debug "CURRENT: ${CURRENT}, words[*]: ${words[*]}"
103
+
104
+    # The user could have moved the cursor backwards on the command-line.
105
+    # We need to trigger completion from the $CURRENT location, so we need
106
+    # to truncate the command-line ($words) up to the $CURRENT location.
107
+    # (We cannot use $CURSOR as its value does not work when a command is an alias.)
108
+    words=("${=words[1,CURRENT]}")
109
+    __%[1]s_debug "Truncated words[*]: ${words[*]},"
110
+
111
+    lastParam=${words[-1]}
112
+    lastChar=${lastParam[-1]}
113
+    __%[1]s_debug "lastParam: ${lastParam}, lastChar: ${lastChar}"
114
+
115
+    # For zsh, when completing a flag with an = (e.g., %[1]s -n=<TAB>)
116
+    # completions must be prefixed with the flag
117
+    setopt local_options BASH_REMATCH
118
+    if [[ "${lastParam}" =~ '-.*=' ]]; then
119
+        # We are dealing with a flag with an =
120
+        flagPrefix="-P ${BASH_REMATCH}"
121
+    fi
122
+
123
+    # Prepare the command to obtain completions
124
+    requestComp="${words[1]} %[2]s ${words[2,-1]}"
125
+    if [ "${lastChar}" = "" ]; then
126
+        # If the last parameter is complete (there is a space following it)
127
+        # We add an extra empty parameter so we can indicate this to the go completion code.
128
+        __%[1]s_debug "Adding extra empty parameter"
129
+        requestComp="${requestComp} \"\""
130
+    fi
131
+
132
+    __%[1]s_debug "About to call: eval ${requestComp}"
133
+
134
+    # Use eval to handle any environment variables and such
135
+    out=$(eval ${requestComp} 2>/dev/null)
136
+    __%[1]s_debug "completion output: ${out}"
137
+
138
+    # Extract the directive integer following a : from the last line
139
+    local lastLine
140
+    while IFS='\n' read -r line; do
141
+        lastLine=${line}
142
+    done < <(printf "%%s\n" "${out[@]}")
143
+    __%[1]s_debug "last line: ${lastLine}"
144
+
145
+    if [ "${lastLine[1]}" = : ]; then
146
+        directive=${lastLine[2,-1]}
147
+        # Remove the directive including the : and the newline
148
+        local suffix
149
+        (( suffix=${#lastLine}+2))
150
+        out=${out[1,-$suffix]}
151
+    else
152
+        # There is no directive specified.  Leave $out as is.
153
+        __%[1]s_debug "No directive found.  Setting do default"
154
+        directive=0
155
+    fi
156
+
157
+    __%[1]s_debug "directive: ${directive}"
158
+    __%[1]s_debug "completions: ${out}"
159
+    __%[1]s_debug "flagPrefix: ${flagPrefix}"
160
+
161
+    if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
162
+        __%[1]s_debug "Completion received error. Ignoring completions."
163
+        return
164
+    fi
165
+
166
+    compCount=0
167
+    while IFS='\n' read -r comp; do
168
+        if [ -n "$comp" ]; then
169
+            # If requested, completions are returned with a description.
170
+            # The description is preceded by a TAB character.
171
+            # For zsh's _describe, we need to use a : instead of a TAB.
172
+            # We first need to escape any : as part of the completion itself.
173
+            comp=${comp//:/\\:}
174
+
175
+            local tab=$(printf '\t')
176
+            comp=${comp//$tab/:}
177
+
178
+            ((compCount++))
179
+            __%[1]s_debug "Adding completion: ${comp}"
180
+            completions+=${comp}
181
+            lastComp=$comp
182
+        fi
183
+    done < <(printf "%%s\n" "${out[@]}")
184
+
185
+    if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
186
+        # File extension filtering
187
+        local filteringCmd
188
+        filteringCmd='_files'
189
+        for filter in ${completions[@]}; do
190
+            if [ ${filter[1]} != '*' ]; then
191
+                # zsh requires a glob pattern to do file filtering
192
+                filter="\*.$filter"
193
+            fi
194
+            filteringCmd+=" -g $filter"
195
+        done
196
+        filteringCmd+=" ${flagPrefix}"
197
+
198
+        __%[1]s_debug "File filtering command: $filteringCmd"
199
+        _arguments '*:filename:'"$filteringCmd"
200
+    elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
201
+        # File completion for directories only
202
+        local subDir
203
+        subdir="${completions[1]}"
204
+        if [ -n "$subdir" ]; then
205
+            __%[1]s_debug "Listing directories in $subdir"
206
+            pushd "${subdir}" >/dev/null 2>&1
207
+        else
208
+            __%[1]s_debug "Listing directories in ."
209
+        fi
210
+
211
+        _arguments '*:dirname:_files -/'" ${flagPrefix}"
212
+        if [ -n "$subdir" ]; then
213
+            popd >/dev/null 2>&1
214
+        fi
215
+    elif [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ] && [ ${compCount} -eq 1 ]; then
216
+        __%[1]s_debug "Activating nospace."
217
+        # We can use compadd here as there is no description when
218
+        # there is only one completion.
219
+        compadd -S '' "${lastComp}"
220
+    elif [ ${compCount} -eq 0 ]; then
221
+        if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
222
+            __%[1]s_debug "deactivating file completion"
223
+        else
224
+            # Perform file completion
225
+            __%[1]s_debug "activating file completion"
226
+            _arguments '*:filename:_files'" ${flagPrefix}"
227
+        fi
228
+    else
229
+        _describe "completions" completions $(echo $flagPrefix)
230
+    fi
231
+}
232
+
233
+# don't run the completion function when being source-ed or eval-ed
234
+if [ "$funcstack[1]" = "_%[1]s" ]; then
235
+	_%[1]s
236
+fi
237
+`, name, compCmd,
238
+		ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
239
+		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs))
336 240
 }