2013-05-30 17 views
5

Sto lavorando alla creazione di uno strumento da riga di comando usando Cmd.cmd di Python e voglio aggiungere un comando "load" con l'argomento filename, che sta supportando tab- completamento.nomefile tab-completion in Cmd.cmd di Python

Riferendosi this e this, io pazzo un codice come questo:

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) 

Questo funziona bene per CWD, tuttavia, quando ho voglia di andare in subdir, dopo subdir/poi il "testo" della funzione complete_load diventa vuoto, quindi _complete_path func ritorna di nuovo cwd.

Non so come ottenere il contenuto della sottodirectory con il completamento della tabulazione. Per favore aiuto!

+0

Trovo difficile credere che nessuno abbia risposto a questo –

risposta

1

Non credo che questa sia la risposta migliore, ma ho avuto la funzione di quello che ho intenzione di come questo:

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) 

non ho usato "testo" dal complete_cmd(), ma l'uso un argomento "linea" di analisi direttamente. Se avete un'idea migliore, fatemelo sapere.

0

Ho la stessa idea con jinserk ma in modo diverso. Ecco il mio codice:

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 

per favore fatemi sapere se avete un'idea migliore. nota: Penso che questo metodo funzioni solo su SO della famiglia Unix perché creo questo codice basato sulla struttura di directory Unix.

3

Implementazione completamento nomi file con cmd è un po 'complicato perché il sottostante libreria readline interpreta i caratteri speciali come '/' e '-' (e altri) come separatori, e questo stabilisce che stringa all'interno di linea è di essere sostituito dai completamenti.

Per esempio,

> load /hom<tab> 

chiamate complete_load() con

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

'testo' non è "/ hom", perché la libreria readline analizzato la linea e restituisce la stringa dopo il '/'separatore. Il file complete_load() deve restituire un elenco di stringhe di completamento che iniziano con "hom", non "/ hom", poiché i completamenti sostituiranno la sottostringa iniziando dal begidx. Se la funzione complete_load() restituisce erroneamente [ '/ home'], la linea diventa,

> load //home 

che non è buono.

Altri caratteri sono considerati separatori tramite readline, non solo barre, , quindi non è possibile assumere la sottostringa prima che "testo" sia una directory principale.Per esempio:

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

chiamate complete_load() con

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

Supponendo/home/microfono contiene i file my-file1 e la mia-file2, i completamenti dovrebbe essere [ 'file1', ' file2 '], non [' my-file1 ',' my-file2 '], né ['/home/mike/my-file1 ','/home/mike/my-file2 ']. Se si restituisce i percorsi completi, il risultato è:

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

L'approccio che ho preso è stato quello di utilizzare il modulo glob per trovare i percorsi completi. Glob funziona per percorsi assoluti e percorsi relativi. Dopo aver trovato i percorsi, rimuovo la porzione "fissa", che è la sottostringa prima dell'hymidx.

In primo luogo, analizzare l'argomento porzione fissa, che è la sottostringa tra lo spazio e l'begidx.

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

L'argomento è tra lo spazio e la fine della riga. Aggiungi una stella per rendere un modello di ricerca glob.

aggiungo un '/' ai risultati che sono le directory, in quanto ciò rende più facile di attraversare le directory con il completamento scheda (altrimenti è necessario premere il tasto scheda due volte per ogni directory), e rende evidente a l'utente quali elementi di completamento sono le directory e quali sono i file.

Infine rimuovere la parte "fissa" dei percorsi, quindi readline sostituirà solo la parte "testo".

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

Suppongo che una sezione sia più efficiente di una chiamata a replace(). Basta calcolare l'indice per la fetta, che è qualcosa come begidx - before_arg. – meffie

+0

Ho provato un approccio simile, sfortunatamente non gestisce gli spazi bianchi nei percorsi. shlex.split (line) non può aiutare eather in quanto rimuove gli spazi tra gli elementi della linea in modo che non sia possibile trovare l'elemento e la posizione corretti in quell'elemento. Davvero sfortunato. –

4

vostro problema principale è che la libreria readline è che delimita le cose sulla base di che è insieme di delimitatori di default:

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

Quando scheda completamento per un nome di file rimuovere tutto da questo, ma gli spazi bianchi.

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

Dopo aver impostato i delimitatori, il parametro "testo" per la funzione di completamento dovrebbe essere più quello che ci si aspetta.

Questo risolve anche i problemi più comuni con il completamento della tabulazione che duplica parte del testo.

0

Ho compiuto questo facendo:

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 

Fuori rotta non c'è molto da migliorare, ma spero che questo aiuta.

=)

0

io uso shlex per analizzare la linea. A differenza di altre soluzioni, supporto i percorsi citati e di escape (ovvero i percorsi con spazi bianchi) e il completamento funziona per qualsiasi posizione del cursore. Non ho eseguito test approfonditi quindi il tuo chilometraggio potrebbe variare.

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

Questo funziona per me.Rimuovi il "sé" se non lo stai utilizzando in una classe.

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