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