From 1881de6d41911f5b1ce8edbde8bf7bf8fc005930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Thu, 19 Aug 2021 20:01:18 +0200 Subject: [PATCH 1/2] Allow fully qualified extension name as argument to `upgrade`, `remove` --- pkg/cmd/extension/command.go | 11 ++++-- pkg/cmd/extension/command_test.go | 60 +++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/extension/command.go b/pkg/cmd/extension/command.go index eaed4618a..88a3e59b3 100644 --- a/pkg/cmd/extension/command.go +++ b/pkg/cmd/extension/command.go @@ -116,7 +116,7 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { var name string if len(args) > 0 { - name = args[0] + name = normalizeExtensionSelector(args[0]) } return m.Upgrade(name, flagForce, io.Out, io.ErrOut) }, @@ -130,7 +130,7 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command { Short: "Remove an installed extension", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - extName := args[0] + extName := normalizeExtensionSelector(args[0]) if err := m.Remove(extName); err != nil { return err } @@ -167,3 +167,10 @@ func checkValidExtension(rootCmd *cobra.Command, m extensions.ExtensionManager, return nil } + +func normalizeExtensionSelector(n string) string { + if idx := strings.IndexRune(n, '/'); idx >= 0 { + n = n[idx+1:] + } + return strings.TrimPrefix(n, "gh-") +} diff --git a/pkg/cmd/extension/command_test.go b/pkg/cmd/extension/command_test.go index 73639fac4..b9e8dce86 100644 --- a/pkg/cmd/extension/command_test.go +++ b/pkg/cmd/extension/command_test.go @@ -100,6 +100,34 @@ func TestNewCmdExtension(t *testing.T) { } }, }, + { + name: "upgrade an extension gh-prefix", + args: []string{"upgrade", "gh-hello"}, + managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) { + em.UpgradeFunc = func(name string, force bool, out, errOut io.Writer) error { + return nil + } + return func(t *testing.T) { + calls := em.UpgradeCalls() + assert.Equal(t, 1, len(calls)) + assert.Equal(t, "hello", calls[0].Name) + } + }, + }, + { + name: "upgrade an extension full name", + args: []string{"upgrade", "monalisa/gh-hello"}, + managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) { + em.UpgradeFunc = func(name string, force bool, out, errOut io.Writer) error { + return nil + } + return func(t *testing.T) { + calls := em.UpgradeCalls() + assert.Equal(t, 1, len(calls)) + assert.Equal(t, "hello", calls[0].Name) + } + }, + }, { name: "upgrade all", args: []string{"upgrade", "--all"}, @@ -146,6 +174,38 @@ func TestNewCmdExtension(t *testing.T) { isTTY: false, wantStdout: "", }, + { + name: "remove extension gh-prefix", + args: []string{"remove", "gh-hello"}, + managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) { + em.RemoveFunc = func(name string) error { + return nil + } + return func(t *testing.T) { + calls := em.RemoveCalls() + assert.Equal(t, 1, len(calls)) + assert.Equal(t, "hello", calls[0].Name) + } + }, + isTTY: false, + wantStdout: "", + }, + { + name: "remove extension full name", + args: []string{"remove", "monalisa/gh-hello"}, + managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) { + em.RemoveFunc = func(name string) error { + return nil + } + return func(t *testing.T) { + calls := em.RemoveCalls() + assert.Equal(t, 1, len(calls)) + assert.Equal(t, "hello", calls[0].Name) + } + }, + isTTY: false, + wantStdout: "", + }, { name: "list extensions", args: []string{"list"}, From 51d609078b51b25ae041283c001818ffa7f4dc29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Thu, 19 Aug 2021 20:38:19 +0200 Subject: [PATCH 2/2] Enable `gh help ` for extensions This sends the `--help` flag to the extension. The extension is reponsible for printing something useful as a result. --- cmd/gh/main.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/cmd/gh/main.go b/cmd/gh/main.go index 86c932988..b252dccbd 100644 --- a/cmd/gh/main.go +++ b/cmd/gh/main.go @@ -102,12 +102,17 @@ func mainRun() exitCode { expandedArgs = os.Args[1:] } - cmd, _, err := rootCmd.Traverse(expandedArgs) - if err != nil || cmd == rootCmd { + // translate `gh help ` to `gh --help` for extensions + if len(expandedArgs) == 2 && expandedArgs[0] == "help" && !hasCommand(rootCmd, expandedArgs[1:]) { + expandedArgs = []string{expandedArgs[1], "--help"} + } + + if !hasCommand(rootCmd, expandedArgs) { originalArgs := expandedArgs isShell := false - expandedArgs, isShell, err = expand.ExpandAlias(cfg, os.Args, nil) + argsForExpansion := append([]string{"gh"}, expandedArgs...) + expandedArgs, isShell, err = expand.ExpandAlias(cfg, argsForExpansion, nil) if err != nil { fmt.Fprintf(stderr, "failed to process aliases: %s\n", err) return exitError @@ -141,7 +146,7 @@ func mainRun() exitCode { } return exitOK - } else if c, _, err := rootCmd.Traverse(expandedArgs); err == nil && c == rootCmd && len(expandedArgs) > 0 { + } else if len(expandedArgs) > 0 && !hasCommand(rootCmd, expandedArgs) { extensionManager := cmdFactory.ExtensionManager if found, err := extensionManager.Dispatch(expandedArgs, os.Stdin, os.Stdout, os.Stderr); err != nil { var execError *exec.ExitError @@ -244,6 +249,12 @@ func mainRun() exitCode { return exitOK } +// hasCommand returns true if args resolve to a built-in command +func hasCommand(rootCmd *cobra.Command, args []string) bool { + c, _, err := rootCmd.Traverse(args) + return err == nil && c != rootCmd +} + func printError(out io.Writer, err error, cmd *cobra.Command, debug bool) { var dnsError *net.DNSError if errors.As(err, &dnsError) {