author |
Steve Losh <steve@stevelosh.com> |
date |
Sat, 01 Oct 2011 19:12:48 -0400 |
parents |
48cacfdc2ca6 |
children |
(none) |
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])