author |
Steve Losh <steve@stevelosh.com> |
date |
Thu, 23 Jun 2011 17:10:55 -0400 |
parents |
48cacfdc2ca6 |
children |
(none) |
import re
from rope.base import (exceptions, pynames, resourceobserver,
taskhandle, pyobjects, builtins, resources)
from rope.refactor import importutils
class AutoImport(object):
"""A class for finding the module that provides a name
This class maintains a cache of global names in python modules.
Note that this cache is not accurate and might be out of date.
"""
def __init__(self, project, observe=True, underlined=False):
"""Construct an AutoImport object
If `observe` is `True`, listen for project changes and update
the cache.
If `underlined` is `True`, underlined names are cached, too.
"""
self.project = project
self.underlined = underlined
self.names = project.data_files.read_data('globalnames')
if self.names is None:
self.names = {}
project.data_files.add_write_hook(self._write)
# XXX: using a filtered observer
observer = resourceobserver.ResourceObserver(
changed=self._changed, moved=self._moved, removed=self._removed)
if observe:
project.add_observer(observer)
def import_assist(self, starting):
"""Return a list of ``(name, module)`` tuples
This function tries to find modules that have a global name
that starts with `starting`.
"""
# XXX: breaking if gave up! use generators
result = []
for module in self.names:
for global_name in self.names[module]:
if global_name.startswith(starting):
result.append((global_name, module))
return result
def get_modules(self, name):
"""Return the list of modules that have global `name`"""
result = []
for module in self.names:
if name in self.names[module]:
result.append(module)
return result
def get_all_names(self):
"""Return the list of all cached global names"""
result = set()
for module in self.names:
result.update(set(self.names[module]))
return result
def get_name_locations(self, name):
"""Return a list of ``(resource, lineno)`` tuples"""
result = []
pycore = self.project.pycore
for module in self.names:
if name in self.names[module]:
try:
pymodule = pycore.get_module(module)
if name in pymodule:
pyname = pymodule[name]
module, lineno = pyname.get_definition_location()
if module is not None:
resource = module.get_module().get_resource()
if resource is not None and lineno is not None:
result.append((resource, lineno))
except exceptions.ModuleNotFoundError:
pass
return result
def generate_cache(self, resources=None, underlined=None,
task_handle=taskhandle.NullTaskHandle()):
"""Generate global name cache for project files
If `resources` is a list of `rope.base.resource.File`\s, only
those files are searched; otherwise all python modules in the
project are cached.
"""
if resources is None:
resources = self.project.pycore.get_python_files()
job_set = task_handle.create_jobset(
'Generatig autoimport cache', len(resources))
for file in resources:
job_set.started_job('Working on <%s>' % file.path)
self.update_resource(file, underlined)
job_set.finished_job()
def generate_modules_cache(self, modules, underlined=None,
task_handle=taskhandle.NullTaskHandle()):
"""Generate global name cache for modules listed in `modules`"""
job_set = task_handle.create_jobset(
'Generatig autoimport cache for modules', len(modules))
for modname in modules:
job_set.started_job('Working on <%s>' % modname)
if modname.endswith('.*'):
mod = self.project.pycore.find_module(modname[:-2])
if mod:
for sub in submodules(mod):
self.update_resource(sub, underlined)
else:
self.update_module(modname, underlined)
job_set.finished_job()
def clear_cache(self):
"""Clear all entries in global-name cache
It might be a good idea to use this function before
regenerating global names.
"""
self.names.clear()
def find_insertion_line(self, code):
"""Guess at what line the new import should be inserted"""
match = re.search(r'^(def|class)\s+', code)
if match is not None:
code = code[:match.start()]
try:
pymodule = self.project.pycore.get_string_module(code)
except exceptions.ModuleSyntaxError:
return 1
testmodname = '__rope_testmodule_rope'
importinfo = importutils.NormalImport(((testmodname, None),))
module_imports = importutils.get_module_imports(
self.project.pycore, pymodule)
module_imports.add_import(importinfo)
code = module_imports.get_changed_source()
offset = code.index(testmodname)
lineno = code.count('\n', 0, offset) + 1
return lineno
def update_resource(self, resource, underlined=None):
"""Update the cache for global names in `resource`"""
try:
pymodule = self.project.pycore.resource_to_pyobject(resource)
modname = self._module_name(resource)
self._add_names(pymodule, modname, underlined)
except exceptions.ModuleSyntaxError:
pass
def update_module(self, modname, underlined=None):
"""Update the cache for global names in `modname` module
`modname` is the name of a module.
"""
try:
pymodule = self.project.pycore.get_module(modname)
self._add_names(pymodule, modname, underlined)
except exceptions.ModuleNotFoundError:
pass
def _module_name(self, resource):
return self.project.pycore.modname(resource)
def _add_names(self, pymodule, modname, underlined):
if underlined is None:
underlined = self.underlined
globals = []
if isinstance(pymodule, pyobjects.PyDefinedObject):
attributes = pymodule._get_structural_attributes()
else:
attributes = pymodule.get_attributes()
for name, pyname in attributes.items():
if not underlined and name.startswith('_'):
continue
if isinstance(pyname, (pynames.AssignedName, pynames.DefinedName)):
globals.append(name)
if isinstance(pymodule, builtins.BuiltinModule):
globals.append(name)
self.names[modname] = globals
def _write(self):
self.project.data_files.write_data('globalnames', self.names)
def _changed(self, resource):
if not resource.is_folder():
self.update_resource(resource)
def _moved(self, resource, newresource):
if not resource.is_folder():
modname = self._module_name(resource)
if modname in self.names:
del self.names[modname]
self.update_resource(newresource)
def _removed(self, resource):
if not resource.is_folder():
modname = self._module_name(resource)
if modname in self.names:
del self.names[modname]
def submodules(mod):
if isinstance(mod, resources.File):
if mod.name.endswith('.py') and mod.name != '__init__.py':
return set([mod])
return set()
if not mod.has_child('__init__.py'):
return set()
result = set([mod])
for child in mod.get_children():
result |= submodules(child)
return result