X Tutup
Skip to content

Commit ac0fe6b

Browse files
author
Nate Smith
authored
Merge pull request cli#3490 from heaths/issue3487
Optionally read stdin for `gh alias set`
2 parents 5a2ec54 + aaa5a9e commit ac0fe6b

File tree

2 files changed

+123
-16
lines changed

2 files changed

+123
-16
lines changed

pkg/cmd/alias/set/set.go

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package set
22

33
import (
44
"fmt"
5+
"io/ioutil"
56
"strings"
67

78
"github.com/MakeNowJust/heredoc"
@@ -37,6 +38,8 @@ func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command
3738
The expansion may specify additional arguments and flags. If the expansion
3839
includes positional placeholders such as '$1', '$2', etc., any extra arguments
3940
that follow the invocation of an alias will be inserted appropriately.
41+
Reads from STDIN if '-' is specified as the expansion parameter. This can be useful
42+
for commands with mixed quotes or multiple lines.
4043
4144
If '--shell' is specified, the alias will be run through a shell interpreter (sh). This allows you
4245
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
4649
Platform note: on Windows, shell aliases are executed via "sh" as installed by Git For Windows. If
4750
you have installed git on Windows in some other way, shell aliases may not work for you.
4851
49-
Quotes must always be used when defining a command as in the examples.
52+
Quotes must always be used when defining a command as in the examples unless you pass '-'
53+
as the expansion parameter and pipe your command to 'gh alias set'.
5054
`),
5155
Example: heredoc.Doc(`
5256
$ gh alias set pv 'pr view'
@@ -66,6 +70,11 @@ func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command
6670
$ gh alias set --shell igrep 'gh issue list --label="$1" | grep $2'
6771
$ gh igrep epic foo
6872
#=> gh issue list --label="epic" | grep "foo"
73+
74+
# users.txt contains multiline 'api graphql -F name="$1" ...' with mixed quotes
75+
$ gh alias set users - < users.txt
76+
$ gh users octocat
77+
#=> gh api graphql -F name="octocat" ...
6978
`),
7079
Args: cobra.ExactArgs(2),
7180
RunE: func(cmd *cobra.Command, args []string) error {
@@ -98,12 +107,16 @@ func setRun(opts *SetOptions) error {
98107
return err
99108
}
100109

110+
expansion, err := getExpansion(opts)
111+
if err != nil {
112+
return fmt.Errorf("did not understand expansion: %w", err)
113+
}
114+
101115
isTerminal := opts.IO.IsStdoutTTY()
102116
if isTerminal {
103-
fmt.Fprintf(opts.IO.ErrOut, "- Adding alias for %s: %s\n", cs.Bold(opts.Name), cs.Bold(opts.Expansion))
117+
fmt.Fprintf(opts.IO.ErrOut, "- Adding alias for %s: %s\n", cs.Bold(opts.Name), cs.Bold(expansion))
104118
}
105119

106-
expansion := opts.Expansion
107120
isShell := opts.IsShell
108121
if isShell && !strings.HasPrefix(expansion, "!") {
109122
expansion = "!" + expansion
@@ -149,3 +162,16 @@ func validCommand(rootCmd *cobra.Command, expansion string) bool {
149162
cmd, _, err := rootCmd.Traverse(split)
150163
return err == nil && cmd != rootCmd
151164
}
165+
166+
func getExpansion(opts *SetOptions) (string, error) {
167+
if opts.Expansion == "-" {
168+
stdin, err := ioutil.ReadAll(opts.IO.In)
169+
if err != nil {
170+
return "", fmt.Errorf("failed to read from STDIN: %w", err)
171+
}
172+
173+
return string(stdin), nil
174+
}
175+
176+
return opts.Expansion, nil
177+
}

pkg/cmd/alias/set/set_test.go

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ import (
1616
"github.com/stretchr/testify/require"
1717
)
1818

19-
func runCommand(cfg config.Config, isTTY bool, cli string) (*test.CmdOut, error) {
20-
io, _, stdout, stderr := iostreams.Test()
19+
func runCommand(cfg config.Config, isTTY bool, cli string, in string) (*test.CmdOut, error) {
20+
io, stdin, stdout, stderr := iostreams.Test()
2121
io.SetStdoutTTY(isTTY)
2222
io.SetStdinTTY(isTTY)
2323
io.SetStderrTTY(isTTY)
24+
stdin.WriteString(in)
2425

2526
factory := &cmdutil.Factory{
2627
IOStreams: io,
@@ -41,14 +42,17 @@ func runCommand(cfg config.Config, isTTY bool, cli string) (*test.CmdOut, error)
4142
issueCmd := &cobra.Command{Use: "issue"}
4243
issueCmd.AddCommand(&cobra.Command{Use: "list"})
4344
rootCmd.AddCommand(issueCmd)
45+
apiCmd := &cobra.Command{Use: "api"}
46+
apiCmd.AddCommand(&cobra.Command{Use: "graphql"})
47+
rootCmd.AddCommand(apiCmd)
4448

4549
argv, err := shlex.Split("set " + cli)
4650
if err != nil {
4751
return nil, err
4852
}
4953
rootCmd.SetArgs(argv)
5054

51-
rootCmd.SetIn(&bytes.Buffer{})
55+
rootCmd.SetIn(stdin)
5256
rootCmd.SetOut(ioutil.Discard)
5357
rootCmd.SetErr(ioutil.Discard)
5458

@@ -64,7 +68,7 @@ func TestAliasSet_gh_command(t *testing.T) {
6468

6569
cfg := config.NewFromString(``)
6670

67-
_, err := runCommand(cfg, true, "pr 'pr status'")
71+
_, err := runCommand(cfg, true, "pr 'pr status'", "")
6872
assert.EqualError(t, err, `could not create alias: "pr" is already a gh command`)
6973
}
7074

@@ -77,7 +81,7 @@ func TestAliasSet_empty_aliases(t *testing.T) {
7781
editor: vim
7882
`))
7983

80-
output, err := runCommand(cfg, true, "co 'pr checkout'")
84+
output, err := runCommand(cfg, true, "co 'pr checkout'", "")
8185

8286
if err != nil {
8387
t.Fatalf("unexpected error: %s", err)
@@ -104,7 +108,7 @@ func TestAliasSet_existing_alias(t *testing.T) {
104108
co: pr checkout
105109
`))
106110

107-
output, err := runCommand(cfg, true, "co 'pr checkout -Rcool/repo'")
111+
output, err := runCommand(cfg, true, "co 'pr checkout -Rcool/repo'", "")
108112
require.NoError(t, err)
109113

110114
//nolint:staticcheck // prefer exact matchers over ExpectLines
@@ -117,7 +121,7 @@ func TestAliasSet_space_args(t *testing.T) {
117121

118122
cfg := config.NewFromString(``)
119123

120-
output, err := runCommand(cfg, true, `il 'issue list -l "cool story"'`)
124+
output, err := runCommand(cfg, true, `il 'issue list -l "cool story"'`, "")
121125
require.NoError(t, err)
122126

123127
//nolint:staticcheck // prefer exact matchers over ExpectLines
@@ -153,7 +157,7 @@ func TestAliasSet_arg_processing(t *testing.T) {
153157

154158
cfg := config.NewFromString(``)
155159

156-
output, err := runCommand(cfg, true, c.Cmd)
160+
output, err := runCommand(cfg, true, c.Cmd, "")
157161
if err != nil {
158162
t.Fatalf("got unexpected error running %s: %s", c.Cmd, err)
159163
}
@@ -174,7 +178,7 @@ func TestAliasSet_init_alias_cfg(t *testing.T) {
174178
editor: vim
175179
`))
176180

177-
output, err := runCommand(cfg, true, "diff 'pr diff'")
181+
output, err := runCommand(cfg, true, "diff 'pr diff'", "")
178182
require.NoError(t, err)
179183

180184
expected := `editor: vim
@@ -196,7 +200,7 @@ func TestAliasSet_existing_aliases(t *testing.T) {
196200
foo: bar
197201
`))
198202

199-
output, err := runCommand(cfg, true, "view 'pr view'")
203+
output, err := runCommand(cfg, true, "view 'pr view'", "")
200204
require.NoError(t, err)
201205

202206
expected := `aliases:
@@ -215,7 +219,7 @@ func TestAliasSet_invalid_command(t *testing.T) {
215219

216220
cfg := config.NewFromString(``)
217221

218-
_, err := runCommand(cfg, true, "co 'pe checkout'")
222+
_, err := runCommand(cfg, true, "co 'pe checkout'", "")
219223
assert.EqualError(t, err, "could not create alias: pe checkout does not correspond to a gh command")
220224
}
221225

@@ -225,7 +229,7 @@ func TestShellAlias_flag(t *testing.T) {
225229

226230
cfg := config.NewFromString(``)
227231

228-
output, err := runCommand(cfg, true, "--shell igrep 'gh issue list | grep'")
232+
output, err := runCommand(cfg, true, "--shell igrep 'gh issue list | grep'", "")
229233
if err != nil {
230234
t.Fatalf("unexpected error: %s", err)
231235
}
@@ -245,7 +249,7 @@ func TestShellAlias_bang(t *testing.T) {
245249

246250
cfg := config.NewFromString(``)
247251

248-
output, err := runCommand(cfg, true, "igrep '!gh issue list | grep'")
252+
output, err := runCommand(cfg, true, "igrep '!gh issue list | grep'", "")
249253
require.NoError(t, err)
250254

251255
//nolint:staticcheck // prefer exact matchers over ExpectLines
@@ -256,3 +260,80 @@ func TestShellAlias_bang(t *testing.T) {
256260
`
257261
assert.Equal(t, expected, mainBuf.String())
258262
}
263+
264+
func TestShellAlias_from_stdin(t *testing.T) {
265+
mainBuf := bytes.Buffer{}
266+
defer config.StubWriteConfig(&mainBuf, ioutil.Discard)()
267+
268+
cfg := config.NewFromString(``)
269+
270+
output, err := runCommand(cfg, true, "users -", `api graphql -F name="$1" -f query='
271+
query ($name: String!) {
272+
user(login: $name) {
273+
name
274+
}
275+
}'`)
276+
277+
require.NoError(t, err)
278+
279+
//nolint:staticcheck // prefer exact matchers over ExpectLines
280+
test.ExpectLines(t, output.Stderr(), "Adding alias for.*users")
281+
282+
expected := `aliases:
283+
users: |-
284+
api graphql -F name="$1" -f query='
285+
query ($name: String!) {
286+
user(login: $name) {
287+
name
288+
}
289+
}'
290+
`
291+
292+
assert.Equal(t, expected, mainBuf.String())
293+
}
294+
295+
func TestShellAlias_getExpansion(t *testing.T) {
296+
tests := []struct {
297+
name string
298+
want string
299+
expansionArg string
300+
stdin string
301+
}{
302+
{
303+
name: "co",
304+
want: "pr checkout",
305+
expansionArg: "pr checkout",
306+
},
307+
{
308+
name: "co",
309+
want: "pr checkout",
310+
expansionArg: "pr checkout",
311+
stdin: "api graphql -F name=\"$1\"",
312+
},
313+
{
314+
name: "stdin",
315+
expansionArg: "-",
316+
want: "api graphql -F name=\"$1\"",
317+
stdin: "api graphql -F name=\"$1\"",
318+
},
319+
}
320+
321+
for _, tt := range tests {
322+
t.Run(tt.name, func(t *testing.T) {
323+
io, stdin, _, _ := iostreams.Test()
324+
325+
io.SetStdinTTY(false)
326+
327+
_, err := stdin.WriteString(tt.stdin)
328+
assert.NoError(t, err)
329+
330+
expansion, err := getExpansion(&SetOptions{
331+
Expansion: tt.expansionArg,
332+
IO: io,
333+
})
334+
assert.NoError(t, err)
335+
336+
assert.Equal(t, expansion, tt.want)
337+
})
338+
}
339+
}

0 commit comments

Comments
 (0)
X Tutup