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