Patch #416224: add readline completion to cmd.Cmd.
This commit is contained in:
parent
9544fc5027
commit
66b6e192b9
|
@ -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
|
||||
|
|
99
Lib/cmd.py
99
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_<command> 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 = {}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue