github.com/theQRL/go-zond@v0.1.1/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 "reflect" 23 "sort" 24 "strconv" 25 "strings" 26 27 "github.com/dop251/goja" 28 "github.com/fatih/color" 29 ) 30 31 const ( 32 maxPrettyPrintLevel = 3 33 indentString = " " 34 ) 35 36 var ( 37 FunctionColor = color.New(color.FgMagenta).SprintfFunc() 38 SpecialColor = color.New(color.Bold).SprintfFunc() 39 NumberColor = color.New(color.FgRed).SprintfFunc() 40 StringColor = color.New(color.FgGreen).SprintfFunc() 41 ErrorColor = color.New(color.FgHiRed).SprintfFunc() 42 ) 43 44 // these fields are hidden when printing objects. 45 var boringKeys = map[string]bool{ 46 "valueOf": true, 47 "toString": true, 48 "toLocaleString": true, 49 "hasOwnProperty": true, 50 "isPrototypeOf": true, 51 "propertyIsEnumerable": true, 52 "constructor": true, 53 } 54 55 // prettyPrint writes value to standard output. 56 func prettyPrint(vm *goja.Runtime, value goja.Value, w io.Writer) { 57 ppctx{vm: vm, w: w}.printValue(value, 0, false) 58 } 59 60 // prettyError writes err to standard output. 61 func prettyError(vm *goja.Runtime, err error, w io.Writer) { 62 failure := err.Error() 63 if gojaErr, ok := err.(*goja.Exception); ok { 64 failure = gojaErr.String() 65 } 66 fmt.Fprint(w, ErrorColor("%s", failure)) 67 } 68 69 func (re *JSRE) prettyPrintJS(call goja.FunctionCall) goja.Value { 70 for _, v := range call.Arguments { 71 prettyPrint(re.vm, v, re.output) 72 fmt.Fprintln(re.output) 73 } 74 return goja.Undefined() 75 } 76 77 type ppctx struct { 78 vm *goja.Runtime 79 w io.Writer 80 } 81 82 func (ctx ppctx) indent(level int) string { 83 return strings.Repeat(indentString, level) 84 } 85 86 func (ctx ppctx) printValue(v goja.Value, level int, inArray bool) { 87 if goja.IsNull(v) || goja.IsUndefined(v) { 88 fmt.Fprint(ctx.w, SpecialColor(v.String())) 89 return 90 } 91 kind := v.ExportType().Kind() 92 switch { 93 case kind == reflect.Bool: 94 fmt.Fprint(ctx.w, SpecialColor("%t", v.ToBoolean())) 95 case kind == reflect.String: 96 fmt.Fprint(ctx.w, StringColor("%q", v.String())) 97 case kind >= reflect.Int && kind <= reflect.Complex128: 98 fmt.Fprint(ctx.w, NumberColor("%s", v.String())) 99 default: 100 if obj, ok := v.(*goja.Object); ok { 101 ctx.printObject(obj, level, inArray) 102 } else { 103 fmt.Fprintf(ctx.w, "<unprintable %T>", v) 104 } 105 } 106 } 107 108 // SafeGet attempt to get the value associated to `key`, and 109 // catches the panic that goja creates if an error occurs in 110 // key. 111 func SafeGet(obj *goja.Object, key string) (ret goja.Value) { 112 defer func() { 113 if r := recover(); r != nil { 114 ret = goja.Undefined() 115 } 116 }() 117 ret = obj.Get(key) 118 119 return ret 120 } 121 122 func (ctx ppctx) printObject(obj *goja.Object, level int, inArray bool) { 123 switch obj.ClassName() { 124 case "Array", "GoArray": 125 lv := obj.Get("length") 126 len := lv.ToInteger() 127 if len == 0 { 128 fmt.Fprintf(ctx.w, "[]") 129 return 130 } 131 if level > maxPrettyPrintLevel { 132 fmt.Fprint(ctx.w, "[...]") 133 return 134 } 135 fmt.Fprint(ctx.w, "[") 136 for i := int64(0); i < len; i++ { 137 el := obj.Get(strconv.FormatInt(i, 10)) 138 if el != nil { 139 ctx.printValue(el, level+1, true) 140 } 141 if i < len-1 { 142 fmt.Fprintf(ctx.w, ", ") 143 } 144 } 145 fmt.Fprint(ctx.w, "]") 146 147 case "Object": 148 // Print values from bignumber.js as regular numbers. 149 if ctx.isBigNumber(obj) { 150 fmt.Fprint(ctx.w, NumberColor("%s", toString(obj))) 151 return 152 } 153 // Otherwise, print all fields indented, but stop if we're too deep. 154 keys := ctx.fields(obj) 155 if len(keys) == 0 { 156 fmt.Fprint(ctx.w, "{}") 157 return 158 } 159 if level > maxPrettyPrintLevel { 160 fmt.Fprint(ctx.w, "{...}") 161 return 162 } 163 fmt.Fprintln(ctx.w, "{") 164 for i, k := range keys { 165 v := SafeGet(obj, k) 166 fmt.Fprintf(ctx.w, "%s%s: ", ctx.indent(level+1), k) 167 ctx.printValue(v, level+1, false) 168 if i < len(keys)-1 { 169 fmt.Fprintf(ctx.w, ",") 170 } 171 fmt.Fprintln(ctx.w) 172 } 173 if inArray { 174 level-- 175 } 176 fmt.Fprintf(ctx.w, "%s}", ctx.indent(level)) 177 178 case "Function": 179 robj := obj.ToString() 180 desc := strings.Trim(strings.Split(robj.String(), "{")[0], " \t\n") 181 desc = strings.Replace(desc, " (", "(", 1) 182 fmt.Fprint(ctx.w, FunctionColor("%s", desc)) 183 184 case "RegExp": 185 fmt.Fprint(ctx.w, StringColor("%s", toString(obj))) 186 187 default: 188 if level <= maxPrettyPrintLevel { 189 s := obj.ToString().String() 190 fmt.Fprintf(ctx.w, "<%s %s>", obj.ClassName(), s) 191 } else { 192 fmt.Fprintf(ctx.w, "<%s>", obj.ClassName()) 193 } 194 } 195 } 196 197 func (ctx ppctx) fields(obj *goja.Object) []string { 198 var ( 199 vals, methods []string 200 seen = make(map[string]bool) 201 ) 202 add := func(k string) { 203 if seen[k] || boringKeys[k] || strings.HasPrefix(k, "_") { 204 return 205 } 206 seen[k] = true 207 208 key := SafeGet(obj, k) 209 if key == nil { 210 // The value corresponding to that key could not be found 211 // (typically because it is backed by an RPC call that is 212 // not supported by this instance. Add it to the list of 213 // values so that it appears as `undefined` to the user. 214 vals = append(vals, k) 215 } else { 216 if _, callable := goja.AssertFunction(key); callable { 217 methods = append(methods, k) 218 } else { 219 vals = append(vals, k) 220 } 221 } 222 } 223 iterOwnAndConstructorKeys(ctx.vm, obj, add) 224 sort.Strings(vals) 225 sort.Strings(methods) 226 return append(vals, methods...) 227 } 228 229 func iterOwnAndConstructorKeys(vm *goja.Runtime, obj *goja.Object, f func(string)) { 230 seen := make(map[string]bool) 231 iterOwnKeys(vm, obj, func(prop string) { 232 seen[prop] = true 233 f(prop) 234 }) 235 if cp := constructorPrototype(vm, obj); cp != nil { 236 iterOwnKeys(vm, cp, func(prop string) { 237 if !seen[prop] { 238 f(prop) 239 } 240 }) 241 } 242 } 243 244 func iterOwnKeys(vm *goja.Runtime, obj *goja.Object, f func(string)) { 245 Object := vm.Get("Object").ToObject(vm) 246 getOwnPropertyNames, isFunc := goja.AssertFunction(Object.Get("getOwnPropertyNames")) 247 if !isFunc { 248 panic(vm.ToValue("Object.getOwnPropertyNames isn't a function")) 249 } 250 rv, err := getOwnPropertyNames(goja.Null(), obj) 251 if err != nil { 252 panic(vm.ToValue(fmt.Sprintf("Error getting object properties: %v", err))) 253 } 254 gv := rv.Export() 255 switch gv := gv.(type) { 256 case []interface{}: 257 for _, v := range gv { 258 f(v.(string)) 259 } 260 case []string: 261 for _, v := range gv { 262 f(v) 263 } 264 default: 265 panic(fmt.Errorf("Object.getOwnPropertyNames returned unexpected type %T", gv)) 266 } 267 } 268 269 func (ctx ppctx) isBigNumber(v *goja.Object) bool { 270 // Handle numbers with custom constructor. 271 if obj := v.Get("constructor").ToObject(ctx.vm); obj != nil { 272 if strings.HasPrefix(toString(obj), "function BigNumber") { 273 return true 274 } 275 } 276 // Handle default constructor. 277 BigNumber := ctx.vm.Get("BigNumber").ToObject(ctx.vm) 278 if BigNumber == nil { 279 return false 280 } 281 prototype := BigNumber.Get("prototype").ToObject(ctx.vm) 282 isPrototypeOf, callable := goja.AssertFunction(prototype.Get("isPrototypeOf")) 283 if !callable { 284 return false 285 } 286 bv, _ := isPrototypeOf(prototype, v) 287 return bv.ToBoolean() 288 } 289 290 func toString(obj *goja.Object) string { 291 return obj.ToString().String() 292 } 293 294 func constructorPrototype(vm *goja.Runtime, obj *goja.Object) *goja.Object { 295 if v := obj.Get("constructor"); v != nil { 296 if v := v.ToObject(vm).Get("prototype"); v != nil { 297 return v.ToObject(vm) 298 } 299 } 300 return nil 301 }