summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/lib/libcmd.tex24
-rw-r--r--Lib/cmd.py99
-rw-r--r--Lib/pstats.py1
-rw-r--r--Misc/NEWS2
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_<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 = {}
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