Pretty-print JSON results of jq filtering (#7236)
When connected to a TTY, tell the jq formatter to indent the output, and enable colorization of the output if the terminal supports it. Co-authored-by: Mislav Marohnić <mislav@github.com>
This commit is contained in:
parent
6a6df0d39b
commit
530002ee7a
5 changed files with 94 additions and 5 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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: <https://stedolan.github.io/jq/manual/v1.6/>
|
||||
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:
|
||||
<https://stedolan.github.io/jq/manual/v1.6/>
|
||||
|
||||
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}}'
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue