Path: blob/master/elisp/emacs-for-python/rope-dist/rope/refactor/encapsulate_field.py
1421 views
from rope.base import pynames, taskhandle, evaluate, exceptions, worder, utils1from rope.base.change import ChangeSet, ChangeContents2from rope.refactor import sourceutils, occurrences345class EncapsulateField(object):67def __init__(self, project, resource, offset):8self.pycore = project.pycore9self.name = worder.get_name_at(resource, offset)10this_pymodule = self.pycore.resource_to_pyobject(resource)11self.pyname = evaluate.eval_location(this_pymodule, offset)12if not self._is_an_attribute(self.pyname):13raise exceptions.RefactoringError(14'Encapsulate field should be performed on class attributes.')15self.resource = self.pyname.get_definition_location()[0].get_resource()1617def get_changes(self, getter=None, setter=None, resources=None,18task_handle=taskhandle.NullTaskHandle()):19"""Get the changes this refactoring makes2021If `getter` is not `None`, that will be the name of the22getter, otherwise ``get_${field_name}`` will be used. The23same is true for `setter` and if it is None set_${field_name} is24used.2526`resources` can be a list of `rope.base.resource.File`\s that27the refactoring should be applied on; if `None` all python28files in the project are searched.2930"""31if resources is None:32resources = self.pycore.get_python_files()33changes = ChangeSet('Encapsulate field <%s>' % self.name)34job_set = task_handle.create_jobset('Collecting Changes',35len(resources))36if getter is None:37getter = 'get_' + self.name38if setter is None:39setter = 'set_' + self.name40renamer = GetterSetterRenameInModule(41self.pycore, self.name, self.pyname, getter, setter)42for file in resources:43job_set.started_job(file.path)44if file == self.resource:45result = self._change_holding_module(changes, renamer,46getter, setter)47changes.add_change(ChangeContents(self.resource, result))48else:49result = renamer.get_changed_module(file)50if result is not None:51changes.add_change(ChangeContents(file, result))52job_set.finished_job()53return changes5455def get_field_name(self):56"""Get the name of the field to be encapsulated"""57return self.name5859def _is_an_attribute(self, pyname):60if pyname is not None and isinstance(pyname, pynames.AssignedName):61pymodule, lineno = self.pyname.get_definition_location()62scope = pymodule.get_scope().\63get_inner_scope_for_line(lineno)64if scope.get_kind() == 'Class':65return pyname in scope.get_names().values()66parent = scope.parent67if parent is not None and parent.get_kind() == 'Class':68return pyname in parent.get_names().values()69return False7071def _get_defining_class_scope(self):72defining_scope = self._get_defining_scope()73if defining_scope.get_kind() == 'Function':74defining_scope = defining_scope.parent75return defining_scope7677def _get_defining_scope(self):78pymodule, line = self.pyname.get_definition_location()79return pymodule.get_scope().get_inner_scope_for_line(line)8081def _change_holding_module(self, changes, renamer, getter, setter):82pymodule = self.pycore.resource_to_pyobject(self.resource)83class_scope = self._get_defining_class_scope()84defining_object = self._get_defining_scope().pyobject85start, end = sourceutils.get_body_region(defining_object)8687new_source = renamer.get_changed_module(pymodule=pymodule,88skip_start=start, skip_end=end)89if new_source is not None:90pymodule = self.pycore.get_string_module(new_source, self.resource)91class_scope = pymodule.get_scope().\92get_inner_scope_for_line(class_scope.get_start())93indents = sourceutils.get_indent(self.pycore) * ' '94getter = 'def %s(self):\n%sreturn self.%s' % \95(getter, indents, self.name)96setter = 'def %s(self, value):\n%sself.%s = value' % \97(setter, indents, self.name)98new_source = sourceutils.add_methods(pymodule, class_scope,99[getter, setter])100return new_source101102103class GetterSetterRenameInModule(object):104105def __init__(self, pycore, name, pyname, getter, setter):106self.pycore = pycore107self.name = name108self.finder = occurrences.create_finder(pycore, name, pyname)109self.getter = getter110self.setter = setter111112def get_changed_module(self, resource=None, pymodule=None,113skip_start=0, skip_end=0):114change_finder = _FindChangesForModule(self, resource, pymodule,115skip_start, skip_end)116return change_finder.get_changed_module()117118119class _FindChangesForModule(object):120121def __init__(self, finder, resource, pymodule, skip_start, skip_end):122self.pycore = finder.pycore123self.finder = finder.finder124self.getter = finder.getter125self.setter = finder.setter126self.resource = resource127self.pymodule = pymodule128self.last_modified = 0129self.last_set = None130self.set_index = None131self.skip_start = skip_start132self.skip_end = skip_end133134def get_changed_module(self):135result = []136for occurrence in self.finder.find_occurrences(self.resource,137self.pymodule):138start, end = occurrence.get_word_range()139if self.skip_start <= start < self.skip_end:140continue141self._manage_writes(start, result)142result.append(self.source[self.last_modified:start])143if self._is_assigned_in_a_tuple_assignment(occurrence):144raise exceptions.RefactoringError(145'Cannot handle tuple assignments in encapsulate field.')146if occurrence.is_written():147assignment_type = self.worder.get_assignment_type(start)148if assignment_type == '=':149result.append(self.setter + '(')150else:151var_name = self.source[occurrence.get_primary_range()[0]:152start] + self.getter + '()'153result.append(self.setter + '(' + var_name154+ ' %s ' % assignment_type[:-1])155current_line = self.lines.get_line_number(start)156start_line, end_line = self.pymodule.logical_lines.\157logical_line_in(current_line)158self.last_set = self.lines.get_line_end(end_line)159end = self.source.index('=', end) + 1160self.set_index = len(result)161else:162result.append(self.getter + '()')163self.last_modified = end164if self.last_modified != 0:165self._manage_writes(len(self.source), result)166result.append(self.source[self.last_modified:])167return ''.join(result)168return None169170def _manage_writes(self, offset, result):171if self.last_set is not None and self.last_set <= offset:172result.append(self.source[self.last_modified:self.last_set])173set_value = ''.join(result[self.set_index:]).strip()174del result[self.set_index:]175result.append(set_value + ')')176self.last_modified = self.last_set177self.last_set = None178179def _is_assigned_in_a_tuple_assignment(self, occurance):180offset = occurance.get_word_range()[0]181return self.worder.is_assigned_in_a_tuple_assignment(offset)182183@property184@utils.saveit185def source(self):186if self.resource is not None:187return self.resource.read()188else:189return self.pymodule.source_code190191@property192@utils.saveit193def lines(self):194if self.pymodule is None:195self.pymodule = self.pycore.resource_to_pyobject(self.resource)196return self.pymodule.lines197198@property199@utils.saveit200def worder(self):201return worder.Worder(self.source)202203204