github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/internal/jsre/pretty.go (about)

     1  //  Copyright 2018 The go-ethereum Authors
     2  //  Copyright 2019 The go-aigar Authors
     3  //  This file is part of the go-aigar library.
     4  //
     5  //  The go-aigar 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-aigar 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-aigar library. If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package jsre
    19  
    20  import (
    21  	"fmt"
    22  	"io"
    23  	"sort"
    24  	"strconv"
    25  	"strings"
    26  
    27  	"github.com/fatih/color"
    28  	"github.com/robertkrimen/otto"
    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 *otto.Otto, value otto.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 *otto.Otto, err error, w io.Writer) {
    62  	failure := err.Error()
    63  	if ottoErr, ok := err.(*otto.Error); ok {
    64  		failure = ottoErr.String()
    65  	}
    66  	fmt.Fprint(w, ErrorColor("%s", failure))
    67  }
    68  
    69  func (re *JSRE) prettyPrintJS(call otto.FunctionCall) otto.Value {
    70  	for _, v := range call.ArgumentList {
    71  		prettyPrint(call.Otto, v, re.output)
    72  		fmt.Fprintln(re.output)
    73  	}
    74  	return otto.UndefinedValue()
    75  }
    76  
    77  type ppctx struct {
    78  	vm *otto.Otto
    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 otto.Value, level int, inArray bool) {
    87  	switch {
    88  	case v.IsObject():
    89  		ctx.printObject(v.Object(), level, inArray)
    90  	case v.IsNull():
    91  		fmt.Fprint(ctx.w, SpecialColor("null"))
    92  	case v.IsUndefined():
    93  		fmt.Fprint(ctx.w, SpecialColor("undefined"))
    94  	case v.IsString():
    95  		s, _ := v.ToString()
    96  		fmt.Fprint(ctx.w, StringColor("%q", s))
    97  	case v.IsBoolean():
    98  		b, _ := v.ToBoolean()
    99  		fmt.Fprint(ctx.w, SpecialColor("%t", b))
   100  	case v.IsNaN():
   101  		fmt.Fprint(ctx.w, NumberColor("NaN"))
   102  	case v.IsNumber():
   103  		s, _ := v.ToString()
   104  		fmt.Fprint(ctx.w, NumberColor("%s", s))
   105  	default:
   106  		fmt.Fprint(ctx.w, "<unprintable>")
   107  	}
   108  }
   109  
   110  func (ctx ppctx) printObject(obj *otto.Object, level int, inArray bool) {
   111  	switch obj.Class() {
   112  	case "Array", "GoArray":
   113  		lv, _ := obj.Get("length")
   114  		len, _ := lv.ToInteger()
   115  		if len == 0 {
   116  			fmt.Fprintf(ctx.w, "[]")
   117  			return
   118  		}
   119  		if level > maxPrettyPrintLevel {
   120  			fmt.Fprint(ctx.w, "[...]")
   121  			return
   122  		}
   123  		fmt.Fprint(ctx.w, "[")
   124  		for i := int64(0); i < len; i++ {
   125  			el, err := obj.Get(strconv.FormatInt(i, 10))
   126  			if err == nil {
   127  				ctx.printValue(el, level+1, true)
   128  			}
   129  			if i < len-1 {
   130  				fmt.Fprintf(ctx.w, ", ")
   131  			}
   132  		}
   133  		fmt.Fprint(ctx.w, "]")
   134  
   135  	case "Object":
   136  		// Print values from bignumber.js as regular numbers.
   137  		if ctx.isBigNumber(obj) {
   138  			fmt.Fprint(ctx.w, NumberColor("%s", toString(obj)))
   139  			return
   140  		}
   141  		// Otherwise, print all fields indented, but stop if we're too deep.
   142  		keys := ctx.fields(obj)
   143  		if len(keys) == 0 {
   144  			fmt.Fprint(ctx.w, "{}")
   145  			return
   146  		}
   147  		if level > maxPrettyPrintLevel {
   148  			fmt.Fprint(ctx.w, "{...}")
   149  			return
   150  		}
   151  		fmt.Fprintln(ctx.w, "{")
   152  		for i, k := range keys {
   153  			v, _ := obj.Get(k)
   154  			fmt.Fprintf(ctx.w, "%s%s: ", ctx.indent(level+1), k)
   155  			ctx.printValue(v, level+1, false)
   156  			if i < len(keys)-1 {
   157  				fmt.Fprintf(ctx.w, ",")
   158  			}
   159  			fmt.Fprintln(ctx.w)
   160  		}
   161  		if inArray {
   162  			level--
   163  		}
   164  		fmt.Fprintf(ctx.w, "%s}", ctx.indent(level))
   165  
   166  	case "Function":
   167  		// Use toString() to display the argument list if possible.
   168  		if robj, err := obj.Call("toString"); err != nil {
   169  			fmt.Fprint(ctx.w, FunctionColor("function()"))
   170  		} else {
   171  			desc := strings.Trim(strings.Split(robj.String(), "{")[0], " \t\n")
   172  			desc = strings.Replace(desc, " (", "(", 1)
   173  			fmt.Fprint(ctx.w, FunctionColor("%s", desc))
   174  		}
   175  
   176  	case "RegExp":
   177  		fmt.Fprint(ctx.w, StringColor("%s", toString(obj)))
   178  
   179  	default:
   180  		if v, _ := obj.Get("toString"); v.IsFunction() && level <= maxPrettyPrintLevel {
   181  			s, _ := obj.Call("toString")
   182  			fmt.Fprintf(ctx.w, "<%s %s>", obj.Class(), s.String())
   183  		} else {
   184  			fmt.Fprintf(ctx.w, "<%s>", obj.Class())
   185  		}
   186  	}
   187  }
   188  
   189  func (ctx ppctx) fields(obj *otto.Object) []string {
   190  	var (
   191  		vals, methods []string
   192  		seen          = make(map[string]bool)
   193  	)
   194  	add := func(k string) {
   195  		if seen[k] || boringKeys[k] || strings.HasPrefix(k, "_") {
   196  			return
   197  		}
   198  		seen[k] = true
   199  		if v, _ := obj.Get(k); v.IsFunction() {
   200  			methods = append(methods, k)
   201  		} else {
   202  			vals = append(vals, k)
   203  		}
   204  	}
   205  	iterOwnAndConstructorKeys(ctx.vm, obj, add)
   206  	sort.Strings(vals)
   207  	sort.Strings(methods)
   208  	return append(vals, methods...)
   209  }
   210  
   211  func iterOwnAndConstructorKeys(vm *otto.Otto, obj *otto.Object, f func(string)) {
   212  	seen := make(map[string]bool)
   213  	iterOwnKeys(vm, obj, func(prop string) {
   214  		seen[prop] = true
   215  		f(prop)
   216  	})
   217  	if cp := constructorPrototype(obj); cp != nil {
   218  		iterOwnKeys(vm, cp, func(prop string) {
   219  			if !seen[prop] {
   220  				f(prop)
   221  			}
   222  		})
   223  	}
   224  }
   225  
   226  func iterOwnKeys(vm *otto.Otto, obj *otto.Object, f func(string)) {
   227  	Object, _ := vm.Object("Object")
   228  	rv, _ := Object.Call("getOwnPropertyNames", obj.Value())
   229  	gv, _ := rv.Export()
   230  	switch gv := gv.(type) {
   231  	case []interface{}:
   232  		for _, v := range gv {
   233  			f(v.(string))
   234  		}
   235  	case []string:
   236  		for _, v := range gv {
   237  			f(v)
   238  		}
   239  	default:
   240  		panic(fmt.Errorf("Object.getOwnPropertyNames returned unexpected type %T", gv))
   241  	}
   242  }
   243  
   244  func (ctx ppctx) isBigNumber(v *otto.Object) bool {
   245  	// Handle numbers with custom constructor.
   246  	if v, _ := v.Get("constructor"); v.Object() != nil {
   247  		if strings.HasPrefix(toString(v.Object()), "function BigNumber") {
   248  			return true
   249  		}
   250  	}
   251  	// Handle default constructor.
   252  	BigNumber, _ := ctx.vm.Object("BigNumber.prototype")
   253  	if BigNumber == nil {
   254  		return false
   255  	}
   256  	bv, _ := BigNumber.Call("isPrototypeOf", v)
   257  	b, _ := bv.ToBoolean()
   258  	return b
   259  }
   260  
   261  func toString(obj *otto.Object) string {
   262  	s, _ := obj.Call("toString")
   263  	return s.String()
   264  }
   265  
   266  func constructorPrototype(obj *otto.Object) *otto.Object {
   267  	if v, _ := obj.Get("constructor"); v.Object() != nil {
   268  		if v, _ = v.Object().Get("prototype"); v.Object() != nil {
   269  			return v.Object()
   270  		}
   271  	}
   272  	return nil
   273  }