github.com/ur-technology/go-ur@v1.5.5/internal/jsre/pretty.go (about)

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