Path: blob/master/elisp/emacs-for-python/rope-dist/rope/base/change.py
1415 views
import datetime1import difflib2import os3import time4import warnings56import rope.base.fscommands7from rope.base import taskhandle, exceptions, utils8910class Change(object):11"""The base class for changes1213Rope refactorings return `Change` objects. They can be previewed,14committed or undone.15"""1617def do(self, job_set=None):18"""Perform the change1920.. note:: Do use this directly. Use `Project.do()` instead.21"""2223def undo(self, job_set=None):24"""Perform the change2526.. note:: Do use this directly. Use `History.undo()` instead.27"""2829def get_description(self):30"""Return the description of this change3132This can be used for previewing the changes.33"""34return str(self)3536def get_changed_resources(self):37"""Return the list of resources that will be changed"""38return []3940@property41@utils.saveit42def _operations(self):43return _ResourceOperations(self.resource.project)444546class ChangeSet(Change):47"""A collection of `Change` objects4849This class holds a collection of changes. This class provides50these fields:5152* `changes`: the list of changes53* `description`: the goal of these changes54"""5556def __init__(self, description, timestamp=None):57self.changes = []58self.description = description59self.time = timestamp6061def do(self, job_set=taskhandle.NullJobSet()):62try:63done = []64for change in self.changes:65change.do(job_set)66done.append(change)67self.time = time.time()68except Exception:69for change in done:70change.undo()71raise7273def undo(self, job_set=taskhandle.NullJobSet()):74try:75done = []76for change in reversed(self.changes):77change.undo(job_set)78done.append(change)79except Exception:80for change in done:81change.do()82raise8384def add_change(self, change):85self.changes.append(change)8687def get_description(self):88result = [str(self) + ':\n\n\n']89for change in self.changes:90result.append(change.get_description())91result.append('\n')92return ''.join(result)9394def __str__(self):95if self.time is not None:96date = datetime.datetime.fromtimestamp(self.time)97if date.date() == datetime.date.today():98string_date = 'today'99elif date.date() == (datetime.date.today() - datetime.timedelta(1)):100string_date = 'yesterday'101elif date.year == datetime.date.today().year:102string_date = date.strftime('%b %d')103else:104string_date = date.strftime('%d %b, %Y')105string_time = date.strftime('%H:%M:%S')106string_time = '%s %s ' % (string_date, string_time)107return self.description + ' - ' + string_time108return self.description109110def get_changed_resources(self):111result = set()112for change in self.changes:113result.update(change.get_changed_resources())114return result115116117def _handle_job_set(function):118"""A decorator for handling `taskhandle.JobSet`\s119120A decorator for handling `taskhandle.JobSet`\s for `do` and `undo`121methods of `Change`\s.122"""123def call(self, job_set=taskhandle.NullJobSet()):124job_set.started_job(str(self))125function(self)126job_set.finished_job()127return call128129130class ChangeContents(Change):131"""A class to change the contents of a file132133Fields:134135* `resource`: The `rope.base.resources.File` to change136* `new_contents`: What to write in the file137"""138139def __init__(self, resource, new_contents, old_contents=None):140self.resource = resource141# IDEA: Only saving diffs; possible problems when undo/redoing142self.new_contents = new_contents143self.old_contents = old_contents144145@_handle_job_set146def do(self):147if self.old_contents is None:148self.old_contents = self.resource.read()149self._operations.write_file(self.resource, self.new_contents)150151@_handle_job_set152def undo(self):153if self.old_contents is None:154raise exceptions.HistoryError(155'Undoing a change that is not performed yet!')156self._operations.write_file(self.resource, self.old_contents)157158def __str__(self):159return 'Change <%s>' % self.resource.path160161def get_description(self):162new = self.new_contents163old = self.old_contents164if old is None:165if self.resource.exists():166old = self.resource.read()167else:168old = ''169result = difflib.unified_diff(170old.splitlines(True), new.splitlines(True),171'a/' + self.resource.path, 'b/' + self.resource.path)172return ''.join(list(result))173174def get_changed_resources(self):175return [self.resource]176177178class MoveResource(Change):179"""Move a resource to a new location180181Fields:182183* `resource`: The `rope.base.resources.Resource` to move184* `new_resource`: The destination for move; It is the moved185resource not the folder containing that resource.186"""187188def __init__(self, resource, new_location, exact=False):189self.project = resource.project190self.resource = resource191if not exact:192new_location = _get_destination_for_move(resource, new_location)193if resource.is_folder():194self.new_resource = self.project.get_folder(new_location)195else:196self.new_resource = self.project.get_file(new_location)197198@_handle_job_set199def do(self):200self._operations.move(self.resource, self.new_resource)201202@_handle_job_set203def undo(self):204self._operations.move(self.new_resource, self.resource)205206def __str__(self):207return 'Move <%s>' % self.resource.path208209def get_description(self):210return 'rename from %s\nrename to %s' % (self.resource.path,211self.new_resource.path)212213def get_changed_resources(self):214return [self.resource, self.new_resource]215216217class CreateResource(Change):218"""A class to create a resource219220Fields:221222* `resource`: The resource to create223"""224225def __init__(self, resource):226self.resource = resource227228@_handle_job_set229def do(self):230self._operations.create(self.resource)231232@_handle_job_set233def undo(self):234self._operations.remove(self.resource)235236def __str__(self):237return 'Create Resource <%s>' % (self.resource.path)238239def get_description(self):240return 'new file %s' % (self.resource.path)241242def get_changed_resources(self):243return [self.resource]244245def _get_child_path(self, parent, name):246if parent.path == '':247return name248else:249return parent.path + '/' + name250251252class CreateFolder(CreateResource):253"""A class to create a folder254255See docs for `CreateResource`.256"""257258def __init__(self, parent, name):259resource = parent.project.get_folder(self._get_child_path(parent, name))260super(CreateFolder, self).__init__(resource)261262263class CreateFile(CreateResource):264"""A class to create a file265266See docs for `CreateResource`.267"""268269def __init__(self, parent, name):270resource = parent.project.get_file(self._get_child_path(parent, name))271super(CreateFile, self).__init__(resource)272273274class RemoveResource(Change):275"""A class to remove a resource276277Fields:278279* `resource`: The resource to be removed280"""281282def __init__(self, resource):283self.resource = resource284285@_handle_job_set286def do(self):287self._operations.remove(self.resource)288289# TODO: Undoing remove operations290@_handle_job_set291def undo(self):292raise NotImplementedError(293'Undoing `RemoveResource` is not implemented yet.')294295def __str__(self):296return 'Remove <%s>' % (self.resource.path)297298def get_changed_resources(self):299return [self.resource]300301302def count_changes(change):303"""Counts the number of basic changes a `Change` will make"""304if isinstance(change, ChangeSet):305result = 0306for child in change.changes:307result += count_changes(child)308return result309return 1310311def create_job_set(task_handle, change):312return task_handle.create_jobset(str(change), count_changes(change))313314315class _ResourceOperations(object):316317def __init__(self, project):318self.project = project319self.fscommands = project.fscommands320self.direct_commands = rope.base.fscommands.FileSystemCommands()321322def _get_fscommands(self, resource):323if self.project.is_ignored(resource):324return self.direct_commands325return self.fscommands326327def write_file(self, resource, contents):328data = rope.base.fscommands.unicode_to_file_data(contents)329fscommands = self._get_fscommands(resource)330fscommands.write(resource.real_path, data)331for observer in list(self.project.observers):332observer.resource_changed(resource)333334def move(self, resource, new_resource):335fscommands = self._get_fscommands(resource)336fscommands.move(resource.real_path, new_resource.real_path)337for observer in list(self.project.observers):338observer.resource_moved(resource, new_resource)339340def create(self, resource):341if resource.is_folder():342self._create_resource(resource.path, kind='folder')343else:344self._create_resource(resource.path)345for observer in list(self.project.observers):346observer.resource_created(resource)347348def remove(self, resource):349fscommands = self._get_fscommands(resource)350fscommands.remove(resource.real_path)351for observer in list(self.project.observers):352observer.resource_removed(resource)353354def _create_resource(self, file_name, kind='file'):355resource_path = self.project._get_resource_path(file_name)356if os.path.exists(resource_path):357raise exceptions.RopeError('Resource <%s> already exists'358% resource_path)359resource = self.project.get_file(file_name)360if not resource.parent.exists():361raise exceptions.ResourceNotFoundError(362'Parent folder of <%s> does not exist' % resource.path)363fscommands = self._get_fscommands(resource)364try:365if kind == 'file':366fscommands.create_file(resource_path)367else:368fscommands.create_folder(resource_path)369except IOError, e:370raise exceptions.RopeError(e)371372373def _get_destination_for_move(resource, destination):374dest_path = resource.project._get_resource_path(destination)375if os.path.isdir(dest_path):376if destination != '':377return destination + '/' + resource.name378else:379return resource.name380return destination381382383class ChangeToData(object):384385def convertChangeSet(self, change):386description = change.description387changes = []388for child in change.changes:389changes.append(self(child))390return (description, changes, change.time)391392def convertChangeContents(self, change):393return (change.resource.path, change.new_contents, change.old_contents)394395def convertMoveResource(self, change):396return (change.resource.path, change.new_resource.path)397398def convertCreateResource(self, change):399return (change.resource.path, change.resource.is_folder())400401def convertRemoveResource(self, change):402return (change.resource.path, change.resource.is_folder())403404def __call__(self, change):405change_type = type(change)406if change_type in (CreateFolder, CreateFile):407change_type = CreateResource408method = getattr(self, 'convert' + change_type.__name__)409return (change_type.__name__, method(change))410411412class DataToChange(object):413414def __init__(self, project):415self.project = project416417def makeChangeSet(self, description, changes, time=None):418result = ChangeSet(description, time)419for child in changes:420result.add_change(self(child))421return result422423def makeChangeContents(self, path, new_contents, old_contents):424resource = self.project.get_file(path)425return ChangeContents(resource, new_contents, old_contents)426427def makeMoveResource(self, old_path, new_path):428resource = self.project.get_file(old_path)429return MoveResource(resource, new_path, exact=True)430431def makeCreateResource(self, path, is_folder):432if is_folder:433resource = self.project.get_folder(path)434else:435resource = self.project.get_file(path)436return CreateResource(resource)437438def makeRemoveResource(self, path, is_folder):439if is_folder:440resource = self.project.get_folder(path)441else:442resource = self.project.get_file(path)443return RemoveResource(resource)444445def __call__(self, data):446method = getattr(self, 'make' + data[0])447return method(*data[1])448449450