04ca7b1d4512fbcca2e39bdb919d01111a0bc33e
[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 Rm extends Command {
164         public Rm(Shell s) { super(s); }
165         public String name() { return "rm"; }
166         public String params() { return "[options] [path]"; }
167         public String summary() { return "Removes objects."; }
168         public String help() { return "Removes objects."; } // FIXME info
169         public void execute(Writer w, String[] c) throws IOException {
170             if (c.length == 1) { usage(w); return; }
171
172             boolean force = false; // FIXME provide ability to set
173
174             StringBuffer func = new StringBuffer();
175
176             for (int ic=1; ic < c.length; ic++) {
177                 String p = c.length == 1 ? "*" : c[ic];
178                 if (p.endsWith("/")) p += "*";
179
180                 try {
181                     // get the base of the path
182                     List path = fromShellPath(p);
183                     Object key = path.remove(path.size() - 1);
184                     Object po = shell.getFromPath(path.toArray());
185                     if (po == null || !(po instanceof JS))
186                         throw new Shell.BadPathException();
187                     JS cur = (JS)po;
188
189                     if (cur.containsKey(key)) {
190                         Object o = cur.get(key);
191                         if (!force && o != null && o instanceof JS && ((JS)o).keys().size() > 0)
192                             throw new Shell.BadPathException("key is not empty");
193                         func.append("prevalent.");
194                         for(int i=0; i < path.size(); i++) {
195                             func.append(path.get(i)); func.append('.'); }
196                         func.append("Delete(\"");
197                         func.append(key.toString());
198                         func.append("\");\n");
199                     } else if (key instanceof String && ((String)key).indexOf('*') >= 0) {
200                         String last = (String)key;
201                         last = last.replaceAll("\\.", "\\.");
202                         last = last.replaceAll("\\*", ".*");
203                         Pattern pat = Pattern.compile(last);
204                         Collection curkeys = cur.keys();
205                         if (curkeys.size() == 0) throw new Shell.BadPathException();
206                         Iterator it = curkeys.iterator(); while (it.hasNext()) {
207                             Object o = it.next();
208                             if (o == null || !(o instanceof String)) continue;
209                             String s = (String)o;
210                             if (!pat.matcher(s).matches()) continue;
211
212                             func.append("prevalent.");
213                             for(int i=0; i < path.size(); i++) {
214                                 func.append(path.get(i)); func.append('.'); }
215                             func.append("Delete(\"");
216                             func.append(s);
217                             func.append("\");\n");
218                         }
219                     } else throw new Shell.BadPathException();
220
221                 } catch (JSExn e) {
222                     w.write("error: cannot remove '");
223                     w.write(p);
224                     w.write("': ");
225                     w.write(e.getMessage());
226                     w.write("\n");
227                     return;
228                 } catch (Shell.BadPathException e) {
229                     w.write("error: cannot remove '");
230                     w.write(p);
231                     w.write("': ");
232                     w.write(e.getMessage() != null ? e.getMessage() : "no such path");
233                     w.write("\n");
234                     return;
235                 }
236             }
237
238             shell.transaction(JS.fromReader(
239                 "rm-transaction", 0, new StringReader(func.toString())));
240         }
241     }
242
243     public static class Pwd extends Command {
244         public Pwd(Shell s) { super(s); }
245         public String name() { return "pwd"; }
246         public String params() { return ""; }
247         public String summary() { return "Path to current object."; }
248         public String help() { return "Print the path to the current object."; }
249         public void execute(Writer w, String[] c) throws IOException {
250             if (c.length != 1) { usage(w); return; }
251             Object[] path = shell.getPath();
252             for (int i=0; i < path.length; i++) {
253                 w.write("/");
254                 w.write(path[i].toString());
255             }
256             if (path.length == 0) w.write("/");
257             w.write("\n");
258         }
259     }
260
261     public static class Cd extends Command {
262         public Cd(Shell s) { super(s); }
263         public String name() { return "cd"; }
264         public String params() { return "[path]"; }
265         public String summary() { return "Change current object."; }
266         public String help() { return
267             "Chnages the current object that all other commands use "+
268             "as the base for running.\n Pass either a relative path "+
269             "(e.g. in .prevalent, type cd myob, now in .prevalent.myob) "+
270             "or an absolute path (e.g. cd .prevalent.myob).\n\n" +
271             "To go up one level, cd .. can be used.";
272         }
273         public void execute(Writer w, String[] c) throws IOException {
274             if (c.length > 2) { usage(w); return; }
275             String path = c.length == 1 ? "/" : c[1];
276
277             try { shell.setPath(fromShellPath(path).toArray()); }
278             catch (Shell.BadPathException e) {
279                 w.write("error: no such path: ");
280                 w.write(path);
281                 w.write("\n");
282             }
283         }
284     }
285
286 }