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