def __init__(self, project_root, filename): self.project_root = project_root if ROPE_AVAILABLE: self.project = Project(project_root) self.resource = path_to_resource(self.project, filename) else: self.project = None with open(filename) as f: self.resource = StringIO(f.read())
class Refactor(object): """The main refactoring interface. Once initialized, the first call should be to get_refactor_options to get a list of refactoring options at a given position. The returned value will also list any additional options required. Once you picked one, you can call get_changes to get the actual refactoring changes. """ def __init__(self, project_root, filename): self.project_root = project_root if ROPE_AVAILABLE: self.project = Project(project_root) self.resource = path_to_resource(self.project, filename) else: self.project = None with open(filename) as f: self.resource = StringIO(f.read()) def get_refactor_options(self, start, end=None): """Return a list of options for refactoring at the given position. If `end` is also given, refactoring on a region is assumed. Each option is a dictionary of key/value pairs. The value of the key 'name' is the one to be used for get_changes. The key 'args' contains a list of additional arguments required for get_changes. """ result = [] for symbol in dir(self): if not symbol.startswith("refactor_"): continue method = getattr(self, symbol) if not method.refactor_notes.get("available", True): continue category = method.refactor_notes["category"] if end is not None and category != "Region": continue if end is None and category == "Region": continue is_on_symbol = self._is_on_symbol(start) if not is_on_symbol and category in ("Symbol", "Method"): continue requires_import = method.refactor_notes.get("only_on_imports", False) if requires_import and not self._is_on_import_statement(start): continue result.append(method.refactor_notes) return result def _is_on_import_statement(self, offset): "Does this offset point to an import statement?" data = self.resource.read() bol = data.rfind("\n", 0, offset) + 1 eol = data.find("\n", 0, bol) if eol == -1: eol = len(data) line = data[bol:eol] line = line.strip() if line.startswith("import ") or line.startswith("from "): return True else: return False def _is_on_symbol(self, offset): "Is this offset on a symbol?" if not ROPE_AVAILABLE: return False data = self.resource.read() if len(data) > offset and not data[offset].isalnum(): return False word = worder.get_name_at(self.resource, offset) if word: return True else: return False def get_changes(self, name, *args): """Return a list of changes for the named refactoring action. Changes are dictionaries describing a single action to be taken for the refactoring to be successful. A change has an action and possibly a type. In the description below, the action is before the slash and the type after it. change: Change file contents - file: The path to the file to change - contents: The new contents for the file - Diff: A unified diff showing the changes introduced create/file: Create a new file - file: The file to create create/directory: Create a new directory - path: The directory to create move/file: Rename a file - source: The path to the source file - destination: The path to the destination file name move/directory: Rename a directory - source: The path to the source directory - destination: The path to the destination directory name delete/file: Delete a file - file: The file to delete delete/directory: Delete a directory - path: The directory to delete """ if not name.startswith("refactor_"): raise ValueError("Bad refactoring name {0}".format(name)) method = getattr(self, name) if not method.refactor_notes.get("available", True): raise RuntimeError("Method not available") return method(*args) @options( "Convert from x import y to import x.y as y", category="Imports", args=[("offset", "offset", None)], only_on_imports=True, available=ROPE_AVAILABLE, ) def refactor_froms_to_imports(self, offset): """Converting imports of the form "from ..." to "import ...".""" refactor = ImportOrganizer(self.project) changes = refactor.froms_to_imports(self.resource, offset) return translate_changes(changes) @options("Reorganize and clean up", category="Imports", available=ROPE_AVAILABLE) def refactor_organize_imports(self): """Clean up and organize imports.""" refactor = ImportOrganizer(self.project) changes = refactor.organize_imports(self.resource) return translate_changes(changes) @options("Add missing imports", category="Imports") def refactor_add_missing_imports(self): return autoimport.get_changes(self.resource.real_path) @options("Convert the current module into a package", category="Module", available=ROPE_AVAILABLE) def refactor_module_to_package(self): """Convert the current module into a package.""" refactor = ModuleToPackage(self.project, self.resource) changes = refactor.get_changes() return translate_changes(changes) @options( "Rename symbol at point", category="Symbol", args=[("offset", "offset", None), ("new_name", "string", "Rename to: ")], available=ROPE_AVAILABLE, ) def refactor_rename_at_point(self, offset, new_name): """Rename the symbol at point.""" refactor = Rename(self.project, self.resource, offset) changes = refactor.get_changes(new_name) return translate_changes(changes) @options( "Rename current module", category="Module", args=[("new_name", "string", "Rename to: ")], available=ROPE_AVAILABLE, ) def refactor_rename_current_module(self, new_name): """Rename the current module.""" refactor = Rename(self.project, self.resource, None) changes = refactor.get_changes(new_name) return translate_changes(changes) @options( "Move the current module to a different package", category="Module", args=[("new_name", "directory", "Destination package: ")], available=ROPE_AVAILABLE, ) def refactor_move_module(self, new_name): """Move the current module.""" refactor = create_move(self.project, self.resource) resource = path_to_resource(self.project, new_name) changes = refactor.get_changes(resource) return translate_changes(changes) @options( "Inline function call at point", category="Symbol", args=[("offset", "offset", None), ("only_this", "boolean", "Only this occurrence? ")], available=ROPE_AVAILABLE, ) def refactor_create_inline(self, offset, only_this): """Inline the function call at point.""" refactor = create_inline(self.project, self.resource, offset) if only_this: changes = refactor.get_changes(remove=False, only_current=True) else: changes = refactor.get_changes(remove=True, only_current=False) return translate_changes(changes) @options( "Extract current region as a method", category="Region", args=[ ("start", "start_offset", None), ("end", "end_offset", None), ("name", "string", "Method name: "), ("make_global", "boolean", "Create global method? "), ], available=ROPE_AVAILABLE, ) def refactor_extract_method(self, start, end, name, make_global): """Extract region as a method.""" refactor = ExtractMethod(self.project, self.resource, start, end) changes = refactor.get_changes(name, similar=True, global_=make_global) return translate_changes(changes) @options( "Use the function at point wherever possible", category="Method", args=[("offset", "offset", None)], available=ROPE_AVAILABLE, ) def refactor_use_function(self, offset): """Use the function at point wherever possible.""" refactor = UseFunction(self.project, self.resource, offset) changes = refactor.get_changes() return translate_changes(changes)
class Refactor(object): """The main refactoring interface. Once initialized, the first call should be to get_refactor_options to get a list of refactoring options at a given position. The returned value will also list any additional options required. Once you picked one, you can call get_changes to get the actual refactoring changes. """ def __init__(self, project_root, filename): self.project_root = project_root if ROPE_AVAILABLE: self.project = Project(project_root) self.resource = path_to_resource(self.project, filename) else: self.project = None with open(filename) as f: self.resource = StringIO(f.read()) def get_refactor_options(self, start, end=None): """Return a list of options for refactoring at the given position. If `end` is also given, refactoring on a region is assumed. Each option is a dictionary of key/value pairs. The value of the key 'name' is the one to be used for get_changes. The key 'args' contains a list of additional arguments required for get_changes. """ result = [] for symbol in dir(self): if not symbol.startswith("refactor_"): continue method = getattr(self, symbol) if not method.refactor_notes.get('available', True): continue category = method.refactor_notes['category'] if end is not None and category != 'Region': continue if end is None and category == 'Region': continue is_on_symbol = self._is_on_symbol(start) if not is_on_symbol and category in ('Symbol', 'Method'): continue requires_import = method.refactor_notes.get( 'only_on_imports', False) if requires_import and not self._is_on_import_statement(start): continue result.append(method.refactor_notes) return result def _is_on_import_statement(self, offset): "Does this offset point to an import statement?" data = self.resource.read() bol = data.rfind("\n", 0, offset) + 1 eol = data.find("\n", 0, bol) if eol == -1: eol = len(data) line = data[bol:eol] line = line.strip() if line.startswith("import ") or line.startswith("from "): return True else: return False def _is_on_symbol(self, offset): "Is this offset on a symbol?" if not ROPE_AVAILABLE: return False data = self.resource.read() if len(data) > offset and not data[offset].isalnum(): return False word = worder.get_name_at(self.resource, offset) if word: return True else: return False def get_changes(self, name, *args): """Return a list of changes for the named refactoring action. Changes are dictionaries describing a single action to be taken for the refactoring to be successful. A change has an action and possibly a type. In the description below, the action is before the slash and the type after it. change: Change file contents - file: The path to the file to change - contents: The new contents for the file - Diff: A unified diff showing the changes introduced create/file: Create a new file - file: The file to create create/directory: Create a new directory - path: The directory to create move/file: Rename a file - source: The path to the source file - destination: The path to the destination file name move/directory: Rename a directory - source: The path to the source directory - destination: The path to the destination directory name delete/file: Delete a file - file: The file to delete delete/directory: Delete a directory - path: The directory to delete """ if not name.startswith("refactor_"): raise ValueError("Bad refactoring name {0}".format(name)) method = getattr(self, name) if not method.refactor_notes.get('available', True): raise RuntimeError("Method not available") return method(*args) @options("Convert from x import y to import x.y as y", category="Imports", args=[("offset", "offset", None)], only_on_imports=True, available=ROPE_AVAILABLE) def refactor_froms_to_imports(self, offset): """Converting imports of the form "from ..." to "import ...".""" refactor = ImportOrganizer(self.project) changes = refactor.froms_to_imports(self.resource, offset) return translate_changes(changes) @options("Reorganize and clean up", category="Imports", available=ROPE_AVAILABLE) def refactor_organize_imports(self): """Clean up and organize imports.""" refactor = ImportOrganizer(self.project) changes = refactor.organize_imports(self.resource) return translate_changes(changes) @options("Add missing imports", category="Imports") def refactor_add_missing_imports(self): return autoimport.get_changes(self.resource.real_path) @options("Convert the current module into a package", category="Module", available=ROPE_AVAILABLE) def refactor_module_to_package(self): """Convert the current module into a package.""" refactor = ModuleToPackage(self.project, self.resource) changes = refactor.get_changes() return translate_changes(changes) @options("Rename symbol at point", category="Symbol", args=[("offset", "offset", None), ("new_name", "string", "Rename to: ")], available=ROPE_AVAILABLE) def refactor_rename_at_point(self, offset, new_name): """Rename the symbol at point.""" refactor = Rename(self.project, self.resource, offset) changes = refactor.get_changes(new_name) return translate_changes(changes) @options("Rename current module", category="Module", args=[("new_name", "string", "Rename to: ")], available=ROPE_AVAILABLE) def refactor_rename_current_module(self, new_name): """Rename the current module.""" refactor = Rename(self.project, self.resource, None) changes = refactor.get_changes(new_name) return translate_changes(changes) @options("Move the current module to a different package", category="Module", args=[("new_name", "directory", "Destination package: ")], available=ROPE_AVAILABLE) def refactor_move_module(self, new_name): """Move the current module.""" refactor = create_move(self.project, self.resource) resource = path_to_resource(self.project, new_name) changes = refactor.get_changes(resource) return translate_changes(changes) @options("Inline function call at point", category="Symbol", args=[("offset", "offset", None), ("only_this", "boolean", "Only this occurrence? ")], available=ROPE_AVAILABLE) def refactor_create_inline(self, offset, only_this): """Inline the function call at point.""" refactor = create_inline(self.project, self.resource, offset) if only_this: changes = refactor.get_changes(remove=False, only_current=True) else: changes = refactor.get_changes(remove=True, only_current=False) return translate_changes(changes) @options("Extract current region as a method", category="Region", args=[("start", "start_offset", None), ("end", "end_offset", None), ("name", "string", "Method name: "), ("make_global", "boolean", "Create global method? ")], available=ROPE_AVAILABLE) def refactor_extract_method(self, start, end, name, make_global): """Extract region as a method.""" refactor = ExtractMethod(self.project, self.resource, start, end) changes = refactor.get_changes(name, similar=True, global_=make_global) return translate_changes(changes) @options("Use the function at point wherever possible", category="Method", args=[("offset", "offset", None)], available=ROPE_AVAILABLE) def refactor_use_function(self, offset): """Use the function at point wherever possible.""" refactor = UseFunction(self.project, self.resource, offset) changes = refactor.get_changes() return translate_changes(changes)