github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/internal/jsre/pretty.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // Copyright 2019 The go-aigar Authors 3 // This file is part of the go-aigar library. 4 // 5 // The go-aigar 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-aigar 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-aigar library. If not, see <http://www.gnu.org/licenses/>. 17 18 package jsre 19 20 import ( 21 "fmt" 22 "io" 23 "sort" 24 "strconv" 25 "strings" 26 27 "github.com/fatih/color" 28 "github.com/robertkrimen/otto" 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 *otto.Otto, value otto.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 *otto.Otto, err error, w io.Writer) { 62 failure := err.Error() 63 if ottoErr, ok := err.(*otto.Error); ok { 64 failure = ottoErr.String() 65 } 66 fmt.Fprint(w, ErrorColor("%s", failure)) 67 } 68 69 func (re *JSRE) prettyPrintJS(call otto.FunctionCall) otto.Value { 70 for _, v := range call.ArgumentList { 71 prettyPrint(call.Otto, v, re.output) 72 fmt.Fprintln(re.output) 73 } 74 return otto.UndefinedValue() 75 } 76 77 type ppctx struct { 78 vm *otto.Otto 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 otto.Value, level int, inArray bool) { 87 switch { 88 case v.IsObject(): 89 ctx.printObject(v.Object(), level, inArray) 90 case v.IsNull(): 91 fmt.Fprint(ctx.w, SpecialColor("null")) 92 case v.IsUndefined(): 93 fmt.Fprint(ctx.w, SpecialColor("undefined")) 94 case v.IsString(): 95 s, _ := v.ToString() 96 fmt.Fprint(ctx.w, StringColor("%q", s)) 97 case v.IsBoolean(): 98 b, _ := v.ToBoolean() 99 fmt.Fprint(ctx.w, SpecialColor("%t", b)) 100 case v.IsNaN(): 101 fmt.Fprint(ctx.w, NumberColor("NaN")) 102 case v.IsNumber(): 103 s, _ := v.ToString() 104 fmt.Fprint(ctx.w, NumberColor("%s", s)) 105 default: 106 fmt.Fprint(ctx.w, "<unprintable>") 107 } 108 } 109 110 func (ctx ppctx) printObject(obj *otto.Object, level int, inArray bool) { 111 switch obj.Class() { 112 case "Array", "GoArray": 113 lv, _ := obj.Get("length") 114 len, _ := lv.ToInteger() 115 if len == 0 { 116 fmt.Fprintf(ctx.w, "[]") 117 return 118 } 119 if level > maxPrettyPrintLevel { 120 fmt.Fprint(ctx.w, "[...]") 121 return 122 } 123 fmt.Fprint(ctx.w, "[") 124 for i := int64(0); i < len; i++ { 125 el, err := obj.Get(strconv.FormatInt(i, 10)) 126 if err == nil { 127 ctx.printValue(el, level+1, true) 128 } 129 if i < len-1 { 130 fmt.Fprintf(ctx.w, ", ") 131 } 132 } 133 fmt.Fprint(ctx.w, "]") 134 135 case "Object": 136 // Print values from bignumber.js as regular numbers. 137 if ctx.isBigNumber(obj) { 138 fmt.Fprint(ctx.w, NumberColor("%s", toString(obj))) 139 return 140 } 141 // Otherwise, print all fields indented, but stop if we're too deep. 142 keys := ctx.fields(obj) 143 if len(keys) == 0 { 144 fmt.Fprint(ctx.w, "{}") 145 return 146 } 147 if level > maxPrettyPrintLevel { 148 fmt.Fprint(ctx.w, "{...}") 149 return 150 } 151 fmt.Fprintln(ctx.w, "{") 152 for i, k := range keys { 153 v, _ := obj.Get(k) 154 fmt.Fprintf(ctx.w, "%s%s: ", ctx.indent(level+1), k) 155 ctx.printValue(v, level+1, false) 156 if i < len(keys)-1 { 157 fmt.Fprintf(ctx.w, ",") 158 } 159 fmt.Fprintln(ctx.w) 160 } 161 if inArray { 162 level-- 163 } 164 fmt.Fprintf(ctx.w, "%s}", ctx.indent(level)) 165 166 case "Function": 167 // Use toString() to display the argument list if possible. 168 if robj, err := obj.Call("toString"); err != nil { 169 fmt.Fprint(ctx.w, FunctionColor("function()")) 170 } else { 171 desc := strings.Trim(strings.Split(robj.String(), "{")[0], " \t\n") 172 desc = strings.Replace(desc, " (", "(", 1) 173 fmt.Fprint(ctx.w, FunctionColor("%s", desc)) 174 } 175 176 case "RegExp": 177 fmt.Fprint(ctx.w, StringColor("%s", toString(obj))) 178 179 default: 180 if v, _ := obj.Get("toString"); v.IsFunction() && level <= maxPrettyPrintLevel { 181 s, _ := obj.Call("toString") 182 fmt.Fprintf(ctx.w, "<%s %s>", obj.Class(), s.String()) 183 } else { 184 fmt.Fprintf(ctx.w, "<%s>", obj.Class()) 185 } 186 } 187 } 188 189 func (ctx ppctx) fields(obj *otto.Object) []string { 190 var ( 191 vals, methods []string 192 seen = make(map[string]bool) 193 ) 194 add := func(k string) { 195 if seen[k] || boringKeys[k] || strings.HasPrefix(k, "_") { 196 return 197 } 198 seen[k] = true 199 if v, _ := obj.Get(k); v.IsFunction() { 200 methods = append(methods, k) 201 } else { 202 vals = append(vals, k) 203 } 204 } 205 iterOwnAndConstructorKeys(ctx.vm, obj, add) 206 sort.Strings(vals) 207 sort.Strings(methods) 208 return append(vals, methods...) 209 } 210 211 func iterOwnAndConstructorKeys(vm *otto.Otto, obj *otto.Object, f func(string)) { 212 seen := make(map[string]bool) 213 iterOwnKeys(vm, obj, func(prop string) { 214 seen[prop] = true 215 f(prop) 216 }) 217 if cp := constructorPrototype(obj); cp != nil { 218 iterOwnKeys(vm, cp, func(prop string) { 219 if !seen[prop] { 220 f(prop) 221 } 222 }) 223 } 224 } 225 226 func iterOwnKeys(vm *otto.Otto, obj *otto.Object, f func(string)) { 227 Object, _ := vm.Object("Object") 228 rv, _ := Object.Call("getOwnPropertyNames", obj.Value()) 229 gv, _ := rv.Export() 230 switch gv := gv.(type) { 231 case []interface{}: 232 for _, v := range gv { 233 f(v.(string)) 234 } 235 case []string: 236 for _, v := range gv { 237 f(v) 238 } 239 default: 240 panic(fmt.Errorf("Object.getOwnPropertyNames returned unexpected type %T", gv)) 241 } 242 } 243 244 func (ctx ppctx) isBigNumber(v *otto.Object) bool { 245 // Handle numbers with custom constructor. 246 if v, _ := v.Get("constructor"); v.Object() != nil { 247 if strings.HasPrefix(toString(v.Object()), "function BigNumber") { 248 return true 249 } 250 } 251 // Handle default constructor. 252 BigNumber, _ := ctx.vm.Object("BigNumber.prototype") 253 if BigNumber == nil { 254 return false 255 } 256 bv, _ := BigNumber.Call("isPrototypeOf", v) 257 b, _ := bv.ToBoolean() 258 return b 259 } 260 261 func toString(obj *otto.Object) string { 262 s, _ := obj.Call("toString") 263 return s.String() 264 } 265 266 func constructorPrototype(obj *otto.Object) *otto.Object { 267 if v, _ := obj.Get("constructor"); v.Object() != nil { 268 if v, _ = v.Object().Get("prototype"); v.Object() != nil { 269 return v.Object() 270 } 271 } 272 return nil 273 }