| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- import datetime
- import difflib
- import os
- import time
- import warnings
- import rope.base.fscommands
- from rope.base import taskhandle, exceptions, utils
- class Change(object):
- """The base class for changes
- Rope refactorings return `Change` objects. They can be previewed,
- committed or undone.
- """
- def do(self, job_set=None):
- """Perform the change
-
- .. note:: Do use this directly. Use `Project.do()` instead.
- """
- def undo(self, job_set=None):
- """Perform the change
-
- .. note:: Do use this directly. Use `History.undo()` instead.
- """
- def get_description(self):
- """Return the description of this change
- This can be used for previewing the changes.
- """
- return str(self)
- def get_changed_resources(self):
- """Return the list of resources that will be changed"""
- return []
- @property
- @utils.saveit
- def _operations(self):
- return _ResourceOperations(self.resource.project)
- class ChangeSet(Change):
- """A collection of `Change` objects
- This class holds a collection of changes. This class provides
- these fields:
- * `changes`: the list of changes
- * `description`: the goal of these changes
- """
- def __init__(self, description, timestamp=None):
- self.changes = []
- self.description = description
- self.time = timestamp
- def do(self, job_set=taskhandle.NullJobSet()):
- try:
- done = []
- for change in self.changes:
- change.do(job_set)
- done.append(change)
- self.time = time.time()
- except Exception:
- for change in done:
- change.undo()
- raise
- def undo(self, job_set=taskhandle.NullJobSet()):
- try:
- done = []
- for change in reversed(self.changes):
- change.undo(job_set)
- done.append(change)
- except Exception:
- for change in done:
- change.do()
- raise
- def add_change(self, change):
- self.changes.append(change)
- def get_description(self):
- result = [str(self) + ':\n\n\n']
- for change in self.changes:
- result.append(change.get_description())
- result.append('\n')
- return ''.join(result)
- def __str__(self):
- if self.time is not None:
- date = datetime.datetime.fromtimestamp(self.time)
- if date.date() == datetime.date.today():
- string_date = 'today'
- elif date.date() == (datetime.date.today() - datetime.timedelta(1)):
- string_date = 'yesterday'
- elif date.year == datetime.date.today().year:
- string_date = date.strftime('%b %d')
- else:
- string_date = date.strftime('%d %b, %Y')
- string_time = date.strftime('%H:%M:%S')
- string_time = '%s %s ' % (string_date, string_time)
- return self.description + ' - ' + string_time
- return self.description
- def get_changed_resources(self):
- result = set()
- for change in self.changes:
- result.update(change.get_changed_resources())
- return result
- def _handle_job_set(function):
- """A decorator for handling `taskhandle.JobSet`\s
- A decorator for handling `taskhandle.JobSet`\s for `do` and `undo`
- methods of `Change`\s.
- """
- def call(self, job_set=taskhandle.NullJobSet()):
- job_set.started_job(str(self))
- function(self)
- job_set.finished_job()
- return call
- class ChangeContents(Change):
- """A class to change the contents of a file
- Fields:
- * `resource`: The `rope.base.resources.File` to change
- * `new_contents`: What to write in the file
- """
- def __init__(self, resource, new_contents, old_contents=None):
- self.resource = resource
- # IDEA: Only saving diffs; possible problems when undo/redoing
- self.new_contents = new_contents
- self.old_contents = old_contents
- @_handle_job_set
- def do(self):
- if self.old_contents is None:
- self.old_contents = self.resource.read()
- self._operations.write_file(self.resource, self.new_contents)
- @_handle_job_set
- def undo(self):
- if self.old_contents is None:
- raise exceptions.HistoryError(
- 'Undoing a change that is not performed yet!')
- self._operations.write_file(self.resource, self.old_contents)
- def __str__(self):
- return 'Change <%s>' % self.resource.path
- def get_description(self):
- new = self.new_contents
- old = self.old_contents
- if old is None:
- if self.resource.exists():
- old = self.resource.read()
- else:
- old = ''
- result = difflib.unified_diff(
- old.splitlines(True), new.splitlines(True),
- 'a/' + self.resource.path, 'b/' + self.resource.path)
- return ''.join(list(result))
- def get_changed_resources(self):
- return [self.resource]
- class MoveResource(Change):
- """Move a resource to a new location
- Fields:
- * `resource`: The `rope.base.resources.Resource` to move
- * `new_resource`: The destination for move; It is the moved
- resource not the folder containing that resource.
- """
- def __init__(self, resource, new_location, exact=False):
- self.project = resource.project
- self.resource = resource
- if not exact:
- new_location = _get_destination_for_move(resource, new_location)
- if resource.is_folder():
- self.new_resource = self.project.get_folder(new_location)
- else:
- self.new_resource = self.project.get_file(new_location)
- @_handle_job_set
- def do(self):
- self._operations.move(self.resource, self.new_resource)
- @_handle_job_set
- def undo(self):
- self._operations.move(self.new_resource, self.resource)
- def __str__(self):
- return 'Move <%s>' % self.resource.path
- def get_description(self):
- return 'rename from %s\nrename to %s' % (self.resource.path,
- self.new_resource.path)
- def get_changed_resources(self):
- return [self.resource, self.new_resource]
- class CreateResource(Change):
- """A class to create a resource
- Fields:
- * `resource`: The resource to create
- """
- def __init__(self, resource):
- self.resource = resource
- @_handle_job_set
- def do(self):
- self._operations.create(self.resource)
- @_handle_job_set
- def undo(self):
- self._operations.remove(self.resource)
- def __str__(self):
- return 'Create Resource <%s>' % (self.resource.path)
- def get_description(self):
- return 'new file %s' % (self.resource.path)
- def get_changed_resources(self):
- return [self.resource]
- def _get_child_path(self, parent, name):
- if parent.path == '':
- return name
- else:
- return parent.path + '/' + name
- class CreateFolder(CreateResource):
- """A class to create a folder
- See docs for `CreateResource`.
- """
- def __init__(self, parent, name):
- resource = parent.project.get_folder(self._get_child_path(parent, name))
- super(CreateFolder, self).__init__(resource)
- class CreateFile(CreateResource):
- """A class to create a file
- See docs for `CreateResource`.
- """
- def __init__(self, parent, name):
- resource = parent.project.get_file(self._get_child_path(parent, name))
- super(CreateFile, self).__init__(resource)
- class RemoveResource(Change):
- """A class to remove a resource
- Fields:
- * `resource`: The resource to be removed
- """
- def __init__(self, resource):
- self.resource = resource
- @_handle_job_set
- def do(self):
- self._operations.remove(self.resource)
- # TODO: Undoing remove operations
- @_handle_job_set
- def undo(self):
- raise NotImplementedError(
- 'Undoing `RemoveResource` is not implemented yet.')
- def __str__(self):
- return 'Remove <%s>' % (self.resource.path)
- def get_changed_resources(self):
- return [self.resource]
- def count_changes(change):
- """Counts the number of basic changes a `Change` will make"""
- if isinstance(change, ChangeSet):
- result = 0
- for child in change.changes:
- result += count_changes(child)
- return result
- return 1
- def create_job_set(task_handle, change):
- return task_handle.create_jobset(str(change), count_changes(change))
- class _ResourceOperations(object):
- def __init__(self, project):
- self.project = project
- self.fscommands = project.fscommands
- self.direct_commands = rope.base.fscommands.FileSystemCommands()
- def _get_fscommands(self, resource):
- if self.project.is_ignored(resource):
- return self.direct_commands
- return self.fscommands
- def write_file(self, resource, contents):
- data = rope.base.fscommands.unicode_to_file_data(contents)
- fscommands = self._get_fscommands(resource)
- fscommands.write(resource.real_path, data)
- for observer in list(self.project.observers):
- observer.resource_changed(resource)
- def move(self, resource, new_resource):
- fscommands = self._get_fscommands(resource)
- fscommands.move(resource.real_path, new_resource.real_path)
- for observer in list(self.project.observers):
- observer.resource_moved(resource, new_resource)
- def create(self, resource):
- if resource.is_folder():
- self._create_resource(resource.path, kind='folder')
- else:
- self._create_resource(resource.path)
- for observer in list(self.project.observers):
- observer.resource_created(resource)
- def remove(self, resource):
- fscommands = self._get_fscommands(resource)
- fscommands.remove(resource.real_path)
- for observer in list(self.project.observers):
- observer.resource_removed(resource)
- def _create_resource(self, file_name, kind='file'):
- resource_path = self.project._get_resource_path(file_name)
- if os.path.exists(resource_path):
- raise exceptions.RopeError('Resource <%s> already exists'
- % resource_path)
- resource = self.project.get_file(file_name)
- if not resource.parent.exists():
- raise exceptions.ResourceNotFoundError(
- 'Parent folder of <%s> does not exist' % resource.path)
- fscommands = self._get_fscommands(resource)
- try:
- if kind == 'file':
- fscommands.create_file(resource_path)
- else:
- fscommands.create_folder(resource_path)
- except IOError, e:
- raise exceptions.RopeError(e)
- def _get_destination_for_move(resource, destination):
- dest_path = resource.project._get_resource_path(destination)
- if os.path.isdir(dest_path):
- if destination != '':
- return destination + '/' + resource.name
- else:
- return resource.name
- return destination
- class ChangeToData(object):
- def convertChangeSet(self, change):
- description = change.description
- changes = []
- for child in change.changes:
- changes.append(self(child))
- return (description, changes, change.time)
- def convertChangeContents(self, change):
- return (change.resource.path, change.new_contents, change.old_contents)
- def convertMoveResource(self, change):
- return (change.resource.path, change.new_resource.path)
- def convertCreateResource(self, change):
- return (change.resource.path, change.resource.is_folder())
- def convertRemoveResource(self, change):
- return (change.resource.path, change.resource.is_folder())
- def __call__(self, change):
- change_type = type(change)
- if change_type in (CreateFolder, CreateFile):
- change_type = CreateResource
- method = getattr(self, 'convert' + change_type.__name__)
- return (change_type.__name__, method(change))
- class DataToChange(object):
- def __init__(self, project):
- self.project = project
- def makeChangeSet(self, description, changes, time=None):
- result = ChangeSet(description, time)
- for child in changes:
- result.add_change(self(child))
- return result
- def makeChangeContents(self, path, new_contents, old_contents):
- resource = self.project.get_file(path)
- return ChangeContents(resource, new_contents, old_contents)
- def makeMoveResource(self, old_path, new_path):
- resource = self.project.get_file(old_path)
- return MoveResource(resource, new_path, exact=True)
- def makeCreateResource(self, path, is_folder):
- if is_folder:
- resource = self.project.get_folder(path)
- else:
- resource = self.project.get_file(path)
- return CreateResource(resource)
- def makeRemoveResource(self, path, is_folder):
- if is_folder:
- resource = self.project.get_folder(path)
- else:
- resource = self.project.get_file(path)
- return RemoveResource(resource)
- def __call__(self, data):
- method = getattr(self, 'make' + data[0])
- return method(*data[1])
|