<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">import re

import rope.base.exceptions
import rope.refactor.functionutils
from rope.base import (pynames, pyobjects, codeanalyze,
                       taskhandle, evaluate, worder, utils)
from rope.base.change import ChangeSet, ChangeContents
from rope.refactor import (occurrences, rename, sourceutils,
                           importutils, move, change_signature)


def create_inline(project, resource, offset):
    """Create a refactoring object for inlining

    Based on `resource` and `offset` it returns an instance of
    `InlineMethod`, `InlineVariable` or `InlineParameter`.

    """
    pycore = project.pycore
    pyname = _get_pyname(pycore, resource, offset)
    message = 'Inline refactoring should be performed on ' \
              'a method, local variable or parameter.'
    if pyname is None:
        raise rope.base.exceptions.RefactoringError(message)
    if isinstance(pyname, pynames.ImportedName):
        pyname = pyname._get_imported_pyname()
    if isinstance(pyname, pynames.AssignedName):
        return InlineVariable(project, resource, offset)
    if isinstance(pyname, pynames.ParameterName):
        return InlineParameter(project, resource, offset)
    if isinstance(pyname.get_object(), pyobjects.PyFunction):
        return InlineMethod(project, resource, offset)
    else:
        raise rope.base.exceptions.RefactoringError(message)


class _Inliner(object):

    def __init__(self, project, resource, offset):
        self.project = project
        self.pycore = project.pycore
        self.pyname = _get_pyname(self.pycore, resource, offset)
        range_finder = worder.Worder(resource.read())
        self.region = range_finder.get_primary_range(offset)
        self.name = range_finder.get_word_at(offset)
        self.offset = offset
        self.original = resource

    def get_changes(self, *args, **kwds):
        pass

    def get_kind(self):
        """Return either 'variable', 'method' or 'parameter'"""


class InlineMethod(_Inliner):

    def __init__(self, *args, **kwds):
        super(InlineMethod, self).__init__(*args, **kwds)
        self.pyfunction = self.pyname.get_object()
        self.pymodule = self.pyfunction.get_module()
        self.resource = self.pyfunction.get_module().get_resource()
        self.occurrence_finder = occurrences.create_finder(
            self.pycore, self.name, self.pyname)
        self.normal_generator = _DefinitionGenerator(self.project,
                                                     self.pyfunction)
        self._init_imports()

    def _init_imports(self):
        body = sourceutils.get_body(self.pyfunction)
        body, imports = move.moving_code_with_imports(
            self.pycore, self.resource, body)
        self.imports = imports
        self.others_generator = _DefinitionGenerator(
            self.project, self.pyfunction, body=body)

    def _get_scope_range(self):
        scope = self.pyfunction.get_scope()
        lines = self.pymodule.lines
        logicals = self.pymodule.logical_lines
        start_line = scope.get_start()
        if self.pyfunction.decorators:
            decorators = self.pyfunction.decorators
            if hasattr(decorators[0], 'lineno'):
                start_line = decorators[0].lineno
        start_offset = lines.get_line_start(start_line)
        end_offset = min(lines.get_line_end(scope.end) + 1,
                         len(self.pymodule.source_code))
        return (start_offset, end_offset)

    def get_changes(self, remove=True, only_current=False, resources=None,
                    task_handle=taskhandle.NullTaskHandle()):
        """Get the changes this refactoring makes

        If `remove` is `False` the definition will not be removed.  If
        `only_current` is `True`, the the current occurrence will be
        inlined, only.
        """
        changes = ChangeSet('Inline method &lt;%s&gt;' % self.name)
        if resources is None:
            resources = self.pycore.get_python_files()
        if only_current:
            resources = [self.original]
            if remove:
                resources.append(self.resource)
        job_set = task_handle.create_jobset('Collecting Changes',
                                            len(resources))
        for file in resources:
            job_set.started_job(file.path)
            if file == self.resource:
                changes.add_change(self._defining_file_changes(
                        changes, remove=remove, only_current=only_current))
            else:
                aim = None
                if only_current and self.original == file:
                    aim = self.offset
                handle = _InlineFunctionCallsForModuleHandle(
                    self.pycore, file, self.others_generator, aim)
                result = move.ModuleSkipRenamer(
                    self.occurrence_finder, file, handle).get_changed_module()
                if result is not None:
                    result = _add_imports(self.pycore, result,
                                          file, self.imports)
                    if remove:
                        result = _remove_from(self.pycore, self.pyname,
                                              result, file)
                    changes.add_change(ChangeContents(file, result))
            job_set.finished_job()
        return changes

    def _get_removed_range(self):
        scope = self.pyfunction.get_scope()
        lines = self.pymodule.lines
        logical = self.pymodule.logical_lines
        start_line = scope.get_start()
        start, end = self._get_scope_range()
        end_line = scope.get_end()
        for i in range(end_line + 1, lines.length()):
            if lines.get_line(i).strip() == '':
                end_line = i
            else:
                break
        end = min(lines.get_line_end(end_line) + 1,
                  len(self.pymodule.source_code))
        return (start, end)

    def _defining_file_changes(self, changes, remove, only_current):
        start_offset, end_offset = self._get_removed_range()
        aim = None
        if only_current:
            if self.resource == self.original:
                aim = self.offset
            else:
                # we don't want to change any of them
                aim = len(self.resource.read()) + 100
        handle = _InlineFunctionCallsForModuleHandle(
            self.pycore, self.resource,
            self.normal_generator, aim_offset=aim)
        replacement = None
        if remove:
            replacement = self._get_method_replacement()
        result = move.ModuleSkipRenamer(
            self.occurrence_finder, self.resource, handle, start_offset,
            end_offset, replacement).get_changed_module()
        return ChangeContents(self.resource, result)

    def _get_method_replacement(self):
        if self._is_the_last_method_of_a_class():
            indents = sourceutils.get_indents(
                self.pymodule.lines, self.pyfunction.get_scope().get_start())
            return ' ' * indents + 'pass\n'
        return ''

    def _is_the_last_method_of_a_class(self):
        pyclass = self.pyfunction.parent
        if not isinstance(pyclass, pyobjects.PyClass):
            return False
        class_start, class_end = sourceutils.get_body_region(pyclass)
        source = self.pymodule.source_code
        lines = self.pymodule.lines
        func_start, func_end = self._get_scope_range()
        if source[class_start:func_start].strip() == '' and \
           source[func_end:class_end].strip() == '':
            return True
        return False

    def get_kind(self):
        return 'method'


class InlineVariable(_Inliner):

    def __init__(self, *args, **kwds):
        super(InlineVariable, self).__init__(*args, **kwds)
        self.pymodule = self.pyname.get_definition_location()[0]
        self.resource = self.pymodule.get_resource()
        self._check_exceptional_conditions()
        self._init_imports()

    def _check_exceptional_conditions(self):
        if len(self.pyname.assignments) != 1:
            raise rope.base.exceptions.RefactoringError(
                'Local variable should be assigned once for inlining.')

    def get_changes(self, remove=True, only_current=False, resources=None,
                    task_handle=taskhandle.NullTaskHandle()):
        if resources is None:
            if rename._is_local(self.pyname):
                resources = [self.resource]
            else:
                resources = self.pycore.get_python_files()
        if only_current:
            resources = [self.original]
            if remove and self.original != self.resource:
                resources.append(self.resource)
        changes = ChangeSet('Inline variable &lt;%s&gt;' % self.name)
        jobset = task_handle.create_jobset('Calculating changes',
                                           len(resources))
        for resource in resources:
            jobset.started_job(resource.path)
            if resource == self.resource:
                source = self._change_main_module(remove, only_current)
                changes.add_change(ChangeContents(self.resource, source))
            else:
                result = self._change_module(resource, remove, only_current)
                if result is not None:
                    result = _add_imports(self.pycore, result,
                                          resource, self.imports)
                    changes.add_change(ChangeContents(resource, result))
            jobset.finished_job()
        return changes

    def _change_main_module(self, remove, only_current):
        region = None
        if only_current and self.original == self.resource:
            region = self.region
        return _inline_variable(self.pycore, self.pymodule, self.pyname,
                                self.name, remove=remove, region=region)

    def _init_imports(self):
        vardef = _getvardef(self.pymodule, self.pyname)
        self.imported, self.imports = move.moving_code_with_imports(
            self.pycore, self.resource, vardef)

    def _change_module(self, resource, remove, only_current):
        filters = [occurrences.NoImportsFilter(),
                   occurrences.PyNameFilter(self.pyname)]
        if only_current and resource == self.original:
            def check_aim(occurrence):
                start, end = occurrence.get_primary_range()
                if self.offset &lt; start or end &lt; self.offset:
                    return False
            filters.insert(0, check_aim)
        finder = occurrences.Finder(self.pycore, self.name, filters=filters)
        changed = rename.rename_in_module(
            finder, self.imported, resource=resource, replace_primary=True)
        if changed and remove:
            changed = _remove_from(self.pycore, self.pyname, changed, resource)
        return changed

    def get_kind(self):
        return 'variable'


class InlineParameter(_Inliner):

    def __init__(self, *args, **kwds):
        super(InlineParameter, self).__init__(*args, **kwds)
        resource, offset = self._function_location()
        index = self.pyname.index
        self.changers = [change_signature.ArgumentDefaultInliner(index)]
        self.signature = change_signature.ChangeSignature(self.project,
                                                          resource, offset)

    def _function_location(self):
        pymodule, lineno = self.pyname.get_definition_location()
        resource = pymodule.get_resource()
        start = pymodule.lines.get_line_start(lineno)
        word_finder = worder.Worder(pymodule.source_code)
        offset = word_finder.find_function_offset(start)
        return resource, offset

    def get_changes(self, **kwds):
        """Get the changes needed by this refactoring

        See `rope.refactor.change_signature.ChangeSignature.get_changes()`
        for arguments.
        """
        return self.signature.get_changes(self.changers, **kwds)

    def get_kind(self):
        return 'parameter'


def _join_lines(lines):
    definition_lines = []
    for unchanged_line in lines:
        line = unchanged_line.strip()
        if line.endswith('\\'):
            line = line[:-1].strip()
        definition_lines.append(line)
    joined = ' '.join(definition_lines)
    return joined


class _DefinitionGenerator(object):

    def __init__(self, project, pyfunction, body=None):
        self.pycore = project.pycore
        self.pyfunction = pyfunction
        self.pymodule = pyfunction.get_module()
        self.resource = self.pymodule.get_resource()
        self.definition_info = self._get_definition_info()
        self.definition_params = self._get_definition_params()
        self._calculated_definitions = {}
        if body is not None:
            self.body = body
        else:
            self.body = sourceutils.get_body(self.pyfunction)

    def _get_definition_info(self):
        return rope.refactor.functionutils.DefinitionInfo.read(self.pyfunction)

    def _get_definition_params(self):
        definition_info = self.definition_info
        paramdict = dict([pair for pair in definition_info.args_with_defaults])
        if definition_info.args_arg is not None or \
           definition_info.keywords_arg is not None:
            raise rope.base.exceptions.RefactoringError(
                'Cannot inline functions with list and keyword arguements.')
        if self.pyfunction.get_kind() == 'classmethod':
            paramdict[definition_info.args_with_defaults[0][0]] = \
                self.pyfunction.parent.get_name()
        return paramdict

    def get_function_name(self):
        return self.pyfunction.get_name()

    def get_definition(self, primary, pyname, call, returns=False):
        # caching already calculated definitions
        key = (call, returns)
        if key not in self._calculated_definitions:
            self._calculated_definitions[key] = self._calculate_definition(
                primary, pyname, call, returns)
        return self._calculated_definitions[key]

    def _calculate_definition(self, primary, pyname, call, returns):
        call_info = rope.refactor.functionutils.CallInfo.read(
            primary, pyname, self.definition_info, call)
        paramdict = self.definition_params
        mapping = rope.refactor.functionutils.ArgumentMapping(
            self.definition_info, call_info)
        for param_name, value in mapping.param_dict.items():
            paramdict[param_name] = value
        header = ''
        to_be_inlined = []
        for name, value in paramdict.items():
            if name != value and value is not None:
                header += name + ' = ' + value.replace('\n', ' ') + '\n'
                to_be_inlined.append(name)
        source = header + self.body
        for name in to_be_inlined:
            pymodule = self.pycore.get_string_module(source, self.resource)
            pyname = pymodule[name]
            source = _inline_variable(self.pycore, pymodule, pyname, name)
        return self._replace_returns_with(source, returns)

    def _replace_returns_with(self, source, returns):
        result = []
        returned = None
        last_changed = 0
        for match in _DefinitionGenerator._get_return_pattern().finditer(source):
            for key, value in match.groupdict().items():
                if value and key == 'return':
                    result.append(source[last_changed:match.start('return')])
                    if returns:
                        self._check_nothing_after_return(source,
                                                         match.end('return'))
                        returned = _join_lines(
                            source[match.end('return'): len(source)].splitlines())
                        last_changed = len(source)
                    else:
                        current = match.end('return')
                        while current &lt; len(source) and source[current] in ' \t':
                            current += 1
                        last_changed = current
                        if current == len(source) or source[current] == '\n':
                            result.append('pass')
        result.append(source[last_changed:])
        return ''.join(result), returned

    def _check_nothing_after_return(self, source, offset):
        lines = codeanalyze.SourceLinesAdapter(source)
        lineno = lines.get_line_number(offset)
        logical_lines = codeanalyze.LogicalLineFinder(lines)
        lineno = logical_lines.logical_line_in(lineno)[1]
        if source[lines.get_line_end(lineno):len(source)].strip() != '':
            raise rope.base.exceptions.RefactoringError(
                'Cannot inline functions with statements after return statement.')

    @classmethod
    def _get_return_pattern(cls):
        if not hasattr(cls, '_return_pattern'):
            def named_pattern(name, list_):
                return "(?P&lt;%s&gt;" % name + "|".join(list_) + ")"
            comment_pattern = named_pattern('comment', [r'#[^\n]*'])
            string_pattern = named_pattern('string',
                                           [codeanalyze.get_string_pattern()])
            return_pattern = r'\b(?P&lt;return&gt;return)\b'
            cls._return_pattern = re.compile(comment_pattern + "|" +
                                             string_pattern + "|" +
                                             return_pattern)
        return cls._return_pattern


class _InlineFunctionCallsForModuleHandle(object):

    def __init__(self, pycore, resource,
                 definition_generator, aim_offset=None):
        """Inlines occurrences

        If `aim` is not `None` only the occurrences that intersect
        `aim` offset will be inlined.

        """
        self.pycore = pycore
        self.generator = definition_generator
        self.resource = resource
        self.aim = aim_offset

    def occurred_inside_skip(self, change_collector, occurrence):
        if not occurrence.is_defined():
            raise rope.base.exceptions.RefactoringError(
                'Cannot inline functions that reference themselves')

    def occurred_outside_skip(self, change_collector, occurrence):
        start, end = occurrence.get_primary_range()
        # we remove out of date imports later
        if occurrence.is_in_import_statement():
            return
        # the function is referenced outside an import statement
        if not occurrence.is_called():
            raise rope.base.exceptions.RefactoringError(
                'Reference to inlining function other than function call'
                ' in &lt;file: %s, offset: %d&gt;' % (self.resource.path, start))
        if self.aim is not None and (self.aim &lt; start or self.aim &gt; end):
            return
        end_parens = self._find_end_parens(self.source, end - 1)
        lineno = self.lines.get_line_number(start)
        start_line, end_line = self.pymodule.logical_lines.\
                               logical_line_in(lineno)
        line_start = self.lines.get_line_start(start_line)
        line_end = self.lines.get_line_end(end_line)
        returns = self.source[line_start:start].strip() != '' or \
                  self.source[end_parens:line_end].strip() != ''
        indents = sourceutils.get_indents(self.lines, start_line)
        primary, pyname = occurrence.get_primary_and_pyname()
        definition, returned = self.generator.get_definition(
            primary, pyname, self.source[start:end_parens], returns=returns)
        end = min(line_end + 1, len(self.source))
        change_collector.add_change(
            line_start, end, sourceutils.fix_indentation(definition, indents))
        if returns:
            name = returned
            if name is None:
                name = 'None'
            change_collector.add_change(
                line_end, end, self.source[line_start:start] + name +
                self.source[end_parens:end])

    def _find_end_parens(self, source, offset):
        finder = worder.Worder(source)
        return finder.get_word_parens_range(offset)[1]

    @property
    @utils.saveit
    def pymodule(self):
        return self.pycore.resource_to_pyobject(self.resource)

    @property
    @utils.saveit
    def source(self):
        if self.resource is not None:
            return self.resource.read()
        else:
            return self.pymodule.source_code

    @property
    @utils.saveit
    def lines(self):
        return self.pymodule.lines


def _inline_variable(pycore, pymodule, pyname, name,
                     remove=True, region=None):
    definition = _getvardef(pymodule, pyname)
    start, end = _assigned_lineno(pymodule, pyname)

    occurrence_finder = occurrences.create_finder(pycore, name, pyname)
    changed_source = rename.rename_in_module(
        occurrence_finder, definition, pymodule=pymodule,
        replace_primary=True, writes=False, region=region)
    if changed_source is None:
        changed_source = pymodule.source_code
    if remove:
        lines = codeanalyze.SourceLinesAdapter(changed_source)
        source = changed_source[:lines.get_line_start(start)] + \
                 changed_source[lines.get_line_end(end) + 1:]
    else:
        source = changed_source
    return source

def _getvardef(pymodule, pyname):
    assignment = pyname.assignments[0]
    lines = pymodule.lines
    start, end = _assigned_lineno(pymodule, pyname)
    definition_with_assignment = _join_lines(
        [lines.get_line(n) for n in range(start, end + 1)])
    if assignment.levels:
        raise rope.base.exceptions.RefactoringError(
            'Cannot inline tuple assignments.')
    definition = definition_with_assignment[definition_with_assignment.\
                                            index('=') + 1:].strip()
    return definition

def _assigned_lineno(pymodule, pyname):
    definition_line = pyname.assignments[0].ast_node.lineno
    return pymodule.logical_lines.logical_line_in(definition_line)

def _add_imports(pycore, source, resource, imports):
    if not imports:
        return source
    pymodule = pycore.get_string_module(source, resource)
    module_import = importutils.get_module_imports(pycore, pymodule)
    for import_info in imports:
        module_import.add_import(import_info)
    source = module_import.get_changed_source()
    pymodule = pycore.get_string_module(source, resource)
    import_tools = importutils.ImportTools(pycore)
    return import_tools.organize_imports(pymodule, unused=False, sort=False)

def _get_pyname(pycore, resource, offset):
    pymodule = pycore.resource_to_pyobject(resource)
    pyname = evaluate.eval_location(pymodule, offset)
    if isinstance(pyname, pynames.ImportedName):
        pyname = pyname._get_imported_pyname()
    return pyname

def _remove_from(pycore, pyname, source, resource):
    pymodule = pycore.get_string_module(source, resource)
    module_import = importutils.get_module_imports(pycore, pymodule)
    module_import.remove_pyname(pyname)
    return module_import.get_changed_source()
</pre></body></html>