|
1 """A generic class to build line-oriented command interpreters. |
|
2 |
|
3 Interpreters constructed with this class obey the following conventions: |
|
4 |
|
5 1. End of file on input is processed as the command 'EOF'. |
|
6 2. A command is parsed out of each line by collecting the prefix composed |
|
7 of characters in the identchars member. |
|
8 3. A command `foo' is dispatched to a method 'do_foo()'; the do_ method |
|
9 is passed a single argument consisting of the remainder of the line. |
|
10 4. Typing an empty line repeats the last command. (Actually, it calls the |
|
11 method `emptyline', which may be overridden in a subclass.) |
|
12 5. There is a predefined `help' method. Given an argument `topic', it |
|
13 calls the command `help_topic'. With no arguments, it lists all topics |
|
14 with defined help_ functions, broken into up to three topics; documented |
|
15 commands, miscellaneous help topics, and undocumented commands. |
|
16 6. The command '?' is a synonym for `help'. The command '!' is a synonym |
|
17 for `shell', if a do_shell method exists. |
|
18 7. If completion is enabled, completing commands will be done automatically, |
|
19 and completing of commands args is done by calling complete_foo() with |
|
20 arguments text, line, begidx, endidx. text is string we are matching |
|
21 against, all returned matches must begin with it. line is the current |
|
22 input line (lstripped), begidx and endidx are the beginning and end |
|
23 indexes of the text being matched, which could be used to provide |
|
24 different completion depending upon which position the argument is in. |
|
25 |
|
26 The `default' method may be overridden to intercept commands for which there |
|
27 is no do_ method. |
|
28 |
|
29 The `completedefault' method may be overridden to intercept completions for |
|
30 commands that have no complete_ method. |
|
31 |
|
32 The data member `self.ruler' sets the character used to draw separator lines |
|
33 in the help messages. If empty, no ruler line is drawn. It defaults to "=". |
|
34 |
|
35 If the value of `self.intro' is nonempty when the cmdloop method is called, |
|
36 it is printed out on interpreter startup. This value may be overridden |
|
37 via an optional argument to the cmdloop() method. |
|
38 |
|
39 The data members `self.doc_header', `self.misc_header', and |
|
40 `self.undoc_header' set the headers used for the help function's |
|
41 listings of documented functions, miscellaneous topics, and undocumented |
|
42 functions respectively. |
|
43 |
|
44 These interpreters use raw_input; thus, if the readline module is loaded, |
|
45 they automatically support Emacs-like command history and editing features. |
|
46 """ |
|
47 |
|
48 import string |
|
49 |
|
50 __all__ = ["Cmd"] |
|
51 |
|
52 PROMPT = '(Cmd) ' |
|
53 IDENTCHARS = string.ascii_letters + string.digits + '_' |
|
54 |
|
55 class Cmd: |
|
56 """A simple framework for writing line-oriented command interpreters. |
|
57 |
|
58 These are often useful for test harnesses, administrative tools, and |
|
59 prototypes that will later be wrapped in a more sophisticated interface. |
|
60 |
|
61 A Cmd instance or subclass instance is a line-oriented interpreter |
|
62 framework. There is no good reason to instantiate Cmd itself; rather, |
|
63 it's useful as a superclass of an interpreter class you define yourself |
|
64 in order to inherit Cmd's methods and encapsulate action methods. |
|
65 |
|
66 """ |
|
67 prompt = PROMPT |
|
68 identchars = IDENTCHARS |
|
69 ruler = '=' |
|
70 lastcmd = '' |
|
71 intro = None |
|
72 doc_leader = "" |
|
73 doc_header = "Documented commands (type help <topic>):" |
|
74 misc_header = "Miscellaneous help topics:" |
|
75 undoc_header = "Undocumented commands:" |
|
76 nohelp = "*** No help on %s" |
|
77 use_rawinput = 1 |
|
78 |
|
79 def __init__(self, completekey='tab', stdin=None, stdout=None): |
|
80 """Instantiate a line-oriented interpreter framework. |
|
81 |
|
82 The optional argument 'completekey' is the readline name of a |
|
83 completion key; it defaults to the Tab key. If completekey is |
|
84 not None and the readline module is available, command completion |
|
85 is done automatically. The optional arguments stdin and stdout |
|
86 specify alternate input and output file objects; if not specified, |
|
87 sys.stdin and sys.stdout are used. |
|
88 |
|
89 """ |
|
90 import sys |
|
91 if stdin is not None: |
|
92 self.stdin = stdin |
|
93 else: |
|
94 self.stdin = sys.stdin |
|
95 if stdout is not None: |
|
96 self.stdout = stdout |
|
97 else: |
|
98 self.stdout = sys.stdout |
|
99 self.cmdqueue = [] |
|
100 self.completekey = completekey |
|
101 |
|
102 def cmdloop(self, intro=None): |
|
103 """Repeatedly issue a prompt, accept input, parse an initial prefix |
|
104 off the received input, and dispatch to action methods, passing them |
|
105 the remainder of the line as argument. |
|
106 |
|
107 """ |
|
108 |
|
109 self.preloop() |
|
110 if self.use_rawinput and self.completekey: |
|
111 try: |
|
112 import readline |
|
113 self.old_completer = readline.get_completer() |
|
114 readline.set_completer(self.complete) |
|
115 readline.parse_and_bind(self.completekey+": complete") |
|
116 except ImportError: |
|
117 pass |
|
118 try: |
|
119 if intro is not None: |
|
120 self.intro = intro |
|
121 if self.intro: |
|
122 self.stdout.write(str(self.intro)+"\n") |
|
123 stop = None |
|
124 while not stop: |
|
125 if self.cmdqueue: |
|
126 line = self.cmdqueue.pop(0) |
|
127 else: |
|
128 if self.use_rawinput: |
|
129 try: |
|
130 line = raw_input(self.prompt) |
|
131 except EOFError: |
|
132 line = 'EOF' |
|
133 else: |
|
134 self.stdout.write(self.prompt) |
|
135 self.stdout.flush() |
|
136 line = self.stdin.readline() |
|
137 if not len(line): |
|
138 line = 'EOF' |
|
139 else: |
|
140 line = line[:-1] # chop \n |
|
141 line = self.precmd(line) |
|
142 stop = self.onecmd(line) |
|
143 stop = self.postcmd(stop, line) |
|
144 self.postloop() |
|
145 finally: |
|
146 if self.use_rawinput and self.completekey: |
|
147 try: |
|
148 import readline |
|
149 readline.set_completer(self.old_completer) |
|
150 except ImportError: |
|
151 pass |
|
152 |
|
153 |
|
154 def precmd(self, line): |
|
155 """Hook method executed just before the command line is |
|
156 interpreted, but after the input prompt is generated and issued. |
|
157 |
|
158 """ |
|
159 return line |
|
160 |
|
161 def postcmd(self, stop, line): |
|
162 """Hook method executed just after a command dispatch is finished.""" |
|
163 return stop |
|
164 |
|
165 def preloop(self): |
|
166 """Hook method executed once when the cmdloop() method is called.""" |
|
167 pass |
|
168 |
|
169 def postloop(self): |
|
170 """Hook method executed once when the cmdloop() method is about to |
|
171 return. |
|
172 |
|
173 """ |
|
174 pass |
|
175 |
|
176 def parseline(self, line): |
|
177 """Parse the line into a command name and a string containing |
|
178 the arguments. Returns a tuple containing (command, args, line). |
|
179 'command' and 'args' may be None if the line couldn't be parsed. |
|
180 """ |
|
181 line = line.strip() |
|
182 if not line: |
|
183 return None, None, line |
|
184 elif line[0] == '?': |
|
185 line = 'help ' + line[1:] |
|
186 elif line[0] == '!': |
|
187 if hasattr(self, 'do_shell'): |
|
188 line = 'shell ' + line[1:] |
|
189 else: |
|
190 return None, None, line |
|
191 i, n = 0, len(line) |
|
192 while i < n and line[i] in self.identchars: i = i+1 |
|
193 cmd, arg = line[:i], line[i:].strip() |
|
194 return cmd, arg, line |
|
195 |
|
196 def onecmd(self, line): |
|
197 """Interpret the argument as though it had been typed in response |
|
198 to the prompt. |
|
199 |
|
200 This may be overridden, but should not normally need to be; |
|
201 see the precmd() and postcmd() methods for useful execution hooks. |
|
202 The return value is a flag indicating whether interpretation of |
|
203 commands by the interpreter should stop. |
|
204 |
|
205 """ |
|
206 cmd, arg, line = self.parseline(line) |
|
207 if not line: |
|
208 return self.emptyline() |
|
209 if cmd is None: |
|
210 return self.default(line) |
|
211 self.lastcmd = line |
|
212 if cmd == '': |
|
213 return self.default(line) |
|
214 else: |
|
215 try: |
|
216 func = getattr(self, 'do_' + cmd) |
|
217 except AttributeError: |
|
218 return self.default(line) |
|
219 return func(arg) |
|
220 |
|
221 def emptyline(self): |
|
222 """Called when an empty line is entered in response to the prompt. |
|
223 |
|
224 If this method is not overridden, it repeats the last nonempty |
|
225 command entered. |
|
226 |
|
227 """ |
|
228 if self.lastcmd: |
|
229 return self.onecmd(self.lastcmd) |
|
230 |
|
231 def default(self, line): |
|
232 """Called on an input line when the command prefix is not recognized. |
|
233 |
|
234 If this method is not overridden, it prints an error message and |
|
235 returns. |
|
236 |
|
237 """ |
|
238 self.stdout.write('*** Unknown syntax: %s\n'%line) |
|
239 |
|
240 def completedefault(self, *ignored): |
|
241 """Method called to complete an input line when no command-specific |
|
242 complete_*() method is available. |
|
243 |
|
244 By default, it returns an empty list. |
|
245 |
|
246 """ |
|
247 return [] |
|
248 |
|
249 def completenames(self, text, *ignored): |
|
250 dotext = 'do_'+text |
|
251 return [a[3:] for a in self.get_names() if a.startswith(dotext)] |
|
252 |
|
253 def complete(self, text, state): |
|
254 """Return the next possible completion for 'text'. |
|
255 |
|
256 If a command has not been entered, then complete against command list. |
|
257 Otherwise try to call complete_<command> to get list of completions. |
|
258 """ |
|
259 if state == 0: |
|
260 import readline |
|
261 origline = readline.get_line_buffer() |
|
262 line = origline.lstrip() |
|
263 stripped = len(origline) - len(line) |
|
264 begidx = readline.get_begidx() - stripped |
|
265 endidx = readline.get_endidx() - stripped |
|
266 if begidx>0: |
|
267 cmd, args, foo = self.parseline(line) |
|
268 if cmd == '': |
|
269 compfunc = self.completedefault |
|
270 else: |
|
271 try: |
|
272 compfunc = getattr(self, 'complete_' + cmd) |
|
273 except AttributeError: |
|
274 compfunc = self.completedefault |
|
275 else: |
|
276 compfunc = self.completenames |
|
277 self.completion_matches = compfunc(text, line, begidx, endidx) |
|
278 try: |
|
279 return self.completion_matches[state] |
|
280 except IndexError: |
|
281 return None |
|
282 |
|
283 def get_names(self): |
|
284 # Inheritance says we have to look in class and |
|
285 # base classes; order is not important. |
|
286 names = [] |
|
287 classes = [self.__class__] |
|
288 while classes: |
|
289 aclass = classes.pop(0) |
|
290 if aclass.__bases__: |
|
291 classes = classes + list(aclass.__bases__) |
|
292 names = names + dir(aclass) |
|
293 return names |
|
294 |
|
295 def complete_help(self, *args): |
|
296 return self.completenames(*args) |
|
297 |
|
298 def do_help(self, arg): |
|
299 if arg: |
|
300 # XXX check arg syntax |
|
301 try: |
|
302 func = getattr(self, 'help_' + arg) |
|
303 except AttributeError: |
|
304 try: |
|
305 doc=getattr(self, 'do_' + arg).__doc__ |
|
306 if doc: |
|
307 self.stdout.write("%s\n"%str(doc)) |
|
308 return |
|
309 except AttributeError: |
|
310 pass |
|
311 self.stdout.write("%s\n"%str(self.nohelp % (arg,))) |
|
312 return |
|
313 func() |
|
314 else: |
|
315 names = self.get_names() |
|
316 cmds_doc = [] |
|
317 cmds_undoc = [] |
|
318 help = {} |
|
319 for name in names: |
|
320 if name[:5] == 'help_': |
|
321 help[name[5:]]=1 |
|
322 names.sort() |
|
323 # There can be duplicates if routines overridden |
|
324 prevname = '' |
|
325 for name in names: |
|
326 if name[:3] == 'do_': |
|
327 if name == prevname: |
|
328 continue |
|
329 prevname = name |
|
330 cmd=name[3:] |
|
331 if cmd in help: |
|
332 cmds_doc.append(cmd) |
|
333 del help[cmd] |
|
334 elif getattr(self, name).__doc__: |
|
335 cmds_doc.append(cmd) |
|
336 else: |
|
337 cmds_undoc.append(cmd) |
|
338 self.stdout.write("%s\n"%str(self.doc_leader)) |
|
339 self.print_topics(self.doc_header, cmds_doc, 15,80) |
|
340 self.print_topics(self.misc_header, help.keys(),15,80) |
|
341 self.print_topics(self.undoc_header, cmds_undoc, 15,80) |
|
342 |
|
343 def print_topics(self, header, cmds, cmdlen, maxcol): |
|
344 if cmds: |
|
345 self.stdout.write("%s\n"%str(header)) |
|
346 if self.ruler: |
|
347 self.stdout.write("%s\n"%str(self.ruler * len(header))) |
|
348 self.columnize(cmds, maxcol-1) |
|
349 self.stdout.write("\n") |
|
350 |
|
351 def columnize(self, list, displaywidth=80): |
|
352 """Display a list of strings as a compact set of columns. |
|
353 |
|
354 Each column is only as wide as necessary. |
|
355 Columns are separated by two spaces (one was not legible enough). |
|
356 """ |
|
357 if not list: |
|
358 self.stdout.write("<empty>\n") |
|
359 return |
|
360 nonstrings = [i for i in range(len(list)) |
|
361 if not isinstance(list[i], str)] |
|
362 if nonstrings: |
|
363 raise TypeError, ("list[i] not a string for i in %s" % |
|
364 ", ".join(map(str, nonstrings))) |
|
365 size = len(list) |
|
366 if size == 1: |
|
367 self.stdout.write('%s\n'%str(list[0])) |
|
368 return |
|
369 # Try every row count from 1 upwards |
|
370 for nrows in range(1, len(list)): |
|
371 ncols = (size+nrows-1) // nrows |
|
372 colwidths = [] |
|
373 totwidth = -2 |
|
374 for col in range(ncols): |
|
375 colwidth = 0 |
|
376 for row in range(nrows): |
|
377 i = row + nrows*col |
|
378 if i >= size: |
|
379 break |
|
380 x = list[i] |
|
381 colwidth = max(colwidth, len(x)) |
|
382 colwidths.append(colwidth) |
|
383 totwidth += colwidth + 2 |
|
384 if totwidth > displaywidth: |
|
385 break |
|
386 if totwidth <= displaywidth: |
|
387 break |
|
388 else: |
|
389 nrows = len(list) |
|
390 ncols = 1 |
|
391 colwidths = [0] |
|
392 for row in range(nrows): |
|
393 texts = [] |
|
394 for col in range(ncols): |
|
395 i = row + nrows*col |
|
396 if i >= size: |
|
397 x = "" |
|
398 else: |
|
399 x = list[i] |
|
400 texts.append(x) |
|
401 while texts and not texts[-1]: |
|
402 del texts[-1] |
|
403 for col in range(len(texts)): |
|
404 texts[col] = texts[col].ljust(colwidths[col]) |
|
405 self.stdout.write("%s\n"%str(" ".join(texts))) |