+++ /dev/null
-"""A generic class to build line-oriented command interpreters.\r
-\r
-Interpreters constructed with this class obey the following conventions:\r
-\r
-1. End of file on input is processed as the command 'EOF'.\r
-2. A command is parsed out of each line by collecting the prefix composed\r
- of characters in the identchars member.\r
-3. A command `foo' is dispatched to a method 'do_foo()'; the do_ method\r
- is passed a single argument consisting of the remainder of the line.\r
-4. Typing an empty line repeats the last command. (Actually, it calls the\r
- method `emptyline', which may be overridden in a subclass.)\r
-5. There is a predefined `help' method. Given an argument `topic', it\r
- calls the command `help_topic'. With no arguments, it lists all topics\r
- with defined help_ functions, broken into up to three topics; documented\r
- commands, miscellaneous help topics, and undocumented commands.\r
-6. The command '?' is a synonym for `help'. The command '!' is a synonym\r
- for `shell', if a do_shell method exists.\r
-7. If completion is enabled, completing commands will be done automatically,\r
- and completing of commands args is done by calling complete_foo() with\r
- arguments text, line, begidx, endidx. text is string we are matching\r
- against, all returned matches must begin with it. line is the current\r
- input line (lstripped), begidx and endidx are the beginning and end\r
- indexes of the text being matched, which could be used to provide\r
- different completion depending upon which position the argument is in.\r
-\r
-The `default' method may be overridden to intercept commands for which there\r
-is no do_ method.\r
-\r
-The `completedefault' method may be overridden to intercept completions for\r
-commands that have no complete_ method.\r
-\r
-The data member `self.ruler' sets the character used to draw separator lines\r
-in the help messages. If empty, no ruler line is drawn. It defaults to "=".\r
-\r
-If the value of `self.intro' is nonempty when the cmdloop method is called,\r
-it is printed out on interpreter startup. This value may be overridden\r
-via an optional argument to the cmdloop() method.\r
-\r
-The data members `self.doc_header', `self.misc_header', and\r
-`self.undoc_header' set the headers used for the help function's\r
-listings of documented functions, miscellaneous topics, and undocumented\r
-functions respectively.\r
-\r
-These interpreters use raw_input; thus, if the readline module is loaded,\r
-they automatically support Emacs-like command history and editing features.\r
-"""\r
-\r
-import string\r
-\r
-__all__ = ["Cmd"]\r
-\r
-PROMPT = '(Cmd) '\r
-IDENTCHARS = string.ascii_letters + string.digits + '_'\r
-\r
-class Cmd:\r
- """A simple framework for writing line-oriented command interpreters.\r
-\r
- These are often useful for test harnesses, administrative tools, and\r
- prototypes that will later be wrapped in a more sophisticated interface.\r
-\r
- A Cmd instance or subclass instance is a line-oriented interpreter\r
- framework. There is no good reason to instantiate Cmd itself; rather,\r
- it's useful as a superclass of an interpreter class you define yourself\r
- in order to inherit Cmd's methods and encapsulate action methods.\r
-\r
- """\r
- prompt = PROMPT\r
- identchars = IDENTCHARS\r
- ruler = '='\r
- lastcmd = ''\r
- intro = None\r
- doc_leader = ""\r
- doc_header = "Documented commands (type help <topic>):"\r
- misc_header = "Miscellaneous help topics:"\r
- undoc_header = "Undocumented commands:"\r
- nohelp = "*** No help on %s"\r
- use_rawinput = 1\r
-\r
- def __init__(self, completekey='tab', stdin=None, stdout=None):\r
- """Instantiate a line-oriented interpreter framework.\r
-\r
- The optional argument 'completekey' is the readline name of a\r
- completion key; it defaults to the Tab key. If completekey is\r
- not None and the readline module is available, command completion\r
- is done automatically. The optional arguments stdin and stdout\r
- specify alternate input and output file objects; if not specified,\r
- sys.stdin and sys.stdout are used.\r
-\r
- """\r
- import sys\r
- if stdin is not None:\r
- self.stdin = stdin\r
- else:\r
- self.stdin = sys.stdin\r
- if stdout is not None:\r
- self.stdout = stdout\r
- else:\r
- self.stdout = sys.stdout\r
- self.cmdqueue = []\r
- self.completekey = completekey\r
-\r
- def cmdloop(self, intro=None):\r
- """Repeatedly issue a prompt, accept input, parse an initial prefix\r
- off the received input, and dispatch to action methods, passing them\r
- the remainder of the line as argument.\r
-\r
- """\r
-\r
- self.preloop()\r
- if self.use_rawinput and self.completekey:\r
- try:\r
- import readline\r
- self.old_completer = readline.get_completer()\r
- readline.set_completer(self.complete)\r
- readline.parse_and_bind(self.completekey+": complete")\r
- except ImportError:\r
- pass\r
- try:\r
- if intro is not None:\r
- self.intro = intro\r
- if self.intro:\r
- self.stdout.write(str(self.intro)+"\n")\r
- stop = None\r
- while not stop:\r
- if self.cmdqueue:\r
- line = self.cmdqueue.pop(0)\r
- else:\r
- if self.use_rawinput:\r
- try:\r
- line = raw_input(self.prompt)\r
- except EOFError:\r
- line = 'EOF'\r
- else:\r
- self.stdout.write(self.prompt)\r
- self.stdout.flush()\r
- line = self.stdin.readline()\r
- if not len(line):\r
- line = 'EOF'\r
- else:\r
- line = line.rstrip('\r\n')\r
- line = self.precmd(line)\r
- stop = self.onecmd(line)\r
- stop = self.postcmd(stop, line)\r
- self.postloop()\r
- finally:\r
- if self.use_rawinput and self.completekey:\r
- try:\r
- import readline\r
- readline.set_completer(self.old_completer)\r
- except ImportError:\r
- pass\r
-\r
-\r
- def precmd(self, line):\r
- """Hook method executed just before the command line is\r
- interpreted, but after the input prompt is generated and issued.\r
-\r
- """\r
- return line\r
-\r
- def postcmd(self, stop, line):\r
- """Hook method executed just after a command dispatch is finished."""\r
- return stop\r
-\r
- def preloop(self):\r
- """Hook method executed once when the cmdloop() method is called."""\r
- pass\r
-\r
- def postloop(self):\r
- """Hook method executed once when the cmdloop() method is about to\r
- return.\r
-\r
- """\r
- pass\r
-\r
- def parseline(self, line):\r
- """Parse the line into a command name and a string containing\r
- the arguments. Returns a tuple containing (command, args, line).\r
- 'command' and 'args' may be None if the line couldn't be parsed.\r
- """\r
- line = line.strip()\r
- if not line:\r
- return None, None, line\r
- elif line[0] == '?':\r
- line = 'help ' + line[1:]\r
- elif line[0] == '!':\r
- if hasattr(self, 'do_shell'):\r
- line = 'shell ' + line[1:]\r
- else:\r
- return None, None, line\r
- i, n = 0, len(line)\r
- while i < n and line[i] in self.identchars: i = i+1\r
- cmd, arg = line[:i], line[i:].strip()\r
- return cmd, arg, line\r
-\r
- def onecmd(self, line):\r
- """Interpret the argument as though it had been typed in response\r
- to the prompt.\r
-\r
- This may be overridden, but should not normally need to be;\r
- see the precmd() and postcmd() methods for useful execution hooks.\r
- The return value is a flag indicating whether interpretation of\r
- commands by the interpreter should stop.\r
-\r
- """\r
- cmd, arg, line = self.parseline(line)\r
- if not line:\r
- return self.emptyline()\r
- if cmd is None:\r
- return self.default(line)\r
- self.lastcmd = line\r
- if cmd == '':\r
- return self.default(line)\r
- else:\r
- try:\r
- func = getattr(self, 'do_' + cmd)\r
- except AttributeError:\r
- return self.default(line)\r
- return func(arg)\r
-\r
- def emptyline(self):\r
- """Called when an empty line is entered in response to the prompt.\r
-\r
- If this method is not overridden, it repeats the last nonempty\r
- command entered.\r
-\r
- """\r
- if self.lastcmd:\r
- return self.onecmd(self.lastcmd)\r
-\r
- def default(self, line):\r
- """Called on an input line when the command prefix is not recognized.\r
-\r
- If this method is not overridden, it prints an error message and\r
- returns.\r
-\r
- """\r
- self.stdout.write('*** Unknown syntax: %s\n'%line)\r
-\r
- def completedefault(self, *ignored):\r
- """Method called to complete an input line when no command-specific\r
- complete_*() method is available.\r
-\r
- By default, it returns an empty list.\r
-\r
- """\r
- return []\r
-\r
- def completenames(self, text, *ignored):\r
- dotext = 'do_'+text\r
- return [a[3:] for a in self.get_names() if a.startswith(dotext)]\r
-\r
- def complete(self, text, state):\r
- """Return the next possible completion for 'text'.\r
-\r
- If a command has not been entered, then complete against command list.\r
- Otherwise try to call complete_<command> to get list of completions.\r
- """\r
- if state == 0:\r
- import readline\r
- origline = readline.get_line_buffer()\r
- line = origline.lstrip()\r
- stripped = len(origline) - len(line)\r
- begidx = readline.get_begidx() - stripped\r
- endidx = readline.get_endidx() - stripped\r
- if begidx>0:\r
- cmd, args, foo = self.parseline(line)\r
- if cmd == '':\r
- compfunc = self.completedefault\r
- else:\r
- try:\r
- compfunc = getattr(self, 'complete_' + cmd)\r
- except AttributeError:\r
- compfunc = self.completedefault\r
- else:\r
- compfunc = self.completenames\r
- self.completion_matches = compfunc(text, line, begidx, endidx)\r
- try:\r
- return self.completion_matches[state]\r
- except IndexError:\r
- return None\r
-\r
- def get_names(self):\r
- # This method used to pull in base class attributes\r
- # at a time dir() didn't do it yet.\r
- return dir(self.__class__)\r
-\r
- def complete_help(self, *args):\r
- commands = set(self.completenames(*args))\r
- topics = set(a[5:] for a in self.get_names()\r
- if a.startswith('help_' + args[0]))\r
- return list(commands | topics)\r
-\r
- def do_help(self, arg):\r
- if arg:\r
- # XXX check arg syntax\r
- try:\r
- func = getattr(self, 'help_' + arg)\r
- except AttributeError:\r
- try:\r
- doc=getattr(self, 'do_' + arg).__doc__\r
- if doc:\r
- self.stdout.write("%s\n"%str(doc))\r
- return\r
- except AttributeError:\r
- pass\r
- self.stdout.write("%s\n"%str(self.nohelp % (arg,)))\r
- return\r
- func()\r
- else:\r
- names = self.get_names()\r
- cmds_doc = []\r
- cmds_undoc = []\r
- help = {}\r
- for name in names:\r
- if name[:5] == 'help_':\r
- help[name[5:]]=1\r
- names.sort()\r
- # There can be duplicates if routines overridden\r
- prevname = ''\r
- for name in names:\r
- if name[:3] == 'do_':\r
- if name == prevname:\r
- continue\r
- prevname = name\r
- cmd=name[3:]\r
- if cmd in help:\r
- cmds_doc.append(cmd)\r
- del help[cmd]\r
- elif getattr(self, name).__doc__:\r
- cmds_doc.append(cmd)\r
- else:\r
- cmds_undoc.append(cmd)\r
- self.stdout.write("%s\n"%str(self.doc_leader))\r
- self.print_topics(self.doc_header, cmds_doc, 15,80)\r
- self.print_topics(self.misc_header, help.keys(),15,80)\r
- self.print_topics(self.undoc_header, cmds_undoc, 15,80)\r
-\r
- def print_topics(self, header, cmds, cmdlen, maxcol):\r
- if cmds:\r
- self.stdout.write("%s\n"%str(header))\r
- if self.ruler:\r
- self.stdout.write("%s\n"%str(self.ruler * len(header)))\r
- self.columnize(cmds, maxcol-1)\r
- self.stdout.write("\n")\r
-\r
- def columnize(self, list, displaywidth=80):\r
- """Display a list of strings as a compact set of columns.\r
-\r
- Each column is only as wide as necessary.\r
- Columns are separated by two spaces (one was not legible enough).\r
- """\r
- if not list:\r
- self.stdout.write("<empty>\n")\r
- return\r
- nonstrings = [i for i in range(len(list))\r
- if not isinstance(list[i], str)]\r
- if nonstrings:\r
- raise TypeError, ("list[i] not a string for i in %s" %\r
- ", ".join(map(str, nonstrings)))\r
- size = len(list)\r
- if size == 1:\r
- self.stdout.write('%s\n'%str(list[0]))\r
- return\r
- # Try every row count from 1 upwards\r
- for nrows in range(1, len(list)):\r
- ncols = (size+nrows-1) // nrows\r
- colwidths = []\r
- totwidth = -2\r
- for col in range(ncols):\r
- colwidth = 0\r
- for row in range(nrows):\r
- i = row + nrows*col\r
- if i >= size:\r
- break\r
- x = list[i]\r
- colwidth = max(colwidth, len(x))\r
- colwidths.append(colwidth)\r
- totwidth += colwidth + 2\r
- if totwidth > displaywidth:\r
- break\r
- if totwidth <= displaywidth:\r
- break\r
- else:\r
- nrows = len(list)\r
- ncols = 1\r
- colwidths = [0]\r
- for row in range(nrows):\r
- texts = []\r
- for col in range(ncols):\r
- i = row + nrows*col\r
- if i >= size:\r
- x = ""\r
- else:\r
- x = list[i]\r
- texts.append(x)\r
- while texts and not texts[-1]:\r
- del texts[-1]\r
- for col in range(len(texts)):\r
- texts[col] = texts[col].ljust(colwidths[col])\r
- self.stdout.write("%s\n"%str(" ".join(texts)))\r