X-Git-Url: http://git.megacz.com/?a=blobdiff_plain;f=src%2Fjava%2Forg%2Fibex%2Fxt%2Fshell%2FCommand.java;h=a55ded43071fbeb8d608ae66635ce86f66f9fc4e;hb=refs%2Fheads%2Fmaster;hp=7edc5ac65e513558807d53213ae973f67e5c81ca;hpb=e086a6515bf5d5f1f179240f229056d2bb699a99;p=org.ibex.xt-crawshaw.git diff --git a/src/java/org/ibex/xt/shell/Command.java b/src/java/org/ibex/xt/shell/Command.java index 7edc5ac..a55ded4 100644 --- a/src/java/org/ibex/xt/shell/Command.java +++ b/src/java/org/ibex/xt/shell/Command.java @@ -5,15 +5,26 @@ import java.io.Writer; import java.io.IOException; import java.util.*; +import java.util.regex.*; import org.ibex.js.*; +import org.ibex.util.*; +/** Provides implementations for the shell commands. General contract + * is commands named after standard unix tools (eg. rm, mkdir) are + * as safe as possible. Extra functions such as replace have no such + * saftey features. + */ 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. pattern] */ - public abstract String usage(); + /** Returns a single-line of parameter information, eg. [pattern] */ + public abstract String params(); /** Returns single-line description of command. */ public abstract String summary(); @@ -22,38 +33,72 @@ public abstract class Command { 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. usage: ls [pattern] */ + 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(" "); @@ -67,145 +112,285 @@ public abstract class Command { } 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); - 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.IsKey(n)); - if (!(ret instanceof Request.IsKey.Res)) { - w.write("error: "); - w.write(ret.error().getMessage()); - w.write("\n"); - } else { - if (((Request.IsKey.Res)ret).exists()) { - 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()))); } } - public static class Set extends Command { - public String name() { return "set"; } - public String usage() { return "[name] [object]"; } - public String summary() { return "Sets ."; } - public String help() { return "Removes objects."; } // FIXME - - public void execute(Writer w, String[] c, Env env) throws IOException { - if (c.length < 3) { w.write(usage()); } + 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"); + } + } - String m = c[1]; - String s = "return ("; - for (int i=2; i < c.length; i++) s += c[i]; - s += ");"; + 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]; - JS js; - try { js = JS.fromReader("input", 0, new StringReader(s)); } - catch (IOException e) { - w.write("error: "); - w.write(e.getMessage()); + try { shell.setPath(fromShellPath(path).toArray()); } + catch (Shell.BadPathException e) { + w.write("error: no such path: "); + w.write(path); w.write("\n"); - return; } - - Request.Response ret = env.send(new Request.SetKey(env.path, m, js)); } }