用 cmd 实现文件名完成有点棘手,因为
底层的 readline 库解释特殊字符,例如 '/' 和 '-'
(和其他)作为分隔符,这将设置该行中的哪个子字符串
替换为补全。
例如,
> load /hom<tab>
调用 complete_load() 与
text='hom', line='load /hom', begidx=6, endidx=9
text is line[begidx:endidx]
'text' 不是 "/hom" 因为 readline 库解析了该行并且
返回 '/' 分隔符后的字符串。 complete_load() 应该返回
以“hom”而不是“/hom”开头的完成字符串列表,因为
完成将替换从 begidx 开始的子字符串。如果
complete_load() 函数错误返回 ['/home'],行变为,
> load //home
这不好。
其他字符被 readline 认为是分隔符,而不仅仅是斜杠,
所以你不能假设 'text' 之前的子字符串是父目录。为了
示例:
> load /home/mike/my-file<tab>
调用 complete_load() 与
text='file', line='load /home/mike/my-file', begidx=19, endidx=23
假设 /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
我采用的方法是使用 glob 模块来查找完整路径。全球
适用于绝对路径和相对路径。找到路径后,我删除
“固定”部分,即begidx之前的子字符串。
首先,解析固定部分参数,即空格之间的子字符串
和begidx。
index = line.rindex(' ', 0, begidx) # -1 if not found
fixed = line[index + 1: begidx]
参数在空格和行尾之间。附加一颗星来制作
全局搜索模式。
我在作为目录的结果后面附加一个“/”,因为这样更容易
使用制表符完成遍历目录(否则您需要点击
每个目录的 tab 键两次),这对用户来说很明显
哪些补全项是目录,哪些是文件。
最后删除路径的“固定”部分,所以 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()