예제 #1
0
 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())
예제 #2
0
 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())
예제 #3
0
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)
예제 #4
0
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)