| // This app provides a simple scripted environment for running common veyron |
| // services as subprocesses and testing interactions between them. It is |
| // structured as an interpreter, with global variables and variable |
| // expansion, but no control flow. The command set that it supports is |
| // extendable by adding new 'commands' that implement the API defined |
| // by veyron/lib/modules. |
| package main |
| |
| import ( |
| "bufio" |
| "flag" |
| "fmt" |
| "os" |
| "strconv" |
| "strings" |
| "time" |
| "unicode" |
| |
| "v.io/core/veyron2" |
| "v.io/core/veyron2/context" |
| "v.io/core/veyron2/rt" |
| |
| "v.io/core/veyron/lib/expect" |
| "v.io/core/veyron/lib/modules" |
| _ "v.io/core/veyron/lib/modules/core" |
| _ "v.io/core/veyron/profiles" |
| ) |
| |
| type cmdState struct { |
| modules.Handle |
| *expect.Session |
| line string |
| } |
| |
| var ( |
| interactive bool |
| handles map[string]*cmdState |
| jsonDict map[string]string |
| ) |
| |
| func init() { |
| flag.BoolVar(&interactive, "interactive", true, "set interactive/batch mode") |
| handles = make(map[string]*cmdState) |
| jsonDict = make(map[string]string) |
| flag.Usage = usage |
| } |
| |
| var usage = func() { |
| fmt.Println( |
| `Welcome to this simple shell that lets you run mount tables, a simple server |
| and sundry other commands from an interactive command line or as scripts. Type |
| 'help' at the prompt to see a list of available commands, or 'help command' to |
| get specific help about that command. The shell provides environment variables |
| with expansion and intrinsic support for managing subprocess, but it does not |
| provide any flow control commands. |
| |
| All commands, except builtin ones (such as help, set, eval etc) are run |
| asynchronously in background. That is, the prompt returns as soon as they are |
| started and no output is displayed from them unless an error is encountered |
| when they are being started. Each input line is numbered and that number is |
| used to refer to the standard output of previous started commands. The variable |
| _ always contains the number of the immediately preceeding line. It is |
| possible to read the output of a command (using the 'read' builtin) and assign |
| it that output to an environment variable. The 'eval' builtin parses output of |
| the form <var>=<val>. In this way subproccess may be started, their output |
| read and used to configure subsequent subprocesses. For example: |
| |
| 1> time |
| 2> read 1 t |
| 3> print $t |
| |
| will print the first line of output from the time command, as will the |
| following: |
| |
| or: |
| time |
| read $_ t |
| print $t |
| |
| The eval builtin is used to directly to assign to variables specified |
| in the output of the command. For example, if the root command |
| prints out MT_NAME=foo then eval will set MT_NAME to foo as follows: |
| |
| root |
| eval $_ |
| print $MT_NAME |
| |
| will print the value of MT_NAME that is output by the root command. |
| `) |
| flag.PrintDefaults() |
| } |
| |
| func prompt(lineno int) { |
| if interactive { |
| fmt.Printf("%d> ", lineno) |
| } |
| } |
| |
| var runtime veyron2.Runtime |
| var ctx *context.T |
| |
| func main() { |
| var err error |
| if runtime, err = rt.New(); err != nil { |
| panic(err) |
| } |
| defer runtime.Cleanup() |
| ctx = runtime.NewContext() |
| |
| // Subprocesses commands are run by fork/execing this binary |
| // so we must test to see if this instance is a subprocess or the |
| // the original command line instance. |
| if modules.IsModulesProcess() { |
| // Subprocess, run the requested command. |
| if err := modules.Dispatch(); err != nil { |
| fmt.Fprintf(os.Stderr, "failed: %v\n", err) |
| os.Exit(1) |
| } |
| return |
| } |
| |
| shell, err := modules.NewShell(nil) |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "unexpected error: %s\n", err) |
| os.Exit(1) |
| } |
| defer shell.Cleanup(os.Stderr, os.Stderr) |
| |
| scanner := bufio.NewScanner(os.Stdin) |
| lineno := 1 |
| prompt(lineno) |
| for scanner.Scan() { |
| line := scanner.Text() |
| if !strings.HasPrefix(line, "#") && len(line) > 0 { |
| if line == "eof" { |
| break |
| } |
| if err := process(shell, line, lineno); err != nil { |
| fmt.Printf("ERROR: %d> %q: %v\n", lineno, line, err) |
| if !interactive { |
| os.Exit(1) |
| } |
| } |
| } |
| shell.SetVar("_", strconv.Itoa(lineno)) |
| lineno++ |
| prompt(lineno) |
| } |
| if err := scanner.Err(); err != nil { |
| fmt.Printf("error reading input: %v\n", err) |
| } |
| |
| } |
| |
| func output(lineno int, line string) { |
| if len(line) > 0 { |
| if !interactive { |
| fmt.Printf("%d> ", lineno) |
| } |
| line = strings.TrimSuffix(line, "\n") |
| fmt.Printf("%s\n", line) |
| } |
| } |
| |
| func process(sh *modules.Shell, line string, lineno int) error { |
| fields, err := splitQuotedFields(line) |
| if err != nil { |
| return err |
| } |
| if len(fields) == 0 { |
| return fmt.Errorf("no input") |
| } |
| name := fields[0] |
| |
| var args []string |
| if len(fields) > 1 { |
| args = fields[1:] |
| } else { |
| args = []string{} |
| } |
| sub, err := subVariables(sh, args) |
| if err != nil { |
| return err |
| } |
| if cmd := builtins[name]; cmd != nil { |
| if cmd.nargs >= 0 && len(sub) != cmd.nargs { |
| return fmt.Errorf("wrong (%d) # args for %q: usage %s", len(sub), name, cmd.usage) |
| } |
| l := "" |
| var err error |
| if cmd.needsHandle { |
| l, err = handleWrapper(sh, cmd.fn, sub...) |
| } else { |
| l, err = cmd.fn(sh, nil, sub...) |
| } |
| if err != nil { |
| return fmt.Errorf("%s : %s", err, l) |
| } |
| output(lineno, l) |
| } else { |
| handle, err := sh.Start(name, nil, sub...) |
| if err != nil { |
| return err |
| } |
| handles[strconv.Itoa(lineno)] = &cmdState{ |
| handle, |
| expect.NewSession(nil, handle.Stdout(), time.Minute), |
| line, |
| } |
| output(lineno, line) |
| } |
| return nil |
| } |
| |
| // splitQuotedFields a line into fields, allowing for quoted strings. |
| func splitQuotedFields(line string) ([]string, error) { |
| fields := []string{} |
| inquote := false |
| var field []rune |
| for _, c := range line { |
| switch { |
| case c == '"': |
| if inquote { |
| fields = append(fields, string(field)) |
| field = nil |
| inquote = false |
| } else { |
| inquote = true |
| } |
| case unicode.IsSpace(c): |
| if inquote { |
| field = append(field, c) |
| } else { |
| if len(field) > 0 { |
| fields = append(fields, string(field)) |
| } |
| field = nil |
| } |
| default: |
| field = append(field, c) |
| } |
| } |
| if inquote { |
| return nil, fmt.Errorf("unterminated quoted input") |
| } |
| |
| if len(field) > 0 { |
| fields = append(fields, string(field)) |
| } |
| return fields, nil |
| } |
| |
| // subVariables substitutes variables that occur in the string slice |
| // args with values from the Shell. |
| func subVariables(sh *modules.Shell, args []string) ([]string, error) { |
| var results []string |
| for _, a := range args { |
| if r, err := subVariablesInArgument(sh, a); err != nil { |
| return results, err |
| } else { |
| results = append(results, r) |
| } |
| } |
| return results, nil |
| } |
| |
| // subVariablesInArgument substitutes variables that occur in the string |
| // parameter with values from vars. |
| // |
| // A variable, is introduced by $, terminated by \t, space, / , : or !. |
| // Variables may also be enclosed by {} (as in ${VAR}) to allow for embedding |
| // within strings. |
| func subVariablesInArgument(sh *modules.Shell, a string) (string, error) { |
| first := strings.Index(a, "$") |
| if first < 0 { |
| return a, nil |
| } |
| parts := strings.Split(a, "$") |
| result := parts[0] |
| vn := "" |
| rem := 0 |
| for _, p := range parts[1:] { |
| start := 0 |
| end := -1 |
| if strings.HasPrefix(p, "{") { |
| start = 1 |
| end = strings.Index(p, "}") |
| if end < 0 { |
| return "", fmt.Errorf("unterminated variable: %q", p) |
| } |
| rem = end + 1 |
| } else { |
| end = strings.IndexAny(p, "\t/,:!= ") |
| if end < 0 { |
| end = len(p) |
| } |
| rem = end |
| } |
| vn = p[start:end] |
| r := p[rem:] |
| v, present := sh.GetVar(vn) |
| if !present { |
| return a, fmt.Errorf("unknown variable: %q", vn) |
| } |
| result += v |
| result += r |
| } |
| return result, nil |
| } |