簡體   English   中英

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

[英]filename tab-completion in Cmd.cmd of Python

我正在使用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)

這適用於cwd,但是,當我想進入subdir時,在subdir /之后,complete_load函數的“text”變為空白,所以_complete_path func再次返回cwd。

我不知道如何使用tab-completion獲取subdir的內容。 請幫忙!

您的主要問題是readline庫基於它的默認分隔符集來分隔事物:

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

當tab填寫文件名時,我刪除了這個但是空白的所有內容。

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

設置分隔符后,完成功能的'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適用於絕對路徑和相對路徑。 找到路徑后,我刪除了“fixed”部分,它是begidx之前的子字符串。

首先,解析fixed部分參數,它是space和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”參數。 如果您有任何更好的想法,請告訴我。

我對jinserk有同樣的想法,但以不同的方式。 這是我的代碼:

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目錄結構創建了這個代碼。

我使用shlex來解析這條線。 與其他一些解決方案相比,我支持引用和轉義路徑(即具有空格的路徑),並且完成適用於任何光標位置。 我沒有廣泛測試,所以你的里程可能會有所不同。

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()

我通過這樣做完成了這個:

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

當然,有很多改善,但希望這有所幫助。

=)

這適合我。 如果您沒有在課堂上使用,請刪除“自我”。

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

def complete_load(self, text, line, start_idx, end_idx):
    mline = line.split(' ')[-1]
    offs = len(mline) - len(text)
    completions = []
    if line.split()[-2] == '-p':
        completions = self._complete_path(mline)
    return [s[offs:] for s in completions if s.startswith(mline)]

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM