4696869dcd0be89cd511a877ab1f48d39cbd6bd9
[org.ibex.xt-crawshaw.git] / src / java / org / ibex / xt / Shell.java
1 package org.ibex.xt;
2
3 import java.io.*;
4 import java.net.*;
5 import java.util.*;
6 import java.util.regex.*;
7
8 import org.ibex.util.*;
9 import org.ibex.util.Collections;
10 import org.ibex.js.*;
11
12 public class Shell {
13
14     public static void main(String[] args) throws Exception {
15         if (args.length == 0 || args.length > 2||  !args[0].startsWith("http://")) {
16             printUsage(); return;
17         }
18         Shell shell = new Shell(new URL(args[0]));
19
20         if (args.length == 2) {
21             // FIXME
22         } else {
23             shell.listen(new InputStreamReader(System.in), new OutputStreamWriter(System.out));
24         }
25     }
26
27     private static void printUsage() {
28         System.out.println("Usage: xish url [command]");
29     }
30
31     protected Command[] commands = new Command[] {
32         new LsCommand(),
33         new PwdCommand(),
34         new CdCommand(),
35         new RmCommand(),
36         new HelpCommand()
37     };
38
39     /** URL of server. */
40     protected URL server;
41
42     /** Current JS path using '.' as a seperator. */
43     protected String pwd = ".";
44
45     /** Create a new Shell using the given url for the server. */
46     public Shell(URL url) { server = url; }
47
48     public void listen(Reader r, Writer w) throws IOException {
49         LineNumberReader in = new LineNumberReader(r);
50         PrintWriter out = new PrintWriter(w);
51
52         out.println("ibex xt shell: type help or exit");
53         out.print("xt: ");
54         out.flush();
55
56         String line;
57         String buffer = "";
58         while ((line = in.readLine()) != null) {
59             if (line.length() > 0) {
60                 if (line.startsWith("exit")) return;
61                 if (line.charAt(line.length() - 1) == '\\') {
62                     buffer += line.substring(0, line.length() - 1);
63                     out.print('>');
64                     out.flush(); continue;
65                 }
66
67                 buffer += line;
68             }
69
70             if (buffer.length() > 0) {
71                 String[] c = buffer.split(" ");
72                 int i=0; while (i < commands.length) {
73                     if (commands[i].name().equals(c[0])) {
74                         commands[i].execute(out, c); break;
75                     }
76                     i++;
77                 }
78                 if (i == commands.length) {
79                     out.write(c[0]);
80                     w.write(": command not found\n");
81                 }
82                 buffer = "";
83             }
84             out.print("xt: ");
85             out.flush();
86         }
87     }
88
89     /** Returns a path, based on console input. */
90     private String path(String c) {
91         if (c.equals("") || c.equals(".") || c.equals("/")) {
92             c = ".";
93         } else if (c.equals("..")) {
94             c = c.substring(0, c.lastIndexOf('.'));
95             if (c.equals("")) c = ".";
96         } else {
97             if (c.charAt(0) != '/') c = pwd + c;
98             c = c.replaceAll("/+", ".");
99             if (c.length() > 1 && c.charAt(c.length() - 1) == '.')
100                 c = c.substring(0, c.length() - 1);
101         }
102
103         return c;
104     }
105
106     private String cookie = null;
107     public Object send(Request request) throws IOException {
108         URLConnection c = server.openConnection();
109         ((HttpURLConnection)c).setRequestMethod("POST");
110         c.setDoOutput(true);
111         if (cookie != null) c.setRequestProperty("Cookie", cookie);
112
113         c.connect();
114
115         ObjectOutputStream out = new ObjectOutputStream(c.getOutputStream());
116         out.writeObject(request);
117         out.close();
118
119         String cook = c.getHeaderField("Set-Cookie");
120         if (cook != null && !cook.equals("")) cookie = cook.substring(0, cook.indexOf(';'));
121
122         try {
123             return new ObjectInputStream(c.getInputStream()).readObject();
124         } catch (ClassNotFoundException e) {
125             e.printStackTrace();
126             throw new IOException("unexpected ClassNotFoundException");
127         }
128     }
129
130     public abstract class Command {
131         /** Returns the command name. */
132         public abstract String name();
133
134         /** Returns single-line of usage information, eg. <tt>pattern]</tt> */
135         public abstract String usage();
136
137         /** Returns single-line description of command. */
138         public abstract String summary();
139
140         /** Returns multi-line description. */
141         public abstract String help();
142
143         /** Writes result of execution, even if result is an error. */
144         public abstract void execute(Writer w, String[] args) throws IOException;
145     }
146
147     /** Returns the command matching the given name. */
148     protected Command command(String name) {
149         for (int i=0; i < commands.length; i++)
150             if (commands[i].name().equals(name)) return commands[i];
151         return null;
152     }
153
154     public class HelpCommand extends Command {
155         public String name() { return "help"; }
156         public String usage() { return "[command name]"; }
157         public String summary() { return "Lists available commands."; }
158         public String help() { return ""; }
159
160         public void execute(Writer w, String[] c) throws IOException {
161             if (c.length > 1) {
162                 Command cmd = command(c[1]);
163                 if (c == null) {
164                     w.write("help: ");
165                     w.write(c[1]);
166                     w.write(": command not found\n");
167                 } else {
168                     w.write("usage: ");
169                     w.write(cmd.name());
170                     w.write(" ");
171                     w.write(cmd.usage());
172                     w.write("\n\n");
173                     w.write(cmd.help());
174                     w.write("\n");
175                 }
176             } else {
177                 int len = 3;
178                 for (int i=0; i < commands.length; i++)
179                     len = Math.max(commands[i].name().length(), len);
180
181                 w.write("Available commands:\n");
182                 for (int i=0; i < commands.length; i++) {
183                     Command cmd = commands[i];
184                     w.write("  ");
185                     w.write(cmd.name());
186                     for (int j=len - cmd.name().length(); j >= 0; j--) w.write(" ");
187                     w.write(" - ");
188                     w.write(cmd.summary());
189                     w.write("\n");
190                 }
191                 w.write("\nFor usage details, type help [command name].\n");
192             }
193         }
194     }
195
196     public class LsCommand extends Command {
197         public String name() { return "ls"; }
198         public String usage() { return "[path]"; }
199         public String summary() { return "List object entries."; }
200         public String help() { return
201             "Lists the keys in an object. Modelled after the UNIX ls command.";
202         }
203
204         public void execute(Writer w, String[] c) throws IOException {
205             if (c.length > 2) { w.write(usage()); return; }
206
207             Object ret = send(new KeyRequest(path(c[1])));
208             if (ret == null) {
209                 w.write("error: (unexpected) returned object is null\n");
210             } else if (ret instanceof JSExn) {
211                 String e = ((JSExn)ret).getMessage();
212                 // FIXME: messy way to get info from server
213                 if (e.endsWith("does not exist")) {
214                     w.write("ls ");
215                     w.write(c[1]);
216                     w.write(": no such path\n");
217                 } else {
218                     w.write("error: ");
219                     w.write(e);
220                     w.write("\n");
221                 }
222             } else if (ret instanceof List) {
223                 List l = (List)ret; Collections.sort(l);
224                 Iterator i = l.iterator(); while (i.hasNext()) {
225                     w.write(i.next().toString());
226                     w.write("\n");
227                 }
228             } else {
229                 w.write("error: (unexpected) returned object is of unknown type: ");
230                 w.write(ret.getClass().getName());
231                 w.write("\n");
232             }
233         }
234     }
235
236     public class PwdCommand extends Command {
237         public String name() { return "pwd"; }
238         public String usage() { return ""; }
239         public String summary() { return "Path to current object."; }
240         public String help() { return "Print the path to the current object."; }
241         public void execute(Writer w, String[] c) throws IOException {
242             w.write(c.length == 1 ? pwd.replace('.', '/') : usage());
243             w.write("\n");
244         }
245     }
246
247     public class CdCommand extends Command {
248         public String name() { return "cd"; }
249         public String usage() { return "[path]"; }
250         public String summary() { return "Change current object."; }
251         public String help() { return
252             "Chnages the current object that all other commands use "+
253             "as the base for running.\n Pass either a relative path "+
254             "(e.g. in /prevalent, type cd myob, now in /prevalent/myob) "+
255             "or an absolute path (e.g. cd /prevalent/myob).\n\n" +
256             "To go up one level, cd .. can be used.";
257         }
258         public void execute(Writer w, String[] c) throws IOException {
259             if (c.length > 2) w.write(usage());
260             else if (c.length == 1 || c[1].equals("") || c[1].equals("/")) pwd = ".";
261             else if (c[1].equals("..")) {
262                 String n = pwd.substring(0, pwd.lastIndexOf('.'));
263                 pwd = n.equals("") ? "." : n;
264             } else {
265                 String n = path(c[1]);
266                 Object ret = send(new KeyRequest(n));
267
268                 if (ret == null) {
269                     w.write("error: (unexpected) server returned null\n");
270                 } else if (ret instanceof List && ((List)ret).size() == 1) {
271                     pwd = n;
272                 } else if (ret instanceof JSExn ||
273                            (ret instanceof List && ((List)ret).size() == 0)) {
274                     w.write("cd ");
275                     w.write(c[1]);
276                     w.write(": no such path\n");
277                 } else {
278                     w.write("error: (unexpected) server returned ");
279                     w.write(ret.toString());
280                     w.write("\n");
281                 }
282             }
283         }
284     }
285
286     public class RmCommand extends Command {
287         public String name() { return "rm"; }
288         public String usage() { return "[options] [path]"; }
289         public String summary() { return "Removes objects."; }
290         public String help() { return "Removes objects."; } // FIXME
291         public void execute(Writer w, String[] c) throws IOException {
292             if (c.length == 1) { w.write(usage()); }
293
294             String[] r = new String[c.length - 1];
295             for (int i=0; i < r.length; i++) r[i] = path(c[i + 1]);
296             // Object ret = send(new KeyRequest( FIXME: CompositeRequest
297         }
298     }
299
300     public static abstract class Request implements Serializable {
301         public abstract Object process(JSScope root) throws JSExn;
302     }
303
304     public static class KeyRequest extends Request {
305         private String path, matcher;
306         public KeyRequest() {}
307         public KeyRequest(String c) {
308             int pos = c.lastIndexOf('.');
309             path = c.substring(0, pos);
310             matcher = c.substring(pos + 1).replaceAll("\\*+", ".*");
311         }
312         public KeyRequest(String path, String matcher) {
313             this.path = path; this.matcher = matcher;
314         }
315
316         /** Returns a List. */
317         public Object process(JSScope root) throws JSExn {
318             String p = path == null ? "" : path.replaceAll("\\.+", "\\.");
319             if (p.length() > 0 && p.charAt(0) == '.') p = p.substring(1);
320             if (p.length() > 0 && p.charAt(p.length() - 1) == '.') p = p.substring(0, p.length() - 1);
321             System.out.println("searching path '"+p+"' for pattern '"+matcher+"'");
322
323             Object o = p.equals("") ? root : root.get(p);
324             if (o == null || o instanceof JSDate ||
325                              o instanceof JSArray ||
326                              !(o instanceof JS)) {
327                 System.out.println("hit bad object: "+o+", class="+
328                     (o == null ? null : o.getClass().getName()));
329                 throw new JSExn("path /" + p + " does not exist");
330             } else {
331                 Pattern pat = Pattern.compile(matcher);
332                 List keys = new ArrayList();
333
334                 Iterator i = ((JS)o).keys().iterator(); while(i.hasNext()) {
335                     String k = i.next().toString();
336                     if (pat.matcher(k).matches()) keys.add(k);
337                 }
338
339                 return keys;
340             }
341         }
342     }
343
344     public static class ExecRequest extends Request {
345         private JS exec;
346         public ExecRequest() {}
347         public ExecRequest(JS exec) { this.exec = exec; }
348         public ExecRequest(String source) throws IOException, JSExn {
349             this(new StringReader(source));
350         }
351         public ExecRequest(Reader source) throws IOException, JSExn {
352             exec = JS.fromReader("xsh", 0, source);
353         }
354
355         /** Returns the result of <tt>JS.eval()</tt>. */
356         public Object process(JSScope root) throws JSExn {
357             return JS.eval(JS.cloneWithNewParentScope(exec, root));
358         }
359     }
360 }