github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/internal/jsre/pretty.go (about)

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