diff --git a/pkg/cmd/api/api.go b/pkg/cmd/api/api.go
index 2a80c464c..ea0734d21 100644
--- a/pkg/cmd/api/api.go
+++ b/pkg/cmd/api/api.go
@@ -387,7 +387,11 @@ func processResponse(resp *http.Response, opts *ApiOptions, bodyWriter, headersW
if opts.FilterOutput != "" && serverError == "" {
// TODO: reuse parsed query across pagination invocations
- err = jq.Evaluate(responseBody, bodyWriter, opts.FilterOutput)
+ indent := ""
+ if opts.IO.IsStdoutTTY() {
+ indent = " "
+ }
+ err = jq.EvaluateFormatted(responseBody, bodyWriter, opts.FilterOutput, indent, opts.IO.ColorEnabled())
if err != nil {
return
}
diff --git a/pkg/cmd/api/api_test.go b/pkg/cmd/api/api_test.go
index d2fdc3c3c..ebbff6b6e 100644
--- a/pkg/cmd/api/api_test.go
+++ b/pkg/cmd/api/api_test.go
@@ -378,6 +378,7 @@ func Test_apiRun(t *testing.T) {
err error
stdout string
stderr string
+ isatty bool
}{
{
name: "success",
@@ -388,6 +389,7 @@ func Test_apiRun(t *testing.T) {
err: nil,
stdout: `bam!`,
stderr: ``,
+ isatty: false,
},
{
name: "show response headers",
@@ -404,6 +406,7 @@ func Test_apiRun(t *testing.T) {
err: nil,
stdout: "HTTP/1.1 200 Okey-dokey\nContent-Type: text/plain\r\n\r\nbody",
stderr: ``,
+ isatty: false,
},
{
name: "success 204",
@@ -414,6 +417,7 @@ func Test_apiRun(t *testing.T) {
err: nil,
stdout: ``,
stderr: ``,
+ isatty: false,
},
{
name: "REST error",
@@ -425,6 +429,7 @@ func Test_apiRun(t *testing.T) {
err: cmdutil.SilentError,
stdout: `{"message": "THIS IS FINE"}`,
stderr: "gh: THIS IS FINE (HTTP 400)\n",
+ isatty: false,
},
{
name: "REST string errors",
@@ -436,6 +441,7 @@ func Test_apiRun(t *testing.T) {
err: cmdutil.SilentError,
stdout: `{"errors": ["ALSO", "FINE"]}`,
stderr: "gh: ALSO\nFINE\n",
+ isatty: false,
},
{
name: "GraphQL error",
@@ -450,6 +456,7 @@ func Test_apiRun(t *testing.T) {
err: cmdutil.SilentError,
stdout: `{"errors": [{"message":"AGAIN"}, {"message":"FINE"}]}`,
stderr: "gh: AGAIN\nFINE\n",
+ isatty: false,
},
{
name: "failure",
@@ -460,6 +467,7 @@ func Test_apiRun(t *testing.T) {
err: cmdutil.SilentError,
stdout: `gateway timeout`,
stderr: "gh: HTTP 502\n",
+ isatty: false,
},
{
name: "silent",
@@ -473,6 +481,7 @@ func Test_apiRun(t *testing.T) {
err: nil,
stdout: ``,
stderr: ``,
+ isatty: false,
},
{
name: "show response headers even when silent",
@@ -490,6 +499,7 @@ func Test_apiRun(t *testing.T) {
err: nil,
stdout: "HTTP/1.1 200 Okey-dokey\nContent-Type: text/plain\r\n\r\n",
stderr: ``,
+ isatty: false,
},
{
name: "output template",
@@ -504,6 +514,7 @@ func Test_apiRun(t *testing.T) {
err: nil,
stdout: "not a cat",
stderr: ``,
+ isatty: false,
},
{
name: "output template when REST error",
@@ -518,6 +529,7 @@ func Test_apiRun(t *testing.T) {
err: cmdutil.SilentError,
stdout: `{"message": "THIS IS FINE"}`,
stderr: "gh: THIS IS FINE (HTTP 400)\n",
+ isatty: false,
},
{
name: "jq filter",
@@ -532,6 +544,7 @@ func Test_apiRun(t *testing.T) {
err: nil,
stdout: "Mona\nHubot\n",
stderr: ``,
+ isatty: false,
},
{
name: "jq filter when REST error",
@@ -546,12 +559,29 @@ func Test_apiRun(t *testing.T) {
err: cmdutil.SilentError,
stdout: `{"message": "THIS IS FINE"}`,
stderr: "gh: THIS IS FINE (HTTP 400)\n",
+ isatty: false,
+ },
+ {
+ name: "jq filter outputting JSON to a TTY",
+ options: ApiOptions{
+ FilterOutput: `.`,
+ },
+ httpResponse: &http.Response{
+ StatusCode: 200,
+ Body: io.NopCloser(bytes.NewBufferString(`[{"name":"Mona"},{"name":"Hubot"}]`)),
+ Header: http.Header{"Content-Type": []string{"application/json"}},
+ },
+ err: nil,
+ stdout: "[\n {\n \"name\": \"Mona\"\n },\n {\n \"name\": \"Hubot\"\n }\n]\n",
+ stderr: ``,
+ isatty: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ios, _, stdout, stderr := iostreams.Test()
+ ios.SetStdoutTTY(tt.isatty)
tt.options.IO = ios
tt.options.Config = func() (config.Config, error) { return config.NewBlankConfig(), nil }
diff --git a/pkg/cmd/root/help_topic.go b/pkg/cmd/root/help_topic.go
index 4a4d19a51..4df7d9e1a 100644
--- a/pkg/cmd/root/help_topic.go
+++ b/pkg/cmd/root/help_topic.go
@@ -111,8 +111,9 @@ var HelpTopics = map[string]map[string]string{
The %[1]s--jq%[1]s flag requires a string argument in jq query syntax, and will only print
those JSON values which match the query. jq queries can be used to select elements from an
array, fields from an object, create a new array, and more. The jq utility does not need
- to be installed on the system to use this formatting directive.
- To learn about jq query syntax, see:
+ to be installed on the system to use this formatting directive. When connected to a terminal,
+ the output is automatically pretty-printed. To learn about jq query syntax, see:
+
The %[1]s--template%[1]s flag requires a string argument in Go template syntax, and will only print
those JSON values which match the query.
@@ -174,7 +175,38 @@ var HelpTopics = map[string]map[string]string{
codercat
cli-maintainer
-
+ # --jq can be used to implement more complex filtering and output changes:
+ $ bin/gh issue list --json number,title,labels --jq \
+ 'map(select((.labels | length) > 0)) # must have labels
+ | map(.labels = (.labels | map(.name))) # show only the label names
+ | .[:3] # select the first 3 results'
+ [
+ {
+ "labels": [
+ "enhancement",
+ "needs triage"
+ ],
+ "number": 123,
+ "title": "A helpful contribution"
+ },
+ {
+ "labels": [
+ "help wanted",
+ "docs",
+ "good first issue"
+ ],
+ "number": 125,
+ "title": "Improve the docs"
+ },
+ {
+ "labels": [
+ "enhancement",
+ ],
+ "number": 7221,
+ "title": "An exciting new feature"
+ }
+ ]
+
# using the --template flag with the hyperlink helper
gh issue list --json title,url --template '{{range .}}{{hyperlink .url .title}}{{"\n"}}{{end}}'
diff --git a/pkg/cmdutil/json_flags.go b/pkg/cmdutil/json_flags.go
index d9bb89762..0aef38db4 100644
--- a/pkg/cmdutil/json_flags.go
+++ b/pkg/cmdutil/json_flags.go
@@ -138,7 +138,13 @@ func (e *exportFormat) Write(ios *iostreams.IOStreams, data interface{}) error {
w := ios.Out
if e.filter != "" {
- return jq.Evaluate(&buf, w, e.filter)
+ indent := ""
+ if ios.IsStdoutTTY() {
+ indent = " "
+ }
+ if err := jq.EvaluateFormatted(&buf, w, e.filter, indent, ios.ColorEnabled()); err != nil {
+ return err
+ }
} else if e.template != "" {
t := template.New(w, ios.TerminalWidth(), ios.ColorEnabled())
if err := t.Parse(e.template); err != nil {
diff --git a/pkg/cmdutil/json_flags_test.go b/pkg/cmdutil/json_flags_test.go
index dd65647ea..975530df4 100644
--- a/pkg/cmdutil/json_flags_test.go
+++ b/pkg/cmdutil/json_flags_test.go
@@ -126,6 +126,7 @@ func Test_exportFormat_Write(t *testing.T) {
args args
wantW string
wantErr bool
+ istty bool
}{
{
name: "regular JSON output",
@@ -135,6 +136,7 @@ func Test_exportFormat_Write(t *testing.T) {
},
wantW: "{\"name\":\"hubot\"}\n",
wantErr: false,
+ istty: false,
},
{
name: "call ExportData",
@@ -144,6 +146,7 @@ func Test_exportFormat_Write(t *testing.T) {
},
wantW: "{\"field1\":\"item1:field1\",\"field2\":\"item1:field2\"}\n",
wantErr: false,
+ istty: false,
},
{
name: "recursively call ExportData",
@@ -156,6 +159,7 @@ func Test_exportFormat_Write(t *testing.T) {
},
wantW: "{\"s1\":[{\"f1\":\"i1:f1\",\"f2\":\"i1:f2\"},{\"f1\":\"i2:f1\",\"f2\":\"i2:f2\"}],\"s2\":[{\"f1\":\"i3:f1\",\"f2\":\"i3:f2\"}]}\n",
wantErr: false,
+ istty: false,
},
{
name: "with jq filter",
@@ -165,6 +169,17 @@ func Test_exportFormat_Write(t *testing.T) {
},
wantW: "hubot\n",
wantErr: false,
+ istty: false,
+ },
+ {
+ name: "with jq filter pretty printing",
+ exporter: exportFormat{filter: "."},
+ args: args{
+ data: map[string]string{"name": "hubot"},
+ },
+ wantW: "{\n \"name\": \"hubot\"\n}\n",
+ wantErr: false,
+ istty: true,
},
{
name: "with Go template",
@@ -174,11 +189,13 @@ func Test_exportFormat_Write(t *testing.T) {
},
wantW: "hubot",
wantErr: false,
+ istty: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
io, _, w, _ := iostreams.Test()
+ io.SetStdoutTTY(tt.istty)
if err := tt.exporter.Write(io, tt.args.data); (err != nil) != tt.wantErr {
t.Errorf("exportFormat.Write() error = %v, wantErr %v", err, tt.wantErr)
return