generate.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. import rope.base.evaluate
  2. from rope.base import change, pyobjects, exceptions, pynames, worder, codeanalyze
  3. from rope.refactor import sourceutils, importutils, functionutils, suites
  4. def create_generate(kind, project, resource, offset):
  5. """A factory for creating `Generate` objects
  6. `kind` can be 'variable', 'function', 'class', 'module' or
  7. 'package'.
  8. """
  9. generate = eval('Generate' + kind.title())
  10. return generate(project, resource, offset)
  11. def create_module(project, name, sourcefolder=None):
  12. """Creates a module and returns a `rope.base.resources.File`"""
  13. if sourcefolder is None:
  14. sourcefolder = project.root
  15. packages = name.split('.')
  16. parent = sourcefolder
  17. for package in packages[:-1]:
  18. parent = parent.get_child(package)
  19. return parent.create_file(packages[-1] + '.py')
  20. def create_package(project, name, sourcefolder=None):
  21. """Creates a package and returns a `rope.base.resources.Folder`"""
  22. if sourcefolder is None:
  23. sourcefolder = project.root
  24. packages = name.split('.')
  25. parent = sourcefolder
  26. for package in packages[:-1]:
  27. parent = parent.get_child(package)
  28. made_packages = parent.create_folder(packages[-1])
  29. made_packages.create_file('__init__.py')
  30. return made_packages
  31. class _Generate(object):
  32. def __init__(self, project, resource, offset):
  33. self.project = project
  34. self.resource = resource
  35. self.info = self._generate_info(project, resource, offset)
  36. self.name = self.info.get_name()
  37. self._check_exceptional_conditions()
  38. def _generate_info(self, project, resource, offset):
  39. return _GenerationInfo(project.pycore, resource, offset)
  40. def _check_exceptional_conditions(self):
  41. if self.info.element_already_exists():
  42. raise exceptions.RefactoringError(
  43. 'Element <%s> already exists.' % self.name)
  44. if not self.info.primary_is_found():
  45. raise exceptions.RefactoringError(
  46. 'Cannot determine the scope <%s> should be defined in.' % self.name)
  47. def get_changes(self):
  48. changes = change.ChangeSet('Generate %s <%s>' %
  49. (self._get_element_kind(), self.name))
  50. indents = self.info.get_scope_indents()
  51. blanks = self.info.get_blank_lines()
  52. base_definition = sourceutils.fix_indentation(self._get_element(), indents)
  53. definition = '\n' * blanks[0] + base_definition + '\n' * blanks[1]
  54. resource = self.info.get_insertion_resource()
  55. start, end = self.info.get_insertion_offsets()
  56. collector = codeanalyze.ChangeCollector(resource.read())
  57. collector.add_change(start, end, definition)
  58. changes.add_change(change.ChangeContents(
  59. resource, collector.get_changed()))
  60. return changes
  61. def get_location(self):
  62. return (self.info.get_insertion_resource(),
  63. self.info.get_insertion_lineno())
  64. def _get_element_kind(self):
  65. raise NotImplementedError()
  66. def _get_element(self):
  67. raise NotImplementedError()
  68. class GenerateFunction(_Generate):
  69. def _generate_info(self, project, resource, offset):
  70. return _FunctionGenerationInfo(project.pycore, resource, offset)
  71. def _get_element(self):
  72. decorator = ''
  73. args = []
  74. if self.info.is_static_method():
  75. decorator = '@staticmethod\n'
  76. if self.info.is_method() or self.info.is_constructor() or \
  77. self.info.is_instance():
  78. args.append('self')
  79. args.extend(self.info.get_passed_args())
  80. definition = '%sdef %s(%s):\n pass\n' % (decorator, self.name,
  81. ', '.join(args))
  82. return definition
  83. def _get_element_kind(self):
  84. return 'Function'
  85. class GenerateVariable(_Generate):
  86. def _get_element(self):
  87. return '%s = None\n' % self.name
  88. def _get_element_kind(self):
  89. return 'Variable'
  90. class GenerateClass(_Generate):
  91. def _get_element(self):
  92. return 'class %s(object):\n pass\n' % self.name
  93. def _get_element_kind(self):
  94. return 'Class'
  95. class GenerateModule(_Generate):
  96. def get_changes(self):
  97. package = self.info.get_package()
  98. changes = change.ChangeSet('Generate Module <%s>' % self.name)
  99. new_resource = self.project.get_file('%s/%s.py' % (package.path, self.name))
  100. if new_resource.exists():
  101. raise exceptions.RefactoringError(
  102. 'Module <%s> already exists' % new_resource.path)
  103. changes.add_change(change.CreateResource(new_resource))
  104. changes.add_change(_add_import_to_module(
  105. self.project.pycore, self.resource, new_resource))
  106. return changes
  107. def get_location(self):
  108. package = self.info.get_package()
  109. return (package.get_child('%s.py' % self.name) , 1)
  110. class GeneratePackage(_Generate):
  111. def get_changes(self):
  112. package = self.info.get_package()
  113. changes = change.ChangeSet('Generate Package <%s>' % self.name)
  114. new_resource = self.project.get_folder('%s/%s' % (package.path, self.name))
  115. if new_resource.exists():
  116. raise exceptions.RefactoringError(
  117. 'Package <%s> already exists' % new_resource.path)
  118. changes.add_change(change.CreateResource(new_resource))
  119. changes.add_change(_add_import_to_module(
  120. self.project.pycore, self.resource, new_resource))
  121. child = self.project.get_folder(package.path + '/' + self.name)
  122. changes.add_change(change.CreateFile(child, '__init__.py'))
  123. return changes
  124. def get_location(self):
  125. package = self.info.get_package()
  126. child = package.get_child(self.name)
  127. return (child.get_child('__init__.py') , 1)
  128. def _add_import_to_module(pycore, resource, imported):
  129. pymodule = pycore.resource_to_pyobject(resource)
  130. import_tools = importutils.ImportTools(pycore)
  131. module_imports = import_tools.module_imports(pymodule)
  132. module_name = pycore.modname(imported)
  133. new_import = importutils.NormalImport(((module_name, None), ))
  134. module_imports.add_import(new_import)
  135. return change.ChangeContents(resource, module_imports.get_changed_source())
  136. class _GenerationInfo(object):
  137. def __init__(self, pycore, resource, offset):
  138. self.pycore = pycore
  139. self.resource = resource
  140. self.offset = offset
  141. self.source_pymodule = self.pycore.resource_to_pyobject(resource)
  142. finder = rope.base.evaluate.ScopeNameFinder(self.source_pymodule)
  143. self.primary, self.pyname = finder.get_primary_and_pyname_at(offset)
  144. self._init_fields()
  145. def _init_fields(self):
  146. self.source_scope = self._get_source_scope()
  147. self.goal_scope = self._get_goal_scope()
  148. self.goal_pymodule = self._get_goal_module(self.goal_scope)
  149. def _get_goal_scope(self):
  150. if self.primary is None:
  151. return self._get_source_scope()
  152. pyobject = self.primary.get_object()
  153. if isinstance(pyobject, pyobjects.PyDefinedObject):
  154. return pyobject.get_scope()
  155. elif isinstance(pyobject.get_type(), pyobjects.PyClass):
  156. return pyobject.get_type().get_scope()
  157. def _get_goal_module(self, scope):
  158. if scope is None:
  159. return
  160. while scope.parent is not None:
  161. scope = scope.parent
  162. return scope.pyobject
  163. def _get_source_scope(self):
  164. module_scope = self.source_pymodule.get_scope()
  165. lineno = self.source_pymodule.lines.get_line_number(self.offset)
  166. return module_scope.get_inner_scope_for_line(lineno)
  167. def get_insertion_lineno(self):
  168. lines = self.goal_pymodule.lines
  169. if self.goal_scope == self.source_scope:
  170. line_finder = self.goal_pymodule.logical_lines
  171. lineno = lines.get_line_number(self.offset)
  172. lineno = line_finder.logical_line_in(lineno)[0]
  173. root = suites.ast_suite_tree(self.goal_scope.pyobject.get_ast())
  174. suite = root.find_suite(lineno)
  175. indents = sourceutils.get_indents(lines, lineno)
  176. while self.get_scope_indents() < indents:
  177. lineno = suite.get_start()
  178. indents = sourceutils.get_indents(lines, lineno)
  179. suite = suite.parent
  180. return lineno
  181. else:
  182. return min(self.goal_scope.get_end() + 1, lines.length())
  183. def get_insertion_resource(self):
  184. return self.goal_pymodule.get_resource()
  185. def get_insertion_offsets(self):
  186. if self.goal_scope.get_kind() == 'Class':
  187. start, end = sourceutils.get_body_region(self.goal_scope.pyobject)
  188. if self.goal_pymodule.source_code[start:end].strip() == 'pass':
  189. return start, end
  190. lines = self.goal_pymodule.lines
  191. start = lines.get_line_start(self.get_insertion_lineno())
  192. return (start, start)
  193. def get_scope_indents(self):
  194. if self.goal_scope.get_kind() == 'Module':
  195. return 0
  196. return sourceutils.get_indents(self.goal_pymodule.lines,
  197. self.goal_scope.get_start()) + 4
  198. def get_blank_lines(self):
  199. if self.goal_scope.get_kind() == 'Module':
  200. base_blanks = 2
  201. if self.goal_pymodule.source_code.strip() == '':
  202. base_blanks = 0
  203. if self.goal_scope.get_kind() == 'Class':
  204. base_blanks = 1
  205. if self.goal_scope.get_kind() == 'Function':
  206. base_blanks = 0
  207. if self.goal_scope == self.source_scope:
  208. return (0, base_blanks)
  209. return (base_blanks, 0)
  210. def get_package(self):
  211. primary = self.primary
  212. if self.primary is None:
  213. return self.pycore.get_source_folders()[0]
  214. if isinstance(primary.get_object(), pyobjects.PyPackage):
  215. return primary.get_object().get_resource()
  216. raise exceptions.RefactoringError(
  217. 'A module/package can be only created in a package.')
  218. def primary_is_found(self):
  219. return self.goal_scope is not None
  220. def element_already_exists(self):
  221. if self.pyname is None or isinstance(self.pyname, pynames.UnboundName):
  222. return False
  223. return self.get_name() in self.goal_scope.get_defined_names()
  224. def get_name(self):
  225. return worder.get_name_at(self.resource, self.offset)
  226. class _FunctionGenerationInfo(_GenerationInfo):
  227. def _get_goal_scope(self):
  228. if self.is_constructor():
  229. return self.pyname.get_object().get_scope()
  230. if self.is_instance():
  231. return self.pyname.get_object().get_type().get_scope()
  232. if self.primary is None:
  233. return self._get_source_scope()
  234. pyobject = self.primary.get_object()
  235. if isinstance(pyobject, pyobjects.PyDefinedObject):
  236. return pyobject.get_scope()
  237. elif isinstance(pyobject.get_type(), pyobjects.PyClass):
  238. return pyobject.get_type().get_scope()
  239. def element_already_exists(self):
  240. if self.pyname is None or isinstance(self.pyname, pynames.UnboundName):
  241. return False
  242. return self.get_name() in self.goal_scope.get_defined_names()
  243. def is_static_method(self):
  244. return self.primary is not None and \
  245. isinstance(self.primary.get_object(), pyobjects.PyClass)
  246. def is_method(self):
  247. return self.primary is not None and \
  248. isinstance(self.primary.get_object().get_type(), pyobjects.PyClass)
  249. def is_constructor(self):
  250. return self.pyname is not None and \
  251. isinstance(self.pyname.get_object(), pyobjects.PyClass)
  252. def is_instance(self):
  253. if self.pyname is None:
  254. return False
  255. pyobject = self.pyname.get_object()
  256. return isinstance(pyobject.get_type(), pyobjects.PyClass)
  257. def get_name(self):
  258. if self.is_constructor():
  259. return '__init__'
  260. if self.is_instance():
  261. return '__call__'
  262. return worder.get_name_at(self.resource, self.offset)
  263. def get_passed_args(self):
  264. result = []
  265. source = self.source_pymodule.source_code
  266. finder = worder.Worder(source)
  267. if finder.is_a_function_being_called(self.offset):
  268. start, end = finder.get_primary_range(self.offset)
  269. parens_start, parens_end = finder.get_word_parens_range(end - 1)
  270. call = source[start:parens_end]
  271. parser = functionutils._FunctionParser(call, False)
  272. args, keywords = parser.get_parameters()
  273. for arg in args:
  274. if self._is_id(arg):
  275. result.append(arg)
  276. else:
  277. result.append('arg%d' % len(result))
  278. for name, value in keywords:
  279. result.append(name)
  280. return result
  281. def _is_id(self, arg):
  282. def id_or_underline(c):
  283. return c.isalpha() or c == '_'
  284. for c in arg:
  285. if not id_or_underline(c) and not c.isdigit():
  286. return False
  287. return id_or_underline(arg[0])