github.com/core-coin/go-core/v2@v2.1.9/internal/jsre/pretty.go (about)

     1  // Copyright 2016 by the Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core 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-core 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-core 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  	}
   224  	iterOwnAndConstructorKeys(ctx.vm, obj, add)
   225  	sort.Strings(vals)
   226  	sort.Strings(methods)
   227  	return append(vals, methods...)
   228  }
   229  
   230  func iterOwnAndConstructorKeys(vm *goja.Runtime, obj *goja.Object, f func(string)) {
   231  	seen := make(map[string]bool)
   232  	iterOwnKeys(vm, obj, func(prop string) {
   233  		seen[prop] = true
   234  		f(prop)
   235  	})
   236  	if cp := constructorPrototype(vm, obj); cp != nil {
   237  		iterOwnKeys(vm, cp, func(prop string) {
   238  			if !seen[prop] {
   239  				f(prop)
   240  			}
   241  		})
   242  	}
   243  }
   244  
   245  func iterOwnKeys(vm *goja.Runtime, obj *goja.Object, f func(string)) {
   246  	Object := vm.Get("Object").ToObject(vm)
   247  	getOwnPropertyNames, isFunc := goja.AssertFunction(Object.Get("getOwnPropertyNames"))
   248  	if !isFunc {
   249  		panic(vm.ToValue("Object.getOwnPropertyNames isn't a function"))
   250  	}
   251  	rv, err := getOwnPropertyNames(goja.Null(), obj)
   252  	if err != nil {
   253  		panic(vm.ToValue(fmt.Sprintf("Error getting object properties: %v", err)))
   254  	}
   255  	gv := rv.Export()
   256  	switch gv := gv.(type) {
   257  	case []interface{}:
   258  		for _, v := range gv {
   259  			f(v.(string))
   260  		}
   261  	case []string:
   262  		for _, v := range gv {
   263  			f(v)
   264  		}
   265  	default:
   266  		panic(fmt.Errorf("Object.getOwnPropertyNames returned unexpected type %T", gv))
   267  	}
   268  }
   269  
   270  func (ctx ppctx) isBigNumber(v *goja.Object) bool {
   271  	// Handle numbers with custom constructor.
   272  	if obj := v.Get("constructor").ToObject(ctx.vm); obj != nil {
   273  		if strings.HasPrefix(toString(obj), "function BigNumber") {
   274  			return true
   275  		}
   276  	}
   277  	// Handle default constructor.
   278  	BigNumber := ctx.vm.Get("BigNumber").ToObject(ctx.vm)
   279  	if BigNumber == nil {
   280  		return false
   281  	}
   282  	prototype := BigNumber.Get("prototype").ToObject(ctx.vm)
   283  	isPrototypeOf, callable := goja.AssertFunction(prototype.Get("isPrototypeOf"))
   284  	if !callable {
   285  		return false
   286  	}
   287  	bv, _ := isPrototypeOf(prototype, v)
   288  	return bv.ToBoolean()
   289  }
   290  
   291  func toString(obj *goja.Object) string {
   292  	return obj.ToString().String()
   293  }
   294  
   295  func constructorPrototype(vm *goja.Runtime, obj *goja.Object) *goja.Object {
   296  	if v := obj.Get("constructor"); v != nil {
   297  		if v := v.ToObject(vm).Get("prototype"); v != nil {
   298  			return v.ToObject(vm)
   299  		}
   300  	}
   301  	return nil
   302  }