Skip to content

Commit

Permalink
updated doc, examples, and readme
Browse files Browse the repository at this point in the history
  • Loading branch information
neilotoole committed Oct 3, 2021
1 parent 31e7295 commit f575167
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 26 deletions.
94 changes: 85 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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)
Expand Down
8 changes: 5 additions & 3 deletions example/fatihcolor/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -20,15 +21,16 @@ 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
fclrs.Number = color.New(color.FgBlue)
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)
Expand Down
13 changes: 11 additions & 2 deletions example/jsoncolor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ package main

import (
"fmt"
"os"

"github.com/mattn/go-colorable"
json "github.com/neilotoole/jsoncolor"
"os"
)

func main() {
Expand All @@ -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)
Expand Down
16 changes: 10 additions & 6 deletions helper/fatihcolor/fatihcolor.go
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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),
Expand All @@ -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()
Expand Down
19 changes: 13 additions & 6 deletions jsoncolor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}

Expand Down Expand Up @@ -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"),
Expand All @@ -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
}
}

Expand Down

0 comments on commit f575167

Please sign in to comment.