github.com/NeowayLabs/nash@v0.2.2-0.20200127205349-a227041ffd50/cmd/nash/cli.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"strings"
     9  
    10  	"github.com/madlambda/nash"
    11  	"github.com/madlambda/nash/ast"
    12  	"github.com/madlambda/nash/parser"
    13  	"github.com/madlambda/nash/sh"
    14  	"github.com/chzyer/readline"
    15  )
    16  
    17  type (
    18  	Interrupted interface {
    19  		Interrupted() bool
    20  	}
    21  
    22  	Ignored interface {
    23  		Ignore() bool
    24  	}
    25  
    26  	BlockNotFinished interface {
    27  		Unfinished() bool
    28  	}
    29  )
    30  
    31  var completers = []readline.PrefixCompleterInterface{}
    32  
    33  func execFn(shell *nash.Shell, fnDef sh.FnDef, args []sh.Obj) {
    34  	fn := fnDef.Build()
    35  	err := fn.SetArgs(args)
    36  	if err != nil {
    37  		fmt.Fprintf(os.Stderr, "%s failed: %s\n", fnDef.Name(), err.Error())
    38  	}
    39  	fn.SetStdin(shell.Stdin())
    40  	fn.SetStdout(shell.Stdout())
    41  	fn.SetStderr(shell.Stderr())
    42  
    43  	if err := fn.Start(); err != nil {
    44  		fmt.Fprintf(os.Stderr, "%s failed: %s\n", fnDef.Name(), err.Error())
    45  		return
    46  	}
    47  
    48  	if err := fn.Wait(); err != nil {
    49  		fmt.Fprintf(os.Stderr, "%s failed: %s\n", fnDef.Name(), err.Error())
    50  		return
    51  	}
    52  }
    53  
    54  func importInitFile(shell *nash.Shell, initFile string) (bool, error) {
    55  	if d, err := os.Stat(initFile); err == nil {
    56  		if m := d.Mode(); !m.IsDir() {
    57  			err := shell.ExecuteString("init",
    58  				fmt.Sprintf("import %q", initFile))
    59  			if err != nil {
    60  				return false, fmt.Errorf("Failed to evaluate '%s': %s", initFile, err.Error())
    61  			}
    62  			return true, nil
    63  		}
    64  	}
    65  	return false, nil
    66  }
    67  
    68  func loadInit(shell *nash.Shell) error {
    69  	
    70  	if noInit {
    71  		return nil
    72  	}
    73  
    74  	initFiles := []string{
    75  		shell.NashPath() + "/init",
    76  		shell.NashPath() + "/init.sh",
    77  	}
    78  
    79  	for _, init := range initFiles {
    80  		imported, err := importInitFile(shell, init)
    81  		if err != nil {
    82  			return err
    83  		}
    84  		if imported {
    85  			break
    86  		}
    87  	}
    88  
    89  	return nil
    90  }
    91  
    92  func cli(shell *nash.Shell) error {
    93  
    94  	shell.SetInteractive(true)
    95  
    96  	if err := loadInit(shell); err != nil {
    97  		fmt.Fprintf(os.Stderr, "error loading init file:\n%s\n", err)
    98  	}
    99  
   100  	historyFile := shell.NashPath() + "/history"
   101  	cfg := readline.Config{
   102  		Prompt:          shell.Prompt(),
   103  		HistoryFile:     historyFile,
   104  		InterruptPrompt: "^C",
   105  		EOFPrompt:       "exit",
   106  	}
   107  
   108  	term, err := readline.NewTerminal(&cfg)
   109  	if err != nil {
   110  		return err
   111  	}
   112  
   113  	op := term.Readline()
   114  	rline := &readline.Instance{
   115  		Config:    &cfg,
   116  		Terminal:  term,
   117  		Operation: op,
   118  	}
   119  
   120  	defer rline.Close()
   121  
   122  	completer := NewCompleter(op, term, shell)
   123  	cfg.AutoComplete = completer
   124  
   125  	if lineMode, ok := shell.Getvar("LINEMODE"); ok {
   126  		if lineStr, ok := lineMode.(*sh.StrObj); ok && lineStr.Str() == "vim" {
   127  			rline.SetVimMode(true)
   128  		} else {
   129  			rline.SetVimMode(false)
   130  		}
   131  	}
   132  
   133  	return docli(shell, rline)
   134  }
   135  
   136  func docli(shell *nash.Shell, rline *readline.Instance) error {
   137  	var (
   138  		content    bytes.Buffer
   139  		lineidx    int
   140  		line       string
   141  		parse      *parser.Parser
   142  		tr         *ast.Tree
   143  		err        error
   144  		unfinished bool
   145  		prompt     string
   146  	)
   147  
   148  	for {
   149  		if fnDef, err := shell.GetFn("nash_repl_before"); err == nil && !unfinished {
   150  			execFn(shell, fnDef, nil)
   151  		}
   152  
   153  		if !unfinished {
   154  			prompt = shell.Prompt()
   155  		}
   156  
   157  		rline.SetPrompt(prompt)
   158  		line, err = rline.Readline()
   159  
   160  		if err == readline.ErrInterrupt {
   161  			goto cont
   162  		} else if err == io.EOF {
   163  			err = nil
   164  			break
   165  		}
   166  
   167  		lineidx++
   168  		line = strings.TrimSpace(line)
   169  
   170  		// handle special cli commands
   171  		switch {
   172  		case strings.HasPrefix(line, "set mode "):
   173  			switch line[9:] {
   174  			case "vi":
   175  				rline.SetVimMode(true)
   176  			case "emacs":
   177  				rline.SetVimMode(false)
   178  			default:
   179  				fmt.Printf("invalid mode: %s\n", line[9:])
   180  			}
   181  
   182  			goto cont
   183  		case line == "mode":
   184  			if rline.IsVimMode() {
   185  				fmt.Printf("Current mode: vim\n")
   186  			} else {
   187  				fmt.Printf("Current mode: emacs\n")
   188  			}
   189  
   190  			goto cont
   191  		case line == "exit":
   192  			break
   193  		}
   194  
   195  		content.Write([]byte(line + "\n"))
   196  		parse = parser.NewParser(fmt.Sprintf("<stdin line %d>", lineidx), string(content.Bytes()))
   197  		line = string(content.Bytes())
   198  
   199  		tr, err = parse.Parse()
   200  		if err != nil {
   201  			if interrupted, ok := err.(Interrupted); ok && interrupted.Interrupted() {
   202  				content.Reset()
   203  				goto cont
   204  			} else if errBlock, ok := err.(BlockNotFinished); ok && errBlock.Unfinished() {
   205  				prompt = ">>> "
   206  				unfinished = true
   207  				goto cont
   208  			}
   209  
   210  			fmt.Printf("ERROR: %s\n", err.Error())
   211  			content.Reset()
   212  			goto cont
   213  		}
   214  
   215  		unfinished = false
   216  		content.Reset()
   217  
   218  		_, err = shell.ExecuteTree(tr)
   219  		if err != nil {
   220  			fmt.Printf("ERROR: %s\n", err.Error())
   221  		}
   222  
   223  	cont:
   224  		if fnDef, err := shell.GetFn("nash_repl_after"); err == nil && !unfinished {
   225  			var status sh.Obj
   226  			var ok bool
   227  
   228  			if status, ok = shell.Getvar("status"); !ok {
   229  				status = sh.NewStrObj("")
   230  			}
   231  
   232  			execFn(shell, fnDef, []sh.Obj{sh.NewStrObj(line), status})
   233  		}
   234  
   235  		rline.SetPrompt(prompt)
   236  	}
   237  
   238  	return nil
   239  }