在Python的Cmd.cmd中完成filename tab-completion

jin*_*erk 6 python cmd tab-completion

我正在使用Python的Cmd.cmd创建一个命令行工具,我想添加一个带有filename参数的"load"命令,它支持tab-completion.

引用这个这个,我疯了这样的代码:

import os, cmd, sys, yaml
import os.path as op
import glob as gb

def _complete_path(path):
    if op.isdir(path):
        return gb.glob(op.join(path, '*'))
    else:
        return gb.glob(path+'*')

class CmdHandler(cmd.Cmd):

    def do_load(self, filename):
        try:
            with open(filename, 'r') as f:
                self.cfg = yaml.load(f)
        except:
            print 'fail to load the file "{:}"'.format(filename)

    def complete_load(self, text, line, start_idx, end_idx):
        return _complete_path(text)
Run Code Online (Sandbox Code Playgroud)

这适用于cwd,但是,当我想进入subdir时,在subdir /之后,complete_load函数的"text"变为空白,所以_complete_path func再次返回cwd.

我不知道如何使用tab-completion获取subdir的内容.请帮忙!

mef*_*fie 5

使用 cmd 实现文件名补全有点棘手,因为底层 readline 库将特殊字符(例如“/”和“-”(以及其他))解释为分隔符,并且这设置了行中的哪个子字符串将被补全替换。

例如,

> load /hom<tab>
Run Code Online (Sandbox Code Playgroud)

调用complete_load()

text='hom', line='load /hom', begidx=6, endidx=9
text is line[begidx:endidx]
Run Code Online (Sandbox Code Playgroud)

'text' 不是“/hom”,因为 readline 库解析了该行并返回“/”分隔符之后的字符串。Complete_load() 应返回以“hom”而不是“/hom”开头的补全字符串列表,因为补全将替换从 begidx 开始的子字符串。如果complete_load()函数错误地返回['/home'],则该行变为,

> load //home
Run Code Online (Sandbox Code Playgroud)

这不好。

其他字符被视为 readline 的分隔符,而不仅仅是斜杠,因此您不能假设“text”之前的子字符串是父目录。例如:

> load /home/mike/my-file<tab>
Run Code Online (Sandbox Code Playgroud)

调用complete_load()

text='file', line='load /home/mike/my-file', begidx=19, endidx=23
Run Code Online (Sandbox Code Playgroud)

假设 /home/mike 包含文件 my-file1 和 my-file2,则补全应该是 ['file1', 'file2'],而不是 ['my-file1', 'my-file2'],也不是 ['/home /mike/my-file1', '/home/mike/my-file2']. 如果返回完整路径,结果为:

> load /home/mike/my-file/home/mike/my-file1
Run Code Online (Sandbox Code Playgroud)

我采取的方法是使用 glob 模块来查找完整路径。Glob 适用于绝对路径和相对路径。找到路径后,我删除了“固定”部分,即 begidx 之前的子字符串。

首先,解析固定部分参数,它是空格和 begidx 之间的子字符串。

index = line.rindex(' ', 0, begidx)  # -1 if not found
fixed = line[index + 1: begidx]
Run Code Online (Sandbox Code Playgroud)

参数位于空格和行尾之间。附加一个星号以形成全局搜索模式。

我将“/”附加到作为目录的结果中,因为这样可以更轻松地使用制表符补全来遍历目录(否则您需要为每个目录按两次制表键),并且它使用户清楚哪些补全项是目录和文件。

最后删除路径的“固定”部分,因此 readline 将仅替换“文本”部分。

import os
import glob
import cmd

def _append_slash_if_dir(p):
    if p and os.path.isdir(p) and p[-1] != os.sep:
        return p + os.sep
    else:
        return p

class MyShell(cmd.Cmd):
    prompt = "> "

    def do_quit(self, line):
        return True

    def do_load(self, line):
        print("load " + line)

    def complete_load(self, text, line, begidx, endidx):
        before_arg = line.rfind(" ", 0, begidx)
        if before_arg == -1:
            return # arg not found

        fixed = line[before_arg+1:begidx]  # fixed portion of the arg
        arg = line[before_arg+1:endidx]
        pattern = arg + '*'

        completions = []
        for path in glob.glob(pattern):
            path = _append_slash_if_dir(path)
            completions.append(path.replace(fixed, "", 1))
        return completions

MyShell().cmdloop()
Run Code Online (Sandbox Code Playgroud)


小智 5

您的主要问题是readline库基于它的默认分隔符集来分隔事物:

import readline
readline.get_completer_delims()
# yields ' \t\n`~!@#$%^&*()-=+[{]}\\|;:\'",<>/?'
Run Code Online (Sandbox Code Playgroud)

当tab填写文件名时,我删除了这个但是空白的所有内容.

import readline
readline.set_completer_delims(' \t\n')
Run Code Online (Sandbox Code Playgroud)

设置分隔符后,完成功能的'text'参数应该更符合您的预期.

这也解决了选项卡完成时复制部分文本时常见的问题.


jin*_*erk 1

我不认为这是最好的答案,但我得到了我想要的功能:

def _complete_path(text, line):
    arg = line.split()[1:]
    dir, base = '', ''
    try: 
        dir, base = op.split(arg[-1])
    except:
        pass
    cwd = os.getcwd()
    try: 
        os.chdir(dir)
    except:
        pass
    ret = [f+os.sep if op.isdir(f) else f for f in os.listdir('.') if f.startswith(base)]
    if base == '' or base == '.': 
        ret.extend(['./', '../'])
    elif base == '..':
        ret.append('../')
    os.chdir(cwd)
    return ret

    .............................

    def complete_load(self, text, line, start_idx, end_idx):
        return _complete_path(text, line)
Run Code Online (Sandbox Code Playgroud)

我没有使用complete_cmd()中的“文本”,而是直接使用解析“行”参数。如果您有更好的想法,请告诉我。