From e39894d4a0585568597cec819c80bc09b90e6136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Wed, 13 May 2020 12:49:52 +0200 Subject: [PATCH 1/3] Update to Cobra 1.0.0 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 193320caa..4f209c0c8 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/shurcooL/githubv4 v0.0.0-20191127044304-8f68eb5628d0 github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f // indirect - github.com/spf13/cobra v0.0.6 + github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.4.0 // indirect golang.org/x/crypto v0.0.0-20200219234226-1ad67e1f0ef4 diff --git a/go.sum b/go.sum index 807e68f62..f5a44fa70 100644 --- a/go.sum +++ b/go.sum @@ -158,8 +158,8 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.6 h1:breEStsVwemnKh2/s6gMvSdMEkwW0sK8vGStnlVBMCs= -github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= From ab4c6e2ad02a07c096478b81bcbe13714aa65bcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Wed, 13 May 2020 12:51:55 +0200 Subject: [PATCH 2/3] Remove obsolete hack to generate fish completions --- command/completion.go | 3 +- internal/cobrafish/completion.go | 171 ------------------------------- 2 files changed, 1 insertion(+), 173 deletions(-) delete mode 100644 internal/cobrafish/completion.go diff --git a/command/completion.go b/command/completion.go index 319d103a2..de7234d82 100644 --- a/command/completion.go +++ b/command/completion.go @@ -5,7 +5,6 @@ import ( "fmt" "os" - "github.com/cli/cli/internal/cobrafish" "github.com/cli/cli/utils" "github.com/spf13/cobra" ) @@ -58,7 +57,7 @@ Homebrew, see case "powershell": return RootCmd.GenPowerShellCompletion(cmd.OutOrStdout()) case "fish": - return cobrafish.GenCompletion(RootCmd, cmd.OutOrStdout()) + return RootCmd.GenFishCompletion(cmd.OutOrStdout(), true) default: return fmt.Errorf("unsupported shell type %q", shellType) } diff --git a/internal/cobrafish/completion.go b/internal/cobrafish/completion.go deleted file mode 100644 index 9fbe6946f..000000000 --- a/internal/cobrafish/completion.go +++ /dev/null @@ -1,171 +0,0 @@ -// imported from https://github.com/spf13/cobra/pull/754 -// author: Tim Reddehase - -package cobrafish - -import ( - "bytes" - "fmt" - "io" - "strings" - - "github.com/spf13/cobra" - "github.com/spf13/pflag" -) - -func GenCompletion(c *cobra.Command, w io.Writer) error { - buf := new(bytes.Buffer) - - writeFishPreamble(c, buf) - writeFishCommandCompletion(c, c, buf) - - _, err := buf.WriteTo(w) - return err -} - -func writeFishPreamble(cmd *cobra.Command, buf *bytes.Buffer) { - subCommandNames := []string{} - rangeCommands(cmd, func(subCmd *cobra.Command) { - subCommandNames = append(subCommandNames, subCmd.Name()) - }) - buf.WriteString(fmt.Sprintf(` -function __fish_%s_no_subcommand --description 'Test if %s has yet to be given the subcommand' - for i in (commandline -opc) - if contains -- $i %s - return 1 - end - end - return 0 -end -function __fish_%s_seen_subcommand_path --description 'Test whether the full path of subcommands is the current path' - set -l cmd (commandline -opc) - set -e cmd[1] - set -l pattern (string replace -a " " ".+" "$argv") - string match -r "$pattern" (string trim -- "$cmd") -end -# borrowed from current fish-shell master, since it is not in current 2.7.1 release -function __fish_seen_argument - argparse 's/short=+' 'l/long=+' -- $argv - set cmd (commandline -co) - set -e cmd[1] - for t in $cmd - for s in $_flag_s - if string match -qr "^-[A-z0-9]*"$s"[A-z0-9]*\$" -- $t - return 0 - end - end - for l in $_flag_l - if string match -q -- "--$l" $t - return 0 - end - end - end - return 1 -end -`, cmd.Name(), cmd.Name(), strings.Join(subCommandNames, " "), cmd.Name())) -} - -func writeFishCommandCompletion(rootCmd, cmd *cobra.Command, buf *bytes.Buffer) { - rangeCommands(cmd, func(subCmd *cobra.Command) { - condition := commandCompletionCondition(rootCmd, cmd) - escapedDescription := strings.Replace(subCmd.Short, "'", "\\'", -1) - buf.WriteString(fmt.Sprintf("complete -c %s -f %s -a %s -d '%s'\n", rootCmd.Name(), condition, subCmd.Name(), escapedDescription)) - }) - for _, validArg := range append(cmd.ValidArgs, cmd.ArgAliases...) { - condition := commandCompletionCondition(rootCmd, cmd) - buf.WriteString( - fmt.Sprintf("complete -c %s -f %s -a %s -d '%s'\n", - rootCmd.Name(), condition, validArg, fmt.Sprintf("Positional Argument to %s", cmd.Name()))) - } - writeCommandFlagsCompletion(rootCmd, cmd, buf) - rangeCommands(cmd, func(subCmd *cobra.Command) { - writeFishCommandCompletion(rootCmd, subCmd, buf) - }) -} - -func writeCommandFlagsCompletion(rootCmd, cmd *cobra.Command, buf *bytes.Buffer) { - cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { - if nonCompletableFlag(flag) { - return - } - writeCommandFlagCompletion(rootCmd, cmd, buf, flag) - }) - cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) { - if nonCompletableFlag(flag) { - return - } - writeCommandFlagCompletion(rootCmd, cmd, buf, flag) - }) -} - -func writeCommandFlagCompletion(rootCmd, cmd *cobra.Command, buf *bytes.Buffer, flag *pflag.Flag) { - shortHandPortion := "" - if len(flag.Shorthand) > 0 { - shortHandPortion = fmt.Sprintf("-s %s", flag.Shorthand) - } - condition := completionCondition(rootCmd, cmd) - escapedUsage := strings.Replace(flag.Usage, "'", "\\'", -1) - buf.WriteString(fmt.Sprintf("complete -c %s -f %s %s %s -l %s -d '%s'\n", - rootCmd.Name(), condition, flagRequiresArgumentCompletion(flag), shortHandPortion, flag.Name, escapedUsage)) -} - -func flagRequiresArgumentCompletion(flag *pflag.Flag) string { - if flag.Value.Type() != "bool" { - return "-r" - } - return "" -} - -func subCommandPath(rootCmd *cobra.Command, cmd *cobra.Command) string { - path := make([]string, 0, 1) - currentCmd := cmd - if rootCmd == cmd { - return "" - } - for { - path = append([]string{currentCmd.Name()}, path...) - if currentCmd.Parent() == rootCmd { - return strings.Join(path, " ") - } - currentCmd = currentCmd.Parent() - } -} - -func rangeCommands(cmd *cobra.Command, callback func(subCmd *cobra.Command)) { - for _, subCmd := range cmd.Commands() { - if !subCmd.IsAvailableCommand() || strings.HasPrefix(subCmd.Use, "help") { - continue - } - callback(subCmd) - } -} - -func commandCompletionCondition(rootCmd, cmd *cobra.Command) string { - localNonPersistentFlags := cmd.LocalNonPersistentFlags() - bareConditions := make([]string, 0, 1) - if rootCmd != cmd { - bareConditions = append(bareConditions, fmt.Sprintf("__fish_%s_seen_subcommand_path %s", rootCmd.Name(), subCommandPath(rootCmd, cmd))) - } else { - bareConditions = append(bareConditions, fmt.Sprintf("__fish_%s_no_subcommand", rootCmd.Name())) - } - localNonPersistentFlags.VisitAll(func(flag *pflag.Flag) { - flagSelector := fmt.Sprintf("-l %s", flag.Name) - if len(flag.Shorthand) > 0 { - flagSelector = fmt.Sprintf("-s %s %s", flag.Shorthand, flagSelector) - } - bareConditions = append(bareConditions, fmt.Sprintf("not __fish_seen_argument %s", flagSelector)) - }) - return fmt.Sprintf("-n '%s'", strings.Join(bareConditions, "; and ")) -} - -func completionCondition(rootCmd, cmd *cobra.Command) string { - condition := fmt.Sprintf("-n '__fish_%s_no_subcommand'", rootCmd.Name()) - if rootCmd != cmd { - condition = fmt.Sprintf("-n '__fish_%s_seen_subcommand_path %s'", rootCmd.Name(), subCommandPath(rootCmd, cmd)) - } - return condition -} - -func nonCompletableFlag(flag *pflag.Flag) bool { - return flag.Hidden || len(flag.Deprecated) > 0 -} From 108f7bd66c4072e2d43b04931fdd0400982c6abb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Fri, 22 May 2020 19:02:34 +0200 Subject: [PATCH 3/3] Dirty workaround to display helpful error message on mistyped subcommands When executing `gh pr re` (note the incomplete command name), Cobra would just display the help text for `gh pr` on standard output, exit with status 0, and not print any message that you have mistyped the "re" subcommand. Each part of this behavior is wrong. This workaround makes sure that the helpful error message is printed on stderr: $ gh pr re unknown command "re" for "gh pr" Did you mean this? reopen ready review However, the exit status is still 0, whereas it should be non-zero. Since `HelpFunc` does not return an error argument, we cannot trigger an error status from this workaround. --- command/root.go | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/command/root.go b/command/root.go index 6233be957..51c26afcc 100644 --- a/command/root.go +++ b/command/root.go @@ -249,9 +249,33 @@ func determineBaseRepo(apiClient *api.Client, cmd *cobra.Command, ctx context.Co return baseRepo, nil } -func rootHelpFunc(command *cobra.Command, s []string) { +func rootHelpFunc(command *cobra.Command, args []string) { if command != RootCmd { - cobraDefaultHelpFunc(command, s) + // Display helpful error message in case subcommand name was mistyped. + // This matches Cobra's behavior for root command, which Cobra + // confusingly doesn't apply to nested commands. + if command.Parent() == RootCmd && len(args) >= 2 { + if command.SuggestionsMinimumDistance <= 0 { + command.SuggestionsMinimumDistance = 2 + } + candidates := command.SuggestionsFor(args[1]) + + errOut := command.OutOrStderr() + fmt.Fprintf(errOut, "unknown command %q for %q\n", args[1], "gh "+args[0]) + + if len(candidates) > 0 { + fmt.Fprint(errOut, "\nDid you mean this?\n") + for _, c := range candidates { + fmt.Fprintf(errOut, "\t%s\n", c) + } + fmt.Fprint(errOut, "\n") + } + + oldOut := command.OutOrStdout() + command.SetOut(errOut) + defer command.SetOut(oldOut) + } + cobraDefaultHelpFunc(command, args) return }