1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
|
package main
import (
"bytes"
"encoding/json"
"fmt"
"unicode"
)
// A token is a chunk of text from a statement with a type
type token struct {
text string
typ tokenTyp
}
// A tokenTyp identifies what kind of token something is
type tokenTyp int
const (
// A bare word is a unquoted key; like 'foo' in json.foo = 1;
typBare tokenTyp = iota
// Numeric key; like '2' in json[2] = "foo";
typNumericKey
// A quoted key; like 'foo bar' in json["foo bar"] = 2;
typQuotedKey
// Punctuation types
typDot // .
typLBrace // [
typRBrace // ]
typEquals // =
typSemi // ;
typComma // ,
// Value types
typString // "foo"
typNumber // 4
typTrue // true
typFalse // false
typNull // null
typEmptyArray // []
typEmptyObject // {}
// Ignored token
typIgnored
// Error token
typError
)
// a sprintFn adds color to its input
type sprintFn func(...interface{}) string
// mapping of token types to the appropriate color sprintFn
var sprintFns = map[tokenTyp]sprintFn{
typBare: bareColor.SprintFunc(),
typNumericKey: numColor.SprintFunc(),
typQuotedKey: strColor.SprintFunc(),
typLBrace: braceColor.SprintFunc(),
typRBrace: braceColor.SprintFunc(),
typString: strColor.SprintFunc(),
typNumber: numColor.SprintFunc(),
typTrue: boolColor.SprintFunc(),
typFalse: boolColor.SprintFunc(),
typNull: boolColor.SprintFunc(),
typEmptyArray: braceColor.SprintFunc(),
typEmptyObject: braceColor.SprintFunc(),
}
// isValue returns true if the token is a valid value type
func (t token) isValue() bool {
switch t.typ {
case typString, typNumber, typTrue, typFalse, typNull, typEmptyArray, typEmptyObject:
return true
default:
return false
}
}
// isPunct returns true if the token is a punctuation type
func (t token) isPunct() bool {
switch t.typ {
case typDot, typLBrace, typRBrace, typEquals, typSemi, typComma:
return true
default:
return false
}
}
// format returns the formatted version of the token text
func (t token) format() string {
if t.typ == typEquals {
return " " + t.text + " "
}
return t.text
}
// formatColor returns the colored formatted version of the token text
func (t token) formatColor() string {
text := t.text
if t.typ == typEquals {
text = " " + text + " "
}
fn, ok := sprintFns[t.typ]
if ok {
return fn(text)
}
return text
}
// valueTokenFromInterface takes any valid value and
// returns a value token to represent it
func valueTokenFromInterface(v interface{}) token {
switch vv := v.(type) {
case map[string]interface{}:
return token{"{}", typEmptyObject}
case []interface{}:
return token{"[]", typEmptyArray}
case json.Number:
return token{vv.String(), typNumber}
case string:
return token{quoteString(vv), typString}
case bool:
if vv {
return token{"true", typTrue}
}
return token{"false", typFalse}
case nil:
return token{"null", typNull}
default:
return token{"", typError}
}
}
// quoteString takes a string and returns a quoted and
// escaped string valid for use in gron output
func quoteString(s string) string {
out := &bytes.Buffer{}
// bytes.Buffer never returns errors on these methods.
// errors are explicitly ignored to keep the linter
// happy. A price worth paying so that the linter
// remains useful.
_ = out.WriteByte('"')
for _, r := range s {
switch r {
case '\\':
_, _ = out.WriteString(`\\`)
case '"':
_, _ = out.WriteString(`\"`)
case '\b':
_, _ = out.WriteString(`\b`)
case '\f':
_, _ = out.WriteString(`\f`)
case '\n':
_, _ = out.WriteString(`\n`)
case '\r':
_, _ = out.WriteString(`\r`)
case '\t':
_, _ = out.WriteString(`\t`)
// \u2028 and \u2029 are separator runes that are not valid
// in javascript strings so they must be escaped.
// See http://timelessrepo.com/json-isnt-a-javascript-subset
case '\u2028':
_, _ = out.WriteString(`\u2028`)
case '\u2029':
_, _ = out.WriteString(`\u2029`)
default:
// Any other control runes must be escaped
if unicode.IsControl(r) {
_, _ = fmt.Fprintf(out, `\u%04X`, r)
} else {
// Unescaped rune
_, _ = out.WriteRune(r)
}
}
}
_ = out.WriteByte('"')
return out.String()
}
|