github.com/core-coin/go-core/v2@v2.1.9/internal/jsre/pretty.go (about) 1 // Copyright 2016 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core 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-core 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-core 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 } 224 iterOwnAndConstructorKeys(ctx.vm, obj, add) 225 sort.Strings(vals) 226 sort.Strings(methods) 227 return append(vals, methods...) 228 } 229 230 func iterOwnAndConstructorKeys(vm *goja.Runtime, obj *goja.Object, f func(string)) { 231 seen := make(map[string]bool) 232 iterOwnKeys(vm, obj, func(prop string) { 233 seen[prop] = true 234 f(prop) 235 }) 236 if cp := constructorPrototype(vm, obj); cp != nil { 237 iterOwnKeys(vm, cp, func(prop string) { 238 if !seen[prop] { 239 f(prop) 240 } 241 }) 242 } 243 } 244 245 func iterOwnKeys(vm *goja.Runtime, obj *goja.Object, f func(string)) { 246 Object := vm.Get("Object").ToObject(vm) 247 getOwnPropertyNames, isFunc := goja.AssertFunction(Object.Get("getOwnPropertyNames")) 248 if !isFunc { 249 panic(vm.ToValue("Object.getOwnPropertyNames isn't a function")) 250 } 251 rv, err := getOwnPropertyNames(goja.Null(), obj) 252 if err != nil { 253 panic(vm.ToValue(fmt.Sprintf("Error getting object properties: %v", err))) 254 } 255 gv := rv.Export() 256 switch gv := gv.(type) { 257 case []interface{}: 258 for _, v := range gv { 259 f(v.(string)) 260 } 261 case []string: 262 for _, v := range gv { 263 f(v) 264 } 265 default: 266 panic(fmt.Errorf("Object.getOwnPropertyNames returned unexpected type %T", gv)) 267 } 268 } 269 270 func (ctx ppctx) isBigNumber(v *goja.Object) bool { 271 // Handle numbers with custom constructor. 272 if obj := v.Get("constructor").ToObject(ctx.vm); obj != nil { 273 if strings.HasPrefix(toString(obj), "function BigNumber") { 274 return true 275 } 276 } 277 // Handle default constructor. 278 BigNumber := ctx.vm.Get("BigNumber").ToObject(ctx.vm) 279 if BigNumber == nil { 280 return false 281 } 282 prototype := BigNumber.Get("prototype").ToObject(ctx.vm) 283 isPrototypeOf, callable := goja.AssertFunction(prototype.Get("isPrototypeOf")) 284 if !callable { 285 return false 286 } 287 bv, _ := isPrototypeOf(prototype, v) 288 return bv.ToBoolean() 289 } 290 291 func toString(obj *goja.Object) string { 292 return obj.ToString().String() 293 } 294 295 func constructorPrototype(vm *goja.Runtime, obj *goja.Object) *goja.Object { 296 if v := obj.Get("constructor"); v != nil { 297 if v := v.ToObject(vm).Get("prototype"); v != nil { 298 return v.ToObject(vm) 299 } 300 } 301 return nil 302 }