github.com/neatlab/neatio@v1.7.3-0.20220425043230-d903e92fcc75/utilities/console/console.go (about) 1 package console 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "os" 8 "os/signal" 9 "path/filepath" 10 "regexp" 11 "sort" 12 "strings" 13 "syscall" 14 15 "github.com/mattn/go-colorable" 16 "github.com/neatlab/neatio/internal/jsre" 17 "github.com/neatlab/neatio/internal/web3ext" 18 "github.com/neatlab/neatio/network/rpc" 19 "github.com/peterh/liner" 20 "github.com/robertkrimen/otto" 21 ) 22 23 var ( 24 passwordRegexp = regexp.MustCompile(`personal.[nus]`) 25 onlyWhitespace = regexp.MustCompile(`^\s*$`) 26 exit = regexp.MustCompile(`^\s*exit\s*;*\s*$`) 27 ) 28 29 const HistoryFile = "history" 30 31 const DefaultPrompt = "> " 32 33 type Config struct { 34 DataDir string 35 DocRoot string 36 Client *rpc.Client 37 Prompt string 38 Prompter UserPrompter 39 Printer io.Writer 40 Preload []string 41 } 42 43 type Console struct { 44 client *rpc.Client 45 jsre *jsre.JSRE 46 prompt string 47 prompter UserPrompter 48 histPath string 49 history []string 50 printer io.Writer 51 } 52 53 func New(config Config) (*Console, error) { 54 55 if config.Prompter == nil { 56 config.Prompter = Stdin 57 } 58 if config.Prompt == "" { 59 config.Prompt = DefaultPrompt 60 } 61 if config.Printer == nil { 62 config.Printer = colorable.NewColorableStdout() 63 } 64 65 console := &Console{ 66 client: config.Client, 67 jsre: jsre.New(config.DocRoot, config.Printer), 68 prompt: config.Prompt, 69 prompter: config.Prompter, 70 printer: config.Printer, 71 histPath: filepath.Join(config.DataDir, HistoryFile), 72 } 73 if err := os.MkdirAll(config.DataDir, 0700); err != nil { 74 return nil, err 75 } 76 if err := console.init(config.Preload); err != nil { 77 return nil, err 78 } 79 return console, nil 80 } 81 82 func (c *Console) init(preload []string) error { 83 84 bridge := newBridge(c.client, c.prompter, c.printer) 85 c.jsre.Set("jeth", struct{}{}) 86 87 jethObj, _ := c.jsre.Get("jeth") 88 jethObj.Object().Set("send", bridge.Send) 89 jethObj.Object().Set("sendAsync", bridge.Send) 90 91 consoleObj, _ := c.jsre.Get("console") 92 consoleObj.Object().Set("log", c.consoleOutput) 93 consoleObj.Object().Set("error", c.consoleOutput) 94 95 if err := c.jsre.Compile("bignumber.js", jsre.BigNumber_JS); err != nil { 96 return fmt.Errorf("bignumber.js: %v", err) 97 } 98 if err := c.jsre.Compile("web3.js", jsre.Web3_JS); err != nil { 99 return fmt.Errorf("web3.js: %v", err) 100 } 101 if _, err := c.jsre.Run("var Web3 = require('web3');"); err != nil { 102 return fmt.Errorf("web3 require: %v", err) 103 } 104 if _, err := c.jsre.Run("var web3 = new Web3(jeth);"); err != nil { 105 return fmt.Errorf("web3 provider: %v", err) 106 } 107 108 apis, err := c.client.SupportedModules() 109 if err != nil { 110 return fmt.Errorf("api modules: %v", err) 111 } 112 flatten := "var eth = web3.eth; var personal = web3.personal; " 113 for api := range apis { 114 if api == "web3" { 115 continue 116 } 117 if file, ok := web3ext.Modules[api]; ok { 118 119 if err = c.jsre.Compile(fmt.Sprintf("%s.js", api), file); err != nil { 120 return fmt.Errorf("%s.js: %v", api, err) 121 } 122 flatten += fmt.Sprintf("var %s = web3.%s; ", api, api) 123 } else if obj, err := c.jsre.Run("web3." + api); err == nil && obj.IsObject() { 124 125 flatten += fmt.Sprintf("var %s = web3.%s; ", api, api) 126 } 127 } 128 if _, err = c.jsre.Run(flatten); err != nil { 129 return fmt.Errorf("namespace flattening: %v", err) 130 } 131 132 if c.prompter != nil { 133 134 personal, err := c.jsre.Get("personal") 135 if err != nil { 136 return err 137 } 138 139 if obj := personal.Object(); obj != nil { 140 if _, err = c.jsre.Run(`jeth.openWallet = personal.openWallet;`); err != nil { 141 return fmt.Errorf("personal.openWallet: %v", err) 142 } 143 if _, err = c.jsre.Run(`jeth.unlockAccount = personal.unlockAccount;`); err != nil { 144 return fmt.Errorf("personal.unlockAccount: %v", err) 145 } 146 if _, err = c.jsre.Run(`jeth.newAccount = personal.newAccount;`); err != nil { 147 return fmt.Errorf("personal.newAccount: %v", err) 148 } 149 if _, err = c.jsre.Run(`jeth.sign = personal.sign;`); err != nil { 150 return fmt.Errorf("personal.sign: %v", err) 151 } 152 obj.Set("openWallet", bridge.OpenWallet) 153 obj.Set("unlockAccount", bridge.UnlockAccount) 154 obj.Set("newAccount", bridge.NewAccount) 155 obj.Set("sign", bridge.Sign) 156 } 157 } 158 159 admin, err := c.jsre.Get("admin") 160 if err != nil { 161 return err 162 } 163 if obj := admin.Object(); obj != nil { 164 obj.Set("sleepBlocks", bridge.SleepBlocks) 165 obj.Set("sleep", bridge.Sleep) 166 obj.Set("clearHistory", c.clearHistory) 167 } 168 169 for _, path := range preload { 170 if err := c.jsre.Exec(path); err != nil { 171 failure := err.Error() 172 if ottoErr, ok := err.(*otto.Error); ok { 173 failure = ottoErr.String() 174 } 175 return fmt.Errorf("%s: %v", path, failure) 176 } 177 } 178 179 if c.prompter != nil { 180 if content, err := ioutil.ReadFile(c.histPath); err != nil { 181 c.prompter.SetHistory(nil) 182 } else { 183 c.history = strings.Split(string(content), "\n") 184 c.prompter.SetHistory(c.history) 185 } 186 c.prompter.SetWordCompleter(c.AutoCompleteInput) 187 } 188 return nil 189 } 190 191 func (c *Console) clearHistory() { 192 c.history = nil 193 c.prompter.ClearHistory() 194 if err := os.Remove(c.histPath); err != nil { 195 fmt.Fprintln(c.printer, "can't delete history file:", err) 196 } else { 197 fmt.Fprintln(c.printer, "history file deleted") 198 } 199 } 200 201 func (c *Console) consoleOutput(call otto.FunctionCall) otto.Value { 202 output := []string{} 203 for _, argument := range call.ArgumentList { 204 output = append(output, fmt.Sprintf("%v", argument)) 205 } 206 fmt.Fprintln(c.printer, strings.Join(output, " ")) 207 return otto.Value{} 208 } 209 210 func (c *Console) AutoCompleteInput(line string, pos int) (string, []string, string) { 211 212 if len(line) == 0 || pos == 0 { 213 return "", nil, "" 214 } 215 216 start := pos - 1 217 for ; start > 0; start-- { 218 219 if line[start] == '.' || (line[start] >= 'a' && line[start] <= 'z') || (line[start] >= 'A' && line[start] <= 'Z') { 220 continue 221 } 222 223 if start >= 3 && line[start-3:start] == "web3" { 224 start -= 3 225 continue 226 } 227 228 start++ 229 break 230 } 231 return line[:start], c.jsre.CompleteKeywords(line[start:pos]), line[pos:] 232 } 233 234 func (c *Console) Welcome() { 235 236 fmt.Fprintf(c.printer, "Welcome to the NEATIO JavaScript console!\n\n") 237 c.jsre.Run(` 238 console.log("instance: " + web3.version.node); 239 console.log("coinbase: " + eth.coinbase); 240 console.log("at block: " + eth.blockNumber + " (" + new Date(1000 * eth.getBlock(eth.blockNumber).timestamp) + ")"); 241 console.log(" datadir: " + admin.datadir); 242 `) 243 244 if apis, err := c.client.SupportedModules(); err == nil { 245 modules := make([]string, 0, len(apis)) 246 for api, version := range apis { 247 modules = append(modules, fmt.Sprintf("%s:%s", api, version)) 248 } 249 sort.Strings(modules) 250 fmt.Fprintln(c.printer, " modules:", strings.Join(modules, " ")) 251 } 252 fmt.Fprintln(c.printer) 253 } 254 255 func (c *Console) Evaluate(statement string) error { 256 defer func() { 257 if r := recover(); r != nil { 258 fmt.Fprintf(c.printer, "[native] error: %v\n", r) 259 } 260 }() 261 return c.jsre.Evaluate(statement, c.printer) 262 } 263 264 func (c *Console) Interactive() { 265 var ( 266 prompt = c.prompt 267 indents = 0 268 input = "" 269 scheduler = make(chan string) 270 ) 271 272 go func() { 273 for { 274 275 line, err := c.prompter.PromptInput(<-scheduler) 276 if err != nil { 277 278 if err == liner.ErrPromptAborted { 279 prompt, indents, input = c.prompt, 0, "" 280 scheduler <- "" 281 continue 282 } 283 close(scheduler) 284 return 285 } 286 287 scheduler <- line 288 } 289 }() 290 291 abort := make(chan os.Signal, 1) 292 signal.Notify(abort, syscall.SIGINT, syscall.SIGTERM) 293 294 for { 295 296 scheduler <- prompt 297 select { 298 case <-abort: 299 300 fmt.Fprintln(c.printer, "caught interrupt, exiting") 301 return 302 303 case line, ok := <-scheduler: 304 305 if !ok || (indents <= 0 && exit.MatchString(line)) { 306 return 307 } 308 if onlyWhitespace.MatchString(line) { 309 continue 310 } 311 312 input += line + "\n" 313 314 indents = countIndents(input) 315 if indents <= 0 { 316 prompt = c.prompt 317 } else { 318 prompt = strings.Repeat(".", indents*3) + " " 319 } 320 321 if indents <= 0 { 322 if len(input) > 0 && input[0] != ' ' && !passwordRegexp.MatchString(input) { 323 if command := strings.TrimSpace(input); len(c.history) == 0 || command != c.history[len(c.history)-1] { 324 c.history = append(c.history, command) 325 if c.prompter != nil { 326 c.prompter.AppendHistory(command) 327 } 328 } 329 } 330 c.Evaluate(input) 331 input = "" 332 } 333 } 334 } 335 } 336 337 func countIndents(input string) int { 338 var ( 339 indents = 0 340 inString = false 341 strOpenChar = ' ' 342 charEscaped = false 343 ) 344 345 for _, c := range input { 346 switch c { 347 case '\\': 348 349 if !charEscaped && inString { 350 charEscaped = true 351 } 352 case '\'', '"': 353 if inString && !charEscaped && strOpenChar == c { 354 inString = false 355 } else if !inString && !charEscaped { 356 inString = true 357 strOpenChar = c 358 } 359 charEscaped = false 360 case '{', '(': 361 if !inString { 362 indents++ 363 } 364 charEscaped = false 365 case '}', ')': 366 if !inString { 367 indents-- 368 } 369 charEscaped = false 370 default: 371 charEscaped = false 372 } 373 } 374 375 return indents 376 } 377 378 func (c *Console) Execute(path string) error { 379 return c.jsre.Exec(path) 380 } 381 382 func (c *Console) Stop(graceful bool) error { 383 if err := ioutil.WriteFile(c.histPath, []byte(strings.Join(c.history, "\n")), 0600); err != nil { 384 return err 385 } 386 if err := os.Chmod(c.histPath, 0600); err != nil { 387 return err 388 } 389 c.jsre.Stop(graceful) 390 return nil 391 }