autoimport.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import re
  2. from rope.base import (exceptions, pynames, resourceobserver,
  3. taskhandle, pyobjects, builtins, resources)
  4. from rope.refactor import importutils
  5. class AutoImport(object):
  6. """A class for finding the module that provides a name
  7. This class maintains a cache of global names in python modules.
  8. Note that this cache is not accurate and might be out of date.
  9. """
  10. def __init__(self, project, observe=True, underlined=False):
  11. """Construct an AutoImport object
  12. If `observe` is `True`, listen for project changes and update
  13. the cache.
  14. If `underlined` is `True`, underlined names are cached, too.
  15. """
  16. self.project = project
  17. self.underlined = underlined
  18. self.names = project.data_files.read_data('globalnames')
  19. if self.names is None:
  20. self.names = {}
  21. project.data_files.add_write_hook(self._write)
  22. # XXX: using a filtered observer
  23. observer = resourceobserver.ResourceObserver(
  24. changed=self._changed, moved=self._moved, removed=self._removed)
  25. if observe:
  26. project.add_observer(observer)
  27. def import_assist(self, starting):
  28. """Return a list of ``(name, module)`` tuples
  29. This function tries to find modules that have a global name
  30. that starts with `starting`.
  31. """
  32. # XXX: breaking if gave up! use generators
  33. result = []
  34. for module in self.names:
  35. for global_name in self.names[module]:
  36. if global_name.startswith(starting):
  37. result.append((global_name, module))
  38. return result
  39. def get_modules(self, name):
  40. """Return the list of modules that have global `name`"""
  41. result = []
  42. for module in self.names:
  43. if name in self.names[module]:
  44. result.append(module)
  45. return result
  46. def get_all_names(self):
  47. """Return the list of all cached global names"""
  48. result = set()
  49. for module in self.names:
  50. result.update(set(self.names[module]))
  51. return result
  52. def get_name_locations(self, name):
  53. """Return a list of ``(resource, lineno)`` tuples"""
  54. result = []
  55. pycore = self.project.pycore
  56. for module in self.names:
  57. if name in self.names[module]:
  58. try:
  59. pymodule = pycore.get_module(module)
  60. if name in pymodule:
  61. pyname = pymodule[name]
  62. module, lineno = pyname.get_definition_location()
  63. if module is not None:
  64. resource = module.get_module().get_resource()
  65. if resource is not None and lineno is not None:
  66. result.append((resource, lineno))
  67. except exceptions.ModuleNotFoundError:
  68. pass
  69. return result
  70. def generate_cache(self, resources=None, underlined=None,
  71. task_handle=taskhandle.NullTaskHandle()):
  72. """Generate global name cache for project files
  73. If `resources` is a list of `rope.base.resource.File`\s, only
  74. those files are searched; otherwise all python modules in the
  75. project are cached.
  76. """
  77. if resources is None:
  78. resources = self.project.pycore.get_python_files()
  79. job_set = task_handle.create_jobset(
  80. 'Generatig autoimport cache', len(resources))
  81. for file in resources:
  82. job_set.started_job('Working on <%s>' % file.path)
  83. self.update_resource(file, underlined)
  84. job_set.finished_job()
  85. def generate_modules_cache(self, modules, underlined=None,
  86. task_handle=taskhandle.NullTaskHandle()):
  87. """Generate global name cache for modules listed in `modules`"""
  88. job_set = task_handle.create_jobset(
  89. 'Generatig autoimport cache for modules', len(modules))
  90. for modname in modules:
  91. job_set.started_job('Working on <%s>' % modname)
  92. if modname.endswith('.*'):
  93. mod = self.project.pycore.find_module(modname[:-2])
  94. if mod:
  95. for sub in submodules(mod):
  96. self.update_resource(sub, underlined)
  97. else:
  98. self.update_module(modname, underlined)
  99. job_set.finished_job()
  100. def clear_cache(self):
  101. """Clear all entries in global-name cache
  102. It might be a good idea to use this function before
  103. regenerating global names.
  104. """
  105. self.names.clear()
  106. def find_insertion_line(self, code):
  107. """Guess at what line the new import should be inserted"""
  108. match = re.search(r'^(def|class)\s+', code)
  109. if match is not None:
  110. code = code[:match.start()]
  111. try:
  112. pymodule = self.project.pycore.get_string_module(code)
  113. except exceptions.ModuleSyntaxError:
  114. return 1
  115. testmodname = '__rope_testmodule_rope'
  116. importinfo = importutils.NormalImport(((testmodname, None),))
  117. module_imports = importutils.get_module_imports(
  118. self.project.pycore, pymodule)
  119. module_imports.add_import(importinfo)
  120. code = module_imports.get_changed_source()
  121. offset = code.index(testmodname)
  122. lineno = code.count('\n', 0, offset) + 1
  123. return lineno
  124. def update_resource(self, resource, underlined=None):
  125. """Update the cache for global names in `resource`"""
  126. try:
  127. pymodule = self.project.pycore.resource_to_pyobject(resource)
  128. modname = self._module_name(resource)
  129. self._add_names(pymodule, modname, underlined)
  130. except exceptions.ModuleSyntaxError:
  131. pass
  132. def update_module(self, modname, underlined=None):
  133. """Update the cache for global names in `modname` module
  134. `modname` is the name of a module.
  135. """
  136. try:
  137. pymodule = self.project.pycore.get_module(modname)
  138. self._add_names(pymodule, modname, underlined)
  139. except exceptions.ModuleNotFoundError:
  140. pass
  141. def _module_name(self, resource):
  142. return self.project.pycore.modname(resource)
  143. def _add_names(self, pymodule, modname, underlined):
  144. if underlined is None:
  145. underlined = self.underlined
  146. globals = []
  147. if isinstance(pymodule, pyobjects.PyDefinedObject):
  148. attributes = pymodule._get_structural_attributes()
  149. else:
  150. attributes = pymodule.get_attributes()
  151. for name, pyname in attributes.items():
  152. if not underlined and name.startswith('_'):
  153. continue
  154. if isinstance(pyname, (pynames.AssignedName, pynames.DefinedName)):
  155. globals.append(name)
  156. if isinstance(pymodule, builtins.BuiltinModule):
  157. globals.append(name)
  158. self.names[modname] = globals
  159. def _write(self):
  160. self.project.data_files.write_data('globalnames', self.names)
  161. def _changed(self, resource):
  162. if not resource.is_folder():
  163. self.update_resource(resource)
  164. def _moved(self, resource, newresource):
  165. if not resource.is_folder():
  166. modname = self._module_name(resource)
  167. if modname in self.names:
  168. del self.names[modname]
  169. self.update_resource(newresource)
  170. def _removed(self, resource):
  171. if not resource.is_folder():
  172. modname = self._module_name(resource)
  173. if modname in self.names:
  174. del self.names[modname]
  175. def submodules(mod):
  176. if isinstance(mod, resources.File):
  177. if mod.name.endswith('.py') and mod.name != '__init__.py':
  178. return set([mod])
  179. return set()
  180. if not mod.has_child('__init__.py'):
  181. return set()
  182. result = set([mod])
  183. for child in mod.get_children():
  184. result |= submodules(child)
  185. return result