package org.ibex.xt.shell;
-import java.util.*;
-
+import java.io.StringReader;
import java.io.Writer;
import java.io.IOException;
+import java.util.*;
+import java.util.regex.*;
+
+import org.ibex.js.*;
+import org.ibex.util.*;
+
public abstract class Command {
+ protected Shell shell;
+
+ protected Command(Shell s) { shell = s; }
+
/** Returns the command name. */
public abstract String name();
- /** Returns single-line of usage information, eg. <tt>pattern]</tt> */
- public abstract String usage();
+ /** Returns a single-line of parameter information, eg. <tt>[pattern]</tt> */
+ public abstract String params();
/** Returns single-line description of command. */
public abstract String summary();
public abstract String help();
/** Writes result of execution, even if result is an error. */
- public abstract void execute(Writer w, String[] args, Env env) throws IOException;
+ public abstract void execute(Writer w, String[] args) throws IOException;
+
+
+ /** Returns single-line of usage information, eg. <tt>usage: ls [pattern]</tt> */
+ public void usage(Writer w) throws IOException {
+ w.write("usage: ");
+ w.write(name());
+ w.write(" ");
+ w.write(params());
+ w.write("\n");
+ }
+
+ public List fromShellPath(String s) throws Shell.BadPathException {
+ return fromShellPath(s, null);
+ }
+
+ /** Converts a shell path "/foo/etc/../bar" to its component form,
+ * { "foo", "bar" } and loads it into the returned list.
+ * Handles relative positioning to shell.getPath(). */
+ public List fromShellPath(String s, List l) throws Shell.BadPathException {
+ if (s == null) return null;
+ if (l == null) l = new ArrayList();
+
+ s = s.trim().replace("/+", "/");
+ if (s.charAt(0) != '/') l.addAll(Arrays.asList(shell.getPath()));
+
+ StringTokenizer st = new StringTokenizer(s, "/");
+ while (st.hasMoreTokens()) {
+ String part = st.nextToken();
+ if (part == null || part.equals("") || part.equals(".")) continue;
+ else if (part.equals("..")) { if (l.size() > 0) l.remove(l.size() - 1); }
+ else l.add(part);
+ }
+
+ return l;
+ }
public static class Help extends Command {
+ public Help(Shell s) { super(s); }
public String name() { return "help"; }
- public String usage() { return "[command name]"; }
+ public String params() { return "[command name]"; }
public String summary() { return "Lists available commands."; }
public String help() { return ""; }
- public void execute(Writer w, String[] c, Env env) throws IOException {
+ public void execute(Writer w, String[] c) throws IOException {
if (c.length > 1) {
- Command cmd = env.command(c[1]);
+ Command cmd = shell.getCommand(c[1]);
if (c == null) {
w.write("help: ");
w.write(c[1]);
w.write(": command not found\n");
} else {
- w.write("usage: ");
- w.write(cmd.name());
- w.write(" ");
- w.write(cmd.usage());
- w.write("\n\n");
+ cmd.usage(w);
+ w.write("\n");
w.write(cmd.help());
w.write("\n");
}
} else {
int len = 3;
- for (int i=0; i < env.commands.length; i++)
- len = Math.max(env.commands[i].name().length(), len);
+ Command[] cmds = shell.getCommands();
+ for (int i=0; i < cmds.length; i++)
+ len = Math.max(cmds[i].name().length(), len);
w.write("Available commands:\n");
- for (int i=0; i < env.commands.length; i++) {
- Command cmd = env.commands[i];
+ for (int i=0; i < cmds.length; i++) {
+ Command cmd = cmds[i];
w.write(" ");
w.write(cmd.name());
for (int j=len - cmd.name().length(); j >= 0; j--) w.write(" ");
}
public static class Ls extends Command {
+ public Ls(Shell s) { super(s); }
public String name() { return "ls"; }
- public String usage() { return "[path]"; }
+ public String params() { return "[path]"; }
public String summary() { return "List object entries."; }
public String help() { return
"Lists the keys in an object. Modelled after the UNIX ls command.";
}
- public void execute(Writer w, String[] c, Env env) throws IOException {
- if (c.length > 2) { w.write(usage()); return; }
- String p = env.path(c.length == 1 ? "*" : c[1]);
+ public void execute(Writer w, String[] c) throws IOException {
+ if (c.length > 2) { usage(w); return; }
+ String p = c.length == 1 ? "*" : c[1];
+ if (p.endsWith("/")) p += "*";
- Request.Response ret = env.send(new Request.Key(p));
- if (!(ret instanceof Request.Key.Res)) {
- w.write("error: ");
- w.write(ret.error().getMessage());
- w.write("\n");
- } else {
- List l = ((Request.Key.Res)ret).keys();
- Iterator i = l.iterator(); while (i.hasNext()) {
- w.write(i.next().toString());
+ try {
+ List path = fromShellPath(p);
+ Object key = path.remove(path.size() - 1);
+ Object po = shell.getFromPath(path.toArray());
+ if (po == null || !(po instanceof JS))
+ throw new Shell.BadPathException();
+ JS cur = (JS)po;
+
+ if (key instanceof String &&
+ ((String)key).indexOf('*') >= 0) {
+ String last = (String)key;
+ last = last.replaceAll("\\.", "\\.");
+ last = last.replaceAll("\\*", ".*");
+ Pattern pat = Pattern.compile(last);
+ Iterator i = cur.keys().iterator(); while (i.hasNext()) {
+ Object o = i.next();
+ if (o == null || !(o instanceof String)) continue;
+ String s = (String)o;
+ if (!pat.matcher(s).matches()) continue;
+ w.write(s);
+ w.write("\n");
+ }
+ } else if (cur.containsKey(key)) {
+ w.write(key.toString());
w.write("\n");
}
+ } catch (Shell.BadPathException e) {
+ w.write("error: no such path: ");
+ w.write(p);
+ w.write("\n");
+ } catch (JSExn e) {
+ w.write("error: no such path: ");
+ w.write(p);
+ w.write(" (");
+ w.write(e.getMessage());
+ w.write(")\n");
}
}
}
- public static class Pwd extends Command {
- public String name() { return "pwd"; }
- public String usage() { return ""; }
- public String summary() { return "Path to current object."; }
- public String help() { return "Print the path to the current object."; }
- public void execute(Writer w, String[] c, Env env) throws IOException {
- if (c.length != 1) { w.write(usage()); return; }
- w.write(env.path.equals("") ? "/" : env.path.replace('.', '/'));
- w.write("\n");
+ public static class Replace extends Command {
+ public Replace(Shell s) { super(s); }
+ public String name() { return "replace"; }
+ public String params() { return "[key] [value]"; }
+ public String summary() { return "Sets a key to a specific value."; }
+ public String help() { return
+ "Sets a key to a specific value. This function accepts " +
+ "an xt shell path for the key name (eg. /foo/bar), and a " +
+ "script object for the value (eg. {}, [], \"foo\").\n\n" +
+
+ "If the key does not already exist, it is created, but an " +
+ "error is thrown if the parent object of the key does not " +
+ "exist.\n\n" +
+
+ "WARNING: This function is dangerous. It gives you a " +
+ "quick and easy way to replace your entire data set with "+
+ "an empty object.";
+ }
+ public void execute(Writer w, String[] c) throws IOException {
+ if (c.length < 3) { usage(w); return; }
+ try {
+ List path = fromShellPath(c[1]);
+ Object key = path.remove(path.size() - 1);
+ Object po = shell.getFromPath(path.toArray());
+
+ if (po == null || !(po instanceof JS))
+ throw new Shell.BadPathException();
+ JS parent = (JS)po;
+
+ String func = "prevalent.";
+ for (int i=0; i < path.size(); i++)
+ func += path.get(i) + ".";
+ func += key + " = ";
+ for (int i=2; i < c.length; i++)
+ func += c[i];
+ func += ";\n";
+
+ shell.transaction(JS.fromReader(
+ "replace-transaction", 0, new StringReader(func)));
+
+ } catch (JSExn e) {
+ w.write("error: cannot replace '");
+ w.write(c[1]);
+ w.write("': ");
+ w.write(e.getMessage());
+ w.write("\n");
+ } catch (Shell.BadPathException e) {
+ w.write("error: cannot replace '");
+ w.write(c[1]);
+ w.write("': ");
+ w.write(e.getMessage() == null ? "no such path" : e.getMessage());
+ w.write("\n");
+ }
}
}
- public static class Cd extends Command {
- public String name() { return "cd"; }
- public String usage() { return "[path]"; }
- public String summary() { return "Change current object."; }
+ public static class Mkdir extends Command {
+ public Mkdir(Shell s) { super(s); }
+ public String name() { return "mkdir"; }
+ public String params() { return "[path]"; }
+ public String summary() { return "Creates a new object ready to handle keys."; }
public String help() { return
- "Chnages the current object that all other commands use "+
- "as the base for running.\n Pass either a relative path "+
- "(e.g. in /prevalent, type cd myob, now in /prevalent/myob) "+
- "or an absolute path (e.g. cd /prevalent/myob).\n\n" +
- "To go up one level, cd .. can be used.";
+ "Creates a new object ready to handle keys. This function " +
+ "is similar to calling replace [path] {}, only it will not " +
+ "overwrite an existing key.";
}
- public void execute(Writer w, String[] c, Env env) throws IOException {
- if (c.length > 2) w.write(usage());
- else if (c.length == 1 || c[1].equals("") || c[1].equals("/")) env.path = "";
- else if (c[1].equals("..")) {
- String n = env.path;
- n = n.substring(0, n.lastIndexOf('.'));
- env.path = n;
- } else {
- String n = env.path(c[1]);
+ public void execute(Writer w, String[] c) throws IOException {
+ if (c.length != 2) { usage(w); return; }
+ try {
+ List path = fromShellPath(c[1]);
+ Object key = path.remove(path.size() - 1);
+ Object po = shell.getFromPath(path.toArray());
- Request.Response ret = env.send(new Request.Key(n));
- if (!(ret instanceof Request.Key.Res)) {
- w.write("error: ");
- w.write(ret.error().getMessage());
- w.write("\n");
- } else {
- List l = ((Request.Key.Res)ret).keys();
- if (l.size() == 0) {
- w.write("error: ");
- w.write(c[1]);
- w.write(": no such path\n");
- } else {
- env.path = n;
- }
- }
+ if (po == null || !(po instanceof JS))
+ throw new Shell.BadPathException();
+
+ JS parent = (JS)po;
+ if (parent.containsKey(key))
+ throw new Shell.BadPathException("already exists");
+
+ String func = "prevalent.";
+ for (int i=0; i < path.size(); i++)
+ func += path.get(i) + ".";
+ func += key + " = {};\n";
+
+ shell.transaction(JS.fromReader(
+ "mkdir-transaction", 0, new StringReader(func)));
+
+ } catch (JSExn e) {
+ w.write("error: cannot create '");
+ w.write(c[1]);
+ w.write("': ");
+ w.write(e.getMessage());
+ w.write("\n");
+ } catch (Shell.BadPathException e) {
+ w.write("error: cannot create '");
+ w.write(c[1]);
+ w.write("': ");
+ w.write(e.getMessage() == null ? "no such path" : e.getMessage());
+ w.write("\n");
}
}
}
public static class Rm extends Command {
+ public Rm(Shell s) { super(s); }
public String name() { return "rm"; }
- public String usage() { return "[options] [path]"; }
+ public String params() { return "[options] [path]"; }
public String summary() { return "Removes objects."; }
- public String help() { return "Removes objects."; } // FIXME
- public void execute(Writer w, String[] c, Env env) throws IOException {
- if (c.length == 1) { w.write(usage()); }
+ public String help() { return
+ "Removes objects. If any one of the specified paths does " +
+ "not exist, the entire remove process is cancelled."; }
+ public void execute(Writer w, String[] c) throws IOException {
+ if (c.length == 1) { usage(w); return; }
- Request.Key[] r = new Request.Key[c.length - 1];
- for (int i=0; i < r.length; i++) r[i] = new Request.RemoveKey(env.path(c[i + 1]));
+ boolean force = false; // FIXME provide ability to set
- Request.Response ret = env.send(new Request.Composite(r, false));
- if (!(ret instanceof Request.Composite.Res)) {
- w.write("error: ");
- w.write(ret.error().getMessage());
- w.write("\n");
- } else {
- Request.Response[] res = ((Request.Composite.Res)ret).responses();
- for (int i=0; i < res.length; i++) {
- if (res[i] instanceof Request.RemoveKey.Res) {
- boolean rm = ((Request.RemoveKey.Res)res[i]).removed();
- if (!rm) {
- w.write("error: cannot remove '");
- w.write(c[i + 1]);
- w.write("': no such key\n");
+ StringBuffer func = new StringBuffer();
+
+ for (int ic=1; ic < c.length; ic++) {
+ String p = c.length == 1 ? "*" : c[ic];
+ if (p.endsWith("/")) p += "*";
+
+ try {
+ // get the base of the path
+ List path = fromShellPath(p);
+ Object key = path.remove(path.size() - 1);
+ Object po = shell.getFromPath(path.toArray());
+ if (po == null || !(po instanceof JS))
+ throw new Shell.BadPathException();
+ JS cur = (JS)po;
+
+ if (cur.containsKey(key)) {
+ Object o = cur.get(key);
+ if (!force && o != null && o instanceof JS && ((JS)o).keys().size() > 0)
+ throw new Shell.BadPathException("key is not empty");
+ func.append("prevalent.");
+ for(int i=0; i < path.size(); i++) {
+ func.append(path.get(i)); func.append('.'); }
+ func.append("Delete(\"");
+ func.append(key.toString());
+ func.append("\");\n");
+ } else if (key instanceof String && ((String)key).indexOf('*') >= 0) {
+ String last = (String)key;
+ last = last.replaceAll("\\.", "\\.");
+ last = last.replaceAll("\\*", ".*");
+ Pattern pat = Pattern.compile(last);
+ Collection curkeys = cur.keys();
+ if (curkeys.size() == 0) throw new Shell.BadPathException();
+ Iterator it = curkeys.iterator(); while (it.hasNext()) {
+ Object o = it.next();
+ if (o == null || !(o instanceof String)) continue;
+ String s = (String)o;
+ if (!pat.matcher(s).matches()) continue;
+
+ func.append("prevalent.");
+ for(int i=0; i < path.size(); i++) {
+ func.append(path.get(i)); func.append('.'); }
+ func.append("Delete(\"");
+ func.append(s);
+ func.append("\");\n");
}
- } else {
- w.write("error: cannot remove '");
- w.write(c[i + 1]);
- w.write("': ");
- w.write(res[i].error().getMessage());
- w.write("\n");
- }
+ } else throw new Shell.BadPathException();
+
+ } catch (JSExn e) {
+ w.write("error: cannot remove '");
+ w.write(p);
+ w.write("': ");
+ w.write(e.getMessage());
+ w.write("\n");
+ return;
+ } catch (Shell.BadPathException e) {
+ w.write("error: cannot remove '");
+ w.write(p);
+ w.write("': ");
+ w.write(e.getMessage() != null ? e.getMessage() : "no such path");
+ w.write("\n");
+ return;
}
}
+ shell.transaction(JS.fromReader(
+ "rm-transaction", 0, new StringReader(func.toString())));
+ }
+ }
- // Object ret = send(new KeyRequest( FIXME: CompositeRequest
+ public static class Pwd extends Command {
+ public Pwd(Shell s) { super(s); }
+ public String name() { return "pwd"; }
+ public String params() { return ""; }
+ public String summary() { return "Path to current object."; }
+ public String help() { return "Print the path to the current object."; }
+ public void execute(Writer w, String[] c) throws IOException {
+ if (c.length != 1) { usage(w); return; }
+ Object[] path = shell.getPath();
+ for (int i=0; i < path.length; i++) {
+ w.write("/");
+ w.write(path[i].toString());
+ }
+ if (path.length == 0) w.write("/");
+ w.write("\n");
}
}
+ public static class Cd extends Command {
+ public Cd(Shell s) { super(s); }
+ public String name() { return "cd"; }
+ public String params() { return "[path]"; }
+ public String summary() { return "Change current object."; }
+ public String help() { return
+ "Chnages the current object that all other commands use "+
+ "as the base for running.\n Pass either a relative path "+
+ "(e.g. in .prevalent, type cd myob, now in .prevalent.myob) "+
+ "or an absolute path (e.g. cd .prevalent.myob).\n\n" +
+ "To go up one level, cd .. can be used.";
+ }
+ public void execute(Writer w, String[] c) throws IOException {
+ if (c.length > 2) { usage(w); return; }
+ String path = c.length == 1 ? "/" : c[1];
+
+ try { shell.setPath(fromShellPath(path).toArray()); }
+ catch (Shell.BadPathException e) {
+ w.write("error: no such path: ");
+ w.write(path);
+ w.write("\n");
+ }
+ }
+ }
}