module_completion.py
171 lines
| 4.8 KiB
| text/x-python
|
PythonLexer
Jeandet Alexis
|
r0 | # -*- coding: utf-8 -*- | ||
""" provide completions for modules 'import', | ||||
copied code with some modifications from IPython!""" | ||||
#----------------------------------------------------------------------------- | ||||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
from __future__ import print_function | ||||
# Stdlib imports | ||||
import inspect | ||||
import os | ||||
import re | ||||
import sys | ||||
from time import time | ||||
from zipimport import zipimporter | ||||
#----------------------------------------------------------------------------- | ||||
# Globals and constants | ||||
#----------------------------------------------------------------------------- | ||||
# Time in seconds after which the rootmodules will be stored in (nonpermanent) cache | ||||
TIMEOUT_STORAGE = 2 | ||||
# Time in seconds after which we give up | ||||
TIMEOUT_GIVEUP = 20 | ||||
# Regular expression for the python import statement | ||||
import_re = re.compile(r'.*(\.so|\.py[cod]?)$', re.IGNORECASE) | ||||
# global cache | ||||
cache = dict() | ||||
def module_list(path): | ||||
""" | ||||
Return the list containing the names of the modules available in the given | ||||
folder. | ||||
""" | ||||
# sys.path has the cwd as an empty string, but isdir/listdir need it as '.' | ||||
if path == '': | ||||
path = '.' | ||||
if os.path.isdir(path): | ||||
folder_list = os.listdir(path) | ||||
elif path.endswith('.egg'): | ||||
try: | ||||
folder_list = [f for f in zipimporter(path)._files] | ||||
except: | ||||
folder_list = [] | ||||
else: | ||||
folder_list = [] | ||||
if not folder_list: | ||||
return [] | ||||
# A few local constants to be used in loops below | ||||
isfile = os.path.isfile | ||||
pjoin = os.path.join | ||||
basename = os.path.basename | ||||
def is_importable_file(path): | ||||
"""Returns True if the provided path is a valid importable module""" | ||||
name, extension = os.path.splitext( path ) | ||||
return import_re.match(path) # and py3compat.isidentifier(name) | ||||
# Now find actual path matches for packages or modules | ||||
folder_list = [p for p in folder_list | ||||
if isfile(pjoin(path, p,'__init__.py')) | ||||
or is_importable_file(p) ] | ||||
return [basename(p).split('.')[0] for p in folder_list] | ||||
def get_root_modules(): | ||||
""" | ||||
Returns a list containing the names of all the modules available in the | ||||
folders of the pythonpath. | ||||
""" | ||||
global cache | ||||
if 'rootmodules' in cache: | ||||
return cache['rootmodules'] | ||||
t = time() | ||||
store = False | ||||
modules = list(sys.builtin_module_names) | ||||
for path in sys.path: | ||||
modules += module_list(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.)\n") | ||||
sys.stdout.flush() | ||||
if time() - t > TIMEOUT_GIVEUP: | ||||
print("This is taking too long, we give up.\n") | ||||
cache['rootmodules'] = [] | ||||
return [] | ||||
modules = set(modules) | ||||
if '__init__' in modules: | ||||
modules.remove('__init__') | ||||
modules = list(modules) | ||||
if store: | ||||
cache['rootmodules'] = modules | ||||
return modules | ||||
def is_importable(module, attr, only_modules): | ||||
if only_modules: | ||||
return inspect.ismodule(getattr(module, attr)) | ||||
else: | ||||
return not(attr[:2] == '__' and attr[-2:] == '__') | ||||
def try_import(mod, only_modules=False): | ||||
try: | ||||
m = __import__(mod) | ||||
except: | ||||
return [] | ||||
mods = mod.split('.') | ||||
for module in mods[1:]: | ||||
m = getattr(m, module) | ||||
m_is_init = hasattr(m, '__file__') and '__init__' in m.__file__ | ||||
completions = [] | ||||
if (not hasattr(m, '__file__')) or (not only_modules) or m_is_init: | ||||
completions.extend( [attr for attr in dir(m) if | ||||
is_importable(m, attr, only_modules)]) | ||||
completions.extend(getattr(m, '__all__', [])) | ||||
if m_is_init: | ||||
completions.extend(module_list(os.path.dirname(m.__file__))) | ||||
completions = set(completions) | ||||
if '__init__' in completions: | ||||
completions.remove('__init__') | ||||
return list(completions) | ||||
def module_completion(line): | ||||
""" | ||||
Returns a list containing the completion possibilities for an import line. | ||||
The line looks like this : | ||||
'import xml.d' | ||||
'from xml.dom import' | ||||
""" | ||||
words = line.split(' ') | ||||
nwords = len(words) | ||||
# from whatever <tab> -> 'import ' | ||||
if nwords == 3 and words[0] == 'from': | ||||
return ['import '] | ||||
# 'from xy<tab>' or 'import xy<tab>' | ||||
if nwords < 3 and (words[0] in ['import','from']) : | ||||
if nwords == 1: | ||||
return get_root_modules() | ||||
mod = words[1].split('.') | ||||
if len(mod) < 2: | ||||
return get_root_modules() | ||||
completion_list = try_import('.'.join(mod[:-1]), True) | ||||
return ['.'.join(mod[:-1] + [el]) for el in completion_list] | ||||
# 'from xyz import abc<tab>' | ||||
if nwords >= 3 and words[0] == 'from': | ||||
mod = words[1] | ||||
return try_import(mod) | ||||