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