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