OLD | NEW |
(Empty) | |
| 1 from rope.base import evaluate |
| 2 from rope.base import exceptions |
| 3 from rope.base import libutils |
| 4 from rope.base import pynames |
| 5 from rope.base import taskhandle |
| 6 from rope.base import utils |
| 7 from rope.base import worder |
| 8 from rope.base.change import ChangeSet, ChangeContents |
| 9 from rope.refactor import sourceutils, occurrences |
| 10 |
| 11 |
| 12 class EncapsulateField(object): |
| 13 |
| 14 def __init__(self, project, resource, offset): |
| 15 self.project = project |
| 16 self.name = worder.get_name_at(resource, offset) |
| 17 this_pymodule = self.project.get_pymodule(resource) |
| 18 self.pyname = evaluate.eval_location(this_pymodule, offset) |
| 19 if not self._is_an_attribute(self.pyname): |
| 20 raise exceptions.RefactoringError( |
| 21 'Encapsulate field should be performed on class attributes.') |
| 22 self.resource = self.pyname.get_definition_location()[0].get_resource() |
| 23 |
| 24 def get_changes(self, getter=None, setter=None, resources=None, |
| 25 task_handle=taskhandle.NullTaskHandle()): |
| 26 """Get the changes this refactoring makes |
| 27 |
| 28 If `getter` is not `None`, that will be the name of the |
| 29 getter, otherwise ``get_${field_name}`` will be used. The |
| 30 same is true for `setter` and if it is None set_${field_name} is |
| 31 used. |
| 32 |
| 33 `resources` can be a list of `rope.base.resource.File`\s that |
| 34 the refactoring should be applied on; if `None` all python |
| 35 files in the project are searched. |
| 36 |
| 37 """ |
| 38 if resources is None: |
| 39 resources = self.project.get_python_files() |
| 40 changes = ChangeSet('Encapsulate field <%s>' % self.name) |
| 41 job_set = task_handle.create_jobset('Collecting Changes', |
| 42 len(resources)) |
| 43 if getter is None: |
| 44 getter = 'get_' + self.name |
| 45 if setter is None: |
| 46 setter = 'set_' + self.name |
| 47 renamer = GetterSetterRenameInModule( |
| 48 self.project, self.name, self.pyname, getter, setter) |
| 49 for file in resources: |
| 50 job_set.started_job(file.path) |
| 51 if file == self.resource: |
| 52 result = self._change_holding_module(changes, renamer, |
| 53 getter, setter) |
| 54 changes.add_change(ChangeContents(self.resource, result)) |
| 55 else: |
| 56 result = renamer.get_changed_module(file) |
| 57 if result is not None: |
| 58 changes.add_change(ChangeContents(file, result)) |
| 59 job_set.finished_job() |
| 60 return changes |
| 61 |
| 62 def get_field_name(self): |
| 63 """Get the name of the field to be encapsulated""" |
| 64 return self.name |
| 65 |
| 66 def _is_an_attribute(self, pyname): |
| 67 if pyname is not None and isinstance(pyname, pynames.AssignedName): |
| 68 pymodule, lineno = self.pyname.get_definition_location() |
| 69 scope = pymodule.get_scope().\ |
| 70 get_inner_scope_for_line(lineno) |
| 71 if scope.get_kind() == 'Class': |
| 72 return pyname in scope.get_names().values() |
| 73 parent = scope.parent |
| 74 if parent is not None and parent.get_kind() == 'Class': |
| 75 return pyname in parent.get_names().values() |
| 76 return False |
| 77 |
| 78 def _get_defining_class_scope(self): |
| 79 defining_scope = self._get_defining_scope() |
| 80 if defining_scope.get_kind() == 'Function': |
| 81 defining_scope = defining_scope.parent |
| 82 return defining_scope |
| 83 |
| 84 def _get_defining_scope(self): |
| 85 pymodule, line = self.pyname.get_definition_location() |
| 86 return pymodule.get_scope().get_inner_scope_for_line(line) |
| 87 |
| 88 def _change_holding_module(self, changes, renamer, getter, setter): |
| 89 pymodule = self.project.get_pymodule(self.resource) |
| 90 class_scope = self._get_defining_class_scope() |
| 91 defining_object = self._get_defining_scope().pyobject |
| 92 start, end = sourceutils.get_body_region(defining_object) |
| 93 |
| 94 new_source = renamer.get_changed_module(pymodule=pymodule, |
| 95 skip_start=start, skip_end=end) |
| 96 if new_source is not None: |
| 97 pymodule = libutils.get_string_module( |
| 98 self.project, new_source, self.resource) |
| 99 class_scope = pymodule.get_scope().\ |
| 100 get_inner_scope_for_line(class_scope.get_start()) |
| 101 indents = sourceutils.get_indent(self.project) * ' ' |
| 102 getter = 'def %s(self):\n%sreturn self.%s' % \ |
| 103 (getter, indents, self.name) |
| 104 setter = 'def %s(self, value):\n%sself.%s = value' % \ |
| 105 (setter, indents, self.name) |
| 106 new_source = sourceutils.add_methods(pymodule, class_scope, |
| 107 [getter, setter]) |
| 108 return new_source |
| 109 |
| 110 |
| 111 class GetterSetterRenameInModule(object): |
| 112 |
| 113 def __init__(self, project, name, pyname, getter, setter): |
| 114 self.project = project |
| 115 self.name = name |
| 116 self.finder = occurrences.create_finder(project, name, pyname) |
| 117 self.getter = getter |
| 118 self.setter = setter |
| 119 |
| 120 def get_changed_module(self, resource=None, pymodule=None, |
| 121 skip_start=0, skip_end=0): |
| 122 change_finder = _FindChangesForModule(self, resource, pymodule, |
| 123 skip_start, skip_end) |
| 124 return change_finder.get_changed_module() |
| 125 |
| 126 |
| 127 class _FindChangesForModule(object): |
| 128 |
| 129 def __init__(self, finder, resource, pymodule, skip_start, skip_end): |
| 130 self.project = finder.project |
| 131 self.finder = finder.finder |
| 132 self.getter = finder.getter |
| 133 self.setter = finder.setter |
| 134 self.resource = resource |
| 135 self.pymodule = pymodule |
| 136 self.last_modified = 0 |
| 137 self.last_set = None |
| 138 self.set_index = None |
| 139 self.skip_start = skip_start |
| 140 self.skip_end = skip_end |
| 141 |
| 142 def get_changed_module(self): |
| 143 result = [] |
| 144 for occurrence in self.finder.find_occurrences(self.resource, |
| 145 self.pymodule): |
| 146 start, end = occurrence.get_word_range() |
| 147 if self.skip_start <= start < self.skip_end: |
| 148 continue |
| 149 self._manage_writes(start, result) |
| 150 result.append(self.source[self.last_modified:start]) |
| 151 if self._is_assigned_in_a_tuple_assignment(occurrence): |
| 152 raise exceptions.RefactoringError( |
| 153 'Cannot handle tuple assignments in encapsulate field.') |
| 154 if occurrence.is_written(): |
| 155 assignment_type = self.worder.get_assignment_type(start) |
| 156 if assignment_type == '=': |
| 157 result.append(self.setter + '(') |
| 158 else: |
| 159 var_name = self.source[occurrence.get_primary_range()[0]: |
| 160 start] + self.getter + '()' |
| 161 result.append(self.setter + '(' + var_name |
| 162 + ' %s ' % assignment_type[:-1]) |
| 163 current_line = self.lines.get_line_number(start) |
| 164 start_line, end_line = self.pymodule.logical_lines.\ |
| 165 logical_line_in(current_line) |
| 166 self.last_set = self.lines.get_line_end(end_line) |
| 167 end = self.source.index('=', end) + 1 |
| 168 self.set_index = len(result) |
| 169 else: |
| 170 result.append(self.getter + '()') |
| 171 self.last_modified = end |
| 172 if self.last_modified != 0: |
| 173 self._manage_writes(len(self.source), result) |
| 174 result.append(self.source[self.last_modified:]) |
| 175 return ''.join(result) |
| 176 return None |
| 177 |
| 178 def _manage_writes(self, offset, result): |
| 179 if self.last_set is not None and self.last_set <= offset: |
| 180 result.append(self.source[self.last_modified:self.last_set]) |
| 181 set_value = ''.join(result[self.set_index:]).strip() |
| 182 del result[self.set_index:] |
| 183 result.append(set_value + ')') |
| 184 self.last_modified = self.last_set |
| 185 self.last_set = None |
| 186 |
| 187 def _is_assigned_in_a_tuple_assignment(self, occurance): |
| 188 offset = occurance.get_word_range()[0] |
| 189 return self.worder.is_assigned_in_a_tuple_assignment(offset) |
| 190 |
| 191 @property |
| 192 @utils.saveit |
| 193 def source(self): |
| 194 if self.resource is not None: |
| 195 return self.resource.read() |
| 196 else: |
| 197 return self.pymodule.source_code |
| 198 |
| 199 @property |
| 200 @utils.saveit |
| 201 def lines(self): |
| 202 if self.pymodule is None: |
| 203 self.pymodule = self.project.get_pymodule(self.resource) |
| 204 return self.pymodule.lines |
| 205 |
| 206 @property |
| 207 @utils.saveit |
| 208 def worder(self): |
| 209 return worder.Worder(self.source) |
OLD | NEW |