Cosmos Nicolaou | 7c659ac | 2014-06-09 22:47:04 -0700 | [diff] [blame] | 1 | // This app provides a simple scripted environment for running common veyron |
| 2 | // services as subprocesses and testing interactions between them. It is |
| 3 | // structured as an interpreter, with global variables and variable |
| 4 | // expansion, but no control flow. The command set that it supports is |
| 5 | // extendable by adding new 'modules' that implement the API defined |
| 6 | // by veyron/lib/testutil/modules. |
| 7 | package main |
| 8 | |
| 9 | import ( |
| 10 | "bufio" |
| 11 | "flag" |
| 12 | "fmt" |
| 13 | "os" |
| 14 | "strings" |
| 15 | "unicode" |
| 16 | |
| 17 | "veyron/lib/testutil/modules" |
| 18 | |
| 19 | "veyron2/rt" |
| 20 | ) |
| 21 | |
| 22 | type commandFunc func() modules.T |
| 23 | |
| 24 | var ( |
| 25 | commands map[string]commandFunc |
| 26 | globals modules.Variables |
| 27 | debug bool |
| 28 | interactive bool |
| 29 | ) |
| 30 | |
| 31 | func init() { |
| 32 | flag.BoolVar(&interactive, "interactive", true, "set interactive/batch mode") |
| 33 | flag.BoolVar(&debug, "debug", false, "set debug mode") |
| 34 | |
| 35 | commands = make(map[string]commandFunc) |
| 36 | |
| 37 | // We maintaing a single, global, dictionary for variables. |
| 38 | globals = make(modules.Variables) |
| 39 | |
| 40 | // 'bultins' |
| 41 | commands["help"] = helpF |
| 42 | commands["get"] = getF |
| 43 | commands["set"] = setF |
| 44 | commands["print"] = printF |
| 45 | commands["sleep"] = sleepF |
| 46 | |
| 47 | // TODO(cnicolaou): add 'STOP' command to shutdown a running server, |
| 48 | // need to return the handle and then call Stop on it. |
| 49 | |
| 50 | // modules |
| 51 | commands["rootMT"] = modules.NewRootMT |
| 52 | commands["nodeMT"] = modules.NewNodeMT |
| 53 | commands["setLocalRoots"] = modules.NewSetRoot |
| 54 | commands["ls"] = modules.NewGlob |
| 55 | commands["lsat"] = modules.NewGlobAt |
| 56 | commands["lsmt"] = modules.NewGlobAtMT |
| 57 | commands["resolve"] = modules.NewResolve |
| 58 | commands["resolveMT"] = modules.NewResolveMT |
| 59 | commands["echoServer"] = modules.NewEchoServer |
| 60 | commands["echo"] = modules.NewEchoClient |
| 61 | commands["clockServer"] = modules.NewClockServer |
| 62 | commands["time"] = modules.NewClockClient |
| 63 | } |
| 64 | |
| 65 | func prompt(lineno int) { |
| 66 | if interactive { |
| 67 | fmt.Printf("%d> ", lineno) |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | func main() { |
| 72 | modules.InModule() |
| 73 | rt.Init() |
| 74 | |
| 75 | scanner := bufio.NewScanner(os.Stdin) |
| 76 | lineno := 1 |
| 77 | prompt(lineno) |
| 78 | for scanner.Scan() { |
| 79 | line := scanner.Text() |
| 80 | if !strings.HasPrefix(line, "#") && len(line) > 0 { |
Cosmos Nicolaou | 3574f12 | 2014-06-11 23:07:15 -0700 | [diff] [blame] | 81 | if line == "eof" { |
| 82 | break |
| 83 | } |
Cosmos Nicolaou | 7c659ac | 2014-06-09 22:47:04 -0700 | [diff] [blame] | 84 | if err := process(line, lineno); err != nil { |
| 85 | if debug { |
| 86 | fmt.Printf("%d> %s: %v\n", lineno, line, err) |
| 87 | } else { |
| 88 | fmt.Printf("%d> %v\n", lineno, err) |
| 89 | } |
| 90 | } |
| 91 | } |
| 92 | lineno++ |
| 93 | prompt(lineno) |
| 94 | } |
| 95 | if err := scanner.Err(); err != nil { |
| 96 | fmt.Printf("error reading input: %v\n", err) |
| 97 | } |
| 98 | |
| 99 | modules.Cleanup() |
| 100 | } |
| 101 | |
| 102 | func process(line string, lineno int) error { |
| 103 | fields, err := splitQuotedFields(line) |
| 104 | if err != nil { |
| 105 | return err |
| 106 | } |
| 107 | if len(fields) == 0 { |
| 108 | return fmt.Errorf("no input") |
| 109 | } |
| 110 | name := fields[0] |
| 111 | |
| 112 | var args []string |
| 113 | if len(fields) > 1 { |
| 114 | args = fields[1:] |
| 115 | } else { |
| 116 | args = []string{} |
| 117 | } |
| 118 | |
| 119 | sub, err := subVariables(args, globals) |
| 120 | if err != nil { |
| 121 | return err |
| 122 | } |
| 123 | |
| 124 | factory := commands[name] |
| 125 | if factory == nil { |
| 126 | return fmt.Errorf("unrecognised command %q", name) |
| 127 | } |
Cosmos Nicolaou | 7c659ac | 2014-06-09 22:47:04 -0700 | [diff] [blame] | 128 | if vars, output, _, err := factory().Run(sub); err != nil { |
| 129 | return err |
| 130 | } else { |
| 131 | if debug || interactive { |
Cosmos Nicolaou | 3574f12 | 2014-06-11 23:07:15 -0700 | [diff] [blame] | 132 | fmt.Printf("%d> %s\n", lineno, line) |
| 133 | } |
| 134 | if len(output) > 0 { |
Cosmos Nicolaou | 7c659ac | 2014-06-09 22:47:04 -0700 | [diff] [blame] | 135 | if !interactive { |
Cosmos Nicolaou | 3574f12 | 2014-06-11 23:07:15 -0700 | [diff] [blame] | 136 | fmt.Printf("%d> ", lineno) |
Cosmos Nicolaou | 7c659ac | 2014-06-09 22:47:04 -0700 | [diff] [blame] | 137 | } |
Cosmos Nicolaou | 3574f12 | 2014-06-11 23:07:15 -0700 | [diff] [blame] | 138 | fmt.Printf("%s\n", strings.Join(output, " ")) |
Cosmos Nicolaou | 7c659ac | 2014-06-09 22:47:04 -0700 | [diff] [blame] | 139 | } |
| 140 | if debug && len(vars) > 0 { |
| 141 | for k, v := range vars { |
| 142 | fmt.Printf("\t%s=%q .... \n", k, v) |
| 143 | } |
| 144 | fmt.Println() |
| 145 | } |
| 146 | globals.UpdateFromVariables(vars) |
| 147 | } |
| 148 | return nil |
| 149 | } |
| 150 | |
| 151 | // splitQuotedFields a line into fields, allowing for quoted strings. |
| 152 | func splitQuotedFields(line string) ([]string, error) { |
| 153 | fields := []string{} |
| 154 | inquote := false |
| 155 | var field []rune |
| 156 | for _, c := range line { |
| 157 | switch { |
| 158 | case c == '"': |
| 159 | if inquote { |
| 160 | fields = append(fields, string(field)) |
| 161 | field = nil |
| 162 | inquote = false |
| 163 | } else { |
| 164 | inquote = true |
| 165 | } |
| 166 | case unicode.IsSpace(c): |
| 167 | if inquote { |
| 168 | field = append(field, c) |
| 169 | } else { |
| 170 | if len(field) > 0 { |
| 171 | fields = append(fields, string(field)) |
| 172 | } |
| 173 | field = nil |
| 174 | } |
| 175 | default: |
| 176 | field = append(field, c) |
| 177 | } |
| 178 | } |
| 179 | if inquote { |
| 180 | return nil, fmt.Errorf("unterminated quoted input") |
| 181 | } |
| 182 | |
| 183 | if len(field) > 0 { |
| 184 | fields = append(fields, string(field)) |
| 185 | } |
| 186 | return fields, nil |
| 187 | } |
| 188 | |
| 189 | // subVariables substitutes variables that occur in the string slice |
| 190 | // args with values from vars. |
| 191 | func subVariables(args []string, vars modules.Variables) ([]string, error) { |
| 192 | var results []string |
| 193 | for _, a := range args { |
| 194 | if r, err := subVariablesInArgument(a, vars); err != nil { |
| 195 | return results, err |
| 196 | } else { |
| 197 | results = append(results, r) |
| 198 | } |
| 199 | } |
| 200 | return results, nil |
| 201 | } |
| 202 | |
| 203 | // subVariablesInArgument substitutes variables that occur in the string |
| 204 | // parameter with values from vars. |
| 205 | // |
| 206 | // A variable, is introduced by $, terminated by \t, space, / , : or !. |
| 207 | // Variables may also be enclosed by {} (as in ${VAR}) to allow for embedding |
| 208 | // within strings. |
| 209 | func subVariablesInArgument(a string, vars modules.Variables) (string, error) { |
| 210 | first := strings.Index(a, "$") |
| 211 | if first < 0 { |
| 212 | return a, nil |
| 213 | } |
| 214 | parts := strings.Split(a, "$") |
| 215 | result := parts[0] |
| 216 | vn := "" |
| 217 | rem := 0 |
| 218 | for _, p := range parts[1:] { |
| 219 | start := 0 |
| 220 | end := -1 |
| 221 | if strings.HasPrefix(p, "{") { |
| 222 | start = 1 |
| 223 | end = strings.Index(p, "}") |
| 224 | if end < 0 { |
| 225 | return "", fmt.Errorf("unterminated variable: %q", p) |
| 226 | } |
| 227 | rem = end + 1 |
| 228 | } else { |
| 229 | end = strings.IndexAny(p, "\t/,:! ") |
| 230 | if end < 0 { |
| 231 | end = len(p) |
| 232 | } |
| 233 | rem = end |
| 234 | } |
| 235 | vn = p[start:end] |
| 236 | r := p[rem:] |
| 237 | v, present := vars[vn] |
| 238 | if !present { |
| 239 | return "", fmt.Errorf("unknown variable: %q", vn) |
| 240 | } |
| 241 | result += v |
| 242 | result += r |
| 243 | } |
| 244 | return result, nil |
| 245 | } |