cmd/geth, jsre: restore command line editing on windows
PR #856 broke command line editing by wrapping stdout with a filter that interprets ANSI escape sequences to fix colored printing on windows. Implement the printer in Go instead so it can do its own platform-dependent coloring. As a nice side effect, the JS console is now noticeably more responsive when printing results. Fixes #1608 Fixes #1612
This commit is contained in:
33
jsre/jsre.go
33
jsre/jsre.go
@ -65,7 +65,6 @@ func New(assetPath string) *JSRE {
|
||||
}
|
||||
re.loopWg.Add(1)
|
||||
go re.runEventLoop()
|
||||
re.Compile("pp.js", pp_js) // load prettyprint func definition
|
||||
re.Set("loadScript", re.loadScript)
|
||||
return re
|
||||
}
|
||||
@ -255,35 +254,19 @@ func (self *JSRE) loadScript(call otto.FunctionCall) otto.Value {
|
||||
return otto.TrueValue()
|
||||
}
|
||||
|
||||
// PrettyPrint writes v to standard output.
|
||||
func (self *JSRE) PrettyPrint(v interface{}) (val otto.Value, err error) {
|
||||
var method otto.Value
|
||||
// EvalAndPrettyPrint evaluates code and pretty prints the result to
|
||||
// standard output.
|
||||
func (self *JSRE) EvalAndPrettyPrint(code string) (err error) {
|
||||
self.do(func(vm *otto.Otto) {
|
||||
val, err = vm.ToValue(v)
|
||||
var val otto.Value
|
||||
val, err = vm.Run(code)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
method, err = vm.Get("prettyPrint")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
val, err = method.Call(method, val)
|
||||
prettyPrint(vm, val)
|
||||
fmt.Println()
|
||||
})
|
||||
return val, err
|
||||
}
|
||||
|
||||
// Eval evaluates JS function and returns result in a pretty printed string format.
|
||||
func (self *JSRE) Eval(code string) (s string, err error) {
|
||||
var val otto.Value
|
||||
val, err = self.Run(code)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
val, err = self.PrettyPrint(val)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return fmt.Sprintf("%v", val), nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Compile compiles and then runs a piece of JS code.
|
||||
|
@ -103,19 +103,14 @@ func TestNatto(t *testing.T) {
|
||||
|
||||
func TestBind(t *testing.T) {
|
||||
jsre := New("")
|
||||
defer jsre.Stop(false)
|
||||
|
||||
jsre.Bind("no", &testNativeObjectBinding{})
|
||||
|
||||
val, err := jsre.Run(`no.TestMethod("testMsg")`)
|
||||
_, err := jsre.Run(`no.TestMethod("testMsg")`)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
pp, err := jsre.PrettyPrint(val)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
t.Logf("no: %v", pp)
|
||||
jsre.Stop(false)
|
||||
}
|
||||
|
||||
func TestLoadScript(t *testing.T) {
|
||||
@ -139,4 +134,4 @@ func TestLoadScript(t *testing.T) {
|
||||
t.Errorf("expected '%v', got '%v'", exp, got)
|
||||
}
|
||||
jsre.Stop(false)
|
||||
}
|
||||
}
|
||||
|
137
jsre/pp_js.go
137
jsre/pp_js.go
@ -1,137 +0,0 @@
|
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package jsre
|
||||
|
||||
const pp_js = `
|
||||
function pp(object, indent) {
|
||||
try {
|
||||
JSON.stringify(object)
|
||||
} catch(e) {
|
||||
return pp(e, indent);
|
||||
}
|
||||
|
||||
var str = "";
|
||||
if(object instanceof Array) {
|
||||
str += "[";
|
||||
for(var i = 0, l = object.length; i < l; i++) {
|
||||
str += pp(object[i], indent);
|
||||
|
||||
if(i < l-1) {
|
||||
str += ", ";
|
||||
}
|
||||
}
|
||||
str += " ]";
|
||||
} else if (object instanceof Error) {
|
||||
str += "\033[31m" + "Error:\033[0m " + object.message;
|
||||
} else if (isBigNumber(object)) {
|
||||
str += "\033[32m'" + object.toString(10) + "'";
|
||||
} else if(typeof(object) === "object") {
|
||||
str += "{\n";
|
||||
indent += " ";
|
||||
|
||||
var fields = getFields(object);
|
||||
var last = fields[fields.length - 1];
|
||||
fields.forEach(function (key) {
|
||||
str += indent + key + ": ";
|
||||
try {
|
||||
str += pp(object[key], indent);
|
||||
} catch (e) {
|
||||
str += pp(e, indent);
|
||||
}
|
||||
if(key !== last) {
|
||||
str += ",";
|
||||
}
|
||||
str += "\n";
|
||||
});
|
||||
str += indent.substr(2, indent.length) + "}";
|
||||
} else if(typeof(object) === "string") {
|
||||
str += "\033[32m'" + object + "'";
|
||||
} else if(typeof(object) === "undefined") {
|
||||
str += "\033[1m\033[30m" + object;
|
||||
} else if(typeof(object) === "number") {
|
||||
str += "\033[31m" + object;
|
||||
} else if(typeof(object) === "function") {
|
||||
str += "\033[35m" + object.toString().split(" {")[0];
|
||||
} else {
|
||||
str += object;
|
||||
}
|
||||
|
||||
str += "\033[0m";
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
var redundantFields = [
|
||||
'valueOf',
|
||||
'toString',
|
||||
'toLocaleString',
|
||||
'hasOwnProperty',
|
||||
'isPrototypeOf',
|
||||
'propertyIsEnumerable',
|
||||
'constructor'
|
||||
];
|
||||
|
||||
var getFields = function (object) {
|
||||
var members = Object.getOwnPropertyNames(object);
|
||||
if (object.constructor && object.constructor.prototype) {
|
||||
members = members.concat(Object.getOwnPropertyNames(object.constructor.prototype));
|
||||
}
|
||||
|
||||
var fields = members.filter(function (member) {
|
||||
return !isMemberFunction(object, member)
|
||||
}).sort()
|
||||
var funcs = members.filter(function (member) {
|
||||
return isMemberFunction(object, member)
|
||||
}).sort()
|
||||
|
||||
var results = fields.concat(funcs);
|
||||
return results.filter(function (field) {
|
||||
return redundantFields.indexOf(field) === -1;
|
||||
});
|
||||
};
|
||||
|
||||
var isMemberFunction = function(object, member) {
|
||||
try {
|
||||
return typeof(object[member]) === "function";
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var isBigNumber = function (object) {
|
||||
var result = typeof BigNumber !== 'undefined' && object instanceof BigNumber;
|
||||
|
||||
if (!result) {
|
||||
if (typeof(object) === "object" && object.constructor != null) {
|
||||
result = object.constructor.toString().indexOf("function BigNumber(") == 0;
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
};
|
||||
|
||||
function prettyPrint(/* */) {
|
||||
var args = arguments;
|
||||
var ret = "";
|
||||
for(var i = 0, l = args.length; i < l; i++) {
|
||||
ret += pp(args[i], "") + "\n";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
var print = prettyPrint;
|
||||
`
|
220
jsre/pretty.go
Normal file
220
jsre/pretty.go
Normal file
@ -0,0 +1,220 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package jsre
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
const (
|
||||
maxPrettyPrintLevel = 3
|
||||
indentString = " "
|
||||
)
|
||||
|
||||
var (
|
||||
functionColor = color.New(color.FgMagenta)
|
||||
specialColor = color.New(color.Bold)
|
||||
numberColor = color.New(color.FgRed)
|
||||
stringColor = color.New(color.FgGreen)
|
||||
)
|
||||
|
||||
// these fields are hidden when printing objects.
|
||||
var boringKeys = map[string]bool{
|
||||
"valueOf": true,
|
||||
"toString": true,
|
||||
"toLocaleString": true,
|
||||
"hasOwnProperty": true,
|
||||
"isPrototypeOf": true,
|
||||
"propertyIsEnumerable": true,
|
||||
"constructor": true,
|
||||
}
|
||||
|
||||
// prettyPrint writes value to standard output.
|
||||
func prettyPrint(vm *otto.Otto, value otto.Value) {
|
||||
ppctx{vm}.printValue(value, 0)
|
||||
}
|
||||
|
||||
type ppctx struct{ vm *otto.Otto }
|
||||
|
||||
func (ctx ppctx) indent(level int) string {
|
||||
return strings.Repeat(indentString, level)
|
||||
}
|
||||
|
||||
func (ctx ppctx) printValue(v otto.Value, level int) {
|
||||
switch {
|
||||
case v.IsObject():
|
||||
ctx.printObject(v.Object(), level)
|
||||
case v.IsNull():
|
||||
specialColor.Print("null")
|
||||
case v.IsUndefined():
|
||||
specialColor.Print("undefined")
|
||||
case v.IsString():
|
||||
s, _ := v.ToString()
|
||||
stringColor.Printf("%q", s)
|
||||
case v.IsBoolean():
|
||||
b, _ := v.ToBoolean()
|
||||
specialColor.Printf("%t", b)
|
||||
case v.IsNaN():
|
||||
numberColor.Printf("NaN")
|
||||
case v.IsNumber():
|
||||
s, _ := v.ToString()
|
||||
numberColor.Printf("%s", s)
|
||||
default:
|
||||
fmt.Printf("<unprintable>")
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx ppctx) printObject(obj *otto.Object, level int) {
|
||||
switch obj.Class() {
|
||||
case "Array":
|
||||
lv, _ := obj.Get("length")
|
||||
len, _ := lv.ToInteger()
|
||||
if len == 0 {
|
||||
fmt.Printf("[]")
|
||||
return
|
||||
}
|
||||
if level > maxPrettyPrintLevel {
|
||||
fmt.Print("[...]")
|
||||
return
|
||||
}
|
||||
fmt.Print("[")
|
||||
for i := int64(0); i < len; i++ {
|
||||
el, err := obj.Get(strconv.FormatInt(i, 10))
|
||||
if err == nil {
|
||||
ctx.printValue(el, level+1)
|
||||
}
|
||||
if i < len-1 {
|
||||
fmt.Printf(", ")
|
||||
}
|
||||
}
|
||||
fmt.Print("]")
|
||||
|
||||
case "Object":
|
||||
// Print values from bignumber.js as regular numbers.
|
||||
if ctx.isBigNumber(obj) {
|
||||
numberColor.Print(toString(obj))
|
||||
return
|
||||
}
|
||||
// Otherwise, print all fields indented, but stop if we're too deep.
|
||||
keys := ctx.fields(obj)
|
||||
if len(keys) == 0 {
|
||||
fmt.Print("{}")
|
||||
return
|
||||
}
|
||||
if level > maxPrettyPrintLevel {
|
||||
fmt.Print("{...}")
|
||||
return
|
||||
}
|
||||
fmt.Println("{")
|
||||
for i, k := range keys {
|
||||
v, _ := obj.Get(k)
|
||||
fmt.Printf("%s%s: ", ctx.indent(level+1), k)
|
||||
ctx.printValue(v, level+1)
|
||||
if i < len(keys)-1 {
|
||||
fmt.Printf(",")
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
fmt.Printf("%s}", ctx.indent(level))
|
||||
|
||||
case "Function":
|
||||
// Use toString() to display the argument list if possible.
|
||||
if robj, err := obj.Call("toString"); err != nil {
|
||||
functionColor.Print("function()")
|
||||
} else {
|
||||
desc := strings.Trim(strings.Split(robj.String(), "{")[0], " \t\n")
|
||||
desc = strings.Replace(desc, " (", "(", 1)
|
||||
functionColor.Print(desc)
|
||||
}
|
||||
|
||||
case "RegExp":
|
||||
stringColor.Print(toString(obj))
|
||||
|
||||
default:
|
||||
if v, _ := obj.Get("toString"); v.IsFunction() && level <= maxPrettyPrintLevel {
|
||||
s, _ := obj.Call("toString")
|
||||
fmt.Printf("<%s %s>", obj.Class(), s.String())
|
||||
} else {
|
||||
fmt.Printf("<%s>", obj.Class())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx ppctx) fields(obj *otto.Object) []string {
|
||||
var (
|
||||
vals, methods []string
|
||||
seen = make(map[string]bool)
|
||||
)
|
||||
add := func(k string) {
|
||||
if seen[k] || boringKeys[k] {
|
||||
return
|
||||
}
|
||||
seen[k] = true
|
||||
if v, _ := obj.Get(k); v.IsFunction() {
|
||||
methods = append(methods, k)
|
||||
} else {
|
||||
vals = append(vals, k)
|
||||
}
|
||||
}
|
||||
// add own properties
|
||||
ctx.doOwnProperties(obj.Value(), add)
|
||||
// add properties of the constructor
|
||||
if cp := constructorPrototype(obj); cp != nil {
|
||||
ctx.doOwnProperties(cp.Value(), add)
|
||||
}
|
||||
sort.Strings(vals)
|
||||
sort.Strings(methods)
|
||||
return append(vals, methods...)
|
||||
}
|
||||
|
||||
func (ctx ppctx) doOwnProperties(v otto.Value, f func(string)) {
|
||||
Object, _ := ctx.vm.Object("Object")
|
||||
rv, _ := Object.Call("getOwnPropertyNames", v)
|
||||
gv, _ := rv.Export()
|
||||
for _, v := range gv.([]interface{}) {
|
||||
f(v.(string))
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx ppctx) isBigNumber(v *otto.Object) bool {
|
||||
BigNumber, err := ctx.vm.Run("BigNumber.prototype")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cp := constructorPrototype(v)
|
||||
return cp != nil && cp.Value() == BigNumber
|
||||
}
|
||||
|
||||
func toString(obj *otto.Object) string {
|
||||
s, _ := obj.Call("toString")
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func constructorPrototype(obj *otto.Object) *otto.Object {
|
||||
if v, _ := obj.Get("constructor"); v.Object() != nil {
|
||||
if v, _ = v.Object().Get("prototype"); v.Object() != nil {
|
||||
return v.Object()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user