From 66b6e192b9d0437029f8fd7aa9f381bb4e9ed248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20v=2E=20L=C3=B6wis?= Date: Sat, 28 Jul 2001 14:44:03 +0000 Subject: Patch #416224: add readline completion to cmd.Cmd. --- Doc/lib/libcmd.tex | 24 ++++++++++++- Lib/cmd.py | 99 +++++++++++++++++++++++++++++++++++++++++++++--------- Lib/pstats.py | 1 + Misc/NEWS | 2 ++ 4 files changed, 110 insertions(+), 16 deletions(-) diff --git a/Doc/lib/libcmd.tex b/Doc/lib/libcmd.tex index 847aa20..e3be7b4 100644 --- a/Doc/lib/libcmd.tex +++ b/Doc/lib/libcmd.tex @@ -11,12 +11,18 @@ 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. -\begin{classdesc}{Cmd}{} +\begin{classdesc}{Cmd}{\optional{completekey}} A \class{Cmd} instance or subclass instance is a line-oriented interpreter framework. There is no good reason to instantiate \class{Cmd} itself; rather, it's useful as a superclass of an interpreter class you define yourself in order to inherit \class{Cmd}'s methods and encapsulate action methods. + +The optional argument is the \refmodule{readline} name of a completion +key; it defaults to \code{``tab''}. If \var{completekey} is not +\code{None} and \module{readline} is available, command completion is +done automatically. + \end{classdesc} \subsection{Cmd Objects} @@ -47,6 +53,16 @@ the method \method{do_help()}. As another special case, a line beginning with the character \character{!} is dispatched to the method \method{do_shell} (if such a method is defined). +If completion is enabled, completing commands will be done +automatically, and completing of commands args is done by calling +\method{complete_foo()} with arguments \samp{text}, \samp{line}, +\samp{begidx}, \samp{endidx}. \samp{text} is string we are matching +against, all returned matches must begin with it. \samp{line} is the +current input line (lstripped), \samp{begidx} and \samp{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. + All subclasses of \class{Cmd} inherit a predefined \method{do_help}. This method, called with an argument \code{bar}, invokes the corresponding method \method{help_bar()}. With no argument, @@ -72,6 +88,12 @@ recognized. If this method is not overridden, it prints an error message and returns. \end{methoddesc} +\begin{methoddesc}{completedefault}{text, line, begidx, endidx} +Method called to complete an input line when no command-specific +\code{complete_} method is available. By default, it returns an +empty list. +\end{methoddesc} + \begin{methoddesc}{precmd}{} Hook method executed just before the command line is interpreted, but after the input prompt is generated and issued. This diff --git a/Lib/cmd.py b/Lib/cmd.py index eacd498..423494a 100644 --- a/Lib/cmd.py +++ b/Lib/cmd.py @@ -15,10 +15,20 @@ Interpreters constructed with this class obey the following conventions: commands, miscellaneous help topics, and undocumented commands. 6. The command '?' is a synonym for `help'. The command '!' is a synonym for `shell', if a do_shell method exists. +7. If completion is enabled, completing commands will be done automatically, + and completing of commands args is done by calling complete_foo() 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 "=". @@ -56,7 +66,14 @@ class Cmd: nohelp = "*** No help on %s" use_rawinput = 1 - def __init__(self): pass + def __init__(self, completekey='tab'): + if completekey: + try: + import readline + readline.set_completer(self.complete) + readline.parse_and_bind(completekey+": complete") + except ImportError: + pass def cmdloop(self, intro=None): self.preloop() @@ -99,21 +116,29 @@ class Cmd: def postloop(self): pass - def onecmd(self, line): + def parseline(self, line): line = line.strip() if not line: - return self.emptyline() + 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 self.default(line) - self.lastcmd = line + 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 cmd == '': return self.default(line) else: @@ -130,6 +155,59 @@ class Cmd: def default(self, line): print '*** Unknown syntax:', 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): + """Return the next possible completion for 'text'. + + If a command has not been entered, then complete against command list. + Otherwise try to call complete_ to get list of completions. + """ + 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): + # Inheritance says we have to look in class and + # base classes; order is not important. + names = [] + classes = [self.__class__] + while classes: + aclass = classes[0] + if aclass.__bases__: + classes = classes + list(aclass.__bases__) + names = names + dir(aclass) + del classes[0] + return names + + def complete_help(self, *args): + return self.completenames(*args) + def do_help(self, arg): if arg: # XXX check arg syntax @@ -147,16 +225,7 @@ class Cmd: return func() else: - # Inheritance says we have to look in class and - # base classes; order is not important. - names = [] - classes = [self.__class__] - while classes: - aclass = classes[0] - if aclass.__bases__: - classes = classes + list(aclass.__bases__) - names = names + dir(aclass) - del classes[0] + names = self.get_names() cmds_doc = [] cmds_undoc = [] help = {} diff --git a/Lib/pstats.py b/Lib/pstats.py index c1183f9..20a6cd1 100644 --- a/Lib/pstats.py +++ b/Lib/pstats.py @@ -538,6 +538,7 @@ if __name__ == '__main__': class ProfileBrowser(cmd.Cmd): def __init__(self, profile=None): + cmd.Cmd.__init__(self) self.prompt = "% " if profile: self.stats = Stats(profile) diff --git a/Misc/NEWS b/Misc/NEWS index 643564e..290d637 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -14,6 +14,8 @@ Library value using the minimal quoting required for the value; more reliable than using xml.sax.saxutils.escape() for attribute values. +- Readline completion support for cmd.Cmd was added. + New platforms C API -- cgit v0.12