Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
jpmcb committed Sep 23, 2020
2 parents ef3bfb2 + 7f8e83d commit d9d533d
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 16 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ Using Cobra is easy. First, use `go get` to install the latest version
of the library. This command will install the `cobra` generator executable
along with the library and its dependencies:

go get -u github.com/spf13/cobra/cobra
go get -u github.com/spf13/cobra

Next, include Cobra in your application:

Expand Down
12 changes: 8 additions & 4 deletions bash_completions.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,12 +495,14 @@ func writeFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) {

func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) {
name := flag.Name
format := " local_nonpersistent_flags+=(\"--%s"
format := " local_nonpersistent_flags+=(\"--%[1]s\")\n"
if len(flag.NoOptDefVal) == 0 {
format += "="
format += " local_nonpersistent_flags+=(\"--%[1]s=\")\n"
}
format += "\")\n"
buf.WriteString(fmt.Sprintf(format, name))
if len(flag.Shorthand) > 0 {
buf.WriteString(fmt.Sprintf(" local_nonpersistent_flags+=(\"-%s\")\n", flag.Shorthand))
}
}

// Setup annotations for go completions for registered flags
Expand Down Expand Up @@ -535,7 +537,9 @@ func writeFlags(buf *bytes.Buffer, cmd *Command) {
if len(flag.Shorthand) > 0 {
writeShortFlag(buf, flag, cmd)
}
if localNonPersistentFlags.Lookup(flag.Name) != nil {
// localNonPersistentFlags are used to stop the completion of subcommands when one is set
// if TraverseChildren is true we should allow to complete subcommands
if localNonPersistentFlags.Lookup(flag.Name) != nil && !cmd.Root().TraverseChildren {
writeLocalNonPersistentFlag(buf, flag)
}
})
Expand Down
25 changes: 25 additions & 0 deletions bash_completions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,13 @@ func TestBashCompletions(t *testing.T) {
checkOmit(t, output, `two_word_flags+=("--two-w-default")`)
checkOmit(t, output, `two_word_flags+=("-T")`)

// check local nonpersistent flag
check(t, output, `local_nonpersistent_flags+=("--two")`)
check(t, output, `local_nonpersistent_flags+=("--two=")`)
check(t, output, `local_nonpersistent_flags+=("-t")`)
check(t, output, `local_nonpersistent_flags+=("--two-w-default")`)
check(t, output, `local_nonpersistent_flags+=("-T")`)

checkOmit(t, output, deprecatedCmd.Name())

// If available, run shellcheck against the script.
Expand Down Expand Up @@ -235,3 +242,21 @@ func TestBashCompletionDeprecatedFlag(t *testing.T) {
t.Errorf("expected completion to not include %q flag: Got %v", flagName, output)
}
}

func TestBashCompletionTraverseChildren(t *testing.T) {
c := &Command{Use: "c", Run: emptyRun, TraverseChildren: true}

c.Flags().StringP("string-flag", "s", "", "string flag")
c.Flags().BoolP("bool-flag", "b", false, "bool flag")

buf := new(bytes.Buffer)
c.GenBashCompletion(buf)
output := buf.String()

// check that local nonpersistent flag are not set since we have TraverseChildren set to true
checkOmit(t, output, `local_nonpersistent_flags+=("--string-flag")`)
checkOmit(t, output, `local_nonpersistent_flags+=("--string-flag=")`)
checkOmit(t, output, `local_nonpersistent_flags+=("-s")`)
checkOmit(t, output, `local_nonpersistent_flags+=("--bool-flag")`)
checkOmit(t, output, `local_nonpersistent_flags+=("-b")`)
}
2 changes: 1 addition & 1 deletion cobra/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ cobra add config
cobra add create -p 'configCmd'
```

*Note: Use camelCase (not snake_case/snake-case) for command names.
*Note: Use camelCase (not snake_case/kebab-case) for command names.
Otherwise, you will encounter errors.
For example, `cobra add add-user` is incorrect, but `cobra add addUser` is valid.*

Expand Down
36 changes: 27 additions & 9 deletions custom_completions.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ func (c *Command) initCompleteCmd(args []string) {
comp = strings.Split(comp, "\t")[0]
}

// Make sure we only write the first line to the output.
// This is needed if a description contains a linebreak.
// Otherwise the shell scripts will interpret the other lines as new flags
// and could therefore provide a wrong completion.
comp = strings.Split(comp, "\n")[0]

// Finally trim the completion. This is especially important to get rid
// of a trailing tab when there are no description following it.
// For example, a sub-command without a description should not be completed
Expand Down Expand Up @@ -175,8 +181,16 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
toComplete := args[len(args)-1]
trimmedArgs := args[:len(args)-1]

var finalCmd *Command
var finalArgs []string
var err error
// Find the real command for which completion must be performed
finalCmd, finalArgs, err := c.Root().Find(trimmedArgs)
// check if we need to traverse here to parse local flags on parent commands
if c.Root().TraverseChildren {
finalCmd, finalArgs, err = c.Root().Traverse(trimmedArgs)
} else {
finalCmd, finalArgs, err = c.Root().Find(trimmedArgs)
}
if err != nil {
// Unable to find the real command. E.g., <program> someInvalidCmd <TAB>
return c, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs)
Expand Down Expand Up @@ -271,20 +285,24 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
var completions []string
directive := ShellCompDirectiveDefault
if flag == nil {
// Check if there are any local, non-persistent flags on the command-line
foundLocalNonPersistentFlag := false
localNonPersistentFlags := finalCmd.LocalNonPersistentFlags()
finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
if localNonPersistentFlags.Lookup(flag.Name) != nil && flag.Changed {
foundLocalNonPersistentFlag = true
}
})
// If TraverseChildren is true on the root command we don't check for
// local flags because we can use a local flag on a parent command
if !finalCmd.Root().TraverseChildren {
// Check if there are any local, non-persistent flags on the command-line
localNonPersistentFlags := finalCmd.LocalNonPersistentFlags()
finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
if localNonPersistentFlags.Lookup(flag.Name) != nil && flag.Changed {
foundLocalNonPersistentFlag = true
}
})
}

// Complete subcommand names, including the help command
if len(finalArgs) == 0 && !foundLocalNonPersistentFlag {
// We only complete sub-commands if:
// - there are no arguments on the command-line and
// - there are no local, non-peristent flag on the command-line
// - there are no local, non-peristent flag on the command-line or TraverseChildren is true
for _, subCmd := range finalCmd.Commands() {
if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand {
if strings.HasPrefix(subCmd.Name(), toComplete) {
Expand Down
62 changes: 61 additions & 1 deletion custom_completions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ func TestNoCmdNameCompletionInGo(t *testing.T) {
Use: "root",
Run: emptyRun,
}
rootCmd.Flags().String("localroot", "", "local root flag")

childCmd1 := &Command{
Use: "childCmd1",
Short: "First command",
Expand Down Expand Up @@ -187,6 +189,64 @@ func TestNoCmdNameCompletionInGo(t *testing.T) {
t.Errorf("expected: %q, got: %q", expected, output)
}

// Test that sub-command names are completed if a local non-persistent flag is present and TraverseChildren is set to true
// set TraverseChildren to true on the root cmd
rootCmd.TraverseChildren = true

output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--localroot", "value", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset TraverseChildren for next command
rootCmd.TraverseChildren = false

expected = strings.Join([]string{
"childCmd1",
"help",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")

if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}

// Test that sub-command names from a child cmd are completed if a local non-persistent flag is present
// and TraverseChildren is set to true on the root cmd
rootCmd.TraverseChildren = true

output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--localroot", "value", "childCmd1", "--nonPersistent", "value", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset TraverseChildren for next command
rootCmd.TraverseChildren = false
// Reset the flag for the next command
nonPersistentFlag.Changed = false

expected = strings.Join([]string{
"childCmd2",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")

if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}

// Test that we don't use Traverse when we shouldn't.
// This command should not return a completion since the command line is invalid without TraverseChildren.
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--localroot", "value", "childCmd1", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}

expected = strings.Join([]string{
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")

if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}

// Test that sub-command names are not completed if a local non-persistent short flag is present
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "-n", "value", "")
if err != nil {
Expand Down Expand Up @@ -501,7 +561,7 @@ func TestFlagNameCompletionInGoWithDesc(t *testing.T) {
}
rootCmd.AddCommand(childCmd)

rootCmd.Flags().IntP("first", "f", -1, "first flag")
rootCmd.Flags().IntP("first", "f", -1, "first flag\nlonger description for flag")
rootCmd.PersistentFlags().BoolP("second", "s", false, "second flag")
childCmd.Flags().String("subFlag", "", "sub flag")

Expand Down
2 changes: 2 additions & 0 deletions projects_using_cobra.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## Projects using Cobra

- [Arduino CLI](https://github.com/arduino/arduino-cli)
- [Bleve](http://www.blevesearch.com/)
- [CockroachDB](http://www.cockroachlabs.com/)
- [Delve](https://github.com/derekparker/delve)
Expand All @@ -15,6 +16,7 @@
- [Helm](https://helm.sh)
- [Hugo](https://gohugo.io)
- [Istio](https://istio.io)
- [Kool](https://github.com/kool-dev/kool)
- [Kubernetes](http://kubernetes.io/)
- [Linkerd](https://linkerd.io/)
- [Mattermost-server](https://github.com/mattermost/mattermost-server)
Expand Down

0 comments on commit d9d533d

Please sign in to comment.