github.com/ethereum/go-ethereum@v1.14.3/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  	"reflect"
    23  	"sort"
    24  	"strconv"
    25  	"strings"
    26  
    27  	"github.com/dop251/goja"
    28  	"github.com/fatih/color"
    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 *goja.Runtime, value goja.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 *goja.Runtime, err error, w io.Writer) {
    62  	failure := err.Error()
    63  	if gojaErr, ok := err.(*goja.Exception); ok {
    64  		failure = gojaErr.String()
    65  	}
    66  	fmt.Fprint(w, ErrorColor("%s", failure))
    67  }
    68  
    69  func (re *JSRE) prettyPrintJS(call goja.FunctionCall) goja.Value {
    70  	for _, v := range call.Arguments {
    71  		prettyPrint(re.vm, v, re.output)
    72  		fmt.Fprintln(re.output)
    73  	}
    74  	return goja.Undefined()
    75  }
    76  
    77  type ppctx struct {
    78  	vm *goja.Runtime
    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 goja.Value, level int, inArray bool) {
    87  	if goja.IsNull(v) || goja.IsUndefined(v) {
    88  		fmt.Fprint(ctx.w, SpecialColor(v.String()))
    89  		return
    90  	}
    91  	kind := v.ExportType().Kind()
    92  	switch {
    93  	case kind == reflect.Bool:
    94  		fmt.Fprint(ctx.w, SpecialColor("%t", v.ToBoolean()))
    95  	case kind == reflect.String:
    96  		fmt.Fprint(ctx.w, StringColor("%q", v.String()))
    97  	case kind >= reflect.Int && kind <= reflect.Complex128:
    98  		fmt.Fprint(ctx.w, NumberColor("%s", v.String()))
    99  	default:
   100  		if obj, ok := v.(*goja.Object); ok {
   101  			ctx.printObject(obj, level, inArray)
   102  		} else {
   103  			fmt.Fprintf(ctx.w, "<unprintable %T>", v)
   104  		}
   105  	}
   106  }
   107  
   108  // SafeGet attempt to get the value associated to `key`, and
   109  // catches the panic that goja creates if an error occurs in
   110  // key.
   111  func SafeGet(obj *goja.Object, key string) (ret goja.Value) {
   112  	defer func() {
   113  		if r := recover(); r != nil {
   114  			ret = goja.Undefined()
   115  		}
   116  	}()
   117  	ret = obj.Get(key)
   118  
   119  	return ret
   120  }
   121  
   122  func (ctx ppctx) printObject(obj *goja.Object, level int, inArray bool) {
   123  	switch obj.ClassName() {
   124  	case "Array", "GoArray":
   125  		lv := obj.Get("length")
   126  		len := lv.ToInteger()
   127  		if len == 0 {
   128  			fmt.Fprintf(ctx.w, "[]")
   129  			return
   130  		}
   131  		if level > maxPrettyPrintLevel {
   132  			fmt.Fprint(ctx.w, "[...]")
   133  			return
   134  		}
   135  		fmt.Fprint(ctx.w, "[")
   136  		for i := int64(0); i < len; i++ {
   137  			el := obj.Get(strconv.FormatInt(i, 10))
   138  			if el != nil {
   139  				ctx.printValue(el, level+1, true)
   140  			}
   141  			if i < len-1 {
   142  				fmt.Fprintf(ctx.w, ", ")
   143  			}
   144  		}
   145  		fmt.Fprint(ctx.w, "]")
   146  
   147  	case "Object":
   148  		// Print values from bignumber.js as regular numbers.
   149  		if ctx.isBigNumber(obj) {
   150  			fmt.Fprint(ctx.w, NumberColor("%s", toString(obj)))
   151  			return
   152  		}
   153  		// Otherwise, print all fields indented, but stop if we're too deep.
   154  		keys := ctx.fields(obj)
   155  		if len(keys) == 0 {
   156  			fmt.Fprint(ctx.w, "{}")
   157  			return
   158  		}
   159  		if level > maxPrettyPrintLevel {
   160  			fmt.Fprint(ctx.w, "{...}")
   161  			return
   162  		}
   163  		fmt.Fprintln(ctx.w, "{")
   164  		for i, k := range keys {
   165  			v := SafeGet(obj, k)
   166  			fmt.Fprintf(ctx.w, "%s%s: ", ctx.indent(level+1), k)
   167  			ctx.printValue(v, level+1, false)
   168  			if i < len(keys)-1 {
   169  				fmt.Fprintf(ctx.w, ",")
   170  			}
   171  			fmt.Fprintln(ctx.w)
   172  		}
   173  		if inArray {
   174  			level--
   175  		}
   176  		fmt.Fprintf(ctx.w, "%s}", ctx.indent(level))
   177  
   178  	case "Function":
   179  		robj := obj.ToString()
   180  		desc := strings.Trim(strings.Split(robj.String(), "{")[0], " \t\n")
   181  		desc = strings.Replace(desc, " (", "(", 1)
   182  		fmt.Fprint(ctx.w, FunctionColor("%s", desc))
   183  
   184  	case "RegExp":
   185  		fmt.Fprint(ctx.w, StringColor("%s", toString(obj)))
   186  
   187  	default:
   188  		if level <= maxPrettyPrintLevel {
   189  			s := obj.ToString().String()
   190  			fmt.Fprintf(ctx.w, "<%s %s>", obj.ClassName(), s)
   191  		} else {
   192  			fmt.Fprintf(ctx.w, "<%s>", obj.ClassName())
   193  		}
   194  	}
   195  }
   196  
   197  func (ctx ppctx) fields(obj *goja.Object) []string {
   198  	var (
   199  		vals, methods []string
   200  		seen          = make(map[string]bool)
   201  	)
   202  	add := func(k string) {
   203  		if seen[k] || boringKeys[k] || strings.HasPrefix(k, "_") {
   204  			return
   205  		}
   206  		seen[k] = true
   207  
   208  		key := SafeGet(obj, k)
   209  		if key == nil {
   210  			// The value corresponding to that key could not be found
   211  			// (typically because it is backed by an RPC call that is
   212  			// not supported by this instance.  Add it to the list of
   213  			// values so that it appears as `undefined` to the user.
   214  			vals = append(vals, k)
   215  		} else {
   216  			if _, callable := goja.AssertFunction(key); callable {
   217  				methods = append(methods, k)
   218  			} else {
   219  				vals = append(vals, k)
   220  			}
   221  		}
   222  	}
   223  	iterOwnAndConstructorKeys(ctx.vm, obj, add)
   224  	sort.Strings(vals)
   225  	sort.Strings(methods)
   226  	return append(vals, methods...)
   227  }
   228  
   229  func iterOwnAndConstructorKeys(vm *goja.Runtime, obj *goja.Object, f func(string)) {
   230  	seen := make(map[string]bool)
   231  	iterOwnKeys(vm, obj, func(prop string) {
   232  		seen[prop] = true
   233  		f(prop)
   234  	})
   235  	if cp := constructorPrototype(vm, obj); cp != nil {
   236  		iterOwnKeys(vm, cp, func(prop string) {
   237  			if !seen[prop] {
   238  				f(prop)
   239  			}
   240  		})
   241  	}
   242  }
   243  
   244  func iterOwnKeys(vm *goja.Runtime, obj *goja.Object, f func(string)) {
   245  	Object := vm.Get("Object").ToObject(vm)
   246  	getOwnPropertyNames, isFunc := goja.AssertFunction(Object.Get("getOwnPropertyNames"))
   247  	if !isFunc {
   248  		panic(vm.ToValue("Object.getOwnPropertyNames isn't a function"))
   249  	}
   250  	rv, err := getOwnPropertyNames(goja.Null(), obj)
   251  	if err != nil {
   252  		panic(vm.ToValue(fmt.Sprintf("Error getting object properties: %v", err)))
   253  	}
   254  	gv := rv.Export()
   255  	switch gv := gv.(type) {
   256  	case []interface{}:
   257  		for _, v := range gv {
   258  			f(v.(string))
   259  		}
   260  	case []string:
   261  		for _, v := range gv {
   262  			f(v)
   263  		}
   264  	default:
   265  		panic(fmt.Errorf("Object.getOwnPropertyNames returned unexpected type %T", gv))
   266  	}
   267  }
   268  
   269  func (ctx ppctx) isBigNumber(v *goja.Object) bool {
   270  	// Handle numbers with custom constructor.
   271  	if obj := v.Get("constructor").ToObject(ctx.vm); obj != nil {
   272  		if strings.HasPrefix(toString(obj), "function BigNumber") {
   273  			return true
   274  		}
   275  	}
   276  	// Handle default constructor.
   277  	BigNumber := ctx.vm.Get("BigNumber").ToObject(ctx.vm)
   278  	if BigNumber == nil {
   279  		return false
   280  	}
   281  	prototype := BigNumber.Get("prototype").ToObject(ctx.vm)
   282  	isPrototypeOf, callable := goja.AssertFunction(prototype.Get("isPrototypeOf"))
   283  	if !callable {
   284  		return false
   285  	}
   286  	bv, _ := isPrototypeOf(prototype, v)
   287  	return bv.ToBoolean()
   288  }
   289  
   290  func toString(obj *goja.Object) string {
   291  	return obj.ToString().String()
   292  }
   293  
   294  func constructorPrototype(vm *goja.Runtime, obj *goja.Object) *goja.Object {
   295  	if v := obj.Get("constructor"); v != nil {
   296  		if v := v.ToObject(vm).Get("prototype"); v != nil {
   297  			return v.ToObject(vm)
   298  		}
   299  	}
   300  	return nil
   301  }