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 が含まれていると仮定すると、補完は ['my-file1', 'my-file2'] や ['/home] ではなく ['file1', 'file2'] になります。 /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]
引数は、スペースと行末の間にあります。スターを追加して、glob 検索パターンを作成します。
ディレクトリである結果に「/」を追加します。これにより、タブ補完を使用してディレクトリを簡単に移動できます (そうしないと、ディレクトリごとにタブ キーを 2 回押す必要があります)。ディレクトリとファイルです。
最後に、パスの「固定」部分を削除すると、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()