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 public abstract class Command {
14 protected Shell shell;
16 protected Command(Shell s) { shell = s; }
18 /** Returns the command name. */
19 public abstract String name();
21 /** Returns a single-line of parameter information, eg. <tt>[pattern]</tt> */
22 public abstract String params();
24 /** Returns single-line description of command. */
25 public abstract String summary();
27 /** Returns multi-line description. */
28 public abstract String help();
30 /** Writes result of execution, even if result is an error. */
31 public abstract void execute(Writer w, String[] args) throws IOException;
34 /** Returns single-line of usage information, eg. <tt>usage: ls [pattern]</tt> */
35 public void usage(Writer w) throws IOException {
43 public List fromShellPath(String s) throws Shell.BadPathException {
44 return fromShellPath(s, null);
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();
54 s = s.trim().replace("/+", "/");
55 if (s.charAt(0) != '/') l.addAll(Arrays.asList(shell.getPath()));
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); }
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 ""; }
75 public void execute(Writer w, String[] c) throws IOException {
77 Command cmd = shell.getCommand(c[1]);
81 w.write(": command not found\n");
90 Command[] cmds = shell.getCommands();
91 for (int i=0; i < cmds.length; i++)
92 len = Math.max(cmds[i].name().length(), len);
94 w.write("Available commands:\n");
95 for (int i=0; i < cmds.length; i++) {
96 Command cmd = cmds[i];
99 for (int j=len - cmd.name().length(); j >= 0; j--) w.write(" ");
101 w.write(cmd.summary());
104 w.write("\nFor usage details, type help [command name].\n");
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.";
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 += "*";
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();
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()) {
139 if (o == null || !(o instanceof String)) continue;
140 String s = (String)o;
141 if (!pat.matcher(s).matches()) continue;
145 } else if (cur.containsKey(key)) {
146 w.write(key.toString());
149 } catch (Shell.BadPathException e) {
150 w.write("error: no such path: ");
154 w.write("error: no such path: ");
157 w.write(e.getMessage());
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" +
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 " +
177 "WARNING: This function is dangerous. It gives you a " +
178 "quick and easy way to replace your entire data set with "+
181 public void execute(Writer w, String[] c) throws IOException {
182 if (c.length < 3) { usage(w); return; }
184 List path = fromShellPath(c[1]);
185 Object key = path.remove(path.size() - 1);
186 Object po = shell.getFromPath(path.toArray());
188 if (po == null || !(po instanceof JS))
189 throw new Shell.BadPathException();
192 String func = "prevalent.";
193 for (int i=0; i < path.size(); i++)
194 func += path.get(i) + ".";
196 for (int i=2; i < c.length; i++)
200 shell.transaction(JS.fromReader(
201 "replace-transaction", 0, new StringReader(func)));
204 w.write("error: cannot replace '");
207 w.write(e.getMessage());
209 } catch (Shell.BadPathException e) {
210 w.write("error: cannot replace '");
213 w.write(e.getMessage() == null ? "no such path" : e.getMessage());
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.";
229 public void execute(Writer w, String[] c) throws IOException {
230 if (c.length != 2) { usage(w); return; }
232 List path = fromShellPath(c[1]);
233 Object key = path.remove(path.size() - 1);
234 Object po = shell.getFromPath(path.toArray());
236 if (po == null || !(po instanceof JS))
237 throw new Shell.BadPathException();
240 if (parent.containsKey(key))
241 throw new Shell.BadPathException("already exists");
243 String func = "prevalent.";
244 for (int i=0; i < path.size(); i++)
245 func += path.get(i) + ".";
246 func += key + " = {};\n";
248 shell.transaction(JS.fromReader(
249 "mkdir-transaction", 0, new StringReader(func)));
252 w.write("error: cannot create '");
255 w.write(e.getMessage());
257 } catch (Shell.BadPathException e) {
258 w.write("error: cannot create '");
261 w.write(e.getMessage() == null ? "no such path" : e.getMessage());
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; }
278 boolean force = false; // FIXME provide ability to set
280 StringBuffer func = new StringBuffer();
282 for (int ic=1; ic < c.length; ic++) {
283 String p = c.length == 1 ? "*" : c[ic];
284 if (p.endsWith("/")) p += "*";
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();
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;
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(\"");
323 func.append("\");\n");
325 } else throw new Shell.BadPathException();
328 w.write("error: cannot remove '");
331 w.write(e.getMessage());
334 } catch (Shell.BadPathException e) {
335 w.write("error: cannot remove '");
338 w.write(e.getMessage() != null ? e.getMessage() : "no such path");
344 shell.transaction(JS.fromReader(
345 "rm-transaction", 0, new StringReader(func.toString())));
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++) {
360 w.write(path[i].toString());
362 if (path.length == 0) w.write("/");
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.";
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];
383 try { shell.setPath(fromShellPath(path).toArray()); }
384 catch (Shell.BadPathException e) {
385 w.write("error: no such path: ");