views:

50

answers:

2

I have a Cmd console set up to auto-complete card names for a Magic: the Gathering collection management system.

It uses the text parameter to query the database for cards, and uses the results to auto-complete/suggest cards.

However, these cards names have multiple words, and Cmd runs auto-completion from the last space to the end of the line.

For example:

mtgdb> add Mage<tab><tab>
Mage Slayer (Alara Reborn)     Magefire Wings (Alara Reborn)
mtgdb> add Mage S<tab><tab>
Sages of the Anima (Alara Reborn)
Sanctum Plowbeast (Alara Reborn)
Sangrite Backlash (Alara Reborn)
Sanity Gnawers (Alara Reborn)
Sen Triplets (Alara Reborn)
[...]
mtgdb> add Mage Sl<tab>
mtgdb> add Mage Slave of Bolas (Alara Reborn)

I tried manually grabbing what I wanted from the line parameter, which gets the results I want from the database, but this fails to overwrite the first word:

mtgdb> add Mage Sl<tab>
mtgdb> add Mage Mage Slayer (Alara Reborn)

In the end, I need the auto-completer to work like this:

mtgdb> add Mage Sl<tab>
mtgdb> add Mage Slayer (Alara Reborn)

Aside from the manual parsing attempt above, I also tried replacing spaces with plus signs, and discovered that Cmd is perfectly happy splitting on those as well. Replacing spaces with underscores works, but there is one card in Unhinged which is named _____, so I have to go through acrobatics to demunge the strings since I can't just line.replace("_", " ").

Here's some runnable test code:

import cmd

commands = [
    "foo",
    "foo bar blah",
    "bar",
    "bar baz blah",
    "baz",
    "baz foo blah"]

class Console(cmd.Cmd):
    intro = "Test console for" + \
            "http://stackoverflow.com/questions/4001708/\n" + \
            "Type \"cmd<space><tab><tab>\" to test " + \
            "auto-completion with spaces in commands\nwith " + \
            "similar beginings."

    def do_cmd(self, line):
        print(line)

    def complete_cmd(self, text, line, start_index, end_index):
        if text:
            return [command for command in commands
                    if command.startswith(text)]
        else:
            return commands

if __name__ == "__main__":
    command = Console()
    command.cmdloop()
+1  A: 

You could do readline.set_completer_delims('').

However, your complete_* functions won't be called anymore; you will have to override Cmd.complete or Cmd.completenames. Look at the sourcecode of the cmd module for details.

adw
Blowing up the `complete_*` functions isn't something I want to do, but altering how they are dispatched is perfect. Not just `Cmd.complete`, but `Cmd.parseline` as well. I'll post what I change in the question after I figure it out. Thanks!
Artanis
+2  A: 

It shouldn't need to be overly complicated. Something like the following:

import cmd

completions = [
    'Mage Slayer (Alara Reborn)',
    'Magefire Wings (Alara Reborn)',
    'Sages of the Anima (Alara Reborn)',
    'Sanctum Plowbeast (Alara Reborn)',
    'Sangrite Backlash (Alara Reborn)',
    'Sanity Gnawers (Alara Reborn)',
    'Sen Triplets (Alara Reborn)'
]

class mycmd(cmd.Cmd):
    def __init__(self):
        cmd.Cmd.__init__(self)

    def do_quit(self, s):
        return True

    def do_add(self, s):
        pass

    def complete_add(self, text, line, begidx, endidx):
        mline = line.partition(' ')[2]
        offs = len(mline) - len(text)
        return [s[offs:] for s in completions if s.startswith(mline)]

if __name__ == '__main__':
    mycmd().cmdloop()
D Krueger