github.com/myafeier/go-ethereum@v1.6.8-0.20170719123245-3e0dbe0eaa72/internal/jsre/pretty.go (about) 1 // Copyright 2016 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package jsre 18 19 import ( 20 "fmt" 21 "io" 22 "sort" 23 "strconv" 24 "strings" 25 26 "github.com/fatih/color" 27 "github.com/robertkrimen/otto" 28 ) 29 30 const ( 31 maxPrettyPrintLevel = 3 32 indentString = " " 33 ) 34 35 var ( 36 FunctionColor = color.New(color.FgMagenta).SprintfFunc() 37 SpecialColor = color.New(color.Bold).SprintfFunc() 38 NumberColor = color.New(color.FgRed).SprintfFunc() 39 StringColor = color.New(color.FgGreen).SprintfFunc() 40 ErrorColor = color.New(color.FgHiRed).SprintfFunc() 41 ) 42 43 // these fields are hidden when printing objects. 44 var boringKeys = map[string]bool{ 45 "valueOf": true, 46 "toString": true, 47 "toLocaleString": true, 48 "hasOwnProperty": true, 49 "isPrototypeOf": true, 50 "propertyIsEnumerable": true, 51 "constructor": true, 52 } 53 54 // prettyPrint writes value to standard output. 55 func prettyPrint(vm *otto.Otto, value otto.Value, w io.Writer) { 56 ppctx{vm: vm, w: w}.printValue(value, 0, false) 57 } 58 59 // prettyError writes err to standard output. 60 func prettyError(vm *otto.Otto, err error, w io.Writer) { 61 failure := err.Error() 62 if ottoErr, ok := err.(*otto.Error); ok { 63 failure = ottoErr.String() 64 } 65 fmt.Fprint(w, ErrorColor("%s", failure)) 66 } 67 68 // jsErrorString adds a backtrace to errors generated by otto. 69 func jsErrorString(err error) string { 70 if ottoErr, ok := err.(*otto.Error); ok { 71 return ottoErr.String() 72 } 73 return err.Error() 74 } 75 76 func (re *JSRE) prettyPrintJS(call otto.FunctionCall) otto.Value { 77 for _, v := range call.ArgumentList { 78 prettyPrint(call.Otto, v, re.output) 79 fmt.Fprintln(re.output) 80 } 81 return otto.UndefinedValue() 82 } 83 84 type ppctx struct { 85 vm *otto.Otto 86 w io.Writer 87 } 88 89 func (ctx ppctx) indent(level int) string { 90 return strings.Repeat(indentString, level) 91 } 92 93 func (ctx ppctx) printValue(v otto.Value, level int, inArray bool) { 94 switch { 95 case v.IsObject(): 96 ctx.printObject(v.Object(), level, inArray) 97 case v.IsNull(): 98 fmt.Fprint(ctx.w, SpecialColor("null")) 99 case v.IsUndefined(): 100 fmt.Fprint(ctx.w, SpecialColor("undefined")) 101 case v.IsString(): 102 s, _ := v.ToString() 103 fmt.Fprint(ctx.w, StringColor("%q", s)) 104 case v.IsBoolean(): 105 b, _ := v.ToBoolean() 106 fmt.Fprint(ctx.w, SpecialColor("%t", b)) 107 case v.IsNaN(): 108 fmt.Fprint(ctx.w, NumberColor("NaN")) 109 case v.IsNumber(): 110 s, _ := v.ToString() 111 fmt.Fprint(ctx.w, NumberColor("%s", s)) 112 default: 113 fmt.Fprint(ctx.w, "<unprintable>") 114 } 115 } 116 117 func (ctx ppctx) printObject(obj *otto.Object, level int, inArray bool) { 118 switch obj.Class() { 119 case "Array", "GoArray": 120 lv, _ := obj.Get("length") 121 len, _ := lv.ToInteger() 122 if len == 0 { 123 fmt.Fprintf(ctx.w, "[]") 124 return 125 } 126 if level > maxPrettyPrintLevel { 127 fmt.Fprint(ctx.w, "[...]") 128 return 129 } 130 fmt.Fprint(ctx.w, "[") 131 for i := int64(0); i < len; i++ { 132 el, err := obj.Get(strconv.FormatInt(i, 10)) 133 if err == nil { 134 ctx.printValue(el, level+1, true) 135 } 136 if i < len-1 { 137 fmt.Fprintf(ctx.w, ", ") 138 } 139 } 140 fmt.Fprint(ctx.w, "]") 141 142 case "Object": 143 // Print values from bignumber.js as regular numbers. 144 if ctx.isBigNumber(obj) { 145 fmt.Fprint(ctx.w, NumberColor("%s", toString(obj))) 146 return 147 } 148 // Otherwise, print all fields indented, but stop if we're too deep. 149 keys := ctx.fields(obj) 150 if len(keys) == 0 { 151 fmt.Fprint(ctx.w, "{}") 152 return 153 } 154 if level > maxPrettyPrintLevel { 155 fmt.Fprint(ctx.w, "{...}") 156 return 157 } 158 fmt.Fprintln(ctx.w, "{") 159 for i, k := range keys { 160 v, _ := obj.Get(k) 161 fmt.Fprintf(ctx.w, "%s%s: ", ctx.indent(level+1), k) 162 ctx.printValue(v, level+1, false) 163 if i < len(keys)-1 { 164 fmt.Fprintf(ctx.w, ",") 165 } 166 fmt.Fprintln(ctx.w) 167 } 168 if inArray { 169 level-- 170 } 171 fmt.Fprintf(ctx.w, "%s}", ctx.indent(level)) 172 173 case "Function": 174 // Use toString() to display the argument list if possible. 175 if robj, err := obj.Call("toString"); err != nil { 176 fmt.Fprint(ctx.w, FunctionColor("function()")) 177 } else { 178 desc := strings.Trim(strings.Split(robj.String(), "{")[0], " \t\n") 179 desc = strings.Replace(desc, " (", "(", 1) 180 fmt.Fprint(ctx.w, FunctionColor("%s", desc)) 181 } 182 183 case "RegExp": 184 fmt.Fprint(ctx.w, StringColor("%s", toString(obj))) 185 186 default: 187 if v, _ := obj.Get("toString"); v.IsFunction() && level <= maxPrettyPrintLevel { 188 s, _ := obj.Call("toString") 189 fmt.Fprintf(ctx.w, "<%s %s>", obj.Class(), s.String()) 190 } else { 191 fmt.Fprintf(ctx.w, "<%s>", obj.Class()) 192 } 193 } 194 } 195 196 func (ctx ppctx) fields(obj *otto.Object) []string { 197 var ( 198 vals, methods []string 199 seen = make(map[string]bool) 200 ) 201 add := func(k string) { 202 if seen[k] || boringKeys[k] || strings.HasPrefix(k, "_") { 203 return 204 } 205 seen[k] = true 206 if v, _ := obj.Get(k); v.IsFunction() { 207 methods = append(methods, k) 208 } else { 209 vals = append(vals, k) 210 } 211 } 212 iterOwnAndConstructorKeys(ctx.vm, obj, add) 213 sort.Strings(vals) 214 sort.Strings(methods) 215 return append(vals, methods...) 216 } 217 218 func iterOwnAndConstructorKeys(vm *otto.Otto, obj *otto.Object, f func(string)) { 219 seen := make(map[string]bool) 220 iterOwnKeys(vm, obj, func(prop string) { 221 seen[prop] = true 222 f(prop) 223 }) 224 if cp := constructorPrototype(obj); cp != nil { 225 iterOwnKeys(vm, cp, func(prop string) { 226 if !seen[prop] { 227 f(prop) 228 } 229 }) 230 } 231 } 232 233 func iterOwnKeys(vm *otto.Otto, obj *otto.Object, f func(string)) { 234 Object, _ := vm.Object("Object") 235 rv, _ := Object.Call("getOwnPropertyNames", obj.Value()) 236 gv, _ := rv.Export() 237 switch gv := gv.(type) { 238 case []interface{}: 239 for _, v := range gv { 240 f(v.(string)) 241 } 242 case []string: 243 for _, v := range gv { 244 f(v) 245 } 246 default: 247 panic(fmt.Errorf("Object.getOwnPropertyNames returned unexpected type %T", gv)) 248 } 249 } 250 251 func (ctx ppctx) isBigNumber(v *otto.Object) bool { 252 // Handle numbers with custom constructor. 253 if v, _ := v.Get("constructor"); v.Object() != nil { 254 if strings.HasPrefix(toString(v.Object()), "function BigNumber") { 255 return true 256 } 257 } 258 // Handle default constructor. 259 BigNumber, _ := ctx.vm.Object("BigNumber.prototype") 260 if BigNumber == nil { 261 return false 262 } 263 bv, _ := BigNumber.Call("isPrototypeOf", v) 264 b, _ := bv.ToBoolean() 265 return b 266 } 267 268 func toString(obj *otto.Object) string { 269 s, _ := obj.Call("toString") 270 return s.String() 271 } 272 273 func constructorPrototype(obj *otto.Object) *otto.Object { 274 if v, _ := obj.Get("constructor"); v.Object() != nil { 275 if v, _ = v.Object().Get("prototype"); v.Object() != nil { 276 return v.Object() 277 } 278 } 279 return nil 280 }