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