github.com/igggame/nebulas-go@v2.1.0+incompatible/cmd/console/console.go (about) 1 // Copyright (C) 2017 go-nebulas authors 2 // 3 // This file is part of the go-nebulas library. 4 // 5 // the go-nebulas library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU 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-nebulas 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 General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with the go-nebulas library. If not, see <http://www.gnu.org/licenses/>. 17 // 18 19 package console 20 21 import ( 22 "fmt" 23 "os" 24 "os/signal" 25 "strings" 26 27 "io" 28 29 "bytes" 30 "encoding/json" 31 32 "github.com/nebulasio/go-nebulas/cmd/console/library" 33 "github.com/nebulasio/go-nebulas/neblet/pb" 34 "github.com/peterh/liner" 35 ) 36 37 var ( 38 defaultPrompt = "> " 39 40 exitCmd = "exit" 41 42 bignumberJS = library.MustAsset("bignumber.js") 43 nebJS = library.MustAsset("neb-light.js") 44 ) 45 46 // Neblet interface breaks cycle import dependency and hides unused services. 47 type Neblet interface { 48 Config() *nebletpb.Config 49 } 50 51 // Console console handler 52 type Console struct { 53 54 // terminal input prompter 55 prompter UserPrompter 56 57 // Channel to send the next prompt on and receive the input 58 promptCh chan string 59 60 // input history 61 history []string 62 63 // js bridge with go func 64 jsBridge *jsBridge 65 66 // js runtime environment 67 jsvm *JSVM 68 69 // output writer 70 writer io.Writer 71 } 72 73 // Config neb console config 74 type Config struct { 75 Prompter UserPrompter 76 PrompterCh chan string 77 Writer io.Writer 78 Neb Neblet 79 } 80 81 // New a console by Config, neb.config params is need 82 func New(conf Config) *Console { 83 c := new(Console) 84 85 if conf.Prompter != nil { 86 c.prompter = conf.Prompter 87 } 88 89 if conf.PrompterCh != nil { 90 c.promptCh = conf.PrompterCh 91 } 92 93 if conf.Writer != nil { 94 c.writer = conf.Writer 95 } 96 97 if conf.Neb != nil { 98 c.jsBridge = newBirdge(conf.Neb.Config(), c.prompter, c.writer) 99 } else { 100 // hack for test with local environment 101 c.jsBridge = newBirdge(nil, c.prompter, c.writer) 102 } 103 104 c.jsvm = newJSVM() 105 if err := c.loadLibraryScripts(); err != nil { 106 fmt.Fprintln(c.writer, err) 107 } 108 109 if err := c.methodSwizzling(); err != nil { 110 fmt.Fprintln(c.writer, err) 111 } 112 return c 113 } 114 115 func (c *Console) loadLibraryScripts() error { 116 if err := c.jsvm.Compile("bignumber.js", bignumberJS); err != nil { 117 return fmt.Errorf("bignumber.js: %v", err) 118 } 119 if err := c.jsvm.Compile("neb-light.js", nebJS); err != nil { 120 return fmt.Errorf("neb.js: %v", err) 121 } 122 return nil 123 } 124 125 // Individual methods use go implementation 126 func (c *Console) methodSwizzling() error { 127 128 // replace js console log & error with go impl 129 jsconsole, _ := c.jsvm.Get("console") 130 jsconsole.Object().Set("log", c.jsBridge.output) 131 jsconsole.Object().Set("error", c.jsBridge.output) 132 133 // replace js xmlhttprequest to go implement 134 c.jsvm.Set("bridge", struct{}{}) 135 bridgeObj, _ := c.jsvm.Get("bridge") 136 bridgeObj.Object().Set("request", c.jsBridge.request) 137 bridgeObj.Object().Set("asyncRequest", c.jsBridge.request) 138 139 if _, err := c.jsvm.Run("var Neb = require('neb');"); err != nil { 140 return fmt.Errorf("neb require: %v", err) 141 } 142 if _, err := c.jsvm.Run("var neb = new Neb(bridge);"); err != nil { 143 return fmt.Errorf("neb create: %v", err) 144 } 145 jsAlias := "var api = neb.api; var admin = neb.admin; " 146 if _, err := c.jsvm.Run(jsAlias); err != nil { 147 return fmt.Errorf("namespace: %v", err) 148 } 149 150 if c.prompter != nil { 151 admin, err := c.jsvm.Get("admin") 152 if err != nil { 153 return err 154 } 155 if obj := admin.Object(); obj != nil { 156 bridgeRequest := `bridge._sendRequest = function (method, api, params, callback) { 157 var action = "/admin" + api; 158 return this.request(method, action, params); 159 };` 160 if _, err = c.jsvm.Run(bridgeRequest); err != nil { 161 return fmt.Errorf("bridge._sendRequest: %v", err) 162 } 163 164 if _, err = c.jsvm.Run(`bridge.newAccount = admin.newAccount;`); err != nil { 165 return fmt.Errorf("admin.newAccount: %v", err) 166 } 167 if _, err = c.jsvm.Run(`bridge.unlockAccount = admin.unlockAccount;`); err != nil { 168 return fmt.Errorf("admin.unlockAccount: %v", err) 169 } 170 if _, err = c.jsvm.Run(`bridge.sendTransactionWithPassphrase = admin.sendTransactionWithPassphrase;`); err != nil { 171 return fmt.Errorf("admin.sendTransactionWithPassphrase: %v", err) 172 } 173 if _, err = c.jsvm.Run(`bridge.signTransactionWithPassphrase = admin.signTransactionWithPassphrase;`); err != nil { 174 return fmt.Errorf("admin.signTransactionWithPassphrase: %v", err) 175 } 176 obj.Set("setHost", c.jsBridge.setHost) 177 obj.Set("newAccount", c.jsBridge.newAccount) 178 obj.Set("unlockAccount", c.jsBridge.unlockAccount) 179 obj.Set("sendTransactionWithPassphrase", c.jsBridge.sendTransactionWithPassphrase) 180 obj.Set("signTransactionWithPassphrase", c.jsBridge.signTransactionWithPassphrase) 181 } 182 } 183 return nil 184 } 185 186 // AutoComplete console auto complete input 187 func (c *Console) AutoComplete(line string, pos int) (string, []string, string) { 188 // No completions can be provided for empty inputs 189 if len(line) == 0 || pos == 0 { 190 return "", nil, "" 191 } 192 start := pos - 1 193 for ; start > 0; start-- { 194 // Skip all methods and namespaces 195 if line[start] == '.' || (line[start] >= 'a' && line[start] <= 'z') || (line[start] >= 'A' && line[start] <= 'Z') { 196 continue 197 } 198 start++ 199 break 200 } 201 if start == pos { 202 return "", nil, "" 203 } 204 return line[:start], c.jsvm.CompleteKeywords(line[start:pos]), line[pos:] 205 } 206 207 // Setup setup console 208 func (c *Console) Setup() { 209 if c.prompter != nil { 210 c.prompter.SetWordCompleter(c.AutoComplete) 211 } 212 fmt.Fprint(c.writer, "Welcome to the Neb JavaScript console!\n") 213 } 214 215 // Interactive starts an interactive user session. 216 func (c *Console) Interactive() { 217 // Start a goroutine to listen for promt requests and send back inputs 218 go func() { 219 for { 220 // Read the next user input 221 line, err := c.prompter.Prompt(<-c.promptCh) 222 if err != nil { 223 if err == liner.ErrPromptAborted { // ctrl-C 224 c.promptCh <- exitCmd 225 continue 226 } 227 close(c.promptCh) 228 return 229 } 230 c.promptCh <- line 231 } 232 }() 233 // Monitor Ctrl-C 234 abort := make(chan os.Signal, 1) 235 signal.Notify(abort, os.Interrupt, os.Kill) 236 237 // Start sending prompts to the user and reading back inputs 238 for { 239 // Send the next prompt, triggering an input read and process the result 240 c.promptCh <- defaultPrompt 241 select { 242 case <-abort: 243 fmt.Fprint(c.writer, "exiting...") 244 return 245 case line, ok := <-c.promptCh: 246 // User exit 247 if !ok || strings.ToLower(line) == exitCmd { 248 return 249 } 250 if len(strings.TrimSpace(line)) == 0 { 251 continue 252 } 253 254 if command := strings.TrimSpace(line); len(c.history) == 0 || command != c.history[len(c.history)-1] { 255 c.history = append(c.history, command) 256 if c.prompter != nil { 257 c.prompter.AppendHistory(command) 258 } 259 } 260 c.Evaluate(line) 261 } 262 } 263 } 264 265 // Evaluate executes code and pretty prints the result 266 func (c *Console) Evaluate(code string) error { 267 defer func() { 268 if r := recover(); r != nil { 269 fmt.Fprintf(c.writer, "[native] error: %v\n", r) 270 } 271 }() 272 v, err := c.jsvm.Run(code) 273 if err != nil { 274 fmt.Fprintln(c.writer, err) 275 return err 276 } 277 if v.IsObject() { 278 result, err := c.jsvm.JSONString(v) 279 if err != nil { 280 fmt.Fprintln(c.writer, err) 281 return err 282 } 283 var buf bytes.Buffer 284 err = json.Indent(&buf, []byte(result), "", " ") 285 if err != nil { 286 fmt.Fprintln(c.writer, err) 287 return err 288 } 289 fmt.Fprintln(c.writer, buf.String()) 290 } else if v.IsString() { 291 fmt.Fprintln(c.writer, v.String()) 292 } 293 return nil 294 } 295 296 // Stop stop js console 297 func (c *Console) Stop() error { 298 return nil 299 }