blob: 34c6e9125364ba3941b1c6348bb8f46f57bc85da [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 Simsa764efb72014-12-25 20:57:03 -080019 "v.io/core/veyron2"
Matt Rosencrantzd599e382015-01-12 11:13:32 -080020 "v.io/core/veyron2/context"
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070021
Jiri Simsa764efb72014-12-25 20:57:03 -080022 "v.io/core/veyron/lib/expect"
23 "v.io/core/veyron/lib/modules"
24 _ "v.io/core/veyron/lib/modules/core"
25 _ "v.io/core/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 Rosencrantzd599e382015-01-12 11:13:32 -080098var ctx *context.T
Matt Rosencrantzc13446b2014-12-03 10:37:00 -080099
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700100func main() {
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800101 var shutdown veyron2.Shutdown
102 ctx, shutdown = veyron2.Init()
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700103
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700104 // Subprocesses commands are run by fork/execing this binary
105 // so we must test to see if this instance is a subprocess or the
106 // the original command line instance.
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800107 if modules.IsModulesProcess() {
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800108 shutdown()
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700109 // Subprocess, run the requested command.
110 if err := modules.Dispatch(); err != nil {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700111 fmt.Fprintf(os.Stderr, "failed: %v\n", err)
Cosmos Nicolaou4e213d72014-10-26 22:21:52 -0700112 os.Exit(1)
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700113 }
114 return
115 }
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800116 defer shutdown()
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700117
Ryan Browna08a2212015-01-15 15:40:10 -0800118 shell, err := modules.NewShell(ctx, nil)
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800119 if err != nil {
120 fmt.Fprintf(os.Stderr, "unexpected error: %s\n", err)
121 os.Exit(1)
122 }
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700123 defer shell.Cleanup(os.Stderr, os.Stderr)
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700124
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700125 scanner := bufio.NewScanner(os.Stdin)
126 lineno := 1
127 prompt(lineno)
128 for scanner.Scan() {
129 line := scanner.Text()
130 if !strings.HasPrefix(line, "#") && len(line) > 0 {
Cosmos Nicolaou3574f122014-06-11 23:07:15 -0700131 if line == "eof" {
132 break
133 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700134 if err := process(shell, line, lineno); err != nil {
135 fmt.Printf("ERROR: %d> %q: %v\n", lineno, line, err)
Cosmos Nicolaoub44f2392014-10-28 22:41:49 -0700136 if !interactive {
137 os.Exit(1)
138 }
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700139 }
140 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700141 shell.SetVar("_", strconv.Itoa(lineno))
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700142 lineno++
143 prompt(lineno)
144 }
145 if err := scanner.Err(); err != nil {
146 fmt.Printf("error reading input: %v\n", err)
147 }
148
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700149}
150
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700151func output(lineno int, line string) {
152 if len(line) > 0 {
153 if !interactive {
154 fmt.Printf("%d> ", lineno)
155 }
156 line = strings.TrimSuffix(line, "\n")
157 fmt.Printf("%s\n", line)
158 }
159}
160
161func process(sh *modules.Shell, line string, lineno int) error {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700162 fields, err := splitQuotedFields(line)
163 if err != nil {
164 return err
165 }
166 if len(fields) == 0 {
167 return fmt.Errorf("no input")
168 }
169 name := fields[0]
170
171 var args []string
172 if len(fields) > 1 {
173 args = fields[1:]
174 } else {
175 args = []string{}
176 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700177 sub, err := subVariables(sh, args)
Cosmos Nicolaou4e213d72014-10-26 22:21:52 -0700178 if err != nil {
179 return err
180 }
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700181 if cmd := builtins[name]; cmd != nil {
182 if cmd.nargs >= 0 && len(sub) != cmd.nargs {
183 return fmt.Errorf("wrong (%d) # args for %q: usage %s", len(sub), name, cmd.usage)
184 }
185 l := ""
186 var err error
187 if cmd.needsHandle {
188 l, err = handleWrapper(sh, cmd.fn, sub...)
189 } else {
190 l, err = cmd.fn(sh, nil, sub...)
191 }
192 if err != nil {
Cosmos Nicolaou4e213d72014-10-26 22:21:52 -0700193 return fmt.Errorf("%s : %s", err, l)
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700194 }
195 output(lineno, l)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700196 } else {
Cosmos Nicolaou612ad382014-10-29 19:41:35 -0700197 handle, err := sh.Start(name, nil, sub...)
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700198 if err != nil {
199 return err
200 }
201 handles[strconv.Itoa(lineno)] = &cmdState{
202 handle,
203 expect.NewSession(nil, handle.Stdout(), time.Minute),
204 line,
205 }
Cosmos Nicolaoub44f2392014-10-28 22:41:49 -0700206 output(lineno, line)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700207 }
208 return nil
209}
210
211// splitQuotedFields a line into fields, allowing for quoted strings.
212func splitQuotedFields(line string) ([]string, error) {
213 fields := []string{}
214 inquote := false
215 var field []rune
216 for _, c := range line {
217 switch {
218 case c == '"':
219 if inquote {
220 fields = append(fields, string(field))
221 field = nil
222 inquote = false
223 } else {
224 inquote = true
225 }
226 case unicode.IsSpace(c):
227 if inquote {
228 field = append(field, c)
229 } else {
230 if len(field) > 0 {
231 fields = append(fields, string(field))
232 }
233 field = nil
234 }
235 default:
236 field = append(field, c)
237 }
238 }
239 if inquote {
240 return nil, fmt.Errorf("unterminated quoted input")
241 }
242
243 if len(field) > 0 {
244 fields = append(fields, string(field))
245 }
246 return fields, nil
247}
248
249// subVariables substitutes variables that occur in the string slice
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700250// args with values from the Shell.
251func subVariables(sh *modules.Shell, args []string) ([]string, error) {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700252 var results []string
253 for _, a := range args {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700254 if r, err := subVariablesInArgument(sh, a); err != nil {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700255 return results, err
256 } else {
257 results = append(results, r)
258 }
259 }
260 return results, nil
261}
262
263// subVariablesInArgument substitutes variables that occur in the string
264// parameter with values from vars.
265//
266// A variable, is introduced by $, terminated by \t, space, / , : or !.
267// Variables may also be enclosed by {} (as in ${VAR}) to allow for embedding
268// within strings.
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700269func subVariablesInArgument(sh *modules.Shell, a string) (string, error) {
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700270 first := strings.Index(a, "$")
271 if first < 0 {
272 return a, nil
273 }
274 parts := strings.Split(a, "$")
275 result := parts[0]
276 vn := ""
277 rem := 0
278 for _, p := range parts[1:] {
279 start := 0
280 end := -1
281 if strings.HasPrefix(p, "{") {
282 start = 1
283 end = strings.Index(p, "}")
284 if end < 0 {
285 return "", fmt.Errorf("unterminated variable: %q", p)
286 }
287 rem = end + 1
288 } else {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700289 end = strings.IndexAny(p, "\t/,:!= ")
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700290 if end < 0 {
291 end = len(p)
292 }
293 rem = end
294 }
295 vn = p[start:end]
296 r := p[rem:]
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700297 v, present := sh.GetVar(vn)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700298 if !present {
Matt Rosencrantz0dd7ce02014-09-23 11:34:26 -0700299 return a, fmt.Errorf("unknown variable: %q", vn)
Cosmos Nicolaou7c659ac2014-06-09 22:47:04 -0700300 }
301 result += v
302 result += r
303 }
304 return result, nil
305}