github.com/klaytn/klaytn@v1.10.2/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 "sort" 27 "strconv" 28 "strings" 29 30 "github.com/robertkrimen/otto" 31 32 "github.com/fatih/color" 33 ) 34 35 const ( 36 maxPrettyPrintLevel = 3 37 indentString = " " 38 ) 39 40 var ( 41 FunctionColor = color.New(color.FgMagenta).SprintfFunc() 42 SpecialColor = color.New(color.Bold).SprintfFunc() 43 NumberColor = color.New(color.FgRed).SprintfFunc() 44 StringColor = color.New(color.FgGreen).SprintfFunc() 45 ErrorColor = color.New(color.FgHiRed).SprintfFunc() 46 ) 47 48 // these fields are hidden when printing objects. 49 var boringKeys = map[string]bool{ 50 "valueOf": true, 51 "toString": true, 52 "toLocaleString": true, 53 "hasOwnProperty": true, 54 "isPrototypeOf": true, 55 "propertyIsEnumerable": true, 56 "constructor": true, 57 } 58 59 // prettyPrint writes value to standard output. 60 func prettyPrint(vm *otto.Otto, value otto.Value, w io.Writer) { 61 ppctx{vm: vm, w: w}.printValue(value, 0, false) 62 } 63 64 // prettyError writes err to standard output. 65 func prettyError(vm *otto.Otto, err error, w io.Writer) { 66 failure := err.Error() 67 if ottoErr, ok := err.(*otto.Error); ok { 68 failure = ottoErr.String() 69 } 70 fmt.Fprint(w, ErrorColor("%s", failure)) 71 } 72 73 func (re *JSRE) prettyPrintJS(call otto.FunctionCall) otto.Value { 74 for _, v := range call.ArgumentList { 75 prettyPrint(call.Otto, v, re.output) 76 fmt.Fprintln(re.output) 77 } 78 return otto.UndefinedValue() 79 } 80 81 type ppctx struct { 82 vm *otto.Otto 83 w io.Writer 84 } 85 86 func (ctx ppctx) indent(level int) string { 87 return strings.Repeat(indentString, level) 88 } 89 90 func (ctx ppctx) printValue(v otto.Value, level int, inArray bool) { 91 switch { 92 case v.IsObject(): 93 ctx.printObject(v.Object(), level, inArray) 94 case v.IsNull(): 95 fmt.Fprint(ctx.w, SpecialColor("null")) 96 case v.IsUndefined(): 97 fmt.Fprint(ctx.w, SpecialColor("undefined")) 98 case v.IsString(): 99 s, _ := v.ToString() 100 fmt.Fprint(ctx.w, StringColor("%q", s)) 101 case v.IsBoolean(): 102 b, _ := v.ToBoolean() 103 fmt.Fprint(ctx.w, SpecialColor("%t", b)) 104 case v.IsNaN(): 105 fmt.Fprint(ctx.w, NumberColor("NaN")) 106 case v.IsNumber(): 107 s, _ := v.ToString() 108 fmt.Fprint(ctx.w, NumberColor("%s", s)) 109 default: 110 fmt.Fprint(ctx.w, "<unprintable>") 111 } 112 } 113 114 func (ctx ppctx) printObject(obj *otto.Object, level int, inArray bool) { 115 switch obj.Class() { 116 case "Array", "GoArray": 117 lv, _ := obj.Get("length") 118 len, _ := lv.ToInteger() 119 if len == 0 { 120 fmt.Fprintf(ctx.w, "[]") 121 return 122 } 123 if level > maxPrettyPrintLevel { 124 fmt.Fprint(ctx.w, "[...]") 125 return 126 } 127 fmt.Fprint(ctx.w, "[") 128 for i := int64(0); i < len; i++ { 129 el, err := obj.Get(strconv.FormatInt(i, 10)) 130 if err == nil { 131 ctx.printValue(el, level+1, true) 132 } 133 if i < len-1 { 134 fmt.Fprintf(ctx.w, ", ") 135 } 136 } 137 fmt.Fprint(ctx.w, "]") 138 139 case "Object": 140 // Print values from bignumber.js as regular numbers. 141 if ctx.isBigNumber(obj) { 142 fmt.Fprint(ctx.w, NumberColor("%s", toString(obj))) 143 return 144 } 145 // Otherwise, print all fields indented, but stop if we're too deep. 146 keys := ctx.fields(obj) 147 if len(keys) == 0 { 148 fmt.Fprint(ctx.w, "{}") 149 return 150 } 151 if level > maxPrettyPrintLevel { 152 fmt.Fprint(ctx.w, "{...}") 153 return 154 } 155 fmt.Fprintln(ctx.w, "{") 156 for i, k := range keys { 157 v, _ := obj.Get(k) 158 fmt.Fprintf(ctx.w, "%s%s: ", ctx.indent(level+1), k) 159 ctx.printValue(v, level+1, false) 160 if i < len(keys)-1 { 161 fmt.Fprintf(ctx.w, ",") 162 } 163 fmt.Fprintln(ctx.w) 164 } 165 if inArray { 166 level-- 167 } 168 fmt.Fprintf(ctx.w, "%s}", ctx.indent(level)) 169 170 case "Function": 171 // Use toString() to display the argument list if possible. 172 if robj, err := obj.Call("toString"); err != nil { 173 fmt.Fprint(ctx.w, FunctionColor("function()")) 174 } else { 175 desc := strings.Trim(strings.Split(robj.String(), "{")[0], " \t\n") 176 desc = strings.Replace(desc, " (", "(", 1) 177 fmt.Fprint(ctx.w, FunctionColor("%s", desc)) 178 } 179 180 case "RegExp": 181 fmt.Fprint(ctx.w, StringColor("%s", toString(obj))) 182 183 default: 184 if v, _ := obj.Get("toString"); v.IsFunction() && level <= maxPrettyPrintLevel { 185 s, _ := obj.Call("toString") 186 fmt.Fprintf(ctx.w, "<%s %s>", obj.Class(), s.String()) 187 } else { 188 fmt.Fprintf(ctx.w, "<%s>", obj.Class()) 189 } 190 } 191 } 192 193 func (ctx ppctx) fields(obj *otto.Object) []string { 194 var ( 195 vals, methods []string 196 seen = make(map[string]bool) 197 ) 198 add := func(k string) { 199 if seen[k] || boringKeys[k] || strings.HasPrefix(k, "_") { 200 return 201 } 202 seen[k] = true 203 if v, _ := obj.Get(k); v.IsFunction() { 204 methods = append(methods, k) 205 } else { 206 vals = append(vals, k) 207 } 208 } 209 iterOwnAndConstructorKeys(ctx.vm, obj, add) 210 sort.Strings(vals) 211 sort.Strings(methods) 212 return append(vals, methods...) 213 } 214 215 func iterOwnAndConstructorKeys(vm *otto.Otto, obj *otto.Object, f func(string)) { 216 seen := make(map[string]bool) 217 iterOwnKeys(vm, obj, func(prop string) { 218 seen[prop] = true 219 f(prop) 220 }) 221 if cp := constructorPrototype(obj); cp != nil { 222 iterOwnKeys(vm, cp, func(prop string) { 223 if !seen[prop] { 224 f(prop) 225 } 226 }) 227 } 228 } 229 230 func iterOwnKeys(vm *otto.Otto, obj *otto.Object, f func(string)) { 231 Object, _ := vm.Object("Object") 232 rv, _ := Object.Call("getOwnPropertyNames", obj.Value()) 233 gv, _ := rv.Export() 234 switch gv := gv.(type) { 235 case []interface{}: 236 for _, v := range gv { 237 f(v.(string)) 238 } 239 case []string: 240 for _, v := range gv { 241 f(v) 242 } 243 default: 244 panic(fmt.Errorf("Object.getOwnPropertyNames returned unexpected type %T", gv)) 245 } 246 } 247 248 func (ctx ppctx) isBigNumber(v *otto.Object) bool { 249 // Handle numbers with custom constructor. 250 if v, _ := v.Get("constructor"); v.Object() != nil { 251 if strings.HasPrefix(toString(v.Object()), "function BigNumber") { 252 return true 253 } 254 } 255 // Handle default constructor. 256 BigNumber, _ := ctx.vm.Object("BigNumber.prototype") 257 if BigNumber == nil { 258 return false 259 } 260 bv, _ := BigNumber.Call("isPrototypeOf", v) 261 b, _ := bv.ToBoolean() 262 return b 263 } 264 265 func toString(obj *otto.Object) string { 266 s, _ := obj.Call("toString") 267 return s.String() 268 } 269 270 func constructorPrototype(obj *otto.Object) *otto.Object { 271 if v, _ := obj.Get("constructor"); v.Object() != nil { 272 if v, _ = v.Object().Get("prototype"); v.Object() != nil { 273 return v.Object() 274 } 275 } 276 return nil 277 }