1 package org.ibex.xt.shell;
3 import java.io.StringReader;
5 import java.io.IOException;
8 import java.util.regex.*;
11 import org.ibex.util.*;
13 /** Provides implementations for the shell commands. General contract
14 * is commands named after standard unix tools (eg. rm, mkdir) are
15 * as safe as possible. Extra functions such as replace have no such
18 public abstract class Command {
19 protected Shell shell;
21 protected Command(Shell s) { shell = s; }
23 /** Returns the command name. */
24 public abstract String name();
26 /** Returns a single-line of parameter information, eg. <tt>[pattern]</tt> */
27 public abstract String params();
29 /** Returns single-line description of command. */
30 public abstract String summary();
32 /** Returns multi-line description. */
33 public abstract String help();
35 /** Writes result of execution, even if result is an error. */
36 public abstract void execute(Writer w, String[] args) throws IOException;
39 /** Returns single-line of usage information, eg. <tt>usage: ls [pattern]</tt> */
40 public void usage(Writer w) throws IOException {
48 public List fromShellPath(String s) throws Shell.BadPathException {
49 return fromShellPath(s, null);
52 /** Converts a shell path "/foo/etc/../bar" to its component form,
53 * { "foo", "bar" } and loads it into the returned list.
54 * Handles relative positioning to shell.getPath(). */
55 public List fromShellPath(String s, List l) throws Shell.BadPathException {
56 if (s == null) return null;
57 if (l == null) l = new ArrayList();
59 s = s.trim().replace("/+", "/");
60 if (s.charAt(0) != '/') l.addAll(Arrays.asList(shell.getPath()));
62 StringTokenizer st = new StringTokenizer(s, "/");
63 while (st.hasMoreTokens()) {
64 String part = st.nextToken();
65 if (part == null || part.equals("") || part.equals(".")) continue;
66 else if (part.equals("..")) { if (l.size() > 0) l.remove(l.size() - 1); }
73 public static class Help extends Command {
74 public Help(Shell s) { super(s); }
75 public String name() { return "help"; }
76 public String params() { return "[command name]"; }
77 public String summary() { return "Lists available commands."; }
78 public String help() { return ""; }
80 public void execute(Writer w, String[] c) throws IOException {
82 Command cmd = shell.getCommand(c[1]);
86 w.write(": command not found\n");
95 Command[] cmds = shell.getCommands();
96 for (int i=0; i < cmds.length; i++)
97 len = Math.max(cmds[i].name().length(), len);
99 w.write("Available commands:\n");
100 for (int i=0; i < cmds.length; i++) {
101 Command cmd = cmds[i];
104 for (int j=len - cmd.name().length(); j >= 0; j--) w.write(" ");
106 w.write(cmd.summary());
109 w.write("\nFor usage details, type help [command name].\n");
114 public static class Ls extends Command {
115 public Ls(Shell s) { super(s); }
116 public String name() { return "ls"; }
117 public String params() { return "[path]"; }
118 public String summary() { return "List object entries."; }
119 public String help() { return
120 "Lists the keys in an object. Modelled after the UNIX ls command.";
123 public void execute(Writer w, String[] c) throws IOException {
124 if (c.length > 2) { usage(w); return; }
125 String p = c.length == 1 ? "*" : c[1];
126 if (p.endsWith("/")) p += "*";
129 List path = fromShellPath(p);
130 Object key = path.remove(path.size() - 1);
131 Object po = shell.getFromPath(path.toArray());
132 if (po == null || !(po instanceof JS))
133 throw new Shell.BadPathException();
136 if (key instanceof String &&
137 ((String)key).indexOf('*') >= 0) {
138 String last = (String)key;
139 last = last.replaceAll("\\.", "\\.");
140 last = last.replaceAll("\\*", ".*");
141 Pattern pat = Pattern.compile(last);
142 Iterator i = cur.keys().iterator(); while (i.hasNext()) {
144 if (o == null || !(o instanceof String)) continue;
145 String s = (String)o;
146 if (!pat.matcher(s).matches()) continue;
150 } else if (cur.containsKey(key)) {
151 w.write(key.toString());
154 } catch (Shell.BadPathException e) {
155 w.write("error: no such path: ");
159 w.write("error: no such path: ");
162 w.write(e.getMessage());
168 public static class Replace extends Command {
169 public Replace(Shell s) { super(s); }
170 public String name() { return "replace"; }
171 public String params() { return "[key] [value]"; }
172 public String summary() { return "Sets a key to a specific value."; }
173 public String help() { return
174 "Sets a key to a specific value. This function accepts " +
175 "an xt shell path for the key name (eg. /foo/bar), and a " +
176 "script object for the value (eg. {}, [], \"foo\").\n\n" +
178 "If the key does not already exist, it is created, but an " +
179 "error is thrown if the parent object of the key does not " +
182 "WARNING: This function is dangerous. It gives you a " +
183 "quick and easy way to replace your entire data set with "+
186 public void execute(Writer w, String[] c) throws IOException {
187 if (c.length < 3) { usage(w); return; }
189 List path = fromShellPath(c[1]);
190 Object key = path.remove(path.size() - 1);
191 Object po = shell.getFromPath(path.toArray());
193 if (po == null || !(po instanceof JS))
194 throw new Shell.BadPathException();
197 String func = "prevalent.";
198 for (int i=0; i < path.size(); i++)
199 func += path.get(i) + ".";
201 for (int i=2; i < c.length; i++)
205 shell.transaction(JS.fromReader(
206 "replace-transaction", 0, new StringReader(func)));
209 w.write("error: cannot replace '");
212 w.write(e.getMessage());
214 } catch (Shell.BadPathException e) {
215 w.write("error: cannot replace '");
218 w.write(e.getMessage() == null ? "no such path" : e.getMessage());
224 public static class Mkdir extends Command {
225 public Mkdir(Shell s) { super(s); }
226 public String name() { return "mkdir"; }
227 public String params() { return "[path]"; }
228 public String summary() { return "Creates a new object ready to handle keys."; }
229 public String help() { return
230 "Creates a new object ready to handle keys. This function " +
231 "is similar to calling replace [path] {}, only it will not " +
232 "overwrite an existing key.";
234 public void execute(Writer w, String[] c) throws IOException {
235 if (c.length != 2) { usage(w); return; }
237 List path = fromShellPath(c[1]);
238 Object key = path.remove(path.size() - 1);
239 Object po = shell.getFromPath(path.toArray());
241 if (po == null || !(po instanceof JS))
242 throw new Shell.BadPathException();
245 if (parent.containsKey(key))
246 throw new Shell.BadPathException("already exists");
248 String func = "prevalent.";
249 for (int i=0; i < path.size(); i++)
250 func += path.get(i) + ".";
251 func += key + " = {};\n";
253 shell.transaction(JS.fromReader(
254 "mkdir-transaction", 0, new StringReader(func)));
257 w.write("error: cannot create '");
260 w.write(e.getMessage());
262 } catch (Shell.BadPathException e) {
263 w.write("error: cannot create '");
266 w.write(e.getMessage() == null ? "no such path" : e.getMessage());
272 public static class Rm extends Command {
273 public Rm(Shell s) { super(s); }
274 public String name() { return "rm"; }
275 public String params() { return "[options] [path]"; }
276 public String summary() { return "Removes objects."; }
277 public String help() { return
278 "Removes objects. If any one of the specified paths does " +
279 "not exist, the entire remove process is cancelled."; }
280 public void execute(Writer w, String[] c) throws IOException {
281 if (c.length == 1) { usage(w); return; }
283 boolean force = false; // FIXME provide ability to set
285 StringBuffer func = new StringBuffer();
287 for (int ic=1; ic < c.length; ic++) {
288 String p = c.length == 1 ? "*" : c[ic];
289 if (p.endsWith("/")) p += "*";
292 // get the base of the path
293 List path = fromShellPath(p);
294 Object key = path.remove(path.size() - 1);
295 Object po = shell.getFromPath(path.toArray());
296 if (po == null || !(po instanceof JS))
297 throw new Shell.BadPathException();
300 if (cur.containsKey(key)) {
301 Object o = cur.get(key);
302 if (!force && o != null && o instanceof JS && ((JS)o).keys().size() > 0)
303 throw new Shell.BadPathException("key is not empty");
304 func.append("prevalent.");
305 for(int i=0; i < path.size(); i++) {
306 func.append(path.get(i)); func.append('.'); }
307 func.append("Delete(\"");
308 func.append(key.toString());
309 func.append("\");\n");
310 } else if (key instanceof String && ((String)key).indexOf('*') >= 0) {
311 String last = (String)key;
312 last = last.replaceAll("\\.", "\\.");
313 last = last.replaceAll("\\*", ".*");
314 Pattern pat = Pattern.compile(last);
315 Collection curkeys = cur.keys();
316 if (curkeys.size() == 0) throw new Shell.BadPathException();
317 Iterator it = curkeys.iterator(); while (it.hasNext()) {
318 Object o = it.next();
319 if (o == null || !(o instanceof String)) continue;
320 String s = (String)o;
321 if (!pat.matcher(s).matches()) continue;
323 func.append("prevalent.");
324 for(int i=0; i < path.size(); i++) {
325 func.append(path.get(i)); func.append('.'); }
326 func.append("Delete(\"");
328 func.append("\");\n");
330 } else throw new Shell.BadPathException();
333 w.write("error: cannot remove '");
336 w.write(e.getMessage());
339 } catch (Shell.BadPathException e) {
340 w.write("error: cannot remove '");
343 w.write(e.getMessage() != null ? e.getMessage() : "no such path");
349 shell.transaction(JS.fromReader(
350 "rm-transaction", 0, new StringReader(func.toString())));
354 public static class Pwd extends Command {
355 public Pwd(Shell s) { super(s); }
356 public String name() { return "pwd"; }
357 public String params() { return ""; }
358 public String summary() { return "Path to current object."; }
359 public String help() { return "Print the path to the current object."; }
360 public void execute(Writer w, String[] c) throws IOException {
361 if (c.length != 1) { usage(w); return; }
362 Object[] path = shell.getPath();
363 for (int i=0; i < path.length; i++) {
365 w.write(path[i].toString());
367 if (path.length == 0) w.write("/");
372 public static class Cd extends Command {
373 public Cd(Shell s) { super(s); }
374 public String name() { return "cd"; }
375 public String params() { return "[path]"; }
376 public String summary() { return "Change current object."; }
377 public String help() { return
378 "Chnages the current object that all other commands use "+
379 "as the base for running.\n Pass either a relative path "+
380 "(e.g. in .prevalent, type cd myob, now in .prevalent.myob) "+
381 "or an absolute path (e.g. cd .prevalent.myob).\n\n" +
382 "To go up one level, cd .. can be used.";
384 public void execute(Writer w, String[] c) throws IOException {
385 if (c.length > 2) { usage(w); return; }
386 String path = c.length == 1 ? "/" : c[1];
388 try { shell.setPath(fromShellPath(path).toArray()); }
389 catch (Shell.BadPathException e) {
390 w.write("error: no such path: ");