【问题标题】:Change how Python Cmd Module handles autocompletion更改 Python Cmd 模块处理自动完成的方式
【发布时间】:2011-04-29 10:31:59
【问题描述】:

我有一个 Cmd 控制台设置为自动完成 Magic: the Gathering 收藏管理系统的卡片名称。

它使用文本参数查询数据库中的卡片,并使用结果自动完成/建议卡片。

但是,这些卡片名称有多个单词,并且 Cmd 会从 last 空格到行尾运行自动补全。

例如:

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)

我尝试从line 参数中手动获取我想要的内容,它从数据库中获取了我想要的结果,但这未能覆盖第一个单词:

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

最后,我需要自动完成器像这样工作:

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

除了上面的手动解析尝试之外,我还尝试用加号替换空格,并发现 Cmd 也非常乐意拆分这些空格。用下划线替换空格是可行的,但是 Unhinged 中有一张名为 _____ 的卡片,所以我必须通过杂技来破解字符串,因为我不能只是 line.replace("_", " ")

这是一些可运行的测试代码:

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()

【问题讨论】:

  • 这里有一些很好的信息:stackoverflow.com/questions/187621/…
  • 能否提供可运行的测试代码?这似乎可以解决
  • 是的,我也看到了那个,@offsound,这是我想到使用 Cmd 的地方。我会编写一些测试代码。

标签: python command-line autocomplete


【解决方案1】:

它不应该过于复杂。类似于以下内容:

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()

【讨论】:

  • 我使用了相同的“技巧”来允许路径完成。我将最后一行换成 return [fp[offs:] for fp in glob.glob(mline+'*')]
  • 如果你在几个 .lower() 调用中折腾,你也可以让你的命令完成不区分大小写。 return [s[offs:] for s in completions if s.lower().startswith(mline.lower())]
【解决方案2】:

你可以readline.set_completer_delims('')

但是,您的 complete_* 函数将不再被调用;您将不得不覆盖Cmd.completeCmd.completenames。详情请看cmd模块的源码。

【讨论】:

  • 炸毁complete_* 函数不是我想做的事情,但是改变它们的调度方式是完美的。不仅仅是Cmd.complete,还有Cmd.parseline。弄清楚后,我将发布我在问题中所做的更改。谢谢!
  • 其实就是这样:readline.set_completer_delims(' \t\n...@#$%^&amp;*()-=+[{]}\\|;:\'",&lt;&gt;?')
【解决方案3】:

我确实重写了 cmdloop 函数,它非常简单。我不必更改任何其他内容。只需从模块中复制 cmdloop 函数(通过执行 import cmdcmd.__file__ 查找代码),并添加两行更改分隔符:

    try:
       import readline
       self.old_completer = readline.get_completer()
       readline.set_completer(self.complete)
       readline.parse_and_bind(self.completekey+": complete")
       # do not use - as delimiter
       old_delims = readline.get_completer_delims() # <-
       readline.set_completer_delims(old_delims.replace('-', '')) # <-
    except ImportError:
        pass

这对我有用。在您的情况下,您可能希望删除导致问题的任何分隔符。

【讨论】:

    猜你喜欢
    • 2014-03-02
    • 1970-01-01
    • 1970-01-01
    • 2020-02-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-15
    • 1970-01-01
    相关资源
    最近更新 更多