Display JSON in indented, colored format in api output

This commit is contained in:
Mislav Marohnić 2020-06-05 17:38:36 +02:00
parent 1036666266
commit 62549465a0
3 changed files with 133 additions and 6 deletions

View file

@ -6,11 +6,13 @@ import (
"io/ioutil"
"net/http"
"os"
"regexp"
"strconv"
"strings"
"github.com/cli/cli/pkg/cmdutil"
"github.com/cli/cli/pkg/iostreams"
"github.com/cli/cli/pkg/jsoncolor"
"github.com/spf13/cobra"
)
@ -109,9 +111,18 @@ func apiRun(opts *ApiOptions) error {
}
defer resp.Body.Close()
_, err = io.Copy(opts.IO.Out, resp.Body)
if err != nil {
return err
isJSON, _ := regexp.MatchString(`[/+]json(;|$)`, resp.Header.Get("Content-Type"))
if isJSON && opts.IO.ColorEnabled() {
err = jsoncolor.Write(opts.IO.Out, resp.Body, " ")
if err != nil {
return err
}
} else {
_, err = io.Copy(opts.IO.Out, resp.Body)
if err != nil {
return err
}
}
// TODO: detect GraphQL errors

View file

@ -5,19 +5,36 @@ import (
"io"
"io/ioutil"
"os"
"github.com/mattn/go-colorable"
"github.com/mattn/go-isatty"
)
type IOStreams struct {
In io.ReadCloser
Out io.Writer
ErrOut io.Writer
colorEnabled bool
}
func (s *IOStreams) ColorEnabled() bool {
return s.colorEnabled
}
func System() *IOStreams {
var out io.Writer = os.Stdout
var colorEnabled bool
if os.Getenv("NO_COLOR") == "" && isTerminal(os.Stdout) {
out = colorable.NewColorable(os.Stdout)
colorEnabled = true
}
return &IOStreams{
In: os.Stdin,
Out: os.Stdout,
ErrOut: os.Stderr,
In: os.Stdin,
Out: out,
ErrOut: os.Stderr,
colorEnabled: colorEnabled,
}
}
@ -31,3 +48,7 @@ func Test() (*IOStreams, *bytes.Buffer, *bytes.Buffer, *bytes.Buffer) {
ErrOut: errOut,
}, in, out, errOut
}
func isTerminal(f *os.File) bool {
return isatty.IsTerminal(f.Fd()) || isatty.IsCygwinTerminal(f.Fd())
}

View file

@ -0,0 +1,95 @@
package jsoncolor
import (
"encoding/json"
"fmt"
"io"
"strings"
)
const (
colorDelim = "1;38" // bright white
colorKey = "1;34" // bright blue
colorNull = "1;30" // gray
colorString = "32" // green
colorBool = "33" // yellow
)
func Write(w io.Writer, r io.Reader, indent string) error {
dec := json.NewDecoder(r)
dec.UseNumber()
var idx int
var stack []json.Delim
for {
t, err := dec.Token()
if err == io.EOF {
break
}
if err != nil {
return err
}
switch tt := t.(type) {
case json.Delim:
switch tt {
case '{', '[':
stack = append(stack, tt)
idx = 0
fmt.Fprintf(w, "\x1b[%sm%s\x1b[m", colorDelim, tt)
if dec.More() {
fmt.Fprint(w, "\n", strings.Repeat(indent, len(stack)))
}
continue
case '}', ']':
stack = stack[:len(stack)-1]
idx = 0
fmt.Fprintf(w, "\x1b[%sm%s\x1b[m", colorDelim, tt)
}
default:
b, err := json.Marshal(tt)
if err != nil {
return err
}
isKey := len(stack) > 0 && stack[len(stack)-1] == '{' && idx%2 == 0
idx++
var color string
if isKey {
color = colorKey
} else if tt == nil {
color = colorNull
} else {
switch t.(type) {
case string:
color = colorString
case bool:
color = colorBool
}
}
if color == "" {
_, _ = w.Write(b)
} else {
fmt.Fprintf(w, "\x1b[%sm%s\x1b[m", color, b)
}
if isKey {
fmt.Fprintf(w, "\x1b[%sm:\x1b[m ", colorDelim)
continue
}
}
if dec.More() {
fmt.Fprintf(w, "\x1b[%sm,\x1b[m\n%s", colorDelim, strings.Repeat(indent, len(stack)))
} else if len(stack) > 0 {
fmt.Fprint(w, "\n", strings.Repeat(indent, len(stack)-1))
} else {
fmt.Fprint(w, "\n")
}
}
return nil
}