From 7ee208b09f12c42bb7f5b0e14cd071706ee17993 Mon Sep 17 00:00:00 2001 From: Rajat Jindal Date: Mon, 23 Apr 2018 05:47:20 -0700 Subject: [PATCH 1/4] support completions for command aliases (#669) * support completions for command aliases * try newer version of shellcheck * initialize aliashash only when BASH_VERSION > 3 --- .circleci/config.yml | 2 +- bash_completions.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bbba32b7..066df225 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,7 +13,7 @@ base: &base name: "All Commands" command: | mkdir -p bin - curl -Lso bin/shellcheck https://github.com/caarlos0/shellcheck-docker/releases/download/v0.4.3/shellcheck + curl -Lso bin/shellcheck https://github.com/caarlos0/shellcheck-docker/releases/download/v0.4.6/shellcheck chmod +x bin/shellcheck go get -t -v ./... PATH=$PATH:$PWD/bin go test -v ./... diff --git a/bash_completions.go b/bash_completions.go index 291eae7d..8fa8f486 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -251,6 +251,14 @@ __%[1]s_handle_word() __%[1]s_handle_command elif [[ $c -eq 0 ]]; then __%[1]s_handle_command + elif __%[1]s_contains_word "${words[c]}" "${command_aliases[@]}"; then + # aliashash variable is an associative array which is only supported in bash > 3. + if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then + words[c]=${aliashash[${words[c]}]} + __%[1]s_handle_command + else + __%[1]s_handle_noun + fi else __%[1]s_handle_noun fi @@ -266,6 +274,7 @@ func writePostscript(buf *bytes.Buffer, name string) { buf.WriteString(fmt.Sprintf(`{ local cur prev words cword declare -A flaghash 2>/dev/null || : + declare -A aliashash 2>/dev/null || : if declare -F _init_completion >/dev/null 2>&1; then _init_completion -s || return else @@ -305,6 +314,7 @@ func writeCommands(buf *bytes.Buffer, cmd *Command) { continue } buf.WriteString(fmt.Sprintf(" commands+=(%q)\n", c.Name())) + writeCmdAliases(buf, c) } buf.WriteString("\n") } @@ -443,6 +453,21 @@ func writeRequiredNouns(buf *bytes.Buffer, cmd *Command) { } } +func writeCmdAliases(buf *bytes.Buffer, cmd *Command) { + if len(cmd.Aliases) == 0 { + return + } + + sort.Sort(sort.StringSlice(cmd.Aliases)) + + buf.WriteString(fmt.Sprint(` if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then`, "\n")) + for _, value := range cmd.Aliases { + buf.WriteString(fmt.Sprintf(" command_aliases+=(%q)\n", value)) + buf.WriteString(fmt.Sprintf(" aliashash[%q]=%q\n", value, cmd.Name())) + } + buf.WriteString(` fi`) + buf.WriteString("\n") +} func writeArgAliases(buf *bytes.Buffer, cmd *Command) { buf.WriteString(" noun_aliases=()\n") sort.Sort(sort.StringSlice(cmd.ArgAliases)) @@ -469,6 +494,10 @@ func gen(buf *bytes.Buffer, cmd *Command) { } buf.WriteString(fmt.Sprintf(" last_command=%q\n", commandName)) + buf.WriteString("\n") + buf.WriteString(" command_aliases=()\n") + buf.WriteString("\n") + writeCommands(buf, cmd) writeFlags(buf, cmd) writeRequiredFlag(buf, cmd) From 0ab5b6bcfcde193577634059f6f211c850a456ec Mon Sep 17 00:00:00 2001 From: James DeFelice Date: Tue, 24 Apr 2018 12:15:12 -0400 Subject: [PATCH 2/4] doc: hide hidden parent flags (#686) * fixes #685 --- doc/man_docs.go | 4 ++-- doc/man_docs_test.go | 36 ++++++++++++++++++++++++++++++++++++ doc/md_docs.go | 4 ++-- doc/md_docs_test.go | 24 ++++++++++++++++++++++++ doc/rest_docs.go | 4 ++-- doc/rest_docs_test.go | 23 +++++++++++++++++++++++ 6 files changed, 89 insertions(+), 6 deletions(-) diff --git a/doc/man_docs.go b/doc/man_docs.go index ce92332d..baa48118 100644 --- a/doc/man_docs.go +++ b/doc/man_docs.go @@ -176,13 +176,13 @@ func manPrintFlags(buf *bytes.Buffer, flags *pflag.FlagSet) { func manPrintOptions(buf *bytes.Buffer, command *cobra.Command) { flags := command.NonInheritedFlags() - if flags.HasFlags() { + if flags.HasAvailableFlags() { buf.WriteString("# OPTIONS\n") manPrintFlags(buf, flags) buf.WriteString("\n") } flags = command.InheritedFlags() - if flags.HasFlags() { + if flags.HasAvailableFlags() { buf.WriteString("# OPTIONS INHERITED FROM PARENT COMMANDS\n") manPrintFlags(buf, flags) buf.WriteString("\n") diff --git a/doc/man_docs_test.go b/doc/man_docs_test.go index 62f85e47..2c400f5d 100644 --- a/doc/man_docs_test.go +++ b/doc/man_docs_test.go @@ -47,6 +47,42 @@ func TestGenManDoc(t *testing.T) { checkStringContains(t, output, translate("Auto generated")) } +func TestGenManNoHiddenParents(t *testing.T) { + header := &GenManHeader{ + Title: "Project", + Section: "2", + } + + // We generate on a subcommand so we have both subcommands and parents + for _, name := range []string{"rootflag", "strtwo"} { + f := rootCmd.PersistentFlags().Lookup(name) + f.Hidden = true + defer func() { f.Hidden = false }() + } + buf := new(bytes.Buffer) + if err := GenMan(echoCmd, header, buf); err != nil { + t.Fatal(err) + } + output := buf.String() + + // Make sure parent has - in CommandPath() in SEE ALSO: + parentPath := echoCmd.Parent().CommandPath() + dashParentPath := strings.Replace(parentPath, " ", "-", -1) + expected := translate(dashParentPath) + expected = expected + "(" + header.Section + ")" + checkStringContains(t, output, expected) + + checkStringContains(t, output, translate(echoCmd.Name())) + checkStringContains(t, output, translate(echoCmd.Name())) + checkStringContains(t, output, "boolone") + checkStringOmits(t, output, "rootflag") + checkStringContains(t, output, translate(rootCmd.Name())) + checkStringContains(t, output, translate(echoSubCmd.Name())) + checkStringOmits(t, output, translate(deprecatedCmd.Name())) + checkStringContains(t, output, translate("Auto generated")) + checkStringOmits(t, output, "OPTIONS INHERITED FROM PARENT COMMANDS") +} + func TestGenManNoGenTag(t *testing.T) { echoCmd.DisableAutoGenTag = true defer func() { echoCmd.DisableAutoGenTag = false }() diff --git a/doc/md_docs.go b/doc/md_docs.go index d7a2c2b6..d76f6d5e 100644 --- a/doc/md_docs.go +++ b/doc/md_docs.go @@ -29,7 +29,7 @@ import ( func printOptions(buf *bytes.Buffer, cmd *cobra.Command, name string) error { flags := cmd.NonInheritedFlags() flags.SetOutput(buf) - if flags.HasFlags() { + if flags.HasAvailableFlags() { buf.WriteString("### Options\n\n```\n") flags.PrintDefaults() buf.WriteString("```\n\n") @@ -37,7 +37,7 @@ func printOptions(buf *bytes.Buffer, cmd *cobra.Command, name string) error { parentFlags := cmd.InheritedFlags() parentFlags.SetOutput(buf) - if parentFlags.HasFlags() { + if parentFlags.HasAvailableFlags() { buf.WriteString("### Options inherited from parent commands\n\n```\n") parentFlags.PrintDefaults() buf.WriteString("```\n\n") diff --git a/doc/md_docs_test.go b/doc/md_docs_test.go index b0fa68c0..c060f32f 100644 --- a/doc/md_docs_test.go +++ b/doc/md_docs_test.go @@ -25,6 +25,30 @@ func TestGenMdDoc(t *testing.T) { checkStringContains(t, output, rootCmd.Short) checkStringContains(t, output, echoSubCmd.Short) checkStringOmits(t, output, deprecatedCmd.Short) + checkStringContains(t, output, "Options inherited from parent commands") +} + +func TestGenMdNoHiddenParents(t *testing.T) { + // We generate on subcommand so we have both subcommands and parents. + for _, name := range []string{"rootflag", "strtwo"} { + f := rootCmd.PersistentFlags().Lookup(name) + f.Hidden = true + defer func() { f.Hidden = false }() + } + buf := new(bytes.Buffer) + if err := GenMarkdown(echoCmd, buf); err != nil { + t.Fatal(err) + } + output := buf.String() + + checkStringContains(t, output, echoCmd.Long) + checkStringContains(t, output, echoCmd.Example) + checkStringContains(t, output, "boolone") + checkStringOmits(t, output, "rootflag") + checkStringContains(t, output, rootCmd.Short) + checkStringContains(t, output, echoSubCmd.Short) + checkStringOmits(t, output, deprecatedCmd.Short) + checkStringOmits(t, output, "Options inherited from parent commands") } func TestGenMdNoTag(t *testing.T) { diff --git a/doc/rest_docs.go b/doc/rest_docs.go index 4913e3ee..051d8dc8 100644 --- a/doc/rest_docs.go +++ b/doc/rest_docs.go @@ -29,7 +29,7 @@ import ( func printOptionsReST(buf *bytes.Buffer, cmd *cobra.Command, name string) error { flags := cmd.NonInheritedFlags() flags.SetOutput(buf) - if flags.HasFlags() { + if flags.HasAvailableFlags() { buf.WriteString("Options\n") buf.WriteString("~~~~~~~\n\n::\n\n") flags.PrintDefaults() @@ -38,7 +38,7 @@ func printOptionsReST(buf *bytes.Buffer, cmd *cobra.Command, name string) error parentFlags := cmd.InheritedFlags() parentFlags.SetOutput(buf) - if parentFlags.HasFlags() { + if parentFlags.HasAvailableFlags() { buf.WriteString("Options inherited from parent commands\n") buf.WriteString("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n::\n\n") parentFlags.PrintDefaults() diff --git a/doc/rest_docs_test.go b/doc/rest_docs_test.go index aa3186e8..330a2e5e 100644 --- a/doc/rest_docs_test.go +++ b/doc/rest_docs_test.go @@ -27,6 +27,29 @@ func TestGenRSTDoc(t *testing.T) { checkStringOmits(t, output, deprecatedCmd.Short) } +func TestGenRSTNoHiddenParents(t *testing.T) { + // We generate on a subcommand so we have both subcommands and parents + for _, name := range []string{"rootflag", "strtwo"} { + f := rootCmd.PersistentFlags().Lookup(name) + f.Hidden = true + defer func() { f.Hidden = false }() + } + buf := new(bytes.Buffer) + if err := GenReST(echoCmd, buf); err != nil { + t.Fatal(err) + } + output := buf.String() + + checkStringContains(t, output, echoCmd.Long) + checkStringContains(t, output, echoCmd.Example) + checkStringContains(t, output, "boolone") + checkStringOmits(t, output, "rootflag") + checkStringContains(t, output, rootCmd.Short) + checkStringContains(t, output, echoSubCmd.Short) + checkStringOmits(t, output, deprecatedCmd.Short) + checkStringOmits(t, output, "Options inherited from parent commands") +} + func TestGenRSTNoTag(t *testing.T) { rootCmd.DisableAutoGenTag = true defer func() { rootCmd.DisableAutoGenTag = false }() From ef82de70bb3f60c65fb8eebacbb2d122ef517385 Mon Sep 17 00:00:00 2001 From: Zef Hemel Date: Fri, 27 Apr 2018 15:45:50 +0200 Subject: [PATCH 3/4] Fixed code sample for bash completion (#687) --- bash_completions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bash_completions.md b/bash_completions.md index 8d01f456..e79d4769 100644 --- a/bash_completions.md +++ b/bash_completions.md @@ -181,7 +181,7 @@ a custom flag completion function with cobra.BashCompCustom: ```go annotation := make(map[string][]string) - annotation[cobra.BashCompFilenameExt] = []string{"__kubectl_get_namespaces"} + annotation[cobra.BashCompCustom] = []string{"__kubectl_get_namespaces"} flag := &pflag.Flag{ Name: "namespace", From 1e58aa3361fd650121dceeedc399e7189c05674a Mon Sep 17 00:00:00 2001 From: Jason Hendry Date: Fri, 1 Jun 2018 04:03:38 +1000 Subject: [PATCH 4/4] Include a basic example #465 (#631) --- bash_completions.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/bash_completions.md b/bash_completions.md index e79d4769..49d06806 100644 --- a/bash_completions.md +++ b/bash_completions.md @@ -1,5 +1,40 @@ # Generating Bash Completions For Your Own cobra.Command +If you are using the generator you can create a completion command by running + +```bash +cobra add completion +``` + +Update the help text show how to install the bash_completion Linux show here [Kubectl docs show mac options](https://kubernetes.io/docs/tasks/tools/install-kubectl/#enabling-shell-autocompletion) + +Writing the shell script to stdout allows the most flexible use. + +```go +// completionCmd represents the completion command +var completionCmd = &cobra.Command{ + Use: "completion", + Short: "Generates bash completion scripts", + Long: `To load completion run + +. <(bitbucket completion) + +To configure your bash shell to load completions for each session add to your bashrc + +# ~/.bashrc or ~/.profile +. <(bitbucket completion) +`, + Run: func(cmd *cobra.Command, args []string) { + rootCmd.GenBashCompletion(os.Stdout); + }, +} +``` + +**Note:** The cobra generator may include messages printed to stdout for example if the config file is loaded, this will break the auto complete script + + +## Example from kubectl + Generating bash completions from a cobra command is incredibly easy. An actual program which does so for the kubernetes kubectl binary is as follows: ```go