github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/internal/jsre/pretty.go (about)

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