a generic class to build lineoriented command interpreters interpreters constructed with this class obey the following conventions 1 end of file on input is processed as the command eof 2 a command is parsed out of each line by collecting the prefix composed of characters in the identchars member 3 a command foo is dispatched to a method dofoo the do method is passed a single argument consisting of the remainder of the line 4 typing an empty line repeats the last command actually it calls the method emptyline which may be overridden in a subclass 5 there is a predefined help method given an argument topic it calls the command helptopic with no arguments it lists all topics with defined help functions broken into up to three topics documented commands miscellaneous help topics and undocumented commands 6 the command is a synonym for help the command is a synonym for shell if a doshell method exists 7 if completion is enabled completing commands will be done automatically and completing of commands args is done by calling completefoo with arguments text line begidx endidx text is string we are matching against all returned matches must begin with it line is the current input line lstripped begidx and endidx are the beginning and end indexes of the text being matched which could be used to provide different completion depending upon which position the argument is in the default method may be overridden to intercept commands for which there is no do method the completedefault method may be overridden to intercept completions for commands that have no complete method the data member self ruler sets the character used to draw separator lines in the help messages if empty no ruler line is drawn it defaults to if the value of self intro is nonempty when the cmdloop method is called it is printed out on interpreter startup this value may be overridden via an optional argument to the cmdloop method the data members self docheader self mischeader and self undocheader set the headers used for the help function s listings of documented functions miscellaneous topics and undocumented functions respectively a simple framework for writing lineoriented command interpreters these are often useful for test harnesses administrative tools and prototypes that will later be wrapped in a more sophisticated interface a cmd instance or subclass instance is a lineoriented interpreter framework there is no good reason to instantiate cmd itself rather it s useful as a superclass of an interpreter class you define yourself in order to inherit cmd s methods and encapsulate action methods instantiate a lineoriented interpreter framework the optional argument completekey is the readline name of a completion key it defaults to the tab key if completekey is not none and the readline module is available command completion is done automatically the optional arguments stdin and stdout specify alternate input and output file objects if not specified sys stdin and sys stdout are used repeatedly issue a prompt accept input parse an initial prefix off the received input and dispatch to action methods passing them the remainder of the line as argument hook method executed just before the command line is interpreted but after the input prompt is generated and issued hook method executed just after a command dispatch is finished return stop def preloopself hook method executed once when the cmdloop method is about to return parse the line into a command name and a string containing the arguments returns a tuple containing command args line command and args may be none if the line couldn t be parsed interpret the argument as though it had been typed in response to the prompt this may be overridden but should not normally need to be see the precmd and postcmd methods for useful execution hooks the return value is a flag indicating whether interpretation of commands by the interpreter should stop called when an empty line is entered in response to the prompt if this method is not overridden it repeats the last nonempty command entered called on an input line when the command prefix is not recognized if this method is not overridden it prints an error message and returns method called to complete an input line when no commandspecific complete method is available by default it returns an empty list return the next possible completion for text if a command has not been entered then complete against command list otherwise try to call completecommand to get list of completions this method used to pull in base class attributes at a time dir didn t do it yet xxx check arg syntax there can be duplicates if routines overridden display a list of strings as a compact set of columns each column is only as wide as necessary columns are separated by two spaces one was not legible enough try every row count from 1 upwards a simple framework for writing line oriented command interpreters these are often useful for test harnesses administrative tools and prototypes that will later be wrapped in a more sophisticated interface a cmd instance or subclass instance is a line oriented interpreter framework there is no good reason to instantiate cmd itself rather it s useful as a superclass of an interpreter class you define yourself in order to inherit cmd s methods and encapsulate action methods instantiate a line oriented interpreter framework the optional argument completekey is the readline name of a completion key it defaults to the tab key if completekey is not none and the readline module is available command completion is done automatically the optional arguments stdin and stdout specify alternate input and output file objects if not specified sys stdin and sys stdout are used repeatedly issue a prompt accept input parse an initial prefix off the received input and dispatch to action methods passing them the remainder of the line as argument hook method executed just before the command line is interpreted but after the input prompt is generated and issued hook method executed just after a command dispatch is finished hook method executed once when the cmdloop method is called hook method executed once when the cmdloop method is about to return parse the line into a command name and a string containing the arguments returns a tuple containing command args line command and args may be none if the line couldn t be parsed interpret the argument as though it had been typed in response to the prompt this may be overridden but should not normally need to be see the precmd and postcmd methods for useful execution hooks the return value is a flag indicating whether interpretation of commands by the interpreter should stop called when an empty line is entered in response to the prompt if this method is not overridden it repeats the last nonempty command entered called on an input line when the command prefix is not recognized if this method is not overridden it prints an error message and returns method called to complete an input line when no command specific complete_ method is available by default it returns an empty list return the next possible completion for text if a command has not been entered then complete against command list otherwise try to call complete_ command to get list of completions this method used to pull in base class attributes at a time dir didn t do it yet xxx check arg syntax there can be duplicates if routines overridden display a list of strings as a compact set of columns each column is only as wide as necessary columns are separated by two spaces one was not legible enough try every row count from 1 upwards
import string, sys

__all__ = ["Cmd"]

PROMPT = '(Cmd) '
IDENTCHARS = string.ascii_letters + string.digits + '_'

class Cmd:
    
    prompt = PROMPT
    identchars = IDENTCHARS
    ruler = '='
    lastcmd = ''
    intro = None
    doc_leader = ""
    doc_header = "Documented commands (type help <topic>):"
    misc_header = "Miscellaneous help topics:"
    undoc_header = "Undocumented commands:"
    nohelp = "*** No help on %s"
    use_rawinput = 1

    def __init__(self, completekey='tab', stdin=None, stdout=None):
        
        if stdin is not None:
            self.stdin = stdin
        else:
            self.stdin = sys.stdin
        if stdout is not None:
            self.stdout = stdout
        else:
            self.stdout = sys.stdout
        self.cmdqueue = []
        self.completekey = completekey

    def cmdloop(self, intro=None):
        

        self.preloop()
        if self.use_rawinput and self.completekey:
            try:
                import readline
                self.old_completer = readline.get_completer()
                readline.set_completer(self.complete)
                readline.parse_and_bind(self.completekey+": complete")
            except ImportError:
                pass
        try:
            if intro is not None:
                self.intro = intro
            if self.intro:
                self.stdout.write(str(self.intro)+"\n")
            stop = None
            while not stop:
                if self.cmdqueue:
                    line = self.cmdqueue.pop(0)
                else:
                    if self.use_rawinput:
                        try:
                            line = input(self.prompt)
                        except EOFError:
                            line = 'EOF'
                    else:
                        self.stdout.write(self.prompt)
                        self.stdout.flush()
                        line = self.stdin.readline()
                        if not len(line):
                            line = 'EOF'
                        else:
                            line = line.rstrip('\r\n')
                line = self.precmd(line)
                stop = self.onecmd(line)
                stop = self.postcmd(stop, line)
            self.postloop()
        finally:
            if self.use_rawinput and self.completekey:
                try:
                    import readline
                    readline.set_completer(self.old_completer)
                except ImportError:
                    pass


    def precmd(self, line):
        
        return line

    def postcmd(self, stop, line):
        
        return stop

    def preloop(self):
        
        pass

    def postloop(self):
        
        pass

    def parseline(self, line):
        
        line = line.strip()
        if not line:
            return None, None, line
        elif line[0] == '?':
            line = 'help ' + line[1:]
        elif line[0] == '!':
            if hasattr(self, 'do_shell'):
                line = 'shell ' + line[1:]
            else:
                return None, None, line
        i, n = 0, len(line)
        while i < n and line[i] in self.identchars: i = i+1
        cmd, arg = line[:i], line[i:].strip()
        return cmd, arg, line

    def onecmd(self, line):
        
        cmd, arg, line = self.parseline(line)
        if not line:
            return self.emptyline()
        if cmd is None:
            return self.default(line)
        self.lastcmd = line
        if line == 'EOF' :
            self.lastcmd = ''
        if cmd == '':
            return self.default(line)
        else:
            func = getattr(self, 'do_' + cmd, None)
            if func is None:
                return self.default(line)
            return func(arg)

    def emptyline(self):
        
        if self.lastcmd:
            return self.onecmd(self.lastcmd)

    def default(self, line):
        
        self.stdout.write('*** Unknown syntax: %s\n'%line)

    def completedefault(self, *ignored):
        
        return []

    def completenames(self, text, *ignored):
        dotext = 'do_'+text
        return [a[3:] for a in self.get_names() if a.startswith(dotext)]

    def complete(self, text, state):
        
        if state == 0:
            import readline
            origline = readline.get_line_buffer()
            line = origline.lstrip()
            stripped = len(origline) - len(line)
            begidx = readline.get_begidx() - stripped
            endidx = readline.get_endidx() - stripped
            if begidx>0:
                cmd, args, foo = self.parseline(line)
                if cmd == '':
                    compfunc = self.completedefault
                else:
                    try:
                        compfunc = getattr(self, 'complete_' + cmd)
                    except AttributeError:
                        compfunc = self.completedefault
            else:
                compfunc = self.completenames
            self.completion_matches = compfunc(text, line, begidx, endidx)
        try:
            return self.completion_matches[state]
        except IndexError:
            return None

    def get_names(self):
        
        
        return dir(self.__class__)

    def complete_help(self, *args):
        commands = set(self.completenames(*args))
        topics = set(a[5:] for a in self.get_names()
                     if a.startswith('help_' + args[0]))
        return list(commands | topics)

    def do_help(self, arg):
        'List available commands with "help" or detailed help with "help cmd".'
        if arg:
            
            try:
                func = getattr(self, 'help_' + arg)
            except AttributeError:
                try:
                    doc=getattr(self, 'do_' + arg).__doc__
                    if doc:
                        self.stdout.write("%s\n"%str(doc))
                        return
                except AttributeError:
                    pass
                self.stdout.write("%s\n"%str(self.nohelp % (arg,)))
                return
            func()
        else:
            names = self.get_names()
            cmds_doc = []
            cmds_undoc = []
            topics = set()
            for name in names:
                if name[:5] == 'help_':
                    topics.add(name[5:])
            names.sort()
            
            prevname = ''
            for name in names:
                if name[:3] == 'do_':
                    if name == prevname:
                        continue
                    prevname = name
                    cmd=name[3:]
                    if cmd in topics:
                        cmds_doc.append(cmd)
                        topics.remove(cmd)
                    elif getattr(self, name).__doc__:
                        cmds_doc.append(cmd)
                    else:
                        cmds_undoc.append(cmd)
            self.stdout.write("%s\n"%str(self.doc_leader))
            self.print_topics(self.doc_header,   cmds_doc,   15,80)
            self.print_topics(self.misc_header,  sorted(topics),15,80)
            self.print_topics(self.undoc_header, cmds_undoc, 15,80)

    def print_topics(self, header, cmds, cmdlen, maxcol):
        if cmds:
            self.stdout.write("%s\n"%str(header))
            if self.ruler:
                self.stdout.write("%s\n"%str(self.ruler * len(header)))
            self.columnize(cmds, maxcol-1)
            self.stdout.write("\n")

    def columnize(self, list, displaywidth=80):
        
        if not list:
            self.stdout.write("<empty>\n")
            return

        nonstrings = [i for i in range(len(list))
                        if not isinstance(list[i], str)]
        if nonstrings:
            raise TypeError("list[i] not a string for i in %s"
                            % ", ".join(map(str, nonstrings)))
        size = len(list)
        if size == 1:
            self.stdout.write('%s\n'%str(list[0]))
            return
        
        for nrows in range(1, len(list)):
            ncols = (size+nrows-1) // nrows
            colwidths = []
            totwidth = -2
            for col in range(ncols):
                colwidth = 0
                for row in range(nrows):
                    i = row + nrows*col
                    if i >= size:
                        break
                    x = list[i]
                    colwidth = max(colwidth, len(x))
                colwidths.append(colwidth)
                totwidth += colwidth + 2
                if totwidth > displaywidth:
                    break
            if totwidth <= displaywidth:
                break
        else:
            nrows = len(list)
            ncols = 1
            colwidths = [0]
        for row in range(nrows):
            texts = []
            for col in range(ncols):
                i = row + nrows*col
                if i >= size:
                    x = ""
                else:
                    x = list[i]
                texts.append(x)
            while texts and not texts[-1]:
                del texts[-1]
            for col in range(len(texts)):
                texts[col] = texts[col].ljust(colwidths[col])
            self.stdout.write("%s\n"%str("  ".join(texts)))