From 38f6d60e6e699db24b7a850b5999823b9e36d5bb Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 6 Mar 2015 03:38:19 +0100 Subject: [PATCH] cmd/ethereum: new JS repl with cross-platform line editing --- cmd/ethereum/js.go | 207 ++++++++++++++++++++ cmd/ethereum/main.go | 5 +- cmd/ethereum/repl/console_colors_windows.go | 97 --------- cmd/ethereum/repl/repl.go | 201 ------------------- cmd/ethereum/repl/repl_darwin.go | 144 -------------- cmd/ethereum/repl/repl_linux.go | 1 - cmd/ethereum/repl/repl_windows.go | 92 --------- 7 files changed, 209 insertions(+), 538 deletions(-) delete mode 100644 cmd/ethereum/repl/console_colors_windows.go delete mode 100644 cmd/ethereum/repl/repl.go delete mode 100644 cmd/ethereum/repl/repl_darwin.go delete mode 120000 cmd/ethereum/repl/repl_linux.go delete mode 100644 cmd/ethereum/repl/repl_windows.go diff --git a/cmd/ethereum/js.go b/cmd/ethereum/js.go index f0aeb45f5..e16ee171e 100644 --- a/cmd/ethereum/js.go +++ b/cmd/ethereum/js.go @@ -18,13 +18,21 @@ package main import ( + "fmt" "io/ioutil" "os" + "path" + "strings" "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/javascript" + "github.com/ethereum/go-ethereum/state" "github.com/ethereum/go-ethereum/xeth" + "github.com/obscuren/otto" + "github.com/peterh/liner" ) func execJsFile(ethereum *eth.Ethereum, filename string) { @@ -41,3 +49,202 @@ func execJsFile(ethereum *eth.Ethereum, filename string) { utils.Fatalf("Javascript Error: %v", err) } } + +type repl struct { + re *javascript.JSRE + ethereum *eth.Ethereum + xeth *xeth.XEth + prompt string + histfile *os.File + lr *liner.State + running bool +} + +func newREPL(ethereum *eth.Ethereum) *repl { + hist, err := os.OpenFile(path.Join(ethereum.DataDir, "history"), os.O_RDWR|os.O_CREATE, os.ModePerm) + if err != nil { + panic(err) + } + xeth := xeth.New(ethereum) + repl := &repl{ + re: javascript.NewJSRE(xeth), + xeth: xeth, + ethereum: ethereum, + prompt: "> ", + histfile: hist, + lr: liner.NewLiner(), + } + repl.initStdFuncs() + return repl +} + +func (self *repl) Start() { + if !self.running { + self.running = true + self.lr.ReadHistory(self.histfile) + go self.read() + } +} + +func (self *repl) Stop() { + if self.running { + self.running = false + self.histfile.Truncate(0) + self.lr.WriteHistory(self.histfile) + self.histfile.Close() + } +} + +func (self *repl) parseInput(code string) { + defer func() { + if r := recover(); r != nil { + fmt.Println("[native] error", r) + } + }() + value, err := self.re.Run(code) + if err != nil { + fmt.Println(err) + return + } + self.printValue(value) +} + +var indentCount = 0 +var str = "" + +func (self *repl) setIndent() { + open := strings.Count(str, "{") + open += strings.Count(str, "(") + closed := strings.Count(str, "}") + closed += strings.Count(str, ")") + indentCount = open - closed + if indentCount <= 0 { + self.prompt = "> " + } else { + self.prompt = strings.Join(make([]string, indentCount*2), "..") + self.prompt += " " + } +} + +func (self *repl) read() { + for { + input, err := self.lr.Prompt(self.prompt) + if err != nil { + return + } + if input == "" { + continue + } + str += input + "\n" + self.setIndent() + if indentCount <= 0 { + if input == "exit" { + self.Stop() + return + } + hist := str[:len(str)-1] + self.lr.AppendHistory(hist) + self.parseInput(str) + str = "" + } + } +} + +func (self *repl) printValue(v interface{}) { + method, _ := self.re.Vm.Get("prettyPrint") + v, err := self.re.Vm.ToValue(v) + if err == nil { + val, err := method.Call(method, v) + if err == nil { + fmt.Printf("%v", val) + } + } +} + +func (self *repl) initStdFuncs() { + t, _ := self.re.Vm.Get("eth") + eth := t.Object() + eth.Set("connect", self.connect) + eth.Set("stopMining", self.stopMining) + eth.Set("startMining", self.startMining) + eth.Set("dump", self.dump) + eth.Set("export", self.export) +} + +/* + * The following methods are natively implemented javascript functions. + */ + +func (self *repl) dump(call otto.FunctionCall) otto.Value { + var block *types.Block + + if len(call.ArgumentList) > 0 { + if call.Argument(0).IsNumber() { + num, _ := call.Argument(0).ToInteger() + block = self.ethereum.ChainManager().GetBlockByNumber(uint64(num)) + } else if call.Argument(0).IsString() { + hash, _ := call.Argument(0).ToString() + block = self.ethereum.ChainManager().GetBlock(ethutil.Hex2Bytes(hash)) + } else { + fmt.Println("invalid argument for dump. Either hex string or number") + } + + if block == nil { + fmt.Println("block not found") + + return otto.UndefinedValue() + } + + } else { + block = self.ethereum.ChainManager().CurrentBlock() + } + + statedb := state.New(block.Root(), self.ethereum.Db()) + + v, _ := self.re.Vm.ToValue(statedb.RawDump()) + + return v +} + +func (self *repl) stopMining(call otto.FunctionCall) otto.Value { + self.xeth.Miner().Stop() + return otto.TrueValue() +} + +func (self *repl) startMining(call otto.FunctionCall) otto.Value { + self.xeth.Miner().Start() + return otto.TrueValue() +} + +func (self *repl) connect(call otto.FunctionCall) otto.Value { + nodeURL, err := call.Argument(0).ToString() + if err != nil { + return otto.FalseValue() + } + if err := self.ethereum.SuggestPeer(nodeURL); err != nil { + return otto.FalseValue() + } + return otto.TrueValue() +} + +func (self *repl) export(call otto.FunctionCall) otto.Value { + if len(call.ArgumentList) == 0 { + fmt.Println("err: require file name") + return otto.FalseValue() + } + + fn, err := call.Argument(0).ToString() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + + data := self.ethereum.ChainManager().Export() + + if err := ethutil.WriteFile(fn, data); err != nil { + fmt.Println(err) + return otto.FalseValue() + } + + return otto.TrueValue() +} diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index a38e012c2..c85caf229 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -28,7 +28,6 @@ import ( "time" "github.com/codegangsta/cli" - "github.com/ethereum/go-ethereum/cmd/ethereum/repl" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" @@ -145,9 +144,9 @@ func runjs(ctx *cli.Context) { eth := utils.GetEthereum(ClientIdentifier, Version, ctx) startEth(ctx, eth) if len(ctx.Args()) == 0 { - repl := ethrepl.NewJSRepl(eth) - repl.Start() + repl := newREPL(eth) utils.RegisterInterrupt(func(os.Signal) { repl.Stop() }) + repl.Start() eth.WaitForShutdown() } else if len(ctx.Args()) == 1 { execJsFile(eth, ctx.Args()[0]) diff --git a/cmd/ethereum/repl/console_colors_windows.go b/cmd/ethereum/repl/console_colors_windows.go deleted file mode 100644 index 8062746fb..000000000 --- a/cmd/ethereum/repl/console_colors_windows.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved. -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This 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 -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this library; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -// MA 02110-1301 USA - -/* Inspired by https://github.com/xuyu/logging/blob/master/colorful_win.go */ - -package ethrepl - -import ( - "syscall" - "unsafe" -) - -type color uint16 - -const ( - green = color(0x0002) - red = color(0x0004) - yellow = color(0x000E) -) - -const ( - mask = uint16(yellow | green | red) -) - -var ( - kernel32 = syscall.NewLazyDLL("kernel32.dll") - procGetStdHandle = kernel32.NewProc("GetStdHandle") - procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") - procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") - hStdout uintptr - initScreenInfo *consoleScreenBufferInfo -) - -func setConsoleTextAttribute(hConsoleOutput uintptr, wAttributes uint16) bool { - ret, _, _ := procSetConsoleTextAttribute.Call(hConsoleOutput, uintptr(wAttributes)) - return ret != 0 -} - -type coord struct { - X, Y int16 -} - -type smallRect struct { - Left, Top, Right, Bottom int16 -} - -type consoleScreenBufferInfo struct { - DwSize coord - DwCursorPosition coord - WAttributes uint16 - SrWindow smallRect - DwMaximumWindowSize coord -} - -func getConsoleScreenBufferInfo(hConsoleOutput uintptr) *consoleScreenBufferInfo { - var csbi consoleScreenBufferInfo - ret, _, _ := procGetConsoleScreenBufferInfo.Call(hConsoleOutput, uintptr(unsafe.Pointer(&csbi))) - if ret == 0 { - return nil - } - return &csbi -} - -const ( - stdOutputHandle = uint32(-11 & 0xFFFFFFFF) -) - -func init() { - hStdout, _, _ = procGetStdHandle.Call(uintptr(stdOutputHandle)) - initScreenInfo = getConsoleScreenBufferInfo(hStdout) -} - -func resetColorful() { - if initScreenInfo == nil { - return - } - setConsoleTextAttribute(hStdout, initScreenInfo.WAttributes) -} - -func changeColor(c color) { - attr := uint16(0) & ^mask | uint16(c) - setConsoleTextAttribute(hStdout, attr) -} diff --git a/cmd/ethereum/repl/repl.go b/cmd/ethereum/repl/repl.go deleted file mode 100644 index 05ea71e79..000000000 --- a/cmd/ethereum/repl/repl.go +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved. -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This 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 -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this library; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -// MA 02110-1301 USA - -package ethrepl - -import ( - "bufio" - "fmt" - "io" - "os" - "path" - - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/ethutil" - "github.com/ethereum/go-ethereum/javascript" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/state" - "github.com/ethereum/go-ethereum/xeth" - "github.com/obscuren/otto" -) - -var repllogger = logger.NewLogger("REPL") - -type Repl interface { - Start() - Stop() -} - -type JSRepl struct { - re *javascript.JSRE - ethereum *eth.Ethereum - xeth *xeth.XEth - - prompt string - - history *os.File - - running bool -} - -func NewJSRepl(ethereum *eth.Ethereum) *JSRepl { - hist, err := os.OpenFile(path.Join(ethereum.DataDir, "history"), os.O_RDWR|os.O_CREATE, os.ModePerm) - if err != nil { - panic(err) - } - - xeth := xeth.New(ethereum) - repl := &JSRepl{re: javascript.NewJSRE(xeth), xeth: xeth, ethereum: ethereum, prompt: "> ", history: hist} - repl.initStdFuncs() - - return repl -} - -func (self *JSRepl) Start() { - if !self.running { - self.running = true - repllogger.Infoln("init JS Console") - - reader := bufio.NewReader(self.history) - for { - line, err := reader.ReadString('\n') - if err != nil && err == io.EOF { - break - } else if err != nil { - fmt.Println("error reading history", err) - break - } - - addHistory(line[:len(line)-1]) - } - self.read() - } -} - -func (self *JSRepl) Stop() { - if self.running { - self.running = false - repllogger.Infoln("exit JS Console") - self.history.Close() - } -} - -func (self *JSRepl) parseInput(code string) { - defer func() { - if r := recover(); r != nil { - fmt.Println("[native] error", r) - } - }() - - value, err := self.re.Run(code) - if err != nil { - fmt.Println(err) - return - } - - self.PrintValue(value) -} - -func (self *JSRepl) initStdFuncs() { - t, _ := self.re.Vm.Get("eth") - eth := t.Object() - eth.Set("connect", self.connect) - eth.Set("stopMining", self.stopMining) - eth.Set("startMining", self.startMining) - eth.Set("dump", self.dump) - eth.Set("export", self.export) -} - -/* - * The following methods are natively implemented javascript functions - */ - -func (self *JSRepl) dump(call otto.FunctionCall) otto.Value { - var block *types.Block - - if len(call.ArgumentList) > 0 { - if call.Argument(0).IsNumber() { - num, _ := call.Argument(0).ToInteger() - block = self.ethereum.ChainManager().GetBlockByNumber(uint64(num)) - } else if call.Argument(0).IsString() { - hash, _ := call.Argument(0).ToString() - block = self.ethereum.ChainManager().GetBlock(ethutil.Hex2Bytes(hash)) - } else { - fmt.Println("invalid argument for dump. Either hex string or number") - } - - if block == nil { - fmt.Println("block not found") - - return otto.UndefinedValue() - } - - } else { - block = self.ethereum.ChainManager().CurrentBlock() - } - - statedb := state.New(block.Root(), self.ethereum.Db()) - - v, _ := self.re.Vm.ToValue(statedb.RawDump()) - - return v -} - -func (self *JSRepl) stopMining(call otto.FunctionCall) otto.Value { - self.xeth.Miner().Stop() - - return otto.TrueValue() -} - -func (self *JSRepl) startMining(call otto.FunctionCall) otto.Value { - self.xeth.Miner().Start() - return otto.TrueValue() -} - -func (self *JSRepl) connect(call otto.FunctionCall) otto.Value { - nodeURL, err := call.Argument(0).ToString() - if err != nil { - return otto.FalseValue() - } - if err := self.ethereum.SuggestPeer(nodeURL); err != nil { - return otto.FalseValue() - } - return otto.TrueValue() -} - -func (self *JSRepl) export(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) == 0 { - fmt.Println("err: require file name") - return otto.FalseValue() - } - - fn, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - data := self.ethereum.ChainManager().Export() - - if err := ethutil.WriteFile(fn, data); err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - return otto.TrueValue() -} diff --git a/cmd/ethereum/repl/repl_darwin.go b/cmd/ethereum/repl/repl_darwin.go deleted file mode 100644 index 3710150cc..000000000 --- a/cmd/ethereum/repl/repl_darwin.go +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved. -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This 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 -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this library; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -// MA 02110-1301 USA - -package ethrepl - -// #cgo darwin CFLAGS: -I/usr/local/opt/readline/include -// #cgo darwin LDFLAGS: -L/usr/local/opt/readline/lib -// #cgo LDFLAGS: -lreadline -// #include -// #include -// #include -// #include -import "C" -import ( - "fmt" - "os" - "os/signal" - "strings" - "syscall" - "unsafe" -) - -func initReadLine() { - C.rl_catch_sigwinch = 0 - C.rl_catch_signals = 0 - c := make(chan os.Signal, 1) - signal.Notify(c, syscall.SIGWINCH) - signal.Notify(c, os.Interrupt) - go func() { - for sig := range c { - switch sig { - case syscall.SIGWINCH: - C.rl_resize_terminal() - - case os.Interrupt: - C.rl_cleanup_after_signal() - default: - - } - } - }() -} - -func readLine(prompt *string) *string { - var p *C.char - - //readline allows an empty prompt(NULL) - if prompt != nil { - p = C.CString(*prompt) - } - - ret := C.readline(p) - - if p != nil { - C.free(unsafe.Pointer(p)) - } - - if ret == nil { - return nil - } //EOF - - s := C.GoString(ret) - C.free(unsafe.Pointer(ret)) - return &s -} - -func addHistory(s string) { - p := C.CString(s) - C.add_history(p) - C.free(unsafe.Pointer(p)) -} - -var indentCount = 0 -var str = "" - -func (self *JSRepl) setIndent() { - open := strings.Count(str, "{") - open += strings.Count(str, "(") - closed := strings.Count(str, "}") - closed += strings.Count(str, ")") - indentCount = open - closed - if indentCount <= 0 { - self.prompt = "> " - } else { - self.prompt = strings.Join(make([]string, indentCount*2), "..") - self.prompt += " " - } -} - -func (self *JSRepl) read() { - initReadLine() -L: - for { - switch result := readLine(&self.prompt); true { - case result == nil: - break L - - case *result != "": - str += *result + "\n" - - self.setIndent() - - if indentCount <= 0 { - if *result == "exit" { - self.Stop() - break L - } - - hist := str[:len(str)-1] - addHistory(hist) //allow user to recall this line - self.history.WriteString(str) - - self.parseInput(str) - - str = "" - } - } - } -} - -func (self *JSRepl) PrintValue(v interface{}) { - method, _ := self.re.Vm.Get("prettyPrint") - v, err := self.re.Vm.ToValue(v) - if err == nil { - val, err := method.Call(method, v) - if err == nil { - fmt.Printf("%v", val) - } - } -} diff --git a/cmd/ethereum/repl/repl_linux.go b/cmd/ethereum/repl/repl_linux.go deleted file mode 120000 index 276f135d7..000000000 --- a/cmd/ethereum/repl/repl_linux.go +++ /dev/null @@ -1 +0,0 @@ -repl_darwin.go \ No newline at end of file diff --git a/cmd/ethereum/repl/repl_windows.go b/cmd/ethereum/repl/repl_windows.go deleted file mode 100644 index d2c405ee9..000000000 --- a/cmd/ethereum/repl/repl_windows.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved. -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This 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 -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this library; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -// MA 02110-1301 USA - -package ethrepl - -import ( - "bufio" - "fmt" - "os" - "strings" -) - -func (self *JSRepl) read() { - reader := bufio.NewReader(os.Stdin) - for { - fmt.Printf(self.prompt) - str, _, err := reader.ReadLine() - if err != nil { - fmt.Println("Error reading input", err) - } else { - if string(str) == "exit" { - self.Stop() - break - } else { - self.parseInput(string(str)) - } - } - } -} - -func addHistory(s string) { -} - -func printColored(outputVal string) { - for outputVal != "" { - codePart := "" - if strings.HasPrefix(outputVal, "\033[32m") { - codePart = "\033[32m" - changeColor(2) - } - if strings.HasPrefix(outputVal, "\033[1m\033[30m") { - codePart = "\033[1m\033[30m" - changeColor(8) - } - if strings.HasPrefix(outputVal, "\033[31m") { - codePart = "\033[31m" - changeColor(red) - } - if strings.HasPrefix(outputVal, "\033[35m") { - codePart = "\033[35m" - changeColor(5) - } - if strings.HasPrefix(outputVal, "\033[0m") { - codePart = "\033[0m" - resetColorful() - } - textPart := outputVal[len(codePart):len(outputVal)] - index := strings.Index(textPart, "\033") - if index == -1 { - outputVal = "" - } else { - outputVal = textPart[index:len(textPart)] - textPart = textPart[0:index] - } - fmt.Printf("%v", textPart) - } -} - -func (self *JSRepl) PrintValue(v interface{}) { - method, _ := self.re.Vm.Get("prettyPrint") - v, err := self.re.Vm.ToValue(v) - if err == nil { - val, err := method.Call(method, v) - if err == nil { - printColored(fmt.Sprintf("%v", val)) - } - } -}