7

Python の Cmd.cmd を使ったコマンドライン ツールの作成に取り組んでおり、タブ補完をサポートするファイル名引数付きの "load" コマンドを追加したいと考えています。

thisthisを参照して、次のようなコードを作成しました。

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を返します。

タブ補完でサブディレクトリの内容を取得する方法がわかりません。助けてください!

4

7 に答える 7

7

あなたの主な問題は、 readline ライブラリがデフォルトの区切り文字セットに基づいて物事を区切っていることです:

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

ファイル名をタブで補完するとき、この空白以外のすべてを削除します。

import readline
readline.set_completer_delims(' \t\n')

区切り文字を設定した後、完了関数への「テキスト」パラメーターは、期待どおりになるはずです。

これにより、タブ補完でテキストの一部が重複するという一般的に発生する問題も解決されます。

于 2015-12-02T20:04:12.813 に答える
4

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()
于 2014-12-02T18:44:49.153 に答える
1

I use shlex to parse the line. In contrast to some other solutions I support quoted and escaped paths (i.e. paths with whitespace) and completion works for any cursor position. I did not test extensively so your mileage may vary.

def path_completion(self, text, line, startidx, endidx):
    try:
        glob_prefix = line[:endidx]

        # add a closing quote if necessary
        quote = ['', '"', "'"]
        while len(quote) > 0:
            try:
                split = [s for s in shlex.split(glob_prefix + quote[0]) if s.strip()]
            except ValueError as ex:
                assert str(ex) == 'No closing quotation', 'Unexpected shlex error'
                quote = quote[1:]
            else:
                break
        assert len(quote) > 0, 'Could not find closing quotation'

        # select relevant line segment
        glob_prefix = split[-1] if len(split) > 1 else ''

        # expand tilde
        glob_prefix = os.path.expanduser(glob_prefix)

        # find matches
        matches = glob.glob(glob_prefix + '*')

        # append os.sep to directories
        matches = [match + os.sep if Path(match).is_dir() else match for match in matches]

        # cutoff prefixes
        cutoff_idx = len(glob_prefix) - len(text)
        matches = [match[cutoff_idx:] for match in matches]

        return matches
    except:
        traceback.print_exc()
于 2016-10-20T11:17:35.337 に答える
1

私はジンセルクと同じ考えを持っていますが、方法は異なります。これが私のコードです:

def complete_load(self, text, line, begidx, endidx):
    arg = line.split()[1:]

    if not arg:
        completions = os.listdir('./')
    else:
        dir, part, base = arg[-1].rpartition('/')
        if part == '':
            dir = './'
        elif dir == '':
            dir = '/'            

        completions = []
        for f in os.listdir(dir):
            if f.startswith(base):
                if os.path.isfile(os.path.join(dir,f)):
                    completions.append(f)
                else:
                    completions.append(f+'/')

    return completions

より良いアイデアがあれば教えてください。注: Unix ディレクトリ構造に基づいてこのコードを作成しているため、この方法は Unix ファミリ OS でのみ機能すると思います。

于 2013-09-12T02:59:51.380 に答える
0

私はこれを行うことでこれを達成しました:

def complete_listFolder(self, text, line, begidx, endidx):
    path = os.path.relpath(os.path.normpath(line.split()[1]))
            if not os.path.isdir(path) and not os.path.isfile(path):
                baseName = os.path.basename(path)
                dirName = os.path.dirname(path)
                return fnmatch.filter(os.listdir(dirName), baseName + "*")

            completions = [completion for completion in os.listdir(path)]    
            return completions

もちろん、改善すべき点はたくさんありますが、これがお役に立てば幸いです。

=)

于 2016-06-17T14:41:10.273 に答える