github.com/Elemental-core/elementalcore@v0.0.0-20191206075037-63891242267a/internal/jsre/pretty.go (about)

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