#!/usr/bin/env python """ Implementations for various useful completers See Extensions/ipy_stock_completers.py on examples of how to enable a completer, but the basic idea is to do: ip.set_hook('complete_command', svn_completer, str_key = 'svn') """ import IPython.ipapi import glob,os,shlex,sys import inspect from time import time ip = IPython.ipapi.get() TIMEOUT_STORAGE = 3 #Time in seconds after which the rootmodules will be stored TIMEOUT_GIVEUP = 20 #Time in seconds after which we give up def quick_completer(cmd, completions): """ Easily create a trivial completer for a command. Takes either a list of completions, or all completions in string (that will be split on whitespace) Example:: [d:\ipython]|1> import ipy_completers [d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz']) [d:\ipython]|3> foo b bar baz [d:\ipython]|3> foo ba """ if isinstance(completions, basestring): completions = completions.split() def do_complete(self,event): return completions ip.set_hook('complete_command',do_complete, str_key = cmd) def getRootModules(): """ Returns a list containing the names of all the modules available in the folders of the pythonpath. """ modules = [] if ip.db.has_key('rootmodules'): return ip.db['rootmodules'] t = time() store = False for path in sys.path: modules += moduleList(path) if time() - t >= TIMEOUT_STORAGE and not store: store = True print "\nCaching the list of root modules, please wait!" print "(This will only be done once - type '%rehashx' to " + \ "reset cache!)" print if time() - t > TIMEOUT_GIVEUP: print "This is taking too long, we give up." print ip.db['rootmodules'] = [] return [] modules += sys.builtin_module_names modules = list(set(modules)) if '__init__' in modules: modules.remove('__init__') modules = list(set(modules)) if store: ip.db['rootmodules'] = modules return modules def moduleList(path): """ Return the list containing the names of the modules available in the given folder. """ if os.path.isdir(path): folder_list = os.listdir(path) else: folder_list = [] #folder_list = glob.glob(os.path.join(path,'*')) folder_list = [path for path in folder_list \ if os.path.exists(os.path.join(path,'__init__.py'))\ or path[-3:] in ('.py','.so')\ or path[-4:] in ('.pyc','.pyo')] folder_list += folder_list folder_list = [os.path.basename(path).split('.')[0] for path in folder_list] return folder_list def moduleCompletion(line): """ Returns a list containing the completion possibilities for an import line. The line looks like this : 'import xml.d' 'from xml.dom import' """ def tryImport(mod, only_modules=False): def isImportable(module, attr): if only_modules: return inspect.ismodule(getattr(module, attr)) else: return not(attr[:2] == '__' and attr[-2:] == '__') try: m = __import__(mod) except: return [] mods = mod.split('.') for module in mods[1:]: m = getattr(m,module) if (not hasattr(m, '__file__')) or (not only_modules) or\ (hasattr(m, '__file__') and '__init__' in m.__file__): completion_list = [attr for attr in dir(m) if isImportable(m, attr)] completion_list.extend(getattr(m,'__all__',[])) if hasattr(m, '__file__') and '__init__' in m.__file__: completion_list.extend(moduleList(os.path.dirname(m.__file__))) completion_list = list(set(completion_list)) if '__init__' in completion_list: completion_list.remove('__init__') return completion_list words = line.split(' ') if len(words) == 3 and words[0] == 'from': return ['import '] if len(words) < 3 and (words[0] in ['import','from']) : if len(words) == 1: return getRootModules() mod = words[1].split('.') if len(mod) < 2: return getRootModules() completion_list = tryImport('.'.join(mod[:-1]), True) completion_list = ['.'.join(mod[:-1] + [el]) for el in completion_list] return completion_list if len(words) >= 3 and words[0] == 'from': mod = words[1] return tryImport(mod) def vcs_completer(commands, event): """ utility to make writing typical version control app completers easier VCS command line apps typically have the format: [sudo ]PROGNAME [help] [command] file file... """ cmd_param = event.line.split() if event.line.endswith(' '): cmd_param.append('') if cmd_param[0] == 'sudo': cmd_param = cmd_param[1:] if len(cmd_param) == 2 or 'help' in cmd_param: return commands.split() return ip.IP.Completer.file_matches(event.symbol) def apt_completers(self, event): """ This should return a list of strings with possible completions. Note that all the included strings that don't start with event.symbol are removed, in order to not confuse readline. """ # print event # dbg # commands are only suggested for the 'command' part of package manager # invocation cmd = (event.line + "").rsplit(None,1)[0] # print cmd if cmd.endswith('apt-get') or cmd.endswith('yum'): return ['update', 'upgrade', 'install', 'remove'] # later on, add dpkg -l / whatever to get list of possible # packages, add switches etc. for the rest of command line # filling raise IPython.ipapi.TryNext pkg_cache = None def module_completer(self,event): """ Give completions after user has typed 'import ...' or 'from ...'""" # This works in all versions of python. While 2.5 has # pkgutil.walk_packages(), that particular routine is fairly dangerous, # since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full # of possibly problematic side effects. # This search the folders in the sys.path for available modules. return moduleCompletion(event.line) svn_commands = """\ add blame praise annotate ann cat checkout co cleanup commit ci copy cp delete del remove rm diff di export help ? h import info list ls lock log merge mkdir move mv rename ren propdel pdel pd propedit pedit pe propget pget pg proplist plist pl propset pset ps resolved revert status stat st switch sw unlock update """ def svn_completer(self,event): return vcs_completer(svn_commands, event) hg_commands = """ add addremove annotate archive backout branch branches bundle cat clone commit copy diff export grep heads help identify import incoming init locate log manifest merge outgoing parents paths pull push qapplied qclone qcommit qdelete qdiff qfold qguard qheader qimport qinit qnew qnext qpop qprev qpush qrefresh qrename qrestore qsave qselect qseries qtop qunapplied recover remove rename revert rollback root serve showconfig status strip tag tags tip unbundle update verify version """ def hg_completer(self,event): """ Completer for mercurial commands """ return vcs_completer(hg_commands, event) bzr_commands = """ add annotate bind branch break-lock bundle-revisions cat check checkout commit conflicts deleted diff export gannotate gbranch gcommit gdiff help ignore ignored info init init-repository inventory log merge missing mkdir mv nick pull push reconcile register-branch remerge remove renames resolve revert revno root serve sign-my-commits status testament unbind uncommit unknowns update upgrade version version-info visualise whoami """ def bzr_completer(self,event): """ Completer for bazaar commands """ cmd_param = event.line.split() if event.line.endswith(' '): cmd_param.append('') if len(cmd_param) > 2: cmd = cmd_param[1] param = cmd_param[-1] output_file = (param == '--output=') if cmd == 'help': return bzr_commands.split() elif cmd in ['bundle-revisions','conflicts', 'deleted','nick','register-branch', 'serve','unbind','upgrade','version', 'whoami'] and not output_file: return [] else: # the rest are probably file names return ip.IP.Completer.file_matches(event.symbol) return bzr_commands.split() def shlex_split(x): """Helper function to split lines into segments.""" #shlex.split raise exception if syntax error in sh syntax #for example if no closing " is found. This function keeps dropping #the last character of the line until shlex.split does not raise #exception. Adds end of the line to the result of shlex.split #example: %run "c:/python -> ['%run','"c:/python'] endofline=[] while x!="": try: comps=shlex.split(x) if len(endofline)>=1: comps.append("".join(endofline)) return comps except ValueError: endofline=[x[-1:]]+endofline x=x[:-1] return ["".join(endofline)] def runlistpy(self, event): comps = shlex_split(event.line) relpath = (len(comps) > 1 and comps[-1] or '').strip("'\"") #print "\nev=",event # dbg #print "rp=",relpath # dbg #print 'comps=',comps # dbg lglob = glob.glob isdir = os.path.isdir if relpath.startswith('~'): relpath = os.path.expanduser(relpath) dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') if isdir(f)] # Find if the user has already typed the first filename, after which we # should complete on all files, since after the first one other files may # be arguments to the input script. #filter( if filter(lambda f: f.endswith('.py') or f.endswith('.ipy'),comps): pys = [f.replace('\\','/') for f in lglob('*')] else: pys = [f.replace('\\','/') for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy')] return dirs + pys def cd_completer(self, event): relpath = event.symbol #print event # dbg if '-b' in event.line: # return only bookmark completions bkms = self.db.get('bookmarks',{}) return bkms.keys() if event.symbol == '-': # jump in directory history by number ents = ['-%d [%s]' % (i,s) for i,s in enumerate(ip.user_ns['_dh'])] if len(ents) > 1: return ents return [] if relpath.startswith('~'): relpath = os.path.expanduser(relpath).replace('\\','/') found = [] for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*') if os.path.isdir(f)]: if ' ' in d: # we don't want to deal with any of that, complex code # for this is elsewhere raise IPython.ipapi.TryNext found.append( d ) if not found: if os.path.isdir(relpath): return [relpath] raise IPython.ipapi.TryNext return found