cmd, readline, and rlcompleter problems

Peter Kazmier pete-python-5-17 at kazmier.com
Thu Jun 15 17:08:02 EDT 2000


I need help debugging a very strange problem.  I'm not a python expert
so it could be something very simple, on the other hand, I did use the
debugger already, but the problem disappears miraculously when I use
the debugger.  It makes no sense to me.

So, I'm basically trying to write my own cmd and rlcompleter.  I've
attached both the module and a small example of its use.  Its almost
identical to the standard cmd however I am trying to offer command
line completion.  

For example, if I have the following commands defined: do_quit and
do_show_version.  You should be able to type just 'qu' and be able to
hit TAB and have it complete to 'quit'.  That works fine of course.
Here is the problem, if a user types 'show ver' it SHOULD complete to
'show version'; however, the readline.completer function only seems to
pass in the current word that needs completing, in this case, 'ver'.
I want to complete based on 'show ver'.  It shouldn't be a problem
because readline provides a function called get_line_buffer() which
returns the contents of the line buffer.  So here's my completer
function: 

    def complete(self, text, state):

        if state == 0:
	    # Without this line, it would only complete on the 
	    # current word, in the above example, that would have
	    # been 'ver'.  But adding this, I should be able to 
	    # complete on the full line, or 'show ver'.
	    text = readline.get_line_buffer()
            self.matches = self.global_matches(text)
        try:
            return self.matches[state]
        except IndexError:
            return None

    # Find the matches, basically, look through this instance's class
    # for any attribute that begins with 'do_', for each attribute
    # that matches, convert underscores to spaces and ditch the 'do'
    # component.  This provides a list of possible completions.
    def global_matches(self, text):
        matches = []
        n = len(text)

        print "DEBUG text =", text
        for word in self.__class__.__dict__.keys():
            if word[0:3] == 'do_':
                word = string.join(string.split(word, '_')[1:], ' ')
                print "DEBUG", word
                if word[:n] == text:
                    matches.append(word)

        return matches

Everything works fine IF I comment out the get_line_buffer() line.
Unfortunately, that would only do completion on the current word.
When I uncomment the get_line_buffer() line, everything seems to work,
but during the 'for word in self.__class__.__dict__.keys()' loop, half
of the keys mysteriously disappear!  The DEBUG output, shows only some
of the keys, not all of them.  Comment out the get_line_buffer() line
and all of the keys() are back!  When I stepped through this in the
debugger, all of the keys() were also there!!  It appears that the
get_line_buffer() call is somehow munging my environment in a weird
way.  Oddly enough, whwen debugging, his munging does not appear.

Could someone PLEASE help me debug this??  I have no idea what I am
doing wrong and the symptoms are like none I've ever seen.  I've
attached two files, cli.py which is my cmd.py replacement, and
shell.py which is a simple test shell.  Run shell.py, and type in 'sh'
and hit TAB, it won't work.  But comment out that get_line_buffer()
line and then do the same.  Viola, it works.  But how come and how do
I get it to work the other way??

Thanks,
Pete

============
shell.py
------------
#!/usr/bin/python

import cli

class CLI(cli.cli):
    def do_quit(self, args):
        """Exit the command interpreter"""
        return 1

    def do_show(self, args):
        """Display information on various components"""
        print "Show function was called:", args
        return None

    def do_show_version(self, args):
        """Display the version of the shell"""
        print "Version is ..."

    def do_show_version_cli(self, args):
        """Display the version of the CLI"""
        print "CLI version is ..."

CLI().cmdloop()

============
cli.py
------------
import string
import readline

# Modified version of cmd.py

class cli:
    prompt = '# '
    
    def __init__(self):
        readline.parse_and_bind("tab: complete")
        readline.set_completer(self.complete)

    def cmdloop(self):
        stop = None
        while not stop:
            try:
                line = raw_input(self.prompt)
            except EOFError:
                print 
                break

            line = self.precmd(line)
            stop = self.onecmd(line)
            stop = self.postcmd(stop, line)

        self.postloop()

    def postloop(self):
        pass

    def precmd(self, line):
        return line

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

    def onecmd(self, line):
        line = string.strip(line)
        if not line:
            return None
        tokens = string.split(line)

        help = 0
        if tokens[len(tokens)-1] == 'help':
            help = 1
            tokens.pop()

        func = None
        for i in range(len(tokens), 0, -1):
            try:
                func = getattr(self, 'do_' + string.join(tokens[:i], '_'))
                args = tokens[i:]
            except AttributeError:
                func = None
            else:
                break

        if help:
            self.do_help(func)
        elif func:
            return func(args)

        return None
        
    def do_help(self, func):
        """Welcome to the help system"""
        if func:
            if func.__doc__:
                print func.__doc__
            else:
                print "No help available"
        else:
            print "No help available"

    def complete(self, text, state):
        
        if state == 0:
            text = readline.get_line_buffer()
            self.matches = self.global_matches(text)
        try:
            return self.matches[state]
        except IndexError:
            return None

    def global_matches(self, text):

        matches = []
        n = len(text)

        print "DEBUG text =", text
        for word in self.__class__.__dict__.keys():
            if word[0:3] == 'do_':
                word = string.join(string.split(word, '_')[1:], ' ')
                print "DEBUG", word
                if word[:n] == text:
                    matches.append(word)

        return matches

    







More information about the Python-list mailing list