blob: 4206289ca3efaf957c00808f36e045522ff192ca [file] [log] [blame]
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -07001// 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.
7package main
8
9import (
10 "bufio"
11 "flag"
12 "fmt"
13 "os"
14 "strings"
15 "unicode"
16
17 "veyron/lib/testutil/modules"
18
19 "veyron2/rt"
20)
21
22type commandFunc func() modules.T
23
24var (
25 commands map[string]commandFunc
26 globals modules.Variables
27 debug bool
28 interactive bool
29)
30
31func 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
65func prompt(lineno int) {
66 if interactive {
67 fmt.Printf("%d> ", lineno)
68 }
69}
70
71func 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 Nicolaou3574f122014-06-11 23:07:15 -070081 if line == "eof" {
82 break
83 }
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070084 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
102func 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 Nicolaou7c659ac2014-06-09 22:47:04 -0700128 if vars, output, _, err := factory().Run(sub); err != nil {
129 return err
130 } else {
131 if debug || interactive {
Cosmos Nicolaou3574f122014-06-11 23:07:15 -0700132 fmt.Printf("%d> %s\n", lineno, line)
133 }
134 if len(output) > 0 {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700135 if !interactive {
Cosmos Nicolaou3574f122014-06-11 23:07:15 -0700136 fmt.Printf("%d> ", lineno)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700137 }
Cosmos Nicolaou3574f122014-06-11 23:07:15 -0700138 fmt.Printf("%s\n", strings.Join(output, " "))
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700139 }
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.
152func 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.
191func 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.
209func 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}