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  }