blob: 35ca8de8c5a938149de962d78b62491d39bf5436 [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
Jiri Simsa519c5072014-09-17 21:37:57 -070019 "veyron.io/veyron/veyron2/rt"
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070020
21 "veyron.io/veyron/veyron/lib/expect"
22 "veyron.io/veyron/veyron/lib/modules"
23 "veyron.io/veyron/veyron/lib/modules/core"
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070024)
25
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070026type cmdState struct {
27 modules.Handle
28 *expect.Session
29 line string
30}
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070031
32var (
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070033 interactive bool
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070034 handles map[string]*cmdState
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070035)
36
37func init() {
38 flag.BoolVar(&interactive, "interactive", true, "set interactive/batch mode")
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070039 handles = make(map[string]*cmdState)
40 flag.Usage = usage
41}
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070042
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070043var usage = func() {
44 fmt.Println(
45 `Welcome to this simple shell that lets you run mount tables, a simple server
46and sundry other commands from an interactive command line or as scripts. Type
47'help' at the prompt to see a list of available commands, or 'help command' to
48get specific help about that command. The shell provides environment variables
49with expansion and intrinsic support for managing subprocess, but it does not
50provide any flow control commands.
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070051
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070052All commands, except builtin ones (such as help, set, eval etc) are run
53asynchronously in background. That is, the prompt returns as soon as they are
54started and no output is displayed from them unless an error is encountered
55when they are being started. Each input line is numbered and that number is
56used to refer to the standard output of previous started commands. The variable
57_ always contains the number of the immediately preceeding line. It is
58possible to read the output of a command (using the 'read' builtin) and assign
59it that output to an environment variable. The 'eval' builtin parses output of
60the form <var>=<val>. In this way subproccess may be started, their output
61read and used to configure subsequent subprocesses. For example:
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070062
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700631> time
642> read 1 t
653> print $t
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070066
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070067will print the first line of output from the time command, as will the
68following:
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070069
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070070or:
71time
72read $_ t
73print $t
74
75The eval builtin is used to directly to assign to variables specified
76in the output of the command. For example, if the root command
77prints out MT_NAME=foo then eval will set MT_NAME to foo as follows:
78
79root
80eval $_
81print $MT_NAME
82
83will print the value of MT_NAME that is output by the root command.
84`)
85 flag.PrintDefaults()
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070086}
87
88func prompt(lineno int) {
89 if interactive {
90 fmt.Printf("%d> ", lineno)
91 }
92}
93
94func main() {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -070095 rt.Init()
96
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070097 // Subprocesses commands are run by fork/execing this binary
98 // so we must test to see if this instance is a subprocess or the
99 // the original command line instance.
100 if os.Getenv(modules.ShellEntryPoint) != "" {
101 // Subprocess, run the requested command.
102 if err := modules.Dispatch(); err != nil {
103 fmt.Fprintf(os.Stdout, "failed: %v\n", err)
104 fmt.Fprintf(os.Stderr, "failed: %v\n", err)
105 return
106 }
107 return
108 }
109
110 shell := modules.NewShell()
111 defer shell.Cleanup(os.Stderr)
112
113 core.Install(shell)
114
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700115 scanner := bufio.NewScanner(os.Stdin)
116 lineno := 1
117 prompt(lineno)
118 for scanner.Scan() {
119 line := scanner.Text()
120 if !strings.HasPrefix(line, "#") && len(line) > 0 {
Cosmos Nicolaou3574f122014-06-11 23:07:15 -0700121 if line == "eof" {
122 break
123 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700124 if err := process(shell, line, lineno); err != nil {
125 fmt.Printf("ERROR: %d> %q: %v\n", lineno, line, err)
126 os.Exit(1)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700127 }
128 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700129 shell.SetVar("_", strconv.Itoa(lineno))
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700130 lineno++
131 prompt(lineno)
132 }
133 if err := scanner.Err(); err != nil {
134 fmt.Printf("error reading input: %v\n", err)
135 }
136
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700137}
138
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700139func output(lineno int, line string) {
140 if len(line) > 0 {
141 if !interactive {
142 fmt.Printf("%d> ", lineno)
143 }
144 line = strings.TrimSuffix(line, "\n")
145 fmt.Printf("%s\n", line)
146 }
147}
148
149func process(sh *modules.Shell, line string, lineno int) error {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700150 fields, err := splitQuotedFields(line)
151 if err != nil {
152 return err
153 }
154 if len(fields) == 0 {
155 return fmt.Errorf("no input")
156 }
157 name := fields[0]
158
159 var args []string
160 if len(fields) > 1 {
161 args = fields[1:]
162 } else {
163 args = []string{}
164 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700165 sub, err := subVariables(sh, args)
166 if cmd := builtins[name]; cmd != nil {
167 if cmd.nargs >= 0 && len(sub) != cmd.nargs {
168 return fmt.Errorf("wrong (%d) # args for %q: usage %s", len(sub), name, cmd.usage)
169 }
170 l := ""
171 var err error
172 if cmd.needsHandle {
173 l, err = handleWrapper(sh, cmd.fn, sub...)
174 } else {
175 l, err = cmd.fn(sh, nil, sub...)
176 }
177 if err != nil {
178 return err
179 }
180 output(lineno, l)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700181 } else {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700182 handle, err := sh.Start(name, sub...)
183 if err != nil {
184 return err
185 }
186 handles[strconv.Itoa(lineno)] = &cmdState{
187 handle,
188 expect.NewSession(nil, handle.Stdout(), time.Minute),
189 line,
190 }
191 if !interactive {
Cosmos Nicolaou3574f122014-06-11 23:07:15 -0700192 fmt.Printf("%d> %s\n", lineno, line)
193 }
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700194 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700195
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700196 return nil
197}
198
199// splitQuotedFields a line into fields, allowing for quoted strings.
200func splitQuotedFields(line string) ([]string, error) {
201 fields := []string{}
202 inquote := false
203 var field []rune
204 for _, c := range line {
205 switch {
206 case c == '"':
207 if inquote {
208 fields = append(fields, string(field))
209 field = nil
210 inquote = false
211 } else {
212 inquote = true
213 }
214 case unicode.IsSpace(c):
215 if inquote {
216 field = append(field, c)
217 } else {
218 if len(field) > 0 {
219 fields = append(fields, string(field))
220 }
221 field = nil
222 }
223 default:
224 field = append(field, c)
225 }
226 }
227 if inquote {
228 return nil, fmt.Errorf("unterminated quoted input")
229 }
230
231 if len(field) > 0 {
232 fields = append(fields, string(field))
233 }
234 return fields, nil
235}
236
237// subVariables substitutes variables that occur in the string slice
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700238// args with values from the Shell.
239func subVariables(sh *modules.Shell, args []string) ([]string, error) {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700240 var results []string
241 for _, a := range args {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700242 if r, err := subVariablesInArgument(sh, a); err != nil {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700243 return results, err
244 } else {
245 results = append(results, r)
246 }
247 }
248 return results, nil
249}
250
251// subVariablesInArgument substitutes variables that occur in the string
252// parameter with values from vars.
253//
254// A variable, is introduced by $, terminated by \t, space, / , : or !.
255// Variables may also be enclosed by {} (as in ${VAR}) to allow for embedding
256// within strings.
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700257func subVariablesInArgument(sh *modules.Shell, a string) (string, error) {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700258 first := strings.Index(a, "$")
259 if first < 0 {
260 return a, nil
261 }
262 parts := strings.Split(a, "$")
263 result := parts[0]
264 vn := ""
265 rem := 0
266 for _, p := range parts[1:] {
267 start := 0
268 end := -1
269 if strings.HasPrefix(p, "{") {
270 start = 1
271 end = strings.Index(p, "}")
272 if end < 0 {
273 return "", fmt.Errorf("unterminated variable: %q", p)
274 }
275 rem = end + 1
276 } else {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700277 end = strings.IndexAny(p, "\t/,:!= ")
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700278 if end < 0 {
279 end = len(p)
280 }
281 rem = end
282 }
283 vn = p[start:end]
284 r := p[rem:]
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700285 v, present := sh.GetVar(vn)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700286 if !present {
Matt Rosencrantz0dd7ce02014-09-23 11:34:26 -0700287 return a, fmt.Errorf("unknown variable: %q", vn)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700288 }
289 result += v
290 result += r
291 }
292 return result, nil
293}