Sat, 23 Sep 2017 12:01:48 +0200
Implemented the distributed "Use Function" method.
# -*- coding: utf-8 -*- # Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the refactoring client interface to rope. """ from __future__ import unicode_literals import sys import os sys.path.insert(0, os.path.dirname(__file__)) if sys.version_info[0] >= 3: rope_path = os.path.join(os.path.dirname(__file__), 'rope_py3') else: rope_path = os.path.join(os.path.dirname(__file__), 'rope_py2') str = unicode # __IGNORE_WARNING__ sys.path.insert(0, rope_path) import rope import rope.base.project import rope.base.libutils from JsonClient import JsonClient from ProgressHandle import ProgressHandle class RefactoringClient(JsonClient): """ Class implementing the refactoring client interface to rope. """ def __init__(self, host, port, projectPath): """ Constructor @param host ip address the background service is listening @type str @param port port of the background service @type int @param projectPath path to the project @type str """ super(RefactoringClient, self).__init__(host, port) self.__methodMapping = { "AbortAction": self.__abortAction, "CloseProject": self.__closeProject, "Validate": self.__validate, "QueryReferences": self.__queryReferences, "QueryDefinition": self.__queryDefinition, "QueryImplementations": self.__queryImplementations, "GetConfig": self.__getConfig, "ConfigChanged": self.__configChanged, "PerformSoa": self.__performSOA, "ReportChanged": self.__reportChanged, "History": self.__processHistory, "PreviewChanges": self.__previewChanges, "ApplyChanges": self.__applyChanges, "ClearChanges": self.__clearChanges, "CalculateRenameChanges": self.__calculateRenameChanges, "CalculateChangeOccurrencesChanges": self.__calculateChangeOccurrencesChanges, "CalculateExtractChanges": self.__calculateExtractChanges, "RequestInlineType": self.__requestInlineType, "CalculateInlineChanges": self.__calculateInlineChanges, "RequestMoveType": self.__requestMoveType, "CalculateMoveChanges": self.__calculateMoveChanges, "RequestUseFunction": self.__requestUseFunction, "CalculateUseFunctionChanges": self.__calculateUseFunctionChanges, } from FileSystemCommands import RefactoringClientFileSystemCommands self.__fsCommands = RefactoringClientFileSystemCommands(self) self.__projectpath = projectPath self.__project = rope.base.project.Project( self.__projectpath, fscommands=self.__fsCommands) self.__progressHandle = None self.__changes = {} # dict storing the retrieved changes for various refactorings def handleCall(self, method, params): """ Public method to handle a method call from the server. @param method requested method name @type str @param params dictionary with method specific parameters @type dict """ ## if "filename" in params and sys.version_info[0] == 2: ## params["filename"] = params["filename"].encode( ## sys.getfilesystemencoding()) self.__methodMapping[method](params) def __handleRopeError(self, err): """ Private method to process a rope error. @param err rope exception object @type Exception @return dictionary containing the error information @rtype dict """ ropeError = str(type(err)).split()[-1] ropeError = ropeError[1:-2].split('.')[-1] errorDict = { "Error": ropeError, "ErrorString": str(err), } if ropeError == 'ModuleSyntaxError': errorDict["ErrorFile"] = err.filename errorDict["ErrorLine"] = err.lineno return errorDict def __abortAction(self, params): """ Private method to abort the current action. @param params dictionary containing the method parameters sent by the server @type dict """ if self.__progressHandle is not None and \ not self.__progressHandle.is_stopped(): self.__progressHandle.stop() def __validate(self, params): """ Private slot to validate the project. @param params dictionary containing the method parameters sent by the server @type dict """ self.__project.validate(self.__project.root) def __closeProject(self, params): """ Private slot to validate the project. @param params dictionary containing the method parameters sent by the server @type dict """ self.__project.close() def __getConfig(self, params): """ Private method to send some configuration data to the server. @param params dictionary containing the method parameters sent by the server @type dict """ result = { "RopeFolderName": self.__project.ropefolder.real_path, "DefaultConfig": self.__project._default_config(), "RopeHelpFile": os.path.join( rope_path, "rope", "docs", "overview.txt"), "RopeInfo": rope.INFO, "RopeVersion": rope.VERSION, "RopeCopyright": rope.COPYRIGHT, } self.sendJson("Config", result) def __configChanged(self, params): """ Private method to handle a change of the configuration file. @param params dictionary containing the method parameters sent by the server @type dict """ self.__project.close() self.__project = rope.base.project.Project( self.__projectpath, fscommands=self.__fsCommands) def __queryReferences(self, params): """ Private method to handle the Find References action. @param params dictionary containing the method parameters sent by the server @type dict """ title = params["Title"] filename = params["FileName"] offset = params["Offset"] errorDict = {} occurrences = [] import rope.contrib.findit resource = rope.base.libutils.path_to_resource( self.__project, filename) self.__progressHandle = ProgressHandle(self, title, True) try: occurrences = rope.contrib.findit.find_occurrences( self.__project, resource, offset, unsure=True, in_hierarchy=True, task_handle=self.__progressHandle) except Exception as err: errorDict = self.__handleRopeError(err) self.__progressHandle.reset() self.__progressHandle = None result = { "Title": title, "EntriesCount": len(occurrences), "Entries": [ [occurrence.resource.real_path, occurrence.lineno, occurrence.unsure] for occurrence in occurrences ], } result.update(errorDict) self.sendJson("QueryReferencesResult", result) def __queryDefinition(self, params): """ Private method to handle the Find Definition action. @param params dictionary containing the method parameters sent by the server @type dict """ title = params["Title"] filename = params["FileName"] offset = params["Offset"] source = params["Source"] subcommand = params["Subcommand"] errorDict = {} location = None import rope.contrib.findit resource = rope.base.libutils.path_to_resource( self.__project, filename) try: location = rope.contrib.findit.find_definition( self.__project, source, offset, resource) except Exception as err: errorDict = self.__handleRopeError(err) result = { "Title": title, "Subcommand": subcommand, } if location is not None: result["Location"] = [ location.resource.real_path, location.lineno ] result.update(errorDict) self.sendJson("QueryDefinitionResult", result) def __queryImplementations(self, params): """ Private method to handle the Find Implementations action. @param params dictionary containing the method parameters sent by the server @type dict """ title = params["Title"] filename = params["FileName"] offset = params["Offset"] errorDict = {} occurrences = [] import rope.contrib.findit resource = rope.base.libutils.path_to_resource( self.__project, filename) self.__progressHandle = ProgressHandle(self, title, True) try: occurrences = rope.contrib.findit.find_implementations( self.__project, resource, offset, task_handle=self.__progressHandle) except Exception as err: errorDict = self.__handleRopeError(err) self.__progressHandle.reset() self.__progressHandle = None result = { "Title": title, "EntriesCount": len(occurrences), "Entries": [ [occurrence.resource.real_path, occurrence.lineno, occurrence.unsure] for occurrence in occurrences ], } result.update(errorDict) self.sendJson("QueryImplementationsResult", result) def __performSOA(self, params): """ Private method to perform SOA on all modules. @param params dictionary containing the method parameters sent by the server @type dict """ title = params["Title"] errorDict = {} self.__progressHandle = ProgressHandle(self, title, True) try: rope.base.libutils.analyze_modules( self.__project, task_handle=self.__progressHandle) except Exception as err: errorDict = self.__handleRopeError(err) self.__progressHandle.reset() self.__progressHandle = None result = { "Title": title, } result.update(errorDict) self.sendJson("SoaFinished", result) def __reportChanged(self, params): """ Private method to register some changed sources. @param params dictionary containing the method parameters sent by the server @type dict """ filename = params["FileName"] oldSource = params["OldSource"] try: rope.base.libutils.report_change( self.__project, filename, oldSource) except Exception: # simply ignore it pass def __processHistory(self, params): """ Private method to process the various history related requests. @param params dictionary containing the method parameters sent by the server @type dict """ subcommand = params["Subcommand"] if subcommand == "Get": changes = {} if params["Filename"]: # file history resource = rope.base.libutils.path_to_resource( self.__project, params["Filename"]) undoList = [] for change in reversed(self.__project.history.undo_list): if resource in change.get_changed_resources(): undoList.append(change) redoList = [] for change in self.__project.history.redo_list: if resource in change.get_changed_resources(): redoList.append(change) else: # project history undoList = list(reversed(self.__project.history.undo_list)) redoList = self.__project.history.redo_list result = {"Subcommand": "Histories"} result["Undo"] = [] for change in undoList: changes[id(change)] = change result["Undo"].append([str(change), id(change)]) result["Redo"] = [] for change in redoList: changes[id(change)] = change result["Redo"].append([str(change), id(change)]) self.__changes["History"] = changes self.sendJson("HistoryResult", result) elif subcommand == "GetChange": result = { "Subcommand": "ChangeDescription", "Description": self.__changes["History"][params["Id"]].get_description() } self.sendJson("HistoryResult", result) elif subcommand in ["Undo", "Redo"]: change = self.__changes["History"][params["Id"]] self.__progressHandle = ProgressHandle(self, change.description, False) if subcommand == "Undo": self.__project.history.undo( change, task_handle=self.__progressHandle) else: self.__project.history.redo( change, task_handle=self.__progressHandle) self.__progressHandle.reset() self.__progressHandle = None result = { "Subcommand": subcommand, "ChangedFiles": [ res.real_path for res in change.get_changed_resources() ], } self.sendJson("HistoryResult", result) elif subcommand == "Clear": self.__project.history.clear() elif subcommand == "ClearChanges": try: del self.__changes["History"] except KeyError: pass def __clearChanges(self, params): """ Private method to clear the changes cache of a given change group. @param params dictionary containing the method parameters sent by the server @type dict """ try: del self.__changes[params["ChangeGroup"]] except KeyError: pass def __applyChanges(self, params): """ Private method to apply the changes of a given change group. @param params dictionary containing the method parameters sent by the server @type dict """ errorDict = {} self.__progressHandle = ProgressHandle(self, params["Title"], False) try: changes = self.__changes[params["ChangeGroup"]] self.__project.do(changes, self.__progressHandle) except Exception as err: errorDict = self.__handleRopeError(err) self.__progressHandle.reset() self.__progressHandle = None result = { "Subcommand": "ChangesApplied", "ChangeGroup": params["ChangeGroup"], "Title": params["Title"], "ChangedFiles": [ res.real_path for res in changes.get_changed_resources() ], } result.update(errorDict) self.sendJson("Changes", result) def __previewChanges(self, params): """ Private method to determine the changes data for a preview. @param params dictionary containing the method parameters sent by the server @type dict """ try: changes = self.__changes[params["ChangeGroup"]] description = changes.description except KeyError: changes = None description = "" changesData = [] if changes is not None: for change in changes.changes: changeTitle = str(change) try: changeText = change.get_description() except AttributeError: changeText = None changesData.append([changeTitle, changeText]) result = { "Subcommand": "PreviewChanges", "ChangeGroup": params["ChangeGroup"], "Description": description, "Changes": changesData, } self.sendJson("Changes", result) def __calculateRenameChanges(self, params): """ Private method to calculate the rename changes based on the parameters sent by the server. @param params dictionary containing the method parameters sent by the server @type dict """ changeGroup = params["ChangeGroup"] title = params["Title"] filename = params["FileName"] offset = params["Offset"] isLocal = params["LocalRename"] newName = params["NewName"] renameHierarchy = params["RenameHierarchy"] renameInStrings = params["RenameInStrings"] errorDict = {} changes = [] result = { "ChangeGroup": changeGroup, "Title": title, } import rope.refactor.rename resource = rope.base.libutils.path_to_resource( self.__project, filename) if isLocal: resources = [resource] else: resources = None self.__progressHandle = ProgressHandle(self, title, True) try: renamer = rope.refactor.rename.Rename( self.__project, resource, offset) changes = renamer.get_changes( newName, resources=resources, in_hierarchy=renameHierarchy, unsure=lambda o: self.__confirmUnsure(o, changeGroup), docs=renameInStrings, task_handle=self.__progressHandle) except Exception as err: errorDict = self.__handleRopeError(err) self.__progressHandle.reset() self.__progressHandle = None self.__changes[changeGroup] = changes result["Subcommand"] = "ChangesCalculated" result.update(errorDict) self.sendJson("Changes", result) def __confirmUnsure(self, occurrence, changeGroup): """ Private method to confirm unsure occurrences. @parameter occurrence reference to the occurrence object @type rope.refactor.occurrences.Occurrence @param changeGroup name of the change group @type str @return flag indicating an occurrence @rtype bool """ filename = occurrence.resource.real_path start, end = occurrence.get_primary_range() self.sendJson("Changes", { "Subcommand": "ConfirmUnsure", "ChangeGroup": changeGroup, "FileName": filename, "StartOffset": start, "EndOffset": end, }) answer = self.poll(waitMethod="ConfirmUnsure") return answer["Answer"] def __calculateChangeOccurrencesChanges(self, params): """ Private method to calculate the 'Change Occurrences' changes based on the parameters sent by the server. @param params dictionary containing the method parameters sent by the server @type dict """ changeGroup = params["ChangeGroup"] title = params["Title"] filename = params["FileName"] offset = params["Offset"] newName = params["NewName"] onlyCalls = params["OnlyCalls"] reads = params["Reads"] writes = params["Writes"] errorDict = {} changes = [] result = { "ChangeGroup": changeGroup, "Title": title, } import rope.refactor.rename resource = rope.base.libutils.path_to_resource( self.__project, filename) try: renamer = rope.refactor.rename.ChangeOccurrences( self.__project, resource, offset) changes = renamer.get_changes( newName, only_calls=onlyCalls, reads=reads, writes=writes) except Exception as err: errorDict = self.__handleRopeError(err) self.__changes[changeGroup] = changes result["Subcommand"] = "ChangesCalculated" result.update(errorDict) self.sendJson("Changes", result) def __calculateExtractChanges(self, params): """ Private method to calculate the 'Extract' changes based on the parameters sent by the server. @param params dictionary containing the method parameters sent by the server @type dict """ changeGroup = params["ChangeGroup"] title = params["Title"] filename = params["FileName"] startOffset = params["StartOffset"] endOffset = params["EndOffset"] kind = params["Kind"] newName = params["NewName"] similar = params["Similar"] global_ = params["Global"] errorDict = {} changes = [] result = { "ChangeGroup": changeGroup, "Title": title, } import rope.refactor.extract resource = rope.base.libutils.path_to_resource( self.__project, filename) try: if kind == "variable": extractor = rope.refactor.extract.ExtractVariable( self.__project, resource, startOffset, endOffset) elif kind == "method": extractor = rope.refactor.extract.ExtractMethod( self.__project, resource, startOffset, endOffset) else: raise Exception("Invalid extraction kind <{0}>.".format(kind)) changes = extractor.get_changes( newName, similar=similar, global_=global_) except Exception as err: errorDict = self.__handleRopeError(err) self.__changes[changeGroup] = changes result["Subcommand"] = "ChangesCalculated" result.update(errorDict) self.sendJson("Changes", result) def __requestInlineType(self, params): """ Private method to determine the 'Inline' changes type based on the parameters sent by the server. @param params dictionary containing the method parameters sent by the server @type dict """ changeGroup = params["ChangeGroup"] title = params["Title"] filename = params["FileName"] offset = params["Offset"] errorDict = {} result = { "Subcommand": "InlineType", "ChangeGroup": changeGroup, "Title": title, } import rope.refactor.inline resource = rope.base.libutils.path_to_resource( self.__project, filename) try: inliner = rope.refactor.inline.create_inline( self.__project, resource, offset) result.update({ "Name": inliner.name, "Kind": inliner.get_kind(), }) except Exception as err: errorDict = self.__handleRopeError(err) result.update(errorDict) self.sendJson("Changes", result) def __calculateInlineChanges(self, params): """ Private method to calculate the 'Inline' changes based on the parameters sent by the server. @param params dictionary containing the method parameters sent by the server @type dict """ changeGroup = params["ChangeGroup"] title = params["Title"] filename = params["FileName"] offset = params["Offset"] kind = params["Kind"] errorDict = {} changes = [] result = { "ChangeGroup": changeGroup, "Title": title, } import rope.refactor.extract resource = rope.base.libutils.path_to_resource( self.__project, filename) self.__progressHandle = ProgressHandle(self, title, True) try: inliner = rope.refactor.inline.create_inline( self.__project, resource, offset) if kind == "parameter": opts = { "in_hierarchy": params["Hierarchy"], } else: opts = { "remove": params["Remove"], "only_current": params["OnlyCurrent"], } changes = inliner.get_changes( task_handle=self.__progressHandle, **opts) except Exception as err: errorDict = self.__handleRopeError(err) self.__progressHandle.reset() self.__progressHandle = None self.__changes[changeGroup] = changes result["Subcommand"] = "ChangesCalculated" result.update(errorDict) self.sendJson("Changes", result) def __requestMoveType(self, params): """ Private method to determine the 'Move Method' changes type based on the parameters sent by the server. @param params dictionary containing the method parameters sent by the server @type dict """ changeGroup = params["ChangeGroup"] title = params["Title"] filename = params["FileName"] offset = params["Offset"] errorDict = {} result = { "Subcommand": "MoveType", "ChangeGroup": changeGroup, "Title": title, } import rope.refactor.move resource = rope.base.libutils.path_to_resource( self.__project, filename) try: mover = rope.refactor.move.create_move( self.__project, resource, offset) if isinstance(mover, rope.refactor.move.MoveGlobal): result.update({ "Kind": "move_global_method", "Method": "", }) else: result.update({ "Kind": "move_method", "Method": mover.get_method_name(), }) except Exception as err: errorDict = self.__handleRopeError(err) result.update(errorDict) self.sendJson("Changes", result) def __calculateMoveChanges(self, params): """ Private method to calculate the 'Move ...' changes based on the parameters sent by the server. @param params dictionary containing the method parameters sent by the server @type dict """ changeGroup = params["ChangeGroup"] title = params["Title"] filename = params["FileName"] offset = params["Offset"] kind = params["Kind"] newName = params["NewName"] attribute = params["Attribute"] destination = params["DestinationModule"] errorDict = {} changes = [] result = { "ChangeGroup": changeGroup, "Title": title, } import rope.refactor.move resource = rope.base.libutils.path_to_resource( self.__project, filename) self.__progressHandle = ProgressHandle(self, title, True) try: mover = rope.refactor.move.create_move( self.__project, resource, offset) if kind == "move_method": changes = mover.get_changes( attribute, newName, task_handle=self.__progressHandle) else: if kind == "move_global_method": dest = self.__project.get_pycore().find_module( os.path.splitext(destination)[0]) else: # move_module if destination.endswith(os.sep): destination = destination[:-1] dest = self.__project.get_pycore().find_module( destination) changes = mover.get_changes( dest, task_handle=self.__progressHandle) except Exception as err: errorDict = self.__handleRopeError(err) self.__progressHandle.reset() self.__progressHandle = None self.__changes[changeGroup] = changes result["Subcommand"] = "ChangesCalculated" result.update(errorDict) self.sendJson("Changes", result) def __requestUseFunction(self, params): """ Private method to determine the 'Use Function' function name based on the parameters sent by the server. @param params dictionary containing the method parameters sent by the server @type dict """ changeGroup = params["ChangeGroup"] title = params["Title"] filename = params["FileName"] offset = params["Offset"] errorDict = {} result = { "Subcommand": "UseFunctionName", "ChangeGroup": changeGroup, "Title": title, } import rope.refactor.usefunction resource = rope.base.libutils.path_to_resource( self.__project, filename) try: user = rope.refactor.usefunction.UseFunction( self.__project, resource, offset) result["FunctionName"] = user.get_function_name() except Exception as err: errorDict = self.__handleRopeError(err) result.update(errorDict) self.sendJson("Changes", result) def __calculateUseFunctionChanges(self, params): """ Private method to calculate the 'Use Function' changes based on the parameters sent by the server. @param params dictionary containing the method parameters sent by the server @type dict """ changeGroup = params["ChangeGroup"] title = params["Title"] filename = params["FileName"] offset = params["Offset"] errorDict = {} changes = [] result = { "ChangeGroup": changeGroup, "Title": title, } import rope.refactor.usefunction resource = rope.base.libutils.path_to_resource( self.__project, filename) self.__progressHandle = ProgressHandle(self, title, True) try: user = rope.refactor.usefunction.UseFunction( self.__project, resource, offset) changes = user.get_changes(task_handle=self.__progressHandle) except Exception as err: errorDict = self.__handleRopeError(err) self.__progressHandle.reset() self.__progressHandle = None self.__changes[changeGroup] = changes result["Subcommand"] = "ChangesCalculated" result.update(errorDict) self.sendJson("Changes", result) if __name__ == '__main__': if len(sys.argv) != 4: print('Host, port and project path parameters are missing. Abort.') sys.exit(1) host, port, projectPath = sys.argv[1:] client = RefactoringClient(host, int(port), projectPath) # Start the main loop client.run() sys.exit(0) # # eflag: noqa = M801