Repository: spf13/cobra
Pull request #1035 (2020-02-16 21:43:49)

Custom completions coded in Go (instead of Bash)
This PR allows programs using Cobra to code their custom completions in **Go** directly inside the program itself.

It is a port to Cobra of the custom completion solution introduced in Helm 3.1 (https://github.com/helm/helm/pull/7323).

It is fully-backwards compatible.
It also allows to use both the new Go custom completions and the existing Bash custom completions at the same time.

The PR **introduces a new hidden sub-command: `__complete`** that is to be called by the auto-completion script. I chose the name with a double underscore prefix to make sure this command will not collide with any command of a program.

Along the lines of the existing `ValidArgs` field, support is added for the **`ValidArgsFunction`** field.  The program can register a function that will provide custom completion through this newly added field. 

For flags, the new function **`Command.RegisterFlagCompletionFunc()`** is added.  The program can register a custom completion function for a flag using this new function.

The bash completion script, before calling the completion `*_custom_func`, will now verify if `Command.ValidArgsFunction` has been specified.  If so, the completion script will call the `__complete` command passing it all command-line arguments; the `__complete` command will call the function specified by `Command.ValidArgsFunction` to obtain completions from the program itself.

To be as feature-rich as custom completions written in Bash, the PR also allows Go completions to control:
- if a single completion should be followed by a space
- if file completion should be performed when no completions are available
 
Some advantages of using Go instead of Bash for custom completions:
- Cobra users are Go developers
- Access to the full program code base to perform completions instead of being limited to relying on externally-exposed functionality.
- Debugging and development much easier because developers can directly call e.g., `<program> __complete <cmd> ""<ENTER>` to test the corresponding custom completion code
- The Go debugger can even be used to troubleshoot custom completion code
- Go tests can be written for the custom completion code
- Flag parsing done automatically by Cobra (instead of having to be coded in bash in the completion script)
- No need to make sure the custom completion code works for both `bash` and `zsh` (for those programs that share completions between the two)
- No more issues with the use of shell tools (e.g., `echo`, `cat`, `tail`) that work differently on different OSes

To illustrate a working program using this PR, I've ported Helm from its own solution to using this PR in Cobra: https://github.com/VilledeMontreal/helm/tree/feat/compInGoWithCobra
Here is an example of using the new `ValidArgsFunction` field:
https://github.com/VilledeMontreal/helm/blob/2c88891a96eb6cb27d124d5a6d750eee85abcc8c/cmd/helm/status.go#L55
and an example of using `RegisterFlagCompletionFunc()`:
https://github.com/VilledeMontreal/helm/blob/2c88891a96eb6cb27d124d5a6d750eee85abcc8c/cmd/helm/root.go#L86

Go tests have been added in Cobra for this PR and the documentation for bash_completion has been updated.

AdamSLevy: You may be interested in this golang package as it is related to your intent here: https://github.com/posener/complete

I use it alongside cobra to achieve the same end as this PR.
jharshman: @marckhouzam this is great work! 

Can this be extended to generate completions for other shells. We currently support ZSH but have had requests for others like FiSH etc.. 

There's a lot to take in on this PR so please keep in mind that the review process for this one might be more lengthy. That being said I have slated this for inclusion in our 1.0.0 release.
marckhouzam: > @marckhouzam this is great work!

Thanks for having a look! 

> Can this be extended to generate completions for other shells. We currently support ZSH but have had requests for others like FiSH etc..

I didn't want to come on too strong, so I started with posting the functionality of this PR.  However you are absolutely right! This can be extended to provide full completions to other shells. In fact, I'm about to post a PR to the helm project that will support full Fish completion (command names, flag names *and* custom completions). And the fish script is less than 100 lines.

> There's a lot to take in on this PR so please keep in mind that the review process for this one might be more lengthy. 

No problem at all. I'll do my best to be responsive to any comment you may have.

> That being said I have slated this for inclusion in our 1.0.0 release.

🎉 


marckhouzam: Here's the Helm PR for full Fish completion: 
https://github.com/helm/helm/pull/7690

I'm going to work with the other helm maintainers to finalize it and then I can try to port it to Cobra.
jharshman: @marckhouzam I'm trying to validate this on my end and can't seem to get the statically defined autocompletion functioning.

i.e:
```
var rootCmd = &cobra.Command{
	ValidArgs: []string{"pod", "rs", "ds"},
	Use:       "testprog1",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("rootCmd called")
	},
}

var childCmd = &cobra.Command{
	Use: "pod",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("child called")
	},
}
```

However, when i compile it and run `./testprog1 [tab][tab]` I don't get any completion suggestions where I assume I should get "pod", "rs", or "ds".
Am I mistaken or missing something key here?
marckhouzam: > However, when i compile it and run `./testprog1 [tab][tab]` I don't get any completion suggestions where I assume I should get "pod", "rs", or "ds".
> Am I mistaken or missing something key here?

Thanks @jharshman for trying things out!

There are a couple things going on here:
1. You will always need to source a completion script first, before completions can work
1. This PR only introduces Go Custom Completions (the ones code by the program using cobra).  It does not modify the way Cobra provides command, flag, or ValidArgs completions. So to give this code a spin, you need try the new `ValidArgsFunction` field.

Here is a test program and how to test with it.  That's how I ran it anyway.

main.go:
```
package main

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
	ValidArgs: []string{"pod", "rs", "ds"},
	Use:       "testprog1",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("rootCmd called")
	},
}

var childCmd = &cobra.Command{
	Use: "pod",
	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.BashCompDirective) {
		return []string{"these", "are", "custom", "completions"}, cobra.BashCompDirectiveDefault
	},
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("child called")
	},
}

var completionCmd = &cobra.Command{
	Use: "completion",
	Run: func(cmd *cobra.Command, args []string) {
		cmd.Root().GenBashCompletion(os.Stdout)
	},
}

func main() {
	rootCmd.AddCommand(childCmd)
	rootCmd.AddCommand(completionCmd)
	rootCmd.Execute()
}
```
go.mod (you'll need to modify the path to your cobra git repo)
```
go 1.13
replace github.com/spf13/cobra => /Users/marckhouzam/git/cobra
```

To build and test:
```
bash

GO111MODULE=on go build -o testprog1 main.go
source <(./testprog1 completion)

./testprog1 <TAB><TAB>
# Should show: ds   pod  rs
# The completions above are done by cobra's existing script

./testprog1 pod <TAB><TAB>
# Should show: are          completions  custom       these
# The completions above are done by this PR through the ValidArgsFunction field called by the cobra completion script
```

Let me know if I can help in any other way, and thanks again.
jharshman: Ah - thank you for clarifying that. Dumb mistake on my part.  I'll continue my review of this change.
marckhouzam: BTW, allowing the `__complete` command to also complete flags, commands, and ValidArgs is added as part of #1048; specifically in commit https://github.com/spf13/cobra/commit/828c60bddd1109bf4a2ad5c578769881c6c00c61
However, the bash completion script of Cobra won't make use of that (not needed), but the Fish one will.

But you could test it manually (using #1048) like this:
```
./testprog1 __complete ""
# will show sub commands and validArgs
```
jharshman: @marckhouzam so for the `ValidArgsFunction`, it looks like static `ValidArgs` takes precedence over `ValidArgsFunction` if both are defined on a command. Is this right? 
marckhouzam: > @marckhouzam so for the `ValidArgsFunction`, it looks like static `ValidArgs` takes precedence over `ValidArgsFunction` if both are defined on a command. Is this right?

@jharshman Good observation.  With this PR, we are only focusing on Custom Completions so I chose to follow the way the existing Bash completion script triggers custom completions.  That meant that if there are `ValidArgs` then `ValidArgsFunction` does not get called.  To be honest, this was the simplest choice, which avoided messing too much with the Bash script code.  But from a user's perspective, this may not be the right choice.

In fact, in #1048 for Fish completion, where `ValidArgs` are handled in Go through the `__completion` command, I actually chose to allow both `ValidArgs` and `ValidArgsFunction` to be used together.  You made me to realize now this would cause incompatibilities between bash completions and fish completions.

So, what do we want to offer the user?  For consistency, I think we have two options:
1. allow to use both `ValidArgs` and `ValidArgsFunction` together
1. disallow using both together and cause some configuration error if they are used together.

Allowing both gives more flexibility to the user, but could cause confusion if they don't realize they have used both... What do you think we should support?

jharshman: @marckhouzam my opinion on this is that a command can either use one or the other but not both. While being able to set both on a command might add more flexibility, I think it would be at the expense of simplicity.

So let's make these fields mutually exclusive and going with your second option. Also please add some documentation for the function and in the readme.
marckhouzam: > @marckhouzam my opinion on this is that a command can either use one or the other but not both. 

@jharshman Agreed. I will post an update and make matching changes to #1048.

This brings up a similar situation.  Cobra allows a command to have sub-commands ***and*** arguments at the same time (your test program even does it, although it would need the `Args:` field on `rootCmd` to actually accept the specified `ValidArgs`).  So we can have a situation where `ValidArgs` is specified as well as sub-commands. When doing completion, it seems that currently `ValidArgs` has precedence over sub-commands.  To explain:

In `testprog1` the `rootCmd` specifies `ValidArgs` of `pod, rs, ds`, but also a sub-command `pod`.  In testing I saw that the completion script only proposes the values of `ValidArgs`.  If `ValidArgs` did not include `pod`, the completions would not propose the sub-command `pod` at all.   Cobra should probably do better to help the user by automatically including non-hidden sub-commands in the completions.  But that is something else (does this deserve an issue?). For this PR:

What shall we do if `ValidArgsFunction` is specified at the same time as sub-commands?  I assume we should propose both the sub-commands and the results of calling `ValidArgsFunction`?  What do you say?





marckhouzam: > So let's make these fields mutually exclusive and going with your second option please also add some documentation for the function and in the readme.

I pushed a commit with a comment added next to the `ValidArgsFunction` field, and mentioned it in the completion readme.

The code behaves as we want, only using `ValidArgs` if both are present, but I couldn't find a good way to warn the user at runtime.
jharshman: > What shall we do if ValidArgsFunction is specified at the same time as sub-commands? I assume we should propose both the sub-commands and the results of calling ValidArgsFunction? What do you say?

Hm... this is a good point.  The intention of `ValidArgsFunction` is to allow dynamic population of completions for arguments. I would say here we want to match the behavior of `ValidArgs`.
marckhouzam: > The intention of `ValidArgsFunction` is to allow dynamic population of completions for arguments. I would say here we want to match the behavior of `ValidArgs`.

I have pushed a commit that makes `ValidArgsFunction` be treated like `ValidArgs` when there are also sub-commands, which is to show completions matching `ValidArgsFunction` but not sub-commands.  I have however also opened #1076 to improve this behaviour.

@jharshman I believe I have addressed all comments for the moment.

marckhouzam: The force-push was rebase to master, as there were conflicts.
marckhouzam: The tests fail because of something to do with stderr output.  Maybe because of the rebase to master.  I will investigate.
jharshman: > The tests fail because of something to do with stderr output

@marckhouzam yes I merged in a small change to start using StdErr and StdOut functions in the test code. 
Ref: #1053
marckhouzam: > @marckhouzam yes I merged in a small change to start using StdErr and StdOut functions in the test code.

@jharshman Thanks for the pointer.  It is now fixed.  I made a mistake rebasing. 

jharshman: @marckhouzam awesome - gonna give this one more pass and then I think we can call it good and start looking at #1048 
jharshman: Okay one observation that I have made is that the `ValidArgs` behavior actually _does_ return both the contents of `ValidArgs` and the subcommand. So If it makes sense, I think the `ValidArgFunction` should match this behavior.
What do you think @marckhouzam.  I think we were mistaken in our interpretation of the behavior of `ValidArgs`.  Can you also validate the behavior I'm seeing?
marckhouzam: > Okay one observation that I have made is that the `ValidArgs` behavior actually _does_ return both the contents of `ValidArgs` and the subcommand. 

When I tested it, `ValidArgs` prevented subcommands from being completed.  In the code it looks like that too. If you look at https://github.com/spf13/cobra/blob/138b98f39dedc35ae01b827fb9bceacaf30b0880/bash_completions.go#L121-L124

Notice that if there are `ValidArgs` (`must_have_one_noun`), then the `completions` array is overwritten completely, which removes the subcommands.  It is just a question of changing the `=` to `+=` to fix that (which makes me wonder if this was an oversight from the very start), but I think it should be done in a separate PR, for traceability, which is why I opened #1076. 

How did you test on your side?  Is it with the `testprog1` we mentionned above?  If so, could it be that you see the subcommand name (`pod`) being completed because it is also listed in the list of `ValidArgs`?  

> @marckhouzam awesome - gonna give this one more pass and then I think we can call it good and start looking at #1048

Awesome!  I'll have to rebase #1048 on the new master once this PR is merged as #1048 is not currently based on the very latest version of this PR.

jharshman: > could it be that you see the subcommand name (pod) being completed because it is also listed in the list of ValidArgs?

Yep, that was the reason, thanks for double checking!

Alright - I think this PR is ready to rock then!
jharshman: @marckhouzam actually can you squash your commits first. This has quite a large commit message. 
marckhouzam: > @marckhouzam actually can you squash your commits first. This has quite a large commit message.

@jharshman All cleaned up.  Thanks again!

Patch:
diff --git a/bash_completions.go b/bash_completions.go
index 1e0e25c..d23f96b 100644
--- a/bash_completions.go
+++ b/bash_completions.go
@@ -58,6 +58,67 @@ __%[1]s_contains_word()
     return 1
 }
 
+__%[1]s_handle_go_custom_completion()
+{
+    __%[1]s_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}"
+
+    local out requestComp lastParam lastChar comp directive args
+
+    # Prepare the command to request completions for the program.
+    # Calling ${words[0]} instead of directly %[1]s allows to handle aliases
+    args=("${words[@]:1}")
+    requestComp="${words[0]} %[2]s ${args[*]}"
+
+    lastParam=${words[$((${#words[@]}-1))]}
+    lastChar=${lastParam:$((${#lastParam}-1)):1}
+    __%[1]s_debug "${FUNCNAME[0]}: lastParam ${lastParam}, lastChar ${lastChar}"
+
+    if [ -z "${cur}" ] && [ "${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 method.
+        __%[1]s_debug "${FUNCNAME[0]}: Adding extra empty parameter"
+        requestComp="${requestComp} \"\""
+    fi
+
+    __%[1]s_debug "${FUNCNAME[0]}: calling ${requestComp}"
+    # Use eval to handle any environment variables and such
+    out=$(eval "${requestComp}" 2>/dev/null)
+
+    # Extract the directive integer at the very end of the output following a colon (:)
+    directive=${out##*:}
+    # Remove the directive
+    out=${out%%:*}
+    if [ "${directive}" = "${out}" ]; then
+        # There is not directive specified
+        directive=0
+    fi
+    __%[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
+        # 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 [[ $(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 [[ $(type -t compopt) = "builtin" ]]; then
+                __%[1]s_debug "${FUNCNAME[0]}: activating no file completion"
+                compopt +o default
+            fi
+        fi
+
+        while IFS='' read -r comp; do
+            COMPREPLY+=("$comp")
+        done < <(compgen -W "${out[*]}" -- "$cur")
+    fi
+}
+
 __%[1]s_handle_reply()
 {
     __%[1]s_debug "${FUNCNAME[0]}"
@@ -121,6 +182,10 @@ __%[1]s_handle_reply()
     completions=("${commands[@]}")
     if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then
         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
         completions+=("${must_have_one_flag[@]}")
@@ -279,7 +344,7 @@ __%[1]s_handle_word()
     __%[1]s_handle_word
 }
 
-`, name))
+`, name, CompRequestCmd, BashCompDirectiveError, BashCompDirectiveNoSpace, BashCompDirectiveNoFileComp))
 }
 
 func writePostscript(buf *bytes.Buffer, name string) {
@@ -304,6 +369,7 @@ func writePostscript(buf *bytes.Buffer, name string) {
     local commands=("%[1]s")
     local must_have_one_flag=()
     local must_have_one_noun=()
+    local has_completion_function
     local last_command
     local nouns=()
 
@@ -404,7 +470,22 @@ func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) {
 	buf.WriteString(fmt.Sprintf(format, name))
 }
 
+// Setup annotations for go completions for registered flags
+func prepareCustomAnnotationsForFlags(cmd *Command) {
+	for flag := range flagCompletionFunctions {
+		// Make sure the completion script calls the __*_go_custom_completion function for
+		// every registered flag.  We need to do this here (and not when the flag was registered
+		// for completion) so that we can know the root command name for the prefix
+		// of __<prefix>_go_custom_completion
+		if flag.Annotations == nil {
+			flag.Annotations = map[string][]string{}
+		}
+		flag.Annotations[BashCompCustom] = []string{fmt.Sprintf("__%[1]s_handle_go_custom_completion", cmd.Root().Name())}
+	}
+}
+
 func writeFlags(buf *bytes.Buffer, cmd *Command) {
+	prepareCustomAnnotationsForFlags(cmd)
 	buf.WriteString(`    flags=()
     two_word_flags=()
     local_nonpersistent_flags=()
@@ -469,6 +550,9 @@ func writeRequiredNouns(buf *bytes.Buffer, cmd *Command) {
 	for _, value := range cmd.ValidArgs {
 		buf.WriteString(fmt.Sprintf("    must_have_one_noun+=(%q)\n", value))
 	}
+	if cmd.ValidArgsFunction != nil {
+		buf.WriteString("    has_completion_function=1\n")
+	}
 }
 
 func writeCmdAliases(buf *bytes.Buffer, cmd *Command) {
diff --git a/bash_completions.md b/bash_completions.md
index 4ac61ee..db67597 100644
--- a/bash_completions.md
+++ b/bash_completions.md
@@ -56,7 +56,147 @@ func main() {
 
 `out.sh` will get you completions of subcommands and flags. Copy it to `/etc/bash_completion.d/` as described [here](https://debian-administration.org/article/316/An_introduction_to_bash_completion_part_1) and reset your terminal to use autocompletion. If you make additional annotations to your code, you can get even more intelligent and flexible behavior.
 
-## Creating your own custom functions
+## Have the completions code complete your 'nouns'
+
+### Static completion of nouns
+
+This method allows you to provide a pre-defined list of completion choices for your nouns using the `validArgs` field.
+For example, if you want `kubectl get [tab][tab]` to show a list of valid "nouns" you have to set them. Simplified code from `kubectl get` looks like:
+
+```go
+validArgs []string = { "pod", "node", "service", "replicationcontroller" }
+
+cmd := &cobra.Command{
+	Use:     "get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)",
+	Short:   "Display one or many resources",
+	Long:    get_long,
+	Example: get_example,
+	Run: func(cmd *cobra.Command, args []string) {
+		err := RunGet(f, out, cmd, args)
+		util.CheckErr(err)
+	},
+	ValidArgs: validArgs,
+}
+```
+
+Notice we put the "ValidArgs" on the "get" subcommand. Doing so will give results like
+
+```bash
+# kubectl get [tab][tab]
+node                 pod                    replicationcontroller  service
+```
+
+### Plural form and shortcuts for nouns
+
+If your nouns have a number of aliases, you can define them alongside `ValidArgs` using `ArgAliases`:
+
+```go
+argAliases []string = { "pods", "nodes", "services", "svc", "replicationcontrollers", "rc" }
+
+cmd := &cobra.Command{
+    ...
+	ValidArgs:  validArgs,
+	ArgAliases: argAliases
+}
+```
+
+The aliases are not shown to the user on tab completion, but they are accepted as valid nouns by
+the completion algorithm if entered manually, e.g. in:
+
+```bash
+# kubectl get rc [tab][tab]
+backend        frontend       database 
+```
+
+Note that without declaring `rc` as an alias, the completion algorithm would show the list of nouns
+in this example again instead of the replication controllers.
+
+### Dynamic completion of nouns
+
+In some cases it is not possible to provide a list of possible completions in advance.  Instead, the list of completions must be determined at execution-time.  Cobra provides two ways of defining such dynamic completion of nouns. Note that both these methods can be used along-side each other as long as they are not both used for the same command.
+
+#### 1. Custom completions of nouns written in Go
+
+In a similar fashion as for static completions, you can use the `ValidArgsFunction` field to provide a Go function that Cobra will execute when it needs the list of completion choices for the nouns of a command.  Note that either `ValidArgs` or `ValidArgsFunction` can be used for a single cobra command, but not both.
+Simplified code from `helm status` looks like:
+
+```go
+cmd := &cobra.Command{
+	Use:   "status RELEASE_NAME",
+	Short: "Display the status of the named release",
+	Long:  status_long,
+	RunE: func(cmd *cobra.Command, args []string) {
+		RunGet(args[0])
+	},
+	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.BashCompDirective) {
+		if len(args) != 0 {
+			return nil, cobra.BashCompDirectiveNoFileComp
+		}
+		return getReleasesFromCluster(toComplete), cobra.BashCompDirectiveNoFileComp
+	},
+}
+```
+Where `getReleasesFromCluster()` is a Go function that obtains the list of current Helm releases running on the Kubernetes cluster.
+Notice we put the `ValidArgsFunction` on the `status` subcommand. Let's assume the Helm releases on the cluster are: `harbor`, `notary`, `rook` and `thanos` then this dynamic completion will give results like
+
+```bash
+# helm status [tab][tab]
+harbor notary rook thanos
+```
+You may have noticed the use of `cobra.BashCompDirective`.  These directives are bit fields allowing to control some shell completion behaviors for your particular completion.  You can combine them with the bit-or operator such as `cobra.BashCompDirectiveNoSpace | cobra.BashCompDirectiveNoFileComp`
+```go
+// Indicates an error occurred and completions should be ignored.
+BashCompDirectiveError
+// Indicates that the shell should not add a space after the completion,
+// even if there is a single completion provided.
+BashCompDirectiveNoSpace
+// Indicates that the shell should not provide file completion even when
+// no completion is provided.
+// This currently does not work for zsh or bash < 4
+BashCompDirectiveNoFileComp
+// Indicates that the shell will perform its default behavior after completions
+// have been provided (this implies !BashCompDirectiveNoSpace && !BashCompDirectiveNoFileComp).
+BashCompDirectiveDefault
+```
+
+When using the `ValidArgsFunction`, Cobra will call your registered function after having parsed all flags and arguments provided in the command-line.  You therefore don't need to do this parsing yourself.  For example, when a user calls `helm status --namespace my-rook-ns [tab][tab]`, Cobra will call your registered `ValidArgsFunction` after having parsed the `--namespace` flag, as it would have done when calling the `RunE` function.
+
+##### Debugging
+
+Cobra achieves dynamic completions written in Go through the use of a hidden command called by the completion script.  To debug your Go completion code, you can call this hidden command directly:
+```bash
+# helm __complete status har<ENTER>
+harbor
+:4
+Completion ended with directive: BashCompDirectiveNoFileComp # This is on stderr
+```
+***Important:*** If the noun to complete is empty, you must pass an empty parameter to the `__complete` command:
+```bash
+# helm __complete status ""<ENTER>
+harbor
+notary
+rook
+thanos
+:4
+Completion ended with directive: BashCompDirectiveNoFileComp # This is on stderr
+```
+Calling the `__complete` command directly allows you to run the Go debugger to troubleshoot your code.  You can also add printouts to your code; Cobra provides the following functions to use for printouts in Go completion code:
+```go
+// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE
+// is set to a file path) and optionally prints to stderr.
+cobra.CompDebug(msg string, printToStdErr bool) {
+cobra.CompDebugln(msg string, printToStdErr bool)
+
+// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE
+// is set to a file path) and to stderr.
+cobra.CompError(msg string)
+cobra.CompErrorln(msg string)
+```
+***Important:*** You should **not** leave traces that print to stdout in your completion code as they will be interpreted as completion choices by the completion script.  Instead, use the cobra-provided debugging traces functions mentioned above.
+
+#### 2. Custom completions of nouns written in Bash
+
+This method allows you to inject bash functions into the completion script.  Those bash functions are responsible for providing the completion choices for your own completions.
 
 Some more actual code that works in kubernetes:
 
@@ -111,58 +251,6 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`,
 
 The `BashCompletionFunction` option is really only valid/useful on the root command. Doing the above will cause `__kubectl_custom_func()` (`__<command-use>_custom_func()`) to be called when the built in processor was unable to find a solution. In the case of kubernetes a valid command might look something like `kubectl get pod [mypod]`. If you type `kubectl get pod [tab][tab]` the `__kubectl_customc_func()` will run because the cobra.Command only understood "kubectl" and "get." `__kubectl_custom_func()` will see that the cobra.Command is "kubectl_get" and will thus call another helper `__kubectl_get_resource()`.  `__kubectl_get_resource` will look at the 'nouns' collected. In our example the only noun will be `pod`.  So it will call `__kubectl_parse_get pod`.  `__kubectl_parse_get` will actually call out to kubernetes and get any pods.  It will then set `COMPREPLY` to valid pods!
 
-## Have the completions code complete your 'nouns'
-
-In the above example "pod" was assumed to already be typed. But if you want `kubectl get [tab][tab]` to show a list of valid "nouns" you have to set them. Simplified code from `kubectl get` looks like:
-
-```go
-validArgs []string = { "pod", "node", "service", "replicationcontroller" }
-
-cmd := &cobra.Command{
-	Use:     "get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)",
-	Short:   "Display one or many resources",
-	Long:    get_long,
-	Example: get_example,
-	Run: func(cmd *cobra.Command, args []string) {
-		err := RunGet(f, out, cmd, args)
-		util.CheckErr(err)
-	},
-	ValidArgs: validArgs,
-}
-```
-
-Notice we put the "ValidArgs" on the "get" subcommand. Doing so will give results like
-
-```bash
-# kubectl get [tab][tab]
-node                 pod                    replicationcontroller  service
-```
-
-## Plural form and shortcuts for nouns
-
-If your nouns have a number of aliases, you can define them alongside `ValidArgs` using `ArgAliases`:
-
-```go
-argAliases []string = { "pods", "nodes", "services", "svc", "replicationcontrollers", "rc" }
-
-cmd := &cobra.Command{
-    ...
-	ValidArgs:  validArgs,
-	ArgAliases: argAliases
-}
-```
-
-The aliases are not shown to the user on tab completion, but they are accepted as valid nouns by
-the completion algorithm if entered manually, e.g. in:
-
-```bash
-# kubectl get rc [tab][tab]
-backend        frontend       database 
-```
-
-Note that without declaring `rc` as an alias, the completion algorithm would show the list of nouns
-in this example again instead of the replication controllers.
-
 ## Mark flags as required
 
 Most of the time completions will only show subcommands. But if a flag is required to make a subcommand work, you probably want it to show up when the user types [tab][tab].  Marking a flag as 'Required' is incredibly easy.
@@ -211,8 +299,43 @@ So while there are many other files in the CWD it only shows me subdirs and thos
 
 # Specify custom flag completion
 
-Similar to the filename completion and filtering using cobra.BashCompFilenameExt, you can specify
-a custom flag completion function with cobra.BashCompCustom:
+As for nouns, Cobra provides two ways of defining dynamic completion of flags.  Note that both these methods can be used along-side each other as long as they are not both used for the same flag.
+
+## 1. Custom completions of flags written in Go
+
+To provide a Go function that Cobra will execute when it needs the list of completion choices for a flag, you must register the function in the following manner:
+
+```go
+flagName := "output"
+cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.BashCompDirective) {
+	return []string{"json", "table", "yaml"}, cobra.BashCompDirectiveDefault
+})
+```
+Notice that calling `RegisterFlagCompletionFunc()` is done through the `command` with which the flag is associated.  In our example this dynamic completion will give results like so:
+
+```bash
+# helm status --output [tab][tab]
+json table yaml
+```
+
+### Debugging
+
+You can also easily debug your Go completion code for flags:
+```bash
+# helm __complete status --output ""
+json
+table
+yaml
+:4
+Completion ended with directive: BashCompDirectiveNoFileComp # This is on stderr
+```
+***Important:*** You should **not** leave traces that print to stdout in your completion code as they will be interpreted as completion choices by the completion script.  Instead, use the cobra-provided debugging traces functions mentioned in the above section.
+
+## 2. Custom completions of flags written in Bash
+
+Alternatively, you can use bash code for flag custom completion. Similar to the filename
+completion and filtering using `cobra.BashCompFilenameExt`, you can specify
+a custom flag completion bash function with `cobra.BashCompCustom`:
 
 ```go
 	annotation := make(map[string][]string)
@@ -226,7 +349,7 @@ a custom flag completion function with cobra.BashCompCustom:
 	cmd.Flags().AddFlag(flag)
 ```
 
-In addition add the `__handle_namespace_flag` implementation in the `BashCompletionFunction`
+In addition add the `__kubectl_get_namespaces` implementation in the `BashCompletionFunction`
 value, e.g.:
 
 ```bash
diff --git a/command.go b/command.go
index fdac9d2..cf02313 100644
--- a/command.go
+++ b/command.go
@@ -57,6 +57,10 @@ type Command struct {
 
 	// ValidArgs is list of all valid non-flag arguments that are accepted in bash completions
 	ValidArgs []string
+	// ValidArgsFunction is an optional function that provides valid non-flag arguments for bash completion.
+	// It is a dynamic version of using ValidArgs.
+	// Only one of ValidArgs and ValidArgsFunction can be used for a command.
+	ValidArgsFunction func(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective)
 
 	// Expected arguments
 	Args PositionalArgs
@@ -911,6 +915,9 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
 		args = os.Args[1:]
 	}
 
+	// initialize the hidden command to be used for bash completion
+	c.initCompleteCmd(args)
+
 	var flags []string
 	if c.TraverseChildren {
 		cmd, flags, err = c.Traverse(args)
diff --git a/custom_completions.go b/custom_completions.go
new file mode 100644
index 0000000..64c7901
--- /dev/null
+++ b/custom_completions.go
@@ -0,0 +1,299 @@
+package cobra
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"strings"
+
+	"github.com/spf13/pflag"
+)
+
+// CompRequestCmd is the name of the hidden command that is used to request
+// completion results from the program.  It is used by the shell completion script.
+const CompRequestCmd = "__complete"
+
+// Global map of flag completion functions.
+var flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective){}
+
+// BashCompDirective is a bit map representing the different behaviors the shell
+// can be instructed to have once completions have been provided.
+type BashCompDirective int
+
+const (
+	// BashCompDirectiveError indicates an error occurred and completions should be ignored.
+	BashCompDirectiveError BashCompDirective = 1 << iota
+
+	// BashCompDirectiveNoSpace indicates that the shell should not add a space
+	// after the completion even if there is a single completion provided.
+	BashCompDirectiveNoSpace
+
+	// BashCompDirectiveNoFileComp indicates that the shell should not provide
+	// file completion even when no completion is provided.
+	// This currently does not work for zsh or bash < 4
+	BashCompDirectiveNoFileComp
+
+	// BashCompDirectiveDefault indicates to let the shell perform its default
+	// behavior after completions have been provided.
+	BashCompDirectiveDefault BashCompDirective = 0
+)
+
+// RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag.
+func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective)) error {
+	flag := c.Flag(flagName)
+	if flag == nil {
+		return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' does not exist", flagName)
+	}
+	if _, exists := flagCompletionFunctions[flag]; exists {
+		return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' already registered", flagName)
+	}
+	flagCompletionFunctions[flag] = f
+	return nil
+}
+
+// Returns a string listing the different directive enabled in the specified parameter
+func (d BashCompDirective) string() string {
+	var directives []string
+	if d&BashCompDirectiveError != 0 {
+		directives = append(directives, "BashCompDirectiveError")
+	}
+	if d&BashCompDirectiveNoSpace != 0 {
+		directives = append(directives, "BashCompDirectiveNoSpace")
+	}
+	if d&BashCompDirectiveNoFileComp != 0 {
+		directives = append(directives, "BashCompDirectiveNoFileComp")
+	}
+	if len(directives) == 0 {
+		directives = append(directives, "BashCompDirectiveDefault")
+	}
+
+	if d > BashCompDirectiveError+BashCompDirectiveNoSpace+BashCompDirectiveNoFileComp {
+		return fmt.Sprintf("ERROR: unexpected BashCompDirective value: %d", d)
+	}
+	return strings.Join(directives, ", ")
+}
+
+// Adds a special hidden command that can be used to request custom completions.
+func (c *Command) initCompleteCmd(args []string) {
+	completeCmd := &Command{
+		Use:                   fmt.Sprintf("%s [command-line]", CompRequestCmd),
+		DisableFlagsInUseLine: true,
+		Hidden:                true,
+		DisableFlagParsing:    true,
+		Args:                  MinimumNArgs(1),
+		Short:                 "Request shell completion choices for the specified command-line",
+		Long: fmt.Sprintf("%[2]s is a special command that is used by the shell completion logic\n%[1]s",
+			"to request completion choices for the specified command-line.", CompRequestCmd),
+		Run: func(cmd *Command, args []string) {
+			finalCmd, completions, directive, err := cmd.getCompletions(args)
+			if err != nil {
+				CompErrorln(err.Error())
+				// Keep going for multiple reasons:
+				// 1- There could be some valid completions even though there was an error
+				// 2- Even without completions, we need to print the directive
+			}
+
+			for _, comp := range completions {
+				// Print each possible completion to stdout for the completion script to consume.
+				fmt.Fprintln(finalCmd.OutOrStdout(), comp)
+			}
+
+			if directive > BashCompDirectiveError+BashCompDirectiveNoSpace+BashCompDirectiveNoFileComp {
+				directive = BashCompDirectiveDefault
+			}
+
+			// As the last printout, print the completion directive for the completion script to parse.
+			// The directive integer must be that last character following a single colon (:).
+			// The completion script expects :<directive>
+			fmt.Fprintf(finalCmd.OutOrStdout(), ":%d\n", directive)
+
+			// Print some helpful info to stderr for the user to understand.
+			// Output from stderr must be ignored by the completion script.
+			fmt.Fprintf(finalCmd.ErrOrStderr(), "Completion ended with directive: %s\n", directive.string())
+		},
+	}
+	c.AddCommand(completeCmd)
+	subCmd, _, err := c.Find(args)
+	if err != nil || subCmd.Name() != CompRequestCmd {
+		// Only create this special command if it is actually being called.
+		// This reduces possible side-effects of creating such a command;
+		// for example, having this command would cause problems to a
+		// cobra program that only consists of the root command, since this
+		// command would cause the root command to suddenly have a subcommand.
+		c.RemoveCommand(completeCmd)
+	}
+}
+
+func (c *Command) getCompletions(args []string) (*Command, []string, BashCompDirective, 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]
+
+	// Find the real command for which completion must be performed
+	finalCmd, finalArgs, err := c.Root().Find(trimmedArgs)
+	if err != nil {
+		// Unable to find the real command. E.g., <program> someInvalidCmd <TAB>
+		return c, completions, BashCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs)
+	}
+
+	var flag *pflag.Flag
+	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, BashCompDirectiveDefault, err
+		}
+	}
+
+	// Parse the flags and extract the arguments to prepare for calling the completion function
+	if err = finalCmd.ParseFlags(finalArgs); err != nil {
+		return finalCmd, completions, BashCompDirectiveDefault, 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, BashCompDirective)
+	if flag != nil {
+		completionFn = flagCompletionFunctions[flag]
+	} else {
+		completionFn = finalCmd.ValidArgsFunction
+	}
+	if completionFn == nil {
+		// Go custom completion not supported/needed for this flag or command
+		return finalCmd, completions, BashCompDirectiveDefault, nil
+	}
+
+	// Call the registered completion function to get the completions
+	comps, directive := completionFn(finalCmd, finalArgs, toComplete)
+	completions = append(completions, comps...)
+	return finalCmd, completions, directive, nil
+}
+
+func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) {
+	var flagName string
+	trimmedArgs := args
+	flagWithEqual := false
+	if isFlagArg(lastArg) {
+		if index := strings.Index(lastArg, "="); index >= 0 {
+			flagName = strings.TrimLeft(lastArg[:index], "-")
+			lastArg = lastArg[index+1:]
+			flagWithEqual = true
+		} else {
+			return nil, nil, "", errors.New("Unexpected completion request for flag")
+		}
+	}
+
+	if len(flagName) == 0 {
+		if len(args) > 0 {
+			prevArg := args[len(args)-1]
+			if isFlagArg(prevArg) {
+				// Only consider the case where the flag does not contain an =.
+				// If the flag contains an = it means it has already been fully processed,
+				// so we don't need to deal with it here.
+				if index := strings.Index(prevArg, "="); index < 0 {
+					flagName = strings.TrimLeft(prevArg, "-")
+
+					// Remove the uncompleted flag or else there could be an error created
+					// for an invalid value for that flag
+					trimmedArgs = args[:len(args)-1]
+				}
+			}
+		}
+	}
+
+	if len(flagName) == 0 {
+		// Not doing flag completion
+		return nil, trimmedArgs, lastArg, nil
+	}
+
+	flag := findFlag(finalCmd, flagName)
+	if flag == nil {
+		// Flag not supported by this command, nothing to complete
+		err := fmt.Errorf("Subcommand '%s' does not support flag '%s'", finalCmd.Name(), flagName)
+		return nil, nil, "", err
+	}
+
+	if !flagWithEqual {
+		if len(flag.NoOptDefVal) != 0 {
+			// We had assumed dealing with a two-word flag but the flag is a boolean flag.
+			// In that case, there is no value following it, so we are not really doing flag completion.
+			// Reset everything to do noun completion.
+			trimmedArgs = args
+			flag = nil
+		}
+	}
+
+	return flag, trimmedArgs, lastArg, nil
+}
+
+func findFlag(cmd *Command, name string) *pflag.Flag {
+	flagSet := cmd.Flags()
+	if len(name) == 1 {
+		// First convert the short flag into a long flag
+		// as the cmd.Flag() search only accepts long flags
+		if short := flagSet.ShorthandLookup(name); short != nil {
+			name = short.Name
+		} else {
+			set := cmd.InheritedFlags()
+			if short = set.ShorthandLookup(name); short != nil {
+				name = short.Name
+			} else {
+				return nil
+			}
+		}
+	}
+	return cmd.Flag(name)
+}
+
+// CompDebug prints the specified string to the same file as where the
+// completion script prints its logs.
+// Note that completion printouts should never be on stdout as they would
+// be wrongly interpreted as actual completion choices by the completion script.
+func CompDebug(msg string, printToStdErr bool) {
+	msg = fmt.Sprintf("[Debug] %s", msg)
+
+	// Such logs are only printed when the user has set the environment
+	// variable BASH_COMP_DEBUG_FILE to the path of some file to be used.
+	if path := os.Getenv("BASH_COMP_DEBUG_FILE"); path != "" {
+		f, err := os.OpenFile(path,
+			os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+		if err == nil {
+			defer f.Close()
+			f.WriteString(msg)
+		}
+	}
+
+	if printToStdErr {
+		// Must print to stderr for this not to be read by the completion script.
+		fmt.Fprintf(os.Stderr, msg)
+	}
+}
+
+// CompDebugln prints the specified string with a newline at the end
+// to the same file as where the completion script prints its logs.
+// Such logs are only printed when the user has set the environment
+// variable BASH_COMP_DEBUG_FILE to the path of some file to be used.
+func CompDebugln(msg string, printToStdErr bool) {
+	CompDebug(fmt.Sprintf("%s\n", msg), printToStdErr)
+}
+
+// CompError prints the specified completion message to stderr.
+func CompError(msg string) {
+	msg = fmt.Sprintf("[Error] %s", msg)
+	CompDebug(msg, true)
+}
+
+// CompErrorln prints the specified completion message to stderr with a newline at the end.
+func CompErrorln(msg string) {
+	CompError(fmt.Sprintf("%s\n", msg))
+}


Test patch:
diff --git a/custom_completions_test.go b/custom_completions_test.go
new file mode 100644
index 0000000..61d214d
--- /dev/null
+++ b/custom_completions_test.go
@@ -0,0 +1,385 @@
+package cobra
+
+import (
+	"bytes"
+	"strings"
+	"testing"
+)
+
+func validArgsFunc(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective) {
+	if len(args) != 0 {
+		return nil, BashCompDirectiveNoFileComp
+	}
+
+	var completions []string
+	for _, comp := range []string{"one", "two"} {
+		if strings.HasPrefix(comp, toComplete) {
+			completions = append(completions, comp)
+		}
+	}
+	return completions, BashCompDirectiveDefault
+}
+
+func validArgsFunc2(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective) {
+	if len(args) != 0 {
+		return nil, BashCompDirectiveNoFileComp
+	}
+
+	var completions []string
+	for _, comp := range []string{"three", "four"} {
+		if strings.HasPrefix(comp, toComplete) {
+			completions = append(completions, comp)
+		}
+	}
+	return completions, BashCompDirectiveDefault
+}
+
+func TestValidArgsFuncSingleCmd(t *testing.T) {
+	rootCmd := &Command{
+		Use:               "root",
+		ValidArgsFunction: validArgsFunc,
+		Run:               emptyRun,
+	}
+
+	// Test completing an empty string
+	output, err := executeCommand(rootCmd, CompRequestCmd, "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		"one",
+		"two",
+		":0",
+		"Completion ended with directive: BashCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Check completing with a prefix
+	output, err = executeCommand(rootCmd, CompRequestCmd, "t")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"two",
+		":0",
+		"Completion ended with directive: BashCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
+func TestValidArgsFuncSingleCmdInvalidArg(t *testing.T) {
+	rootCmd := &Command{
+		Use: "root",
+		// If we don't specify a value for Args, this test fails.
+		// This is only true for a root command without any subcommands, and is caused
+		// by the fact that the __complete command becomes a subcommand when there should not be one.
+		// The problem is in the implementation of legacyArgs().
+		Args:              MinimumNArgs(1),
+		ValidArgsFunction: validArgsFunc,
+		Run:               emptyRun,
+	}
+
+	// Check completing with wrong number of args
+	output, err := executeCommand(rootCmd, CompRequestCmd, "unexpectedArg", "t")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		":4",
+		"Completion ended with directive: BashCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
+func TestValidArgsFuncChildCmds(t *testing.T) {
+	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
+	child1Cmd := &Command{
+		Use:               "child1",
+		ValidArgsFunction: validArgsFunc,
+		Run:               emptyRun,
+	}
+	child2Cmd := &Command{
+		Use:               "child2",
+		ValidArgsFunction: validArgsFunc2,
+		Run:               emptyRun,
+	}
+	rootCmd.AddCommand(child1Cmd, child2Cmd)
+
+	// Test completion of first sub-command with empty argument
+	output, err := executeCommand(rootCmd, CompRequestCmd, "child1", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		"one",
+		"two",
+		":0",
+		"Completion ended with directive: BashCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test completion of first sub-command with a prefix to complete
+	output, err = executeCommand(rootCmd, CompRequestCmd, "child1", "t")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"two",
+		":0",
+		"Completion ended with directive: BashCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Check completing with wrong number of args
+	output, err = executeCommand(rootCmd, CompRequestCmd, "child1", "unexpectedArg", "t")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		":4",
+		"Completion ended with directive: BashCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test completion of second sub-command with empty argument
+	output, err = executeCommand(rootCmd, CompRequestCmd, "child2", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"three",
+		"four",
+		":0",
+		"Completion ended with directive: BashCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	output, err = executeCommand(rootCmd, CompRequestCmd, "child2", "t")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"three",
+		":0",
+		"Completion ended with directive: BashCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Check completing with wrong number of args
+	output, err = executeCommand(rootCmd, CompRequestCmd, "child2", "unexpectedArg", "t")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		":4",
+		"Completion ended with directive: BashCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
+func TestValidArgsFuncAliases(t *testing.T) {
+	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
+	child := &Command{
+		Use:               "child",
+		Aliases:           []string{"son", "daughter"},
+		ValidArgsFunction: validArgsFunc,
+		Run:               emptyRun,
+	}
+	rootCmd.AddCommand(child)
+
+	// Test completion of first sub-command with empty argument
+	output, err := executeCommand(rootCmd, CompRequestCmd, "son", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		"one",
+		"two",
+		":0",
+		"Completion ended with directive: BashCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test completion of first sub-command with a prefix to complete
+	output, err = executeCommand(rootCmd, CompRequestCmd, "daughter", "t")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"two",
+		":0",
+		"Completion ended with directive: BashCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Check completing with wrong number of args
+	output, err = executeCommand(rootCmd, CompRequestCmd, "son", "unexpectedArg", "t")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		":4",
+		"Completion ended with directive: BashCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}
+
+func TestValidArgsFuncInScript(t *testing.T) {
+	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
+	child := &Command{
+		Use:               "child",
+		ValidArgsFunction: validArgsFunc,
+		Run:               emptyRun,
+	}
+	rootCmd.AddCommand(child)
+
+	buf := new(bytes.Buffer)
+	rootCmd.GenBashCompletion(buf)
+	output := buf.String()
+
+	check(t, output, "has_completion_function=1")
+}
+
+func TestNoValidArgsFuncInScript(t *testing.T) {
+	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
+	child := &Command{
+		Use: "child",
+		Run: emptyRun,
+	}
+	rootCmd.AddCommand(child)
+
+	buf := new(bytes.Buffer)
+	rootCmd.GenBashCompletion(buf)
+	output := buf.String()
+
+	checkOmit(t, output, "has_completion_function=1")
+}
+
+func TestFlagCompletionInGo(t *testing.T) {
+	rootCmd := &Command{
+		Use: "root",
+		Run: emptyRun,
+	}
+	rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot")
+	rootCmd.RegisterFlagCompletionFunc("introot", func(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective) {
+		completions := []string{}
+		for _, comp := range []string{"1", "2", "10"} {
+			if strings.HasPrefix(comp, toComplete) {
+				completions = append(completions, comp)
+			}
+		}
+		return completions, BashCompDirectiveDefault
+	})
+	rootCmd.Flags().String("filename", "", "Enter a filename")
+	rootCmd.RegisterFlagCompletionFunc("filename", func(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective) {
+		completions := []string{}
+		for _, comp := range []string{"file.yaml", "myfile.json", "file.xml"} {
+			if strings.HasPrefix(comp, toComplete) {
+				completions = append(completions, comp)
+			}
+		}
+		return completions, BashCompDirectiveNoSpace | BashCompDirectiveNoFileComp
+	})
+
+	// Test completing an empty string
+	output, err := executeCommand(rootCmd, CompRequestCmd, "--introot", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected := strings.Join([]string{
+		"1",
+		"2",
+		"10",
+		":0",
+		"Completion ended with directive: BashCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Check completing with a prefix
+	output, err = executeCommand(rootCmd, CompRequestCmd, "--introot", "1")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"1",
+		"10",
+		":0",
+		"Completion ended with directive: BashCompDirectiveDefault", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Test completing an empty string
+	output, err = executeCommand(rootCmd, CompRequestCmd, "--filename", "")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"file.yaml",
+		"myfile.json",
+		"file.xml",
+		":6",
+		"Completion ended with directive: BashCompDirectiveNoSpace, BashCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+
+	// Check completing with a prefix
+	output, err = executeCommand(rootCmd, CompRequestCmd, "--filename", "f")
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expected = strings.Join([]string{
+		"file.yaml",
+		"file.xml",
+		":6",
+		"Completion ended with directive: BashCompDirectiveNoSpace, BashCompDirectiveNoFileComp", ""}, "\n")
+
+	if output != expected {
+		t.Errorf("expected: %q, got: %q", expected, output)
+	}
+}