Repository: spf13/cobra
Pull request #1196 (2020-08-20 19:58:51)

Modifying "snake-case" to "kebab-case" for clarity.

CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/spf13/cobra?pullRequest=1196) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/spf13/cobra?pullRequest=1196) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/spf13/cobra?pullRequest=1196) it.</sub>

Patch:
diff --git a/README.md b/README.md
index 53d174c..dd937e5 100644
--- a/README.md
+++ b/README.md
@@ -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:
 
diff --git a/bash_completions.go b/bash_completions.go
index ab428cc..846636d 100644
--- a/bash_completions.go
+++ b/bash_completions.go
@@ -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
@@ -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)
 		}
 	})
diff --git a/cobra/README.md b/cobra/README.md
index 0f718c2..dcaf0c5 100644
--- a/cobra/README.md
+++ b/cobra/README.md
@@ -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.*
 
diff --git a/cobra/go.mod b/cobra/go.mod
index c53413d..ae1e15e 100644
--- a/cobra/go.mod
+++ b/cobra/go.mod
@@ -4,7 +4,7 @@ go 1.12
 
 require (
 	github.com/mitchellh/go-homedir v1.1.0
-	github.com/spf13/cobra v0.0.0
+	github.com/spf13/cobra v1.0.0
 	github.com/spf13/viper v1.7.0
 )
 
diff --git a/custom_completions.go b/custom_completions.go
index c25c03e..f9e88e0 100644
--- a/custom_completions.go
+++ b/custom_completions.go
@@ -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
@@ -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)
@@ -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) {
diff --git a/doc/md_docs.go b/doc/md_docs.go
index d76f6d5..5181af8 100644
--- a/doc/md_docs.go
+++ b/doc/md_docs.go
@@ -58,16 +58,12 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string)
 	buf := new(bytes.Buffer)
 	name := cmd.CommandPath()
 
-	short := cmd.Short
-	long := cmd.Long
-	if len(long) == 0 {
-		long = short
-	}
-
 	buf.WriteString("## " + name + "\n\n")
-	buf.WriteString(short + "\n\n")
-	buf.WriteString("### Synopsis\n\n")
-	buf.WriteString(long + "\n\n")
+	buf.WriteString(cmd.Short + "\n\n")
+	if len(cmd.Long) > 0 {
+		buf.WriteString("### Synopsis\n\n")
+		buf.WriteString(cmd.Long + "\n\n")
+	}
 
 	if cmd.Runnable() {
 		buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.UseLine()))
diff --git a/projects_using_cobra.md b/projects_using_cobra.md
index 0397c30..5cc4e8d 100644
--- a/projects_using_cobra.md
+++ b/projects_using_cobra.md
@@ -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)
@@ -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)


Test patch:
diff --git a/bash_completions_test.go b/bash_completions_test.go
index eefa3de..2c182ba 100644
--- a/bash_completions_test.go
+++ b/bash_completions_test.go
@@ -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.
@@ -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")`)
+}
diff --git a/custom_completions_test.go b/custom_completions_test.go
index 276b8a7..14ec5a9 100644
--- a/custom_completions_test.go
+++ b/custom_completions_test.go
@@ -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",
@@ -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 {
@@ -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")
 
diff --git a/doc/cmd_test.go b/doc/cmd_test.go
index d29c577..0917d60 100644
--- a/doc/cmd_test.go
+++ b/doc/cmd_test.go
@@ -27,7 +27,7 @@ func init() {
 	printCmd.Flags().BoolP("boolthree", "b", true, "help message for flag boolthree")
 
 	echoCmd.AddCommand(timesCmd, echoSubCmd, deprecatedCmd)
-	rootCmd.AddCommand(printCmd, echoCmd)
+	rootCmd.AddCommand(printCmd, echoCmd, dummyCmd)
 }
 
 var rootCmd = &cobra.Command{
@@ -73,6 +73,11 @@ var printCmd = &cobra.Command{
 	Long:  `an absolutely utterly useless command for testing.`,
 }
 
+var dummyCmd = &cobra.Command{
+	Use:   "dummy [action]",
+	Short: "Performs a dummy action",
+}
+
 func checkStringContains(t *testing.T, got, expected string) {
 	if !strings.Contains(got, expected) {
 		t.Errorf("Expected to contain: \n %v\nGot:\n %v\n", expected, got)
diff --git a/doc/md_docs_test.go b/doc/md_docs_test.go
index c060f32..f325167 100644
--- a/doc/md_docs_test.go
+++ b/doc/md_docs_test.go
@@ -28,6 +28,20 @@ func TestGenMdDoc(t *testing.T) {
 	checkStringContains(t, output, "Options inherited from parent commands")
 }
 
+func TestGenMdDocWithNoLongOrSynopsis(t *testing.T) {
+	// We generate on subcommand so we have both subcommands and parents.
+	buf := new(bytes.Buffer)
+	if err := GenMarkdown(dummyCmd, buf); err != nil {
+		t.Fatal(err)
+	}
+	output := buf.String()
+
+	checkStringContains(t, output, dummyCmd.Example)
+	checkStringContains(t, output, dummyCmd.Short)
+	checkStringContains(t, output, "Options inherited from parent commands")
+	checkStringOmits(t, output, "### Synopsis")
+}
+
 func TestGenMdNoHiddenParents(t *testing.T) {
 	// We generate on subcommand so we have both subcommands and parents.
 	for _, name := range []string{"rootflag", "strtwo"} {