blob: 32e9ff99c6a8dcd4d3d946976706e5b19f9b4141 [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
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -07005// extendable by adding new 'commands' that implement the API defined
6// by veyron/lib/modules.
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -07007package main
8
9import (
10 "bufio"
11 "flag"
12 "fmt"
13 "os"
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070014 "strconv"
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070015 "strings"
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070016 "time"
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070017 "unicode"
18
Matt Rosencrantzc13446b2014-12-03 10:37:00 -080019 "veyron.io/veyron/veyron2"
Jiri Simsa519c5072014-09-17 21:37:57 -070020 "veyron.io/veyron/veyron2/rt"
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070021
22 "veyron.io/veyron/veyron/lib/expect"
23 "veyron.io/veyron/veyron/lib/modules"
Cosmos Nicolaou28f35c32014-12-01 20:36:27 -080024 _ "veyron.io/veyron/veyron/lib/modules/core"
Cosmos Nicolaou4e213d72014-10-26 22:21:52 -070025 _ "veyron.io/veyron/veyron/profiles"
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070026)
27
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070028type cmdState struct {
29 modules.Handle
30 *expect.Session
31 line string
32}
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070033
34var (
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070035 interactive bool
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070036 handles map[string]*cmdState
Cosmos Nicolaoub44f2392014-10-28 22:41:49 -070037 jsonDict map[string]string
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070038)
39
40func init() {
41 flag.BoolVar(&interactive, "interactive", true, "set interactive/batch mode")
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070042 handles = make(map[string]*cmdState)
Cosmos Nicolaoub44f2392014-10-28 22:41:49 -070043 jsonDict = make(map[string]string)
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070044 flag.Usage = usage
45}
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070046
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070047var usage = func() {
48 fmt.Println(
49 `Welcome to this simple shell that lets you run mount tables, a simple server
50and sundry other commands from an interactive command line or as scripts. Type
51'help' at the prompt to see a list of available commands, or 'help command' to
52get specific help about that command. The shell provides environment variables
53with expansion and intrinsic support for managing subprocess, but it does not
54provide any flow control commands.
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070055
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070056All commands, except builtin ones (such as help, set, eval etc) are run
57asynchronously in background. That is, the prompt returns as soon as they are
58started and no output is displayed from them unless an error is encountered
59when they are being started. Each input line is numbered and that number is
60used to refer to the standard output of previous started commands. The variable
61_ always contains the number of the immediately preceeding line. It is
62possible to read the output of a command (using the 'read' builtin) and assign
63it that output to an environment variable. The 'eval' builtin parses output of
64the form <var>=<val>. In this way subproccess may be started, their output
65read and used to configure subsequent subprocesses. For example:
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070066
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700671> time
682> read 1 t
693> print $t
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070070
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070071will print the first line of output from the time command, as will the
72following:
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070073
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070074or:
75time
76read $_ t
77print $t
78
79The eval builtin is used to directly to assign to variables specified
80in the output of the command. For example, if the root command
81prints out MT_NAME=foo then eval will set MT_NAME to foo as follows:
82
83root
84eval $_
85print $MT_NAME
86
87will print the value of MT_NAME that is output by the root command.
88`)
89 flag.PrintDefaults()
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070090}
91
92func prompt(lineno int) {
93 if interactive {
94 fmt.Printf("%d> ", lineno)
95 }
96}
97
Matt Rosencrantzc13446b2014-12-03 10:37:00 -080098var runtime veyron2.Runtime
99
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700100func main() {
Matt Rosencrantzc13446b2014-12-03 10:37:00 -0800101 var err error
102 if runtime, err = rt.New(); err != nil {
103 panic(err)
104 }
105 defer runtime.Cleanup()
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700106
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700107 // Subprocesses commands are run by fork/execing this binary
108 // so we must test to see if this instance is a subprocess or the
109 // the original command line instance.
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800110 if modules.IsModulesProcess() {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700111 // Subprocess, run the requested command.
112 if err := modules.Dispatch(); err != nil {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700113 fmt.Fprintf(os.Stderr, "failed: %v\n", err)
Cosmos Nicolaou4e213d72014-10-26 22:21:52 -0700114 os.Exit(1)
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700115 }
116 return
117 }
118
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800119 shell, err := modules.NewShell(nil)
120 if err != nil {
121 fmt.Fprintf(os.Stderr, "unexpected error: %s\n", err)
122 os.Exit(1)
123 }
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700124 defer shell.Cleanup(os.Stderr, os.Stderr)
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700125
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700126 scanner := bufio.NewScanner(os.Stdin)
127 lineno := 1
128 prompt(lineno)
129 for scanner.Scan() {
130 line := scanner.Text()
131 if !strings.HasPrefix(line, "#") && len(line) > 0 {
Cosmos Nicolaou3574f122014-06-11 23:07:15 -0700132 if line == "eof" {
133 break
134 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700135 if err := process(shell, line, lineno); err != nil {
136 fmt.Printf("ERROR: %d> %q: %v\n", lineno, line, err)
Cosmos Nicolaoub44f2392014-10-28 22:41:49 -0700137 if !interactive {
138 os.Exit(1)
139 }
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700140 }
141 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700142 shell.SetVar("_", strconv.Itoa(lineno))
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700143 lineno++
144 prompt(lineno)
145 }
146 if err := scanner.Err(); err != nil {
147 fmt.Printf("error reading input: %v\n", err)
148 }
149
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700150}
151
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700152func output(lineno int, line string) {
153 if len(line) > 0 {
154 if !interactive {
155 fmt.Printf("%d> ", lineno)
156 }
157 line = strings.TrimSuffix(line, "\n")
158 fmt.Printf("%s\n", line)
159 }
160}
161
162func process(sh *modules.Shell, line string, lineno int) error {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700163 fields, err := splitQuotedFields(line)
164 if err != nil {
165 return err
166 }
167 if len(fields) == 0 {
168 return fmt.Errorf("no input")
169 }
170 name := fields[0]
171
172 var args []string
173 if len(fields) > 1 {
174 args = fields[1:]
175 } else {
176 args = []string{}
177 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700178 sub, err := subVariables(sh, args)
Cosmos Nicolaou4e213d72014-10-26 22:21:52 -0700179 if err != nil {
180 return err
181 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700182 if cmd := builtins[name]; cmd != nil {
183 if cmd.nargs >= 0 && len(sub) != cmd.nargs {
184 return fmt.Errorf("wrong (%d) # args for %q: usage %s", len(sub), name, cmd.usage)
185 }
186 l := ""
187 var err error
188 if cmd.needsHandle {
189 l, err = handleWrapper(sh, cmd.fn, sub...)
190 } else {
191 l, err = cmd.fn(sh, nil, sub...)
192 }
193 if err != nil {
Cosmos Nicolaou4e213d72014-10-26 22:21:52 -0700194 return fmt.Errorf("%s : %s", err, l)
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700195 }
196 output(lineno, l)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700197 } else {
Cosmos Nicolaou612ad382014-10-29 19:41:35 -0700198 handle, err := sh.Start(name, nil, sub...)
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700199 if err != nil {
200 return err
201 }
202 handles[strconv.Itoa(lineno)] = &cmdState{
203 handle,
204 expect.NewSession(nil, handle.Stdout(), time.Minute),
205 line,
206 }
Cosmos Nicolaoub44f2392014-10-28 22:41:49 -0700207 output(lineno, line)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700208 }
209 return nil
210}
211
212// splitQuotedFields a line into fields, allowing for quoted strings.
213func splitQuotedFields(line string) ([]string, error) {
214 fields := []string{}
215 inquote := false
216 var field []rune
217 for _, c := range line {
218 switch {
219 case c == '"':
220 if inquote {
221 fields = append(fields, string(field))
222 field = nil
223 inquote = false
224 } else {
225 inquote = true
226 }
227 case unicode.IsSpace(c):
228 if inquote {
229 field = append(field, c)
230 } else {
231 if len(field) > 0 {
232 fields = append(fields, string(field))
233 }
234 field = nil
235 }
236 default:
237 field = append(field, c)
238 }
239 }
240 if inquote {
241 return nil, fmt.Errorf("unterminated quoted input")
242 }
243
244 if len(field) > 0 {
245 fields = append(fields, string(field))
246 }
247 return fields, nil
248}
249
250// subVariables substitutes variables that occur in the string slice
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700251// args with values from the Shell.
252func subVariables(sh *modules.Shell, args []string) ([]string, error) {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700253 var results []string
254 for _, a := range args {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700255 if r, err := subVariablesInArgument(sh, a); err != nil {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700256 return results, err
257 } else {
258 results = append(results, r)
259 }
260 }
261 return results, nil
262}
263
264// subVariablesInArgument substitutes variables that occur in the string
265// parameter with values from vars.
266//
267// A variable, is introduced by $, terminated by \t, space, / , : or !.
268// Variables may also be enclosed by {} (as in ${VAR}) to allow for embedding
269// within strings.
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700270func subVariablesInArgument(sh *modules.Shell, a string) (string, error) {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700271 first := strings.Index(a, "$")
272 if first < 0 {
273 return a, nil
274 }
275 parts := strings.Split(a, "$")
276 result := parts[0]
277 vn := ""
278 rem := 0
279 for _, p := range parts[1:] {
280 start := 0
281 end := -1
282 if strings.HasPrefix(p, "{") {
283 start = 1
284 end = strings.Index(p, "}")
285 if end < 0 {
286 return "", fmt.Errorf("unterminated variable: %q", p)
287 }
288 rem = end + 1
289 } else {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700290 end = strings.IndexAny(p, "\t/,:!= ")
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700291 if end < 0 {
292 end = len(p)
293 }
294 rem = end
295 }
296 vn = p[start:end]
297 r := p[rem:]
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700298 v, present := sh.GetVar(vn)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700299 if !present {
Matt Rosencrantz0dd7ce02014-09-23 11:34:26 -0700300 return a, fmt.Errorf("unknown variable: %q", vn)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700301 }
302 result += v
303 result += r
304 }
305 return result, nil
306}