Skip to content

Commit 6254946

Browse files
committed
Display JSON in indented, colored format in api output
1 parent 1036666 commit 6254946

File tree

3 files changed

+133
-6
lines changed

3 files changed

+133
-6
lines changed

pkg/cmd/api/api.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import (
66
"io/ioutil"
77
"net/http"
88
"os"
9+
"regexp"
910
"strconv"
1011
"strings"
1112

1213
"github.com/cli/cli/pkg/cmdutil"
1314
"github.com/cli/cli/pkg/iostreams"
15+
"github.com/cli/cli/pkg/jsoncolor"
1416
"github.com/spf13/cobra"
1517
)
1618

@@ -109,9 +111,18 @@ func apiRun(opts *ApiOptions) error {
109111
}
110112
defer resp.Body.Close()
111113

112-
_, err = io.Copy(opts.IO.Out, resp.Body)
113-
if err != nil {
114-
return err
114+
isJSON, _ := regexp.MatchString(`[/+]json(;|$)`, resp.Header.Get("Content-Type"))
115+
116+
if isJSON && opts.IO.ColorEnabled() {
117+
err = jsoncolor.Write(opts.IO.Out, resp.Body, " ")
118+
if err != nil {
119+
return err
120+
}
121+
} else {
122+
_, err = io.Copy(opts.IO.Out, resp.Body)
123+
if err != nil {
124+
return err
125+
}
115126
}
116127

117128
// TODO: detect GraphQL errors

pkg/iostreams/iostreams.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,36 @@ import (
55
"io"
66
"io/ioutil"
77
"os"
8+
9+
"github.com/mattn/go-colorable"
10+
"github.com/mattn/go-isatty"
811
)
912

1013
type IOStreams struct {
1114
In io.ReadCloser
1215
Out io.Writer
1316
ErrOut io.Writer
17+
18+
colorEnabled bool
19+
}
20+
21+
func (s *IOStreams) ColorEnabled() bool {
22+
return s.colorEnabled
1423
}
1524

1625
func System() *IOStreams {
26+
var out io.Writer = os.Stdout
27+
var colorEnabled bool
28+
if os.Getenv("NO_COLOR") == "" && isTerminal(os.Stdout) {
29+
out = colorable.NewColorable(os.Stdout)
30+
colorEnabled = true
31+
}
32+
1733
return &IOStreams{
18-
In: os.Stdin,
19-
Out: os.Stdout,
20-
ErrOut: os.Stderr,
34+
In: os.Stdin,
35+
Out: out,
36+
ErrOut: os.Stderr,
37+
colorEnabled: colorEnabled,
2138
}
2239
}
2340

@@ -31,3 +48,7 @@ func Test() (*IOStreams, *bytes.Buffer, *bytes.Buffer, *bytes.Buffer) {
3148
ErrOut: errOut,
3249
}, in, out, errOut
3350
}
51+
52+
func isTerminal(f *os.File) bool {
53+
return isatty.IsTerminal(f.Fd()) || isatty.IsCygwinTerminal(f.Fd())
54+
}

pkg/jsoncolor/jsoncolor.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package jsoncolor
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"strings"
8+
)
9+
10+
const (
11+
colorDelim = "1;38" // bright white
12+
colorKey = "1;34" // bright blue
13+
colorNull = "1;30" // gray
14+
colorString = "32" // green
15+
colorBool = "33" // yellow
16+
)
17+
18+
func Write(w io.Writer, r io.Reader, indent string) error {
19+
dec := json.NewDecoder(r)
20+
dec.UseNumber()
21+
22+
var idx int
23+
var stack []json.Delim
24+
25+
for {
26+
t, err := dec.Token()
27+
if err == io.EOF {
28+
break
29+
}
30+
if err != nil {
31+
return err
32+
}
33+
34+
switch tt := t.(type) {
35+
case json.Delim:
36+
switch tt {
37+
case '{', '[':
38+
stack = append(stack, tt)
39+
idx = 0
40+
fmt.Fprintf(w, "\x1b[%sm%s\x1b[m", colorDelim, tt)
41+
if dec.More() {
42+
fmt.Fprint(w, "\n", strings.Repeat(indent, len(stack)))
43+
}
44+
continue
45+
case '}', ']':
46+
stack = stack[:len(stack)-1]
47+
idx = 0
48+
fmt.Fprintf(w, "\x1b[%sm%s\x1b[m", colorDelim, tt)
49+
}
50+
default:
51+
b, err := json.Marshal(tt)
52+
if err != nil {
53+
return err
54+
}
55+
56+
isKey := len(stack) > 0 && stack[len(stack)-1] == '{' && idx%2 == 0
57+
idx++
58+
59+
var color string
60+
if isKey {
61+
color = colorKey
62+
} else if tt == nil {
63+
color = colorNull
64+
} else {
65+
switch t.(type) {
66+
case string:
67+
color = colorString
68+
case bool:
69+
color = colorBool
70+
}
71+
}
72+
73+
if color == "" {
74+
_, _ = w.Write(b)
75+
} else {
76+
fmt.Fprintf(w, "\x1b[%sm%s\x1b[m", color, b)
77+
}
78+
79+
if isKey {
80+
fmt.Fprintf(w, "\x1b[%sm:\x1b[m ", colorDelim)
81+
continue
82+
}
83+
}
84+
85+
if dec.More() {
86+
fmt.Fprintf(w, "\x1b[%sm,\x1b[m\n%s", colorDelim, strings.Repeat(indent, len(stack)))
87+
} else if len(stack) > 0 {
88+
fmt.Fprint(w, "\n", strings.Repeat(indent, len(stack)-1))
89+
} else {
90+
fmt.Fprint(w, "\n")
91+
}
92+
}
93+
94+
return nil
95+
}

0 commit comments

Comments
 (0)