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