在Python的Cmd.cmd中完成
我正在使用Python的Cmd.cmd制作一个命令行工具,并且我想添加一个带有文件名参数的“加载”命令,它支持制表符完成。
引用这个和这个,我疯狂的代码是这样的:
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)
这适用于cwd,但是,当我想要进入子目录时,在subdir /之后,complete_load函数的“文本”变为空白,因此_complete_path func会再次返回cwd。
我不知道如何通过制表符完成来获取subdir的内容。 请帮忙!
你的主要问题是readline库是基于它的默认分隔符集来分隔东西的:
import readline
readline.get_completer_delims()
# yields ' tn`~!@#$%^&*()-=+[{]}|;:'",<>/?'
当选项卡完成一个文件名时,我从这里删除了一切,但删除了空格。
import readline
readline.set_completer_delims(' tn')
设置分隔符后,完成函数的'text'参数应该更符合您的期望。
这也解决了选项卡完成复制部分文本的常见问题。
使用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模块来查找完整路径。 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()
我不认为这是最好的答案,但我得到了我想要的功能:
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)
我没有使用complete_cmd()中的“text”,而是直接使用解析“line”参数。 如果你有什么更好的想法,请让我知道。
链接地址: http://www.djcxy.com/p/56421.html