introduce_factory.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import rope.base.exceptions
  2. import rope.base.pyobjects
  3. from rope.base import taskhandle, evaluate
  4. from rope.base.change import (ChangeSet, ChangeContents)
  5. from rope.refactor import rename, occurrences, sourceutils, importutils
  6. class IntroduceFactory(object):
  7. def __init__(self, project, resource, offset):
  8. self.pycore = project.pycore
  9. self.offset = offset
  10. this_pymodule = self.pycore.resource_to_pyobject(resource)
  11. self.old_pyname = evaluate.eval_location(this_pymodule, offset)
  12. if self.old_pyname is None or not isinstance(self.old_pyname.get_object(),
  13. rope.base.pyobjects.PyClass):
  14. raise rope.base.exceptions.RefactoringError(
  15. 'Introduce factory should be performed on a class.')
  16. self.old_name = self.old_pyname.get_object().get_name()
  17. self.pymodule = self.old_pyname.get_object().get_module()
  18. self.resource = self.pymodule.get_resource()
  19. def get_changes(self, factory_name, global_factory=False, resources=None,
  20. task_handle=taskhandle.NullTaskHandle()):
  21. """Get the changes this refactoring makes
  22. `factory_name` indicates the name of the factory function to
  23. be added. If `global_factory` is `True` the factory will be
  24. global otherwise a static method is added to the class.
  25. `resources` can be a list of `rope.base.resource.File`\s that
  26. this refactoring should be applied on; if `None` all python
  27. files in the project are searched.
  28. """
  29. if resources is None:
  30. resources = self.pycore.get_python_files()
  31. changes = ChangeSet('Introduce factory method <%s>' % factory_name)
  32. job_set = task_handle.create_jobset('Collecting Changes',
  33. len(resources))
  34. self._change_module(resources, changes, factory_name,
  35. global_factory, job_set)
  36. return changes
  37. def get_name(self):
  38. """Return the name of the class"""
  39. return self.old_name
  40. def _change_module(self, resources, changes,
  41. factory_name, global_, job_set):
  42. if global_:
  43. replacement = '__rope_factory_%s_' % factory_name
  44. else:
  45. replacement = self._new_function_name(factory_name, global_)
  46. for file_ in resources:
  47. job_set.started_job(file_.path)
  48. if file_ == self.resource:
  49. self._change_resource(changes, factory_name, global_)
  50. job_set.finished_job()
  51. continue
  52. changed_code = self._rename_occurrences(file_, replacement,
  53. global_)
  54. if changed_code is not None:
  55. if global_:
  56. new_pymodule = self.pycore.get_string_module(changed_code,
  57. self.resource)
  58. modname = self.pycore.modname(self.resource)
  59. changed_code, imported = importutils.add_import(
  60. self.pycore, new_pymodule, modname, factory_name)
  61. changed_code = changed_code.replace(replacement, imported)
  62. changes.add_change(ChangeContents(file_, changed_code))
  63. job_set.finished_job()
  64. def _change_resource(self, changes, factory_name, global_):
  65. class_scope = self.old_pyname.get_object().get_scope()
  66. source_code = self._rename_occurrences(
  67. self.resource, self._new_function_name(factory_name,
  68. global_), global_)
  69. if source_code is None:
  70. source_code = self.pymodule.source_code
  71. else:
  72. self.pymodule = self.pycore.get_string_module(
  73. source_code, resource=self.resource)
  74. lines = self.pymodule.lines
  75. start = self._get_insertion_offset(class_scope, lines)
  76. result = source_code[:start]
  77. result += self._get_factory_method(lines, class_scope,
  78. factory_name, global_)
  79. result += source_code[start:]
  80. changes.add_change(ChangeContents(self.resource, result))
  81. def _get_insertion_offset(self, class_scope, lines):
  82. start_line = class_scope.get_end()
  83. if class_scope.get_scopes():
  84. start_line = class_scope.get_scopes()[-1].get_end()
  85. start = lines.get_line_end(start_line) + 1
  86. return start
  87. def _get_factory_method(self, lines, class_scope,
  88. factory_name, global_):
  89. unit_indents = ' ' * sourceutils.get_indent(self.pycore)
  90. if global_:
  91. if self._get_scope_indents(lines, class_scope) > 0:
  92. raise rope.base.exceptions.RefactoringError(
  93. 'Cannot make global factory method for nested classes.')
  94. return ('\ndef %s(*args, **kwds):\n%sreturn %s(*args, **kwds)\n' %
  95. (factory_name, unit_indents, self.old_name))
  96. unindented_factory = \
  97. ('@staticmethod\ndef %s(*args, **kwds):\n' % factory_name +
  98. '%sreturn %s(*args, **kwds)\n' % (unit_indents, self.old_name))
  99. indents = self._get_scope_indents(lines, class_scope) + \
  100. sourceutils.get_indent(self.pycore)
  101. return '\n' + sourceutils.indent_lines(unindented_factory, indents)
  102. def _get_scope_indents(self, lines, scope):
  103. return sourceutils.get_indents(lines, scope.get_start())
  104. def _new_function_name(self, factory_name, global_):
  105. if global_:
  106. return factory_name
  107. else:
  108. return self.old_name + '.' + factory_name
  109. def _rename_occurrences(self, file_, changed_name, global_factory):
  110. finder = occurrences.create_finder(self.pycore, self.old_name,
  111. self.old_pyname, only_calls=True)
  112. result = rename.rename_in_module(finder, changed_name, resource=file_,
  113. replace_primary=global_factory)
  114. return result
  115. IntroduceFactoryRefactoring = IntroduceFactory