add mkdir and replace functions
[org.ibex.xt-crawshaw.git] / src / java / org / ibex / xt / shell / Command.java
1 package org.ibex.xt.shell;
2
3 import java.io.StringReader;
4 import java.io.Writer;
5 import java.io.IOException;
6
7 import java.util.*;
8 import java.util.regex.*;
9
10 import org.ibex.js.*;
11 import org.ibex.util.*;
12
13 public abstract class Command {
14     protected Shell shell;
15
16     protected Command(Shell s) { shell = s; }
17
18     /** Returns the command name. */
19     public abstract String name();
20
21     /** Returns a single-line of parameter information, eg. <tt>[pattern]</tt> */
22     public abstract String params();
23
24     /** Returns single-line description of command. */
25     public abstract String summary();
26
27     /** Returns multi-line description. */
28     public abstract String help();
29
30     /** Writes result of execution, even if result is an error. */
31     public abstract void execute(Writer w, String[] args) throws IOException;
32
33
34     /** Returns single-line of usage information, eg. <tt>usage: ls [pattern]</tt> */
35     public void usage(Writer w) throws IOException {
36         w.write("usage: ");
37         w.write(name());
38         w.write(" ");
39         w.write(params());
40         w.write("\n");
41     }
42
43     public List fromShellPath(String s) throws Shell.BadPathException {
44         return fromShellPath(s, null);
45     }
46
47     /** Converts a shell path "/foo/etc/../bar" to its component form,
48      *  { "foo", "bar" } and loads it into the returned list.
49      *  Handles relative positioning to shell.getPath(). */
50     public List fromShellPath(String s, List l) throws Shell.BadPathException {
51         if (s == null) return null;
52         if (l == null) l = new ArrayList();
53
54         s = s.trim().replace("/+", "/");
55         if (s.charAt(0) != '/') l.addAll(Arrays.asList(shell.getPath()));
56
57         StringTokenizer st = new StringTokenizer(s, "/");
58         while (st.hasMoreTokens()) {
59             String part = st.nextToken();
60             if (part == null || part.equals("") || part.equals(".")) continue;
61             else if (part.equals("..")) { if (l.size() > 0) l.remove(l.size() - 1); }
62             else l.add(part);
63         }
64
65         return l;
66     }
67
68     public static class Help extends Command {
69         public Help(Shell s) { super(s); }
70         public String name() { return "help"; }
71         public String params() { return "[command name]"; }
72         public String summary() { return "Lists available commands."; }
73         public String help() { return ""; }
74
75         public void execute(Writer w, String[] c) throws IOException {
76             if (c.length > 1) {
77                 Command cmd = shell.getCommand(c[1]);
78                 if (c == null) {
79                     w.write("help: ");
80                     w.write(c[1]);
81                     w.write(": command not found\n");
82                 } else {
83                     cmd.usage(w);
84                     w.write("\n");
85                     w.write(cmd.help());
86                     w.write("\n");
87                 }
88             } else {
89                 int len = 3;
90                 Command[] cmds = shell.getCommands();
91                 for (int i=0; i < cmds.length; i++)
92                     len = Math.max(cmds[i].name().length(), len);
93
94                 w.write("Available commands:\n");
95                 for (int i=0; i < cmds.length; i++) {
96                     Command cmd = cmds[i];
97                     w.write("  ");
98                     w.write(cmd.name());
99                     for (int j=len - cmd.name().length(); j >= 0; j--) w.write(" ");
100                     w.write(" - ");
101                     w.write(cmd.summary());
102                     w.write("\n");
103                 }
104                 w.write("\nFor usage details, type help [command name].\n");
105             }
106         }
107     }
108
109     public static class Ls extends Command {
110         public Ls(Shell s) { super(s); }
111         public String name() { return "ls"; }
112         public String params() { return "[path]"; }
113         public String summary() { return "List object entries."; }
114         public String help() { return
115             "Lists the keys in an object. Modelled after the UNIX ls command.";
116         }
117
118         public void execute(Writer w, String[] c) throws IOException {
119             if (c.length > 2) { usage(w); return; }
120             String p = c.length == 1 ? "*" : c[1];
121             if (p.endsWith("/")) p += "*";
122
123             try {
124                 List path = fromShellPath(p);
125                 Object key = path.remove(path.size() - 1);
126                 Object po = shell.getFromPath(path.toArray());
127                 if (po == null || !(po instanceof JS))
128                     throw new Shell.BadPathException();
129                 JS cur = (JS)po;
130
131                 if (key instanceof String &&
132                         ((String)key).indexOf('*') >= 0) {
133                     String last = (String)key;
134                     last = last.replaceAll("\\.", "\\.");
135                     last = last.replaceAll("\\*", ".*");
136                     Pattern pat = Pattern.compile(last);
137                     Iterator i = cur.keys().iterator(); while (i.hasNext()) {
138                         Object o = i.next();
139                         if (o == null || !(o instanceof String)) continue;
140                         String s = (String)o;
141                         if (!pat.matcher(s).matches()) continue;
142                         w.write(s);
143                         w.write("\n");
144                     }
145                 } else if (cur.containsKey(key)) {
146                     w.write(key.toString());
147                     w.write("\n");
148                 }
149             } catch (Shell.BadPathException e) {
150                 w.write("error: no such path: ");
151                 w.write(p);
152                 w.write("\n");
153             } catch (JSExn e) {
154                 w.write("error: no such path: ");
155                 w.write(p);
156                 w.write(" (");
157                 w.write(e.getMessage());
158                 w.write(")\n");
159             }
160         }
161     }
162
163     public static class Replace extends Command {
164         public Replace(Shell s) { super(s); }
165         public String name() { return "replace"; }
166         public String params() { return "[key] [value]"; }
167         public String summary() { return "Sets a key to a specific value."; }
168         public String help() { return
169             "Sets a key to a specific value. This function accepts " +
170             "an xt shell path for the key name (eg. /foo/bar), and a " +
171             "script object for the value (eg. {}, [], \"foo\").\n\n" +
172
173             "If the key does not already exist, it is created, but an " +
174             "error is thrown if the parent object of the key does not " +
175             "exist.\n\n" +
176
177             "WARNING: This function is dangerous. It gives you a " +
178             "quick and easy way to replace your entire data set with "+
179             "an empty object.";
180         }
181         public void execute(Writer w, String[] c) throws IOException {
182             if (c.length < 3) { usage(w); return; }
183             try {
184                 List path = fromShellPath(c[1]);
185                 Object key = path.remove(path.size() - 1);
186                 Object po = shell.getFromPath(path.toArray());
187
188                 if (po == null || !(po instanceof JS))
189                     throw new Shell.BadPathException();
190                 JS parent = (JS)po;
191
192                 String func = "prevalent.";
193                 for (int i=0; i < path.size(); i++)
194                     func += path.get(i) + ".";
195                 func += key + " = ";
196                 for (int i=2; i < c.length; i++)
197                     func += c[i];
198                 func += ";\n";
199
200                 shell.transaction(JS.fromReader(
201                     "replace-transaction", 0, new StringReader(func)));
202
203             } catch (JSExn e) {
204                 w.write("error: cannot replace '");
205                 w.write(c[1]);
206                 w.write("': ");
207                 w.write(e.getMessage());
208                 w.write("\n");
209             } catch (Shell.BadPathException e) {
210                 w.write("error: cannot replace '");
211                 w.write(c[1]);
212                 w.write("': ");
213                 w.write(e.getMessage() == null ? "no such path" : e.getMessage());
214                 w.write("\n");
215             }
216         }
217     }
218
219     public static class Mkdir extends Command {
220         public Mkdir(Shell s) { super(s); }
221         public String name() { return "mkdir"; }
222         public String params() { return "[path]"; }
223         public String summary() { return "Creates a new object ready to handle keys."; }
224         public String help() { return
225             "Creates a new object ready to handle keys. This function " +
226             "is similar to calling replace [path] {}, only it will not " +
227             "overwrite an existing key.";
228         }
229         public void execute(Writer w, String[] c) throws IOException {
230             if (c.length != 2) { usage(w); return; }
231             try {
232                 List path = fromShellPath(c[1]);
233                 Object key = path.remove(path.size() - 1);
234                 Object po = shell.getFromPath(path.toArray());
235
236                 if (po == null || !(po instanceof JS))
237                     throw new Shell.BadPathException();
238
239                 JS parent = (JS)po;
240                 if (parent.containsKey(key))
241                     throw new Shell.BadPathException("already exists");
242
243                 String func = "prevalent.";
244                 for (int i=0; i < path.size(); i++)
245                     func += path.get(i) + ".";
246                 func += key + " = {};\n";
247
248                 shell.transaction(JS.fromReader(
249                     "mkdir-transaction", 0, new StringReader(func)));
250
251             } catch (JSExn e) {
252                 w.write("error: cannot create '");
253                 w.write(c[1]);
254                 w.write("': ");
255                 w.write(e.getMessage());
256                 w.write("\n");
257             } catch (Shell.BadPathException e) {
258                 w.write("error: cannot create '");
259                 w.write(c[1]);
260                 w.write("': ");
261                 w.write(e.getMessage() == null ? "no such path" : e.getMessage());
262                 w.write("\n");
263             }
264         }
265     }
266
267     public static class Rm extends Command {
268         public Rm(Shell s) { super(s); }
269         public String name() { return "rm"; }
270         public String params() { return "[options] [path]"; }
271         public String summary() { return "Removes objects."; }
272         public String help() { return
273             "Removes objects. If any one of the specified paths does " +
274             "not exist, the entire remove process is cancelled."; }
275         public void execute(Writer w, String[] c) throws IOException {
276             if (c.length == 1) { usage(w); return; }
277
278             boolean force = false; // FIXME provide ability to set
279
280             StringBuffer func = new StringBuffer();
281
282             for (int ic=1; ic < c.length; ic++) {
283                 String p = c.length == 1 ? "*" : c[ic];
284                 if (p.endsWith("/")) p += "*";
285
286                 try {
287                     // get the base of the path
288                     List path = fromShellPath(p);
289                     Object key = path.remove(path.size() - 1);
290                     Object po = shell.getFromPath(path.toArray());
291                     if (po == null || !(po instanceof JS))
292                         throw new Shell.BadPathException();
293                     JS cur = (JS)po;
294
295                     if (cur.containsKey(key)) {
296                         Object o = cur.get(key);
297                         if (!force && o != null && o instanceof JS && ((JS)o).keys().size() > 0)
298                             throw new Shell.BadPathException("key is not empty");
299                         func.append("prevalent.");
300                         for(int i=0; i < path.size(); i++) {
301                             func.append(path.get(i)); func.append('.'); }
302                         func.append("Delete(\"");
303                         func.append(key.toString());
304                         func.append("\");\n");
305                     } else if (key instanceof String && ((String)key).indexOf('*') >= 0) {
306                         String last = (String)key;
307                         last = last.replaceAll("\\.", "\\.");
308                         last = last.replaceAll("\\*", ".*");
309                         Pattern pat = Pattern.compile(last);
310                         Collection curkeys = cur.keys();
311                         if (curkeys.size() == 0) throw new Shell.BadPathException();
312                         Iterator it = curkeys.iterator(); while (it.hasNext()) {
313                             Object o = it.next();
314                             if (o == null || !(o instanceof String)) continue;
315                             String s = (String)o;
316                             if (!pat.matcher(s).matches()) continue;
317
318                             func.append("prevalent.");
319                             for(int i=0; i < path.size(); i++) {
320                                 func.append(path.get(i)); func.append('.'); }
321                             func.append("Delete(\"");
322                             func.append(s);
323                             func.append("\");\n");
324                         }
325                     } else throw new Shell.BadPathException();
326
327                 } catch (JSExn e) {
328                     w.write("error: cannot remove '");
329                     w.write(p);
330                     w.write("': ");
331                     w.write(e.getMessage());
332                     w.write("\n");
333                     return;
334                 } catch (Shell.BadPathException e) {
335                     w.write("error: cannot remove '");
336                     w.write(p);
337                     w.write("': ");
338                     w.write(e.getMessage() != null ? e.getMessage() : "no such path");
339                     w.write("\n");
340                     return;
341                 }
342             }
343
344             shell.transaction(JS.fromReader(
345                 "rm-transaction", 0, new StringReader(func.toString())));
346         }
347     }
348
349     public static class Pwd extends Command {
350         public Pwd(Shell s) { super(s); }
351         public String name() { return "pwd"; }
352         public String params() { return ""; }
353         public String summary() { return "Path to current object."; }
354         public String help() { return "Print the path to the current object."; }
355         public void execute(Writer w, String[] c) throws IOException {
356             if (c.length != 1) { usage(w); return; }
357             Object[] path = shell.getPath();
358             for (int i=0; i < path.length; i++) {
359                 w.write("/");
360                 w.write(path[i].toString());
361             }
362             if (path.length == 0) w.write("/");
363             w.write("\n");
364         }
365     }
366
367     public static class Cd extends Command {
368         public Cd(Shell s) { super(s); }
369         public String name() { return "cd"; }
370         public String params() { return "[path]"; }
371         public String summary() { return "Change current object."; }
372         public String help() { return
373             "Chnages the current object that all other commands use "+
374             "as the base for running.\n Pass either a relative path "+
375             "(e.g. in .prevalent, type cd myob, now in .prevalent.myob) "+
376             "or an absolute path (e.g. cd .prevalent.myob).\n\n" +
377             "To go up one level, cd .. can be used.";
378         }
379         public void execute(Writer w, String[] c) throws IOException {
380             if (c.length > 2) { usage(w); return; }
381             String path = c.length == 1 ? "/" : c[1];
382
383             try { shell.setPath(fromShellPath(path).toArray()); }
384             catch (Shell.BadPathException e) {
385                 w.write("error: no such path: ");
386                 w.write(path);
387                 w.write("\n");
388             }
389         }
390     }
391
392 }