github.com/klaytn/klaytn@v1.12.1/console/jsre/pretty.go (about)

     1  // Modifications Copyright 2018 The klaytn Authors
     2  // Copyright 2016 The go-ethereum Authors
     3  // This file is part of the go-ethereum library.
     4  //
     5  // The go-ethereum library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The go-ethereum library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // This file is derived from internal/jsre/pretty.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  package jsre
    22  
    23  import (
    24  	"fmt"
    25  	"io"
    26  	"reflect"
    27  	"sort"
    28  	"strconv"
    29  	"strings"
    30  
    31  	"github.com/dop251/goja"
    32  
    33  	"github.com/fatih/color"
    34  )
    35  
    36  const (
    37  	maxPrettyPrintLevel = 3
    38  	indentString        = "  "
    39  )
    40  
    41  var (
    42  	FunctionColor = color.New(color.FgMagenta).SprintfFunc()
    43  	SpecialColor  = color.New(color.Bold).SprintfFunc()
    44  	NumberColor   = color.New(color.FgRed).SprintfFunc()
    45  	StringColor   = color.New(color.FgGreen).SprintfFunc()
    46  	ErrorColor    = color.New(color.FgHiRed).SprintfFunc()
    47  )
    48  
    49  // these fields are hidden when printing objects.
    50  var boringKeys = map[string]bool{
    51  	"valueOf":              true,
    52  	"toString":             true,
    53  	"toLocaleString":       true,
    54  	"hasOwnProperty":       true,
    55  	"isPrototypeOf":        true,
    56  	"propertyIsEnumerable": true,
    57  	"constructor":          true,
    58  }
    59  
    60  // prettyPrint writes value to standard output.
    61  func prettyPrint(vm *goja.Runtime, value goja.Value, w io.Writer) {
    62  	ppctx{vm: vm, w: w}.printValue(value, 0, false)
    63  }
    64  
    65  // prettyError writes err to standard output.
    66  func prettyError(vm *goja.Runtime, err error, w io.Writer) {
    67  	failure := err.Error()
    68  	if gojaErr, ok := err.(*goja.Exception); ok {
    69  		failure = gojaErr.String()
    70  	}
    71  	fmt.Fprint(w, ErrorColor("%s", failure))
    72  }
    73  
    74  func (re *JSRE) prettyPrintJS(call goja.FunctionCall) goja.Value {
    75  	for _, v := range call.Arguments {
    76  		prettyPrint(re.vm, v, re.output)
    77  		fmt.Fprintln(re.output)
    78  	}
    79  	return goja.Undefined()
    80  }
    81  
    82  type ppctx struct {
    83  	vm *goja.Runtime
    84  	w  io.Writer
    85  }
    86  
    87  func (ctx ppctx) indent(level int) string {
    88  	return strings.Repeat(indentString, level)
    89  }
    90  
    91  func (ctx ppctx) printValue(v goja.Value, level int, inArray bool) {
    92  	if goja.IsNull(v) || goja.IsUndefined(v) {
    93  		fmt.Fprint(ctx.w, SpecialColor(v.String()))
    94  		return
    95  	}
    96  	kind := v.ExportType().Kind()
    97  	switch {
    98  	case kind == reflect.Bool:
    99  		fmt.Fprint(ctx.w, SpecialColor("%t", v.ToBoolean()))
   100  	case kind == reflect.String:
   101  		fmt.Fprint(ctx.w, StringColor("%q", v.String()))
   102  	case kind >= reflect.Int && kind <= reflect.Complex128:
   103  		fmt.Fprint(ctx.w, NumberColor("%s", v.String()))
   104  	default:
   105  		if obj, ok := v.(*goja.Object); ok {
   106  			ctx.printObject(obj, level, inArray)
   107  		} else {
   108  			fmt.Fprintf(ctx.w, "<unprintable %T>", v)
   109  		}
   110  	}
   111  }
   112  
   113  // SafeGet attempt to get the value associated to `key`, and
   114  // catches the panic that goja creates if an error occurs in
   115  // key.
   116  func SafeGet(obj *goja.Object, key string) (ret goja.Value) {
   117  	defer func() {
   118  		if r := recover(); r != nil {
   119  			ret = goja.Undefined()
   120  		}
   121  	}()
   122  	ret = obj.Get(key)
   123  
   124  	return ret
   125  }
   126  
   127  func (ctx ppctx) printObject(obj *goja.Object, level int, inArray bool) {
   128  	switch obj.ClassName() {
   129  	case "Array", "GoArray":
   130  		lv := obj.Get("length")
   131  		len := lv.ToInteger()
   132  		if len == 0 {
   133  			fmt.Fprintf(ctx.w, "[]")
   134  			return
   135  		}
   136  		if level > maxPrettyPrintLevel {
   137  			fmt.Fprint(ctx.w, "[...]")
   138  			return
   139  		}
   140  		fmt.Fprint(ctx.w, "[")
   141  		for i := int64(0); i < len; i++ {
   142  			el := obj.Get(strconv.FormatInt(i, 10))
   143  			if el != nil {
   144  				ctx.printValue(el, level+1, true)
   145  			}
   146  			if i < len-1 {
   147  				fmt.Fprintf(ctx.w, ", ")
   148  			}
   149  		}
   150  		fmt.Fprint(ctx.w, "]")
   151  
   152  	case "Object":
   153  		// Print values from bignumber.js as regular numbers.
   154  		if ctx.isBigNumber(obj) {
   155  			fmt.Fprint(ctx.w, NumberColor("%s", toString(obj)))
   156  			return
   157  		}
   158  		// Otherwise, print all fields indented, but stop if we're too deep.
   159  		keys := ctx.fields(obj)
   160  		if len(keys) == 0 {
   161  			fmt.Fprint(ctx.w, "{}")
   162  			return
   163  		}
   164  		if level > maxPrettyPrintLevel {
   165  			fmt.Fprint(ctx.w, "{...}")
   166  			return
   167  		}
   168  		fmt.Fprintln(ctx.w, "{")
   169  		for i, k := range keys {
   170  			v := SafeGet(obj, k)
   171  			fmt.Fprintf(ctx.w, "%s%s: ", ctx.indent(level+1), k)
   172  			ctx.printValue(v, level+1, false)
   173  			if i < len(keys)-1 {
   174  				fmt.Fprintf(ctx.w, ",")
   175  			}
   176  			fmt.Fprintln(ctx.w)
   177  		}
   178  		if inArray {
   179  			level--
   180  		}
   181  		fmt.Fprintf(ctx.w, "%s}", ctx.indent(level))
   182  
   183  	case "Function":
   184  		robj := obj.ToString()
   185  		desc := strings.Trim(strings.Split(robj.String(), "{")[0], " \t\n")
   186  		desc = strings.Replace(desc, " (", "(", 1)
   187  		fmt.Fprint(ctx.w, FunctionColor("%s", desc))
   188  
   189  	case "RegExp":
   190  		fmt.Fprint(ctx.w, StringColor("%s", toString(obj)))
   191  
   192  	default:
   193  		if level <= maxPrettyPrintLevel {
   194  			s := obj.ToString().String()
   195  			fmt.Fprintf(ctx.w, "<%s %s>", obj.ClassName(), s)
   196  		} else {
   197  			fmt.Fprintf(ctx.w, "<%s>", obj.ClassName())
   198  		}
   199  	}
   200  }
   201  
   202  func (ctx ppctx) fields(obj *goja.Object) []string {
   203  	var (
   204  		vals, methods []string
   205  		seen          = make(map[string]bool)
   206  	)
   207  	add := func(k string) {
   208  		if seen[k] || boringKeys[k] || strings.HasPrefix(k, "_") {
   209  			return
   210  		}
   211  		seen[k] = true
   212  
   213  		key := SafeGet(obj, k)
   214  		if key == nil {
   215  			// The value corresponding to that key could not be found
   216  			// (typically because it is backed by an RPC call that is
   217  			// not supported by this instance.  Add it to the list of
   218  			// values so that it appears as `undefined` to the user.
   219  			vals = append(vals, k)
   220  		} else {
   221  			if _, callable := goja.AssertFunction(key); callable {
   222  				methods = append(methods, k)
   223  			} else {
   224  				vals = append(vals, k)
   225  			}
   226  		}
   227  	}
   228  	iterOwnAndConstructorKeys(ctx.vm, obj, add)
   229  	sort.Strings(vals)
   230  	sort.Strings(methods)
   231  	return append(vals, methods...)
   232  }
   233  
   234  func iterOwnAndConstructorKeys(vm *goja.Runtime, obj *goja.Object, f func(string)) {
   235  	seen := make(map[string]bool)
   236  	iterOwnKeys(vm, obj, func(prop string) {
   237  		seen[prop] = true
   238  		f(prop)
   239  	})
   240  	if cp := constructorPrototype(vm, obj); cp != nil {
   241  		iterOwnKeys(vm, cp, func(prop string) {
   242  			if !seen[prop] {
   243  				f(prop)
   244  			}
   245  		})
   246  	}
   247  }
   248  
   249  func iterOwnKeys(vm *goja.Runtime, obj *goja.Object, f func(string)) {
   250  	Object := vm.Get("Object").ToObject(vm)
   251  	getOwnPropertyNames, isFunc := goja.AssertFunction(Object.Get("getOwnPropertyNames"))
   252  	if !isFunc {
   253  		panic(vm.ToValue("Object.getOwnPropertyNames isn't a function"))
   254  	}
   255  	rv, err := getOwnPropertyNames(goja.Null(), obj)
   256  	if err != nil {
   257  		panic(vm.ToValue(fmt.Sprintf("Error getting object properties: %v", err)))
   258  	}
   259  	gv := rv.Export()
   260  	switch gv := gv.(type) {
   261  	case []interface{}:
   262  		for _, v := range gv {
   263  			f(v.(string))
   264  		}
   265  	case []string:
   266  		for _, v := range gv {
   267  			f(v)
   268  		}
   269  	default:
   270  		panic(fmt.Errorf("Object.getOwnPropertyNames returned unexpected type %T", gv))
   271  	}
   272  }
   273  
   274  func (ctx ppctx) isBigNumber(v *goja.Object) bool {
   275  	// Handle numbers with custom constructor.
   276  	if obj := v.Get("constructor").ToObject(ctx.vm); obj != nil {
   277  		if strings.HasPrefix(toString(obj), "function BigNumber") {
   278  			return true
   279  		}
   280  	}
   281  	// Handle default constructor.
   282  	BigNumber := ctx.vm.Get("BigNumber").ToObject(ctx.vm)
   283  	if BigNumber == nil {
   284  		return false
   285  	}
   286  	prototype := BigNumber.Get("prototype").ToObject(ctx.vm)
   287  	isPrototypeOf, callable := goja.AssertFunction(prototype.Get("isPrototypeOf"))
   288  	if !callable {
   289  		return false
   290  	}
   291  	bv, _ := isPrototypeOf(prototype, v)
   292  	return bv.ToBoolean()
   293  }
   294  
   295  func toString(obj *goja.Object) string {
   296  	return obj.ToString().String()
   297  }
   298  
   299  func constructorPrototype(vm *goja.Runtime, obj *goja.Object) *goja.Object {
   300  	if v := obj.Get("constructor"); v != nil {
   301  		if v := v.ToObject(vm).Get("prototype"); v != nil {
   302  			return v.ToObject(vm)
   303  		}
   304  	}
   305  	return nil
   306  }