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