2013-05-30 28 views
5

Tôi đang làm một công cụ dòng lệnh bằng Cmd.cmd của Python và tôi muốn thêm lệnh "tải" với đối số tên tệp, là tab hỗ trợ- hoàn thành.hoàn thành tab tên tệp trong Cmd.cmd của Python

Nhắc thisthis, tôi điên một mã như thế này:

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) 

này hoạt động tốt cho trẻ khuyết tật, tuy nhiên, khi tôi muốn đi vào subdir, sau subdir/sau đó là "văn bản" của chức năng complete_load trở thành trống, vì vậy _complete_path func trả về cwd một lần nữa.

Tôi không biết cách tải nội dung của subdir bằng tab-completion. Xin vui lòng giúp đỡ!

+0

Tôi thấy khó tin rằng không ai trả lời câu hỏi này –

Trả lời

1

Tôi không nghĩ rằng đây là câu trả lời tốt nhất, nhưng tôi có chức năng gì tôi có ý định như thế này:

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) 

Tôi không sử dụng "văn bản" từ complete_cmd(), nhưng việc sử dụng một đối số "dòng" phân tích cú pháp trực tiếp. Nếu bạn có bất kỳ ý tưởng nào tốt hơn, vui lòng cho tôi biết.

0

Tôi có cùng ý tưởng với jinserk nhưng theo cách khác. Đây là mã của tôi:

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 

hãy cho tôi biết nếu bạn có ý tưởng tốt hơn. lưu ý: Tôi nghĩ rằng phương pháp này chỉ hoạt động trên hệ điều hành Unix gia đình bởi vì tôi tạo mã này dựa trên cấu trúc thư mục Unix.

3

Thực hiện hoàn thành tên tập tin với cmd là một chút khó khăn vì thư viện readline cơ bản giải thích ký tự đặc biệt như '/' và '-' (và những người khác) như dải phân cách, và điều này đặt ra mà chuỗi con trong dòng là được thay thế bằng các lần hoàn thành.

Ví dụ,

> load /hom<tab> 

cuộc gọi complete_load() với

text='hom', line='load /hom', begidx=6, endidx=9 
text is line[begidx:endidx] 

'text' không phải là "/ hom" vì thư viện readline phân tích dòng và trả về chuỗi sau '/'dấu phân tách. Hàm complete_load() phải trả lại danh sách các chuỗi hoàn thành bắt đầu bằng "hom", không phải "/ hom", vì số lần hoàn thành sẽ thay thế chuỗi con bắt đầu từ begidx. Nếu hàm complete_load() trả về không chính xác ['/ home'], dòng trở thành,

> load //home 

không tốt.

Các ký tự khác được coi là dấu phân cách bằng dòng đọc, không chỉ các dấu gạch chéo, do đó bạn không thể giả sử chuỗi con trước 'văn bản' là thư mục chính.Đối với dụ:

> load /home/mike/my-file<tab> 

cuộc gọi complete_load() với

text='file', line='load /home/mike/my-file', begidx=19, endidx=23 

Giả sử/home/mike chứa các tập tin my-file1 và my-file2, các lần hoàn thành nên [ 'file1', ' file2 '], không phải [' my-file1 ',' my-file2 '], cũng không phải ['/home/mike/my-file1 ','/home/mike/my-file2 ']. Nếu bạn trả lại đường dẫn đầy đủ, kết quả là:

> load /home/mike/my-file/home/mike/my-file1 

Cách tiếp cận tôi lấy là sử dụng mô-đun glob để tìm đường dẫn đầy đủ. Glob hoạt động cho đường dẫn tuyệt đối và đường dẫn tương đối. Sau khi tìm thấy các đường dẫn, tôi xóa phần "cố định", là chuỗi con trước dấu ngã.

Đầu tiên, phân tích cú pháp đối số phần cố định, là chuỗi con giữa không gian và begidx.

index = line.rindex(' ', 0, begidx) # -1 if not found 
fixed = line[index + 1: begidx] 

Đối số nằm giữa khoảng trắng và cuối dòng. Nối một ngôi sao để tạo mẫu tìm kiếm toàn cục.

Tôi nối thêm '/' vào kết quả là thư mục, vì điều này giúp dễ dàng hơn để duyệt thư mục có hoàn thành tab (nếu không bạn cần nhấn phím tab hai lần cho mỗi thư mục). người dùng mục hoàn thành nào là các thư mục và đó là các tệp.

Cuối cùng, hãy xóa phần "cố định" của đường dẫn, vì vậy đường readline sẽ thay thế chỉ phần "văn bản".

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

Tôi cho rằng một lát sẽ hiệu quả hơn lệnh gọi thay thế(). Chỉ cần tính toán chỉ mục cho slice, cái gì đó giống như begidx - before_arg. – meffie

+0

Tôi đã thử một cách tiếp cận tương tự, tiếc là nó không xử lý khoảng trống trong đường dẫn. shlex.split (dòng) không thể giúp eather vì nó loại bỏ khoảng trắng giữa các phần tử đường nên không thể tìm thấy phần tử và vị trí chính xác trong phần tử đó. Thật không may. –

4

vấn đề chính của bạn là thư viện readline được phân chia ranh giới điều dựa trên đó là bộ delimiter mặc định:

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

Khi tab hoàn thành cho một tên tập tin để loại bỏ tất cả mọi thứ từ này nhưng khoảng trắng.

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

Sau khi đặt dấu phân cách, tham số 'văn bản' thành hàm hoàn thành của bạn phải là những gì bạn mong đợi hơn.

Điều này cũng giải quyết các sự cố thường gặp phải khi hoàn thành tab sao chép một phần văn bản của bạn.

0

tôi thực hiện điều này bằng cách thực hiện:

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 

Tắt Tất nhiên có rất nhiều là để cải thiện nhưng hy vọng điều này sẽ giúp.

=)

0

Tôi sử dụng shlex để phân tích cú pháp dòng. Ngược lại với một số giải pháp khác, tôi hỗ trợ đường dẫn được trích dẫn và thoát (tức là đường dẫn có khoảng trống) và công việc hoàn thành hoạt động cho bất kỳ vị trí con trỏ nào. Tôi đã không kiểm tra rộng rãi để số dặm của bạn có thể thay đổi.

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

Điều này phù hợp với tôi.Hủy bỏ "tự" nếu bạn không sử dụng trong một lớp học.

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