Optionally read stdin for gh alias set

Resolves #3487
This commit is contained in:
Heath Stewart 2021-04-21 22:16:41 -07:00
parent 5821065ac0
commit bd2738379b
2 changed files with 126 additions and 19 deletions

View file

@ -2,6 +2,7 @@ package set
import (
"fmt"
"io/ioutil"
"strings"
"github.com/MakeNowJust/heredoc"
@ -37,6 +38,7 @@ func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command
The expansion may specify additional arguments and flags. If the expansion
includes positional placeholders such as '$1', '$2', etc., any extra arguments
that follow the invocation of an alias will be inserted appropriately.
Reads from STDIN if not specified.
If '--shell' is specified, the alias will be run through a shell interpreter (sh). This allows you
to compose commands with "|" or redirect with ">". Note that extra arguments following the alias
@ -66,13 +68,21 @@ func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command
$ gh alias set --shell igrep 'gh issue list --label="$1" | grep $2'
$ gh igrep epic foo
#=> gh issue list --label="epic" | grep "foo"
# users.txt contains multiline 'api graphql -F name="$1" ...' with mixed quotes
$ gh alias set users < users.txt
$ gh users octocat
#=> gh api graphql -F name="octocat" ...
`),
Args: cobra.ExactArgs(2),
Args: cobra.RangeArgs(1, 2),
RunE: func(cmd *cobra.Command, args []string) error {
opts.RootCmd = cmd.Root()
opts.Name = args[0]
opts.Expansion = args[1]
if len(args) > 1 {
opts.Expansion = args[1]
}
if runF != nil {
return runF(opts)
@ -98,12 +108,16 @@ func setRun(opts *SetOptions) error {
return err
}
isTerminal := opts.IO.IsStdoutTTY()
if isTerminal {
fmt.Fprintf(opts.IO.ErrOut, "- Adding alias for %s: %s\n", cs.Bold(opts.Name), cs.Bold(opts.Expansion))
expansion, err := getExpansion(opts)
if err != nil {
return fmt.Errorf("did not understand expansion: %w", err)
}
isTerminal := opts.IO.IsStdoutTTY()
if isTerminal {
fmt.Fprintf(opts.IO.ErrOut, "- Adding alias for %s: %s\n", cs.Bold(opts.Name), cs.Bold(expansion))
}
expansion := opts.Expansion
isShell := opts.IsShell
if isShell && !strings.HasPrefix(expansion, "!") {
expansion = "!" + expansion
@ -149,3 +163,16 @@ func validCommand(rootCmd *cobra.Command, expansion string) bool {
cmd, _, err := rootCmd.Traverse(split)
return err == nil && cmd != rootCmd
}
func getExpansion(opts *SetOptions) (string, error) {
if opts.Expansion == "" {
stdin, err := ioutil.ReadAll(opts.IO.In)
if err != nil {
return "", fmt.Errorf("failed to read from STDIN: %w", err)
}
return string(stdin), nil
}
return opts.Expansion, nil
}

View file

@ -16,11 +16,12 @@ import (
"github.com/stretchr/testify/require"
)
func runCommand(cfg config.Config, isTTY bool, cli string) (*test.CmdOut, error) {
io, _, stdout, stderr := iostreams.Test()
func runCommand(cfg config.Config, isTTY bool, cli string, in string) (*test.CmdOut, error) {
io, stdin, stdout, stderr := iostreams.Test()
io.SetStdoutTTY(isTTY)
io.SetStdinTTY(isTTY)
io.SetStderrTTY(isTTY)
stdin.WriteString(in)
factory := &cmdutil.Factory{
IOStreams: io,
@ -41,6 +42,9 @@ func runCommand(cfg config.Config, isTTY bool, cli string) (*test.CmdOut, error)
issueCmd := &cobra.Command{Use: "issue"}
issueCmd.AddCommand(&cobra.Command{Use: "list"})
rootCmd.AddCommand(issueCmd)
apiCmd := &cobra.Command{Use: "api"}
apiCmd.AddCommand(&cobra.Command{Use: "graphql"})
rootCmd.AddCommand(apiCmd)
argv, err := shlex.Split("set " + cli)
if err != nil {
@ -48,7 +52,7 @@ func runCommand(cfg config.Config, isTTY bool, cli string) (*test.CmdOut, error)
}
rootCmd.SetArgs(argv)
rootCmd.SetIn(&bytes.Buffer{})
rootCmd.SetIn(stdin)
rootCmd.SetOut(ioutil.Discard)
rootCmd.SetErr(ioutil.Discard)
@ -64,7 +68,7 @@ func TestAliasSet_gh_command(t *testing.T) {
cfg := config.NewFromString(``)
_, err := runCommand(cfg, true, "pr 'pr status'")
_, err := runCommand(cfg, true, "pr 'pr status'", "")
assert.EqualError(t, err, `could not create alias: "pr" is already a gh command`)
}
@ -77,7 +81,7 @@ func TestAliasSet_empty_aliases(t *testing.T) {
editor: vim
`))
output, err := runCommand(cfg, true, "co 'pr checkout'")
output, err := runCommand(cfg, true, "co 'pr checkout'", "")
if err != nil {
t.Fatalf("unexpected error: %s", err)
@ -104,7 +108,7 @@ func TestAliasSet_existing_alias(t *testing.T) {
co: pr checkout
`))
output, err := runCommand(cfg, true, "co 'pr checkout -Rcool/repo'")
output, err := runCommand(cfg, true, "co 'pr checkout -Rcool/repo'", "")
require.NoError(t, err)
//nolint:staticcheck // prefer exact matchers over ExpectLines
@ -117,7 +121,7 @@ func TestAliasSet_space_args(t *testing.T) {
cfg := config.NewFromString(``)
output, err := runCommand(cfg, true, `il 'issue list -l "cool story"'`)
output, err := runCommand(cfg, true, `il 'issue list -l "cool story"'`, "")
require.NoError(t, err)
//nolint:staticcheck // prefer exact matchers over ExpectLines
@ -153,7 +157,7 @@ func TestAliasSet_arg_processing(t *testing.T) {
cfg := config.NewFromString(``)
output, err := runCommand(cfg, true, c.Cmd)
output, err := runCommand(cfg, true, c.Cmd, "")
if err != nil {
t.Fatalf("got unexpected error running %s: %s", c.Cmd, err)
}
@ -174,7 +178,7 @@ func TestAliasSet_init_alias_cfg(t *testing.T) {
editor: vim
`))
output, err := runCommand(cfg, true, "diff 'pr diff'")
output, err := runCommand(cfg, true, "diff 'pr diff'", "")
require.NoError(t, err)
expected := `editor: vim
@ -196,7 +200,7 @@ func TestAliasSet_existing_aliases(t *testing.T) {
foo: bar
`))
output, err := runCommand(cfg, true, "view 'pr view'")
output, err := runCommand(cfg, true, "view 'pr view'", "")
require.NoError(t, err)
expected := `aliases:
@ -215,7 +219,7 @@ func TestAliasSet_invalid_command(t *testing.T) {
cfg := config.NewFromString(``)
_, err := runCommand(cfg, true, "co 'pe checkout'")
_, err := runCommand(cfg, true, "co 'pe checkout'", "")
assert.EqualError(t, err, "could not create alias: pe checkout does not correspond to a gh command")
}
@ -225,7 +229,7 @@ func TestShellAlias_flag(t *testing.T) {
cfg := config.NewFromString(``)
output, err := runCommand(cfg, true, "--shell igrep 'gh issue list | grep'")
output, err := runCommand(cfg, true, "--shell igrep 'gh issue list | grep'", "")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
@ -245,7 +249,7 @@ func TestShellAlias_bang(t *testing.T) {
cfg := config.NewFromString(``)
output, err := runCommand(cfg, true, "igrep '!gh issue list | grep'")
output, err := runCommand(cfg, true, "igrep '!gh issue list | grep'", "")
require.NoError(t, err)
//nolint:staticcheck // prefer exact matchers over ExpectLines
@ -256,3 +260,79 @@ func TestShellAlias_bang(t *testing.T) {
`
assert.Equal(t, expected, mainBuf.String())
}
func TestShellAlias_from_stdin(t *testing.T) {
mainBuf := bytes.Buffer{}
defer config.StubWriteConfig(&mainBuf, ioutil.Discard)()
cfg := config.NewFromString(``)
output, err := runCommand(cfg, true, "users", `api graphql -F name="$1" -f query='
query ($name: String!) {
user(login: $name) {
name
}
}'`)
require.NoError(t, err)
//nolint:staticcheck // prefer exact matchers over ExpectLines
test.ExpectLines(t, output.Stderr(), "Adding alias for.*users")
expected := `aliases:
users: |-
api graphql -F name="$1" -f query='
query ($name: String!) {
user(login: $name) {
name
}
}'
`
assert.Equal(t, expected, mainBuf.String())
}
func TestShellAlias_getExpansion(t *testing.T) {
tests := []struct {
name string
want string
expansionArg string
stdin string
}{
{
name: "co",
want: "pr checkout",
expansionArg: "pr checkout",
},
{
name: "co",
want: "pr checkout",
expansionArg: "pr checkout",
stdin: "api graphql -F name=\"$1\"",
},
{
name: "stdin",
want: "api graphql -F name=\"$1\"",
stdin: "api graphql -F name=\"$1\"",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
io, stdin, _, _ := iostreams.Test()
io.SetStdinTTY(false)
_, err := stdin.WriteString(tt.stdin)
assert.NoError(t, err)
expansion, err := getExpansion(&SetOptions{
Expansion: tt.expansionArg,
IO: io,
})
assert.NoError(t, err)
assert.Equal(t, expansion, tt.want)
})
}
}