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