Merge pull request #3490 from heaths/issue3487
Optionally read stdin for `gh alias set`
This commit is contained in:
commit
ac0fe6bf71
2 changed files with 125 additions and 18 deletions
|
|
@ -2,6 +2,7 @@ package set
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
|
|
@ -37,6 +38,8 @@ 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 '-' is specified as the expansion parameter. This can be useful
|
||||
for commands with mixed quotes or multiple lines.
|
||||
|
||||
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
|
||||
|
|
@ -46,7 +49,8 @@ func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command
|
|||
Platform note: on Windows, shell aliases are executed via "sh" as installed by Git For Windows. If
|
||||
you have installed git on Windows in some other way, shell aliases may not work for you.
|
||||
|
||||
Quotes must always be used when defining a command as in the examples.
|
||||
Quotes must always be used when defining a command as in the examples unless you pass '-'
|
||||
as the expansion parameter and pipe your command to 'gh alias set'.
|
||||
`),
|
||||
Example: heredoc.Doc(`
|
||||
$ gh alias set pv 'pr view'
|
||||
|
|
@ -66,6 +70,11 @@ 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),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
|
@ -98,12 +107,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 +162,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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,80 @@ 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",
|
||||
expansionArg: "-",
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue