From f57516722f7cafc6dbe2721daed6974bd6727764 Mon Sep 17 00:00:00 2001 From: Neil O'Toole Date: Sun, 3 Oct 2021 13:56:57 -0600 Subject: [PATCH] updated doc, examples, and readme --- README.md | 94 +++++++++++++++++++++++++++++---- example/fatihcolor/example.go | 8 +-- example/jsoncolor/main.go | 13 ++++- helper/fatihcolor/fatihcolor.go | 16 +++--- jsoncolor.go | 19 ++++--- 5 files changed, 124 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 71ca0af..5262397 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,13 @@ # jsoncolor Package `neilotoole/jsoncolor` is a drop-in replacement for `encoding/json` -that can output colorized JSON. +that outputs colorized JSON. + +Why? Well, `jq` colorizes its output by default. And at the time this package was +created, I was not aware of any other JSON colorization package that performed +colorization in-line in the encoder. + +From the example [`jc`](./cmd/jc) app: ![jsoncolor-output](https://github.com/neilotoole/jsoncolor/wiki/images/jsoncolor-example-output2.png) @@ -33,12 +39,22 @@ import ( func main() { var enc *json.Encoder - + + // Note: this check will fail if running inside Goland (and + // other IDEs?) as IsColorTerminal will return false. if json.IsColorTerminal(os.Stdout) { // Safe to use color out := colorable.NewColorable(os.Stdout) // needed for Windows enc = json.NewEncoder(out) - enc.SetColors(json.DefaultColors()) + + // DefaultColors are similar to jq + clrs := json.DefaultColors() + + // Change some values, just for fun + clrs.Bool = json.Color("\x1b[36m") // Change the bool color + clrs.String = json.Color{} // Disable the string color + + enc.SetColors(clrs) } else { // Can't use color; but the encoder will still work enc = json.NewEncoder(os.Stdout) @@ -57,10 +73,73 @@ func main() { } ``` +### Configuration + +To enable colorization, invoke `enc.SetColors`. + +The `jsoncolor.Colors` struct holds color config. The zero value +and `nil` are both safe for use (resulting in no colorization). + +The `DefaultColors` func returns a `Colors` struct that produces results +similar to `jq`: + +```go +// DefaultColors returns the default Colors configuration. +// These colors largely follow jq's default colorization, +// with some deviation. +func DefaultColors() *Colors { + return &Colors{ + Null: Color("\x1b[2m"), + Bool: Color("\x1b[1m"), + Number: Color("\x1b[36m"), + String: Color("\x1b[32m"), + Key: Color("\x1b[34;1m"), + Bytes: Color("\x1b[2m"), + Time: Color("\x1b[32;2m"), + Punc: Color{}, // No colorization + } +} +``` + +As seen above, use the `Color` zero value (`Color{}`) to +disable colorization for that JSON element. + + +### Helper for `fatih/color` + +It can be inconvenient to use terminal codes, e.g. `json.Color("\x1b[36m")`. +A helper package provides an adapter for the [`fatih/color`](https://github.com/fatih/color) package. + +```go + // import "github.com/neilotoole/jsoncolor/helper/fatihcolor" + // import "github.com/fatih/color" + + out := colorable.NewColorable(os.Stdout) // needed for Windows + enc = json.NewEncoder(out) + + fclrs := fatihcolor.DefaultColors() + // Change some values, just for fun + fclrs.Number = color.New(color.FgBlue) + fclrs.String = color.New(color.FgCyan) + + clrs := fatihcolor.ToCoreColors(fclrs) + enc.SetColors(clrs) +``` + +### Drop-in for `encoding/json` + +This package is a full drop-in for stdlib `encoding/json` +(thanks to the `segmentio/encoding/json` pkg being a full drop-in). + +To drop-in, just use an import alias: + +```go + import json "github.com/neilotoole/jsoncolor" +``` ## Example app: `jc` -See `./cmd/jc` for a trivial CLI implementation that can accept JSON input, +See [`./cmd/jc`](.cmd/jc) for a trivial CLI implementation that can accept JSON input, and output that JSON in color. ```shell @@ -76,14 +155,11 @@ This package is an extract of [`sq`](https://github.com/neilotoole/sq)'s JSON en package, which itself is a fork of the [`segment.io/encoding`](https://github.com/segmentio/encoding) JSON encoding package. -Note that the original `jsoncolor` codebase was forked from Segment's package at `v0.1.14`, so -this codebase is quite of out sync by now. +Note that the original `jsoncolor` codebase was forked from Segment's codebase at `v0.1.14`, so +the codebases are quite of out sync by now. ### Notes -- Given the popularity of the [`fatih/color`](https://github.com/fatih/color) pkg, there is - a helper pkg (`jsoncolor/helper/fatihcolor`) to build `jsoncolor` specs - from `fatih/color`. - The `.golangci.yml` linter settings have been fiddled with to hush linting issues inherited from the `segmentio` codebase at the time of forking. Thus, the linter report may not be of great use. In an ideal world, the `jsoncolor` functionality would be ported to a more recent (and better-linted) diff --git a/example/fatihcolor/example.go b/example/fatihcolor/example.go index 8794133..0add6c1 100644 --- a/example/fatihcolor/example.go +++ b/example/fatihcolor/example.go @@ -7,8 +7,9 @@ import ( "fmt" "os" - "github.com/fatih/color" "github.com/mattn/go-colorable" + + "github.com/fatih/color" json "github.com/neilotoole/jsoncolor" "github.com/neilotoole/jsoncolor/helper/fatihcolor" @@ -20,6 +21,9 @@ func main() { // Note: this check will fail if running inside Goland (and // other IDEs?) as IsColorTerminal will return false. if json.IsColorTerminal(os.Stdout) { + out := colorable.NewColorable(os.Stdout) + enc = json.NewEncoder(out) + fclrs := fatihcolor.DefaultColors() // Change some values, just for fun @@ -27,8 +31,6 @@ func main() { fclrs.String = color.New(color.FgCyan) clrs := fatihcolor.ToCoreColors(fclrs) - out := colorable.NewColorable(os.Stdout) - enc = json.NewEncoder(out) enc.SetColors(clrs) } else { enc = json.NewEncoder(os.Stdout) diff --git a/example/jsoncolor/main.go b/example/jsoncolor/main.go index fb84e04..dc48066 100644 --- a/example/jsoncolor/main.go +++ b/example/jsoncolor/main.go @@ -4,9 +4,10 @@ package main import ( "fmt" + "os" + "github.com/mattn/go-colorable" json "github.com/neilotoole/jsoncolor" - "os" ) func main() { @@ -18,7 +19,15 @@ func main() { // Safe to use color out := colorable.NewColorable(os.Stdout) // needed for Windows enc = json.NewEncoder(out) - enc.SetColors(json.DefaultColors()) + + // DefaultColors are similar to jq + clrs := json.DefaultColors() + + // Change some values, just for fun + clrs.Bool = json.Color("\x1b[36m") // Change the bool color + clrs.String = json.Color{} // Disable the string color + + enc.SetColors(clrs) } else { // Can't use color; but the encoder will still work enc = json.NewEncoder(os.Stdout) diff --git a/helper/fatihcolor/fatihcolor.go b/helper/fatihcolor/fatihcolor.go index 489086b..e43ba9c 100644 --- a/helper/fatihcolor/fatihcolor.go +++ b/helper/fatihcolor/fatihcolor.go @@ -1,4 +1,4 @@ -// Package fatihcolor provides a bridge between fatih/color +// Package fatihcolor provides an adapter between fatih/color // and neilotoole/jsoncolor's native mechanism. See ToCoreColors. package fatihcolor @@ -55,6 +55,10 @@ func DefaultColors() *Colors { // ToCoreColors converts clrs to a core jsoncolor.Colors instance. func ToCoreColors(clrs *Colors) *jsoncolor.Colors { + if clrs == nil { + return nil + } + return &jsoncolor.Colors{ Null: ToCoreColor(clrs.Null), Bool: ToCoreColor(clrs.Bool), @@ -70,16 +74,16 @@ func ToCoreColors(clrs *Colors) *jsoncolor.Colors { // ToCoreColor creates a jsoncolor.Color instance from a fatih/color // instance. func ToCoreColor(c *color.Color) jsoncolor.Color { + if c == nil { + return jsoncolor.Color{} + } + // Dirty conversion function ahead: print // a space using c, then grab the bytes printed - // before the space, as those are the bytes we need for. + // before the space, as those are the bytes we need. // There's definitely a better way of doing this, but // it works for now. - if c == nil { - return jsoncolor.Color{} - } - // Make a copy because the pkg-level color.NoColor could be false. c2 := *c c2.EnableColor() diff --git a/jsoncolor.go b/jsoncolor.go index b0ca458..1031541 100644 --- a/jsoncolor.go +++ b/jsoncolor.go @@ -10,7 +10,8 @@ import ( "golang.org/x/term" ) -// Colors specifies colorization of JSON output. +// Colors specifies colorization of JSON output. Each field +// is a Color, which is simply the bytes of the terminal color code. type Colors struct { // Null is the color for JSON nil. Null Color @@ -33,7 +34,7 @@ type Colors struct { // Time is the color for datetime values. Time Color - // Punc is the color for JSON punctuation. + // Punc is the color for JSON punctuation: []{},: etc. Punc Color } @@ -113,15 +114,21 @@ func (c *Colors) appendPunc(b []byte, v byte) []byte { } // Color is used to render terminal colors. In effect, Color is -// the ANSI prefix code: the prefix is written, then the actual value, -// then the ANSI reset code. +// the bytes of the ANSI prefix code. The zero value is valid (results in +// no colorization). When Color is non-zero, the encoder writes the prefix, +//then the actual value, then the ANSI reset code. +// +// Example value: +// +// number := Color("\x1b[36m") type Color []byte // ansiReset is the ANSI ansiReset escape code. const ansiReset = "\x1b[0m" // DefaultColors returns the default Colors configuration. -// These colors attempt to follow jq's default colorization. +// These colors largely follow jq's default colorization, +// with some deviation. func DefaultColors() *Colors { return &Colors{ Null: Color("\x1b[2m"), @@ -131,7 +138,7 @@ func DefaultColors() *Colors { Key: Color("\x1b[34;1m"), Bytes: Color("\x1b[2m"), Time: Color("\x1b[32;2m"), - Punc: Color{}, + Punc: Color{}, // No colorization } }