method_object.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. import warnings
  2. from rope.base import pyobjects, exceptions, change, evaluate, codeanalyze
  3. from rope.refactor import sourceutils, occurrences, rename
  4. class MethodObject(object):
  5. def __init__(self, project, resource, offset):
  6. self.pycore = project.pycore
  7. this_pymodule = self.pycore.resource_to_pyobject(resource)
  8. pyname = evaluate.eval_location(this_pymodule, offset)
  9. if pyname is None or not isinstance(pyname.get_object(),
  10. pyobjects.PyFunction):
  11. raise exceptions.RefactoringError(
  12. 'Replace method with method object refactoring should be '
  13. 'performed on a function.')
  14. self.pyfunction = pyname.get_object()
  15. self.pymodule = self.pyfunction.get_module()
  16. self.resource = self.pymodule.get_resource()
  17. def get_new_class(self, name):
  18. body = sourceutils.fix_indentation(
  19. self._get_body(), sourceutils.get_indent(self.pycore) * 2)
  20. return 'class %s(object):\n\n%s%sdef __call__(self):\n%s' % \
  21. (name, self._get_init(),
  22. ' ' * sourceutils.get_indent(self.pycore), body)
  23. def get_changes(self, classname=None, new_class_name=None):
  24. if new_class_name is not None:
  25. warnings.warn(
  26. 'new_class_name parameter is deprecated; use classname',
  27. DeprecationWarning, stacklevel=2)
  28. classname = new_class_name
  29. collector = codeanalyze.ChangeCollector(self.pymodule.source_code)
  30. start, end = sourceutils.get_body_region(self.pyfunction)
  31. indents = sourceutils.get_indents(
  32. self.pymodule.lines, self.pyfunction.get_scope().get_start()) + \
  33. sourceutils.get_indent(self.pycore)
  34. new_contents = ' ' * indents + 'return %s(%s)()\n' % \
  35. (classname, ', '.join(self._get_parameter_names()))
  36. collector.add_change(start, end, new_contents)
  37. insertion = self._get_class_insertion_point()
  38. collector.add_change(insertion, insertion,
  39. '\n\n' + self.get_new_class(classname))
  40. changes = change.ChangeSet('Replace method with method object refactoring')
  41. changes.add_change(change.ChangeContents(self.resource,
  42. collector.get_changed()))
  43. return changes
  44. def _get_class_insertion_point(self):
  45. current = self.pyfunction
  46. while current.parent != self.pymodule:
  47. current = current.parent
  48. end = self.pymodule.lines.get_line_end(current.get_scope().get_end())
  49. return min(end + 1, len(self.pymodule.source_code))
  50. def _get_body(self):
  51. body = sourceutils.get_body(self.pyfunction)
  52. for param in self._get_parameter_names():
  53. body = param + ' = None\n' + body
  54. pymod = self.pycore.get_string_module(body, self.resource)
  55. pyname = pymod[param]
  56. finder = occurrences.create_finder(self.pycore, param, pyname)
  57. result = rename.rename_in_module(finder, 'self.' + param,
  58. pymodule=pymod)
  59. body = result[result.index('\n') + 1:]
  60. return body
  61. def _get_init(self):
  62. params = self._get_parameter_names()
  63. indents = ' ' * sourceutils.get_indent(self.pycore)
  64. if not params:
  65. return ''
  66. header = indents + 'def __init__(self'
  67. body = ''
  68. for arg in params:
  69. new_name = arg
  70. if arg == 'self':
  71. new_name = 'host'
  72. header += ', %s' % new_name
  73. body += indents * 2 + 'self.%s = %s\n' % (arg, new_name)
  74. header += '):'
  75. return '%s\n%s\n' % (header, body)
  76. def _get_parameter_names(self):
  77. return self.pyfunction.get_param_names()