github.com/neilotoole/jsoncolor@v0.6.0/jsoncolor.go (about)

     1  package jsoncolor
     2  
     3  import (
     4  	"io"
     5  	"os"
     6  	"strconv"
     7  
     8  	"github.com/mattn/go-isatty"
     9  
    10  	"golang.org/x/term"
    11  )
    12  
    13  // Colors specifies colorization of JSON output. Each field
    14  // is a Color, which is simply the bytes of the terminal color code.
    15  type Colors struct {
    16  	// Null is the color for JSON nil.
    17  	Null Color
    18  
    19  	// Bool is the color for boolean values.
    20  	Bool Color
    21  
    22  	// Number is the color for number values.
    23  	Number Color
    24  
    25  	// String is the color for string values.
    26  	String Color
    27  
    28  	// Key is the color for JSON keys.
    29  	Key Color
    30  
    31  	// Bytes is the color for byte data.
    32  	Bytes Color
    33  
    34  	// Time is the color for datetime values.
    35  	Time Color
    36  
    37  	// Punc is the color for JSON punctuation: []{},: etc.
    38  	Punc Color
    39  }
    40  
    41  // appendNull appends a colorized "null" to b.
    42  func (c *Colors) appendNull(b []byte) []byte {
    43  	if c == nil {
    44  		return append(b, "null"...)
    45  	}
    46  
    47  	b = append(b, c.Null...)
    48  	b = append(b, "null"...)
    49  	return append(b, ansiReset...)
    50  }
    51  
    52  // appendBool appends the colorized bool v to b.
    53  func (c *Colors) appendBool(b []byte, v bool) []byte {
    54  	if c == nil {
    55  		if v {
    56  			return append(b, "true"...)
    57  		}
    58  
    59  		return append(b, "false"...)
    60  	}
    61  
    62  	b = append(b, c.Bool...)
    63  	if v {
    64  		b = append(b, "true"...)
    65  	} else {
    66  		b = append(b, "false"...)
    67  	}
    68  
    69  	return append(b, ansiReset...)
    70  }
    71  
    72  // appendKey appends the colorized key v to b.
    73  func (c *Colors) appendKey(b []byte, v []byte) []byte {
    74  	if c == nil {
    75  		return append(b, v...)
    76  	}
    77  
    78  	b = append(b, c.Key...)
    79  	b = append(b, v...)
    80  	return append(b, ansiReset...)
    81  }
    82  
    83  // appendInt64 appends the colorized int64 v to b.
    84  func (c *Colors) appendInt64(b []byte, v int64) []byte {
    85  	if c == nil {
    86  		return strconv.AppendInt(b, v, 10)
    87  	}
    88  
    89  	b = append(b, c.Number...)
    90  	b = strconv.AppendInt(b, v, 10)
    91  	return append(b, ansiReset...)
    92  }
    93  
    94  // appendUint64 appends the colorized uint64 v to b.
    95  func (c *Colors) appendUint64(b []byte, v uint64) []byte {
    96  	if c == nil {
    97  		return strconv.AppendUint(b, v, 10)
    98  	}
    99  
   100  	b = append(b, c.Number...)
   101  	b = strconv.AppendUint(b, v, 10)
   102  	return append(b, ansiReset...)
   103  }
   104  
   105  // appendPunc appends the colorized punctuation mark v to b.
   106  func (c *Colors) appendPunc(b []byte, v byte) []byte {
   107  	if c == nil {
   108  		return append(b, v)
   109  	}
   110  
   111  	b = append(b, c.Punc...)
   112  	b = append(b, v)
   113  	return append(b, ansiReset...)
   114  }
   115  
   116  // Color is used to render terminal colors. In effect, Color is
   117  // the bytes of the ANSI prefix code. The zero value is valid (results in
   118  // no colorization). When Color is non-zero, the encoder writes the prefix,
   119  //then the actual value, then the ANSI reset code.
   120  //
   121  // Example value:
   122  //
   123  //  number := Color("\x1b[36m")
   124  type Color []byte
   125  
   126  // ansiReset is the ANSI ansiReset escape code.
   127  const ansiReset = "\x1b[0m"
   128  
   129  // DefaultColors returns the default Colors configuration.
   130  // These colors largely follow jq's default colorization,
   131  // with some deviation.
   132  func DefaultColors() *Colors {
   133  	return &Colors{
   134  		Null:   Color("\x1b[2m"),
   135  		Bool:   Color("\x1b[1m"),
   136  		Number: Color("\x1b[36m"),
   137  		String: Color("\x1b[32m"),
   138  		Key:    Color("\x1b[34;1m"),
   139  		Bytes:  Color("\x1b[2m"),
   140  		Time:   Color("\x1b[32;2m"),
   141  		Punc:   Color{}, // No colorization
   142  	}
   143  }
   144  
   145  // IsColorTerminal returns true if w is a colorable terminal.
   146  func IsColorTerminal(w io.Writer) bool {
   147  	// This logic could be pretty dodgy; use at your own risk.
   148  	if w == nil {
   149  		return false
   150  	}
   151  
   152  	if !isTerminal(w) {
   153  		return false
   154  	}
   155  
   156  	if os.Getenv("TERM") == "dumb" {
   157  		return false
   158  	}
   159  
   160  	f, ok := w.(*os.File)
   161  	if !ok {
   162  		return false
   163  	}
   164  
   165  	if isatty.IsCygwinTerminal(f.Fd()) {
   166  		return false
   167  	}
   168  
   169  	return true
   170  }
   171  
   172  // isTerminal returns true if w is a terminal.
   173  func isTerminal(w io.Writer) bool {
   174  	switch v := w.(type) {
   175  	case *os.File:
   176  		return term.IsTerminal(int(v.Fd()))
   177  	default:
   178  		return false
   179  	}
   180  }