from rope.base import pynames, taskhandle, evaluate, exceptions, worder, utils from rope.base.change import ChangeSet, ChangeContents from rope.refactor import sourceutils, occurrences class EncapsulateField(object): def __init__(self, project, resource, offset): self.pycore = project.pycore self.name = worder.get_name_at(resource, offset) this_pymodule = self.pycore.resource_to_pyobject(resource) self.pyname = evaluate.eval_location(this_pymodule, offset) if not self._is_an_attribute(self.pyname): raise exceptions.RefactoringError( 'Encapsulate field should be performed on class attributes.') self.resource = self.pyname.get_definition_location()[0].get_resource() def get_changes(self, getter=None, setter=None, resources=None, task_handle=taskhandle.NullTaskHandle()): """Get the changes this refactoring makes If `getter` is not `None`, that will be the name of the getter, otherwise ``get_${field_name}`` will be used. The same is true for `setter` and if it is None set_${field_name} is used. `resources` can be a list of `rope.base.resource.File`\s that the refactoring should be applied on; if `None` all python files in the project are searched. """ if resources is None: resources = self.pycore.get_python_files() changes = ChangeSet('Encapsulate field <%s>' % self.name) job_set = task_handle.create_jobset('Collecting Changes', len(resources)) if getter is None: getter = 'get_' + self.name if setter is None: setter = 'set_' + self.name renamer = GetterSetterRenameInModule( self.pycore, self.name, self.pyname, getter, setter) for file in resources: job_set.started_job(file.path) if file == self.resource: result = self._change_holding_module(changes, renamer, getter, setter) changes.add_change(ChangeContents(self.resource, result)) else: result = renamer.get_changed_module(file) if result is not None: changes.add_change(ChangeContents(file, result)) job_set.finished_job() return changes def get_field_name(self): """Get the name of the field to be encapsulated""" return self.name def _is_an_attribute(self, pyname): if pyname is not None and isinstance(pyname, pynames.AssignedName): pymodule, lineno = self.pyname.get_definition_location() scope = pymodule.get_scope().\ get_inner_scope_for_line(lineno) if scope.get_kind() == 'Class': return pyname in scope.get_names().values() parent = scope.parent if parent is not None and parent.get_kind() == 'Class': return pyname in parent.get_names().values() return False def _get_defining_class_scope(self): defining_scope = self._get_defining_scope() if defining_scope.get_kind() == 'Function': defining_scope = defining_scope.parent return defining_scope def _get_defining_scope(self): pymodule, line = self.pyname.get_definition_location() return pymodule.get_scope().get_inner_scope_for_line(line) def _change_holding_module(self, changes, renamer, getter, setter): pymodule = self.pycore.resource_to_pyobject(self.resource) class_scope = self._get_defining_class_scope() defining_object = self._get_defining_scope().pyobject start, end = sourceutils.get_body_region(defining_object) new_source = renamer.get_changed_module(pymodule=pymodule, skip_start=start, skip_end=end) if new_source is not None: pymodule = self.pycore.get_string_module(new_source, self.resource) class_scope = pymodule.get_scope().\ get_inner_scope_for_line(class_scope.get_start()) indents = sourceutils.get_indent(self.pycore) * ' ' getter = 'def %s(self):\n%sreturn self.%s' % \ (getter, indents, self.name) setter = 'def %s(self, value):\n%sself.%s = value' % \ (setter, indents, self.name) new_source = sourceutils.add_methods(pymodule, class_scope, [getter, setter]) return new_source class GetterSetterRenameInModule(object): def __init__(self, pycore, name, pyname, getter, setter): self.pycore = pycore self.name = name self.finder = occurrences.create_finder(pycore, name, pyname) self.getter = getter self.setter = setter def get_changed_module(self, resource=None, pymodule=None, skip_start=0, skip_end=0): change_finder = _FindChangesForModule(self, resource, pymodule, skip_start, skip_end) return change_finder.get_changed_module() class _FindChangesForModule(object): def __init__(self, finder, resource, pymodule, skip_start, skip_end): self.pycore = finder.pycore self.finder = finder.finder self.getter = finder.getter self.setter = finder.setter self.resource = resource self.pymodule = pymodule self.last_modified = 0 self.last_set = None self.set_index = None self.skip_start = skip_start self.skip_end = skip_end def get_changed_module(self): result = [] for occurrence in self.finder.find_occurrences(self.resource, self.pymodule): start, end = occurrence.get_word_range() if self.skip_start <= start < self.skip_end: continue self._manage_writes(start, result) result.append(self.source[self.last_modified:start]) if self._is_assigned_in_a_tuple_assignment(occurrence): raise exceptions.RefactoringError( 'Cannot handle tuple assignments in encapsulate field.') if occurrence.is_written(): assignment_type = self.worder.get_assignment_type(start) if assignment_type == '=': result.append(self.setter + '(') else: var_name = self.source[occurrence.get_primary_range()[0]: start] + self.getter + '()' result.append(self.setter + '(' + var_name + ' %s ' % assignment_type[:-1]) current_line = self.lines.get_line_number(start) start_line, end_line = self.pymodule.logical_lines.\ logical_line_in(current_line) self.last_set = self.lines.get_line_end(end_line) end = self.source.index('=', end) + 1 self.set_index = len(result) else: result.append(self.getter + '()') self.last_modified = end if self.last_modified != 0: self._manage_writes(len(self.source), result) result.append(self.source[self.last_modified:]) return ''.join(result) return None def _manage_writes(self, offset, result): if self.last_set is not None and self.last_set <= offset: result.append(self.source[self.last_modified:self.last_set]) set_value = ''.join(result[self.set_index:]).strip() del result[self.set_index:] result.append(set_value + ')') self.last_modified = self.last_set self.last_set = None def _is_assigned_in_a_tuple_assignment(self, occurance): offset = occurance.get_word_range()[0] return self.worder.is_assigned_in_a_tuple_assignment(offset) @property @utils.saveit def source(self): if self.resource is not None: return self.resource.read() else: return self.pymodule.source_code @property @utils.saveit def lines(self): if self.pymodule is None: self.pymodule = self.pycore.resource_to_pyobject(self.resource) return self.pymodule.lines @property @utils.saveit def worder(self): return worder.Worder(self.source)