| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- import warnings
- from rope.base import change, taskhandle, builtins, ast, codeanalyze
- from rope.refactor import patchedast, similarfinder, sourceutils
- from rope.refactor.importutils import module_imports
- class Restructure(object):
- """A class to perform python restructurings
- A restructuring transforms pieces of code matching `pattern` to
- `goal`. In the `pattern` wildcards can appear. Wildcards match
- some piece of code based on their kind and arguments that are
- passed to them through `args`.
- `args` is a dictionary of wildcard names to wildcard arguments.
- If the argument is a tuple, the first item of the tuple is
- considered to be the name of the wildcard to use; otherwise the
- "default" wildcard is used. For getting the list arguments a
- wildcard supports, see the pydoc of the wildcard. (see
- `rope.refactor.wildcard.DefaultWildcard` for the default
- wildcard.)
- `wildcards` is the list of wildcard types that can appear in
- `pattern`. See `rope.refactor.wildcards`. If a wildcard does not
- specify its kind (by using a tuple in args), the wildcard named
- "default" is used. So there should be a wildcard with "default"
- name in `wildcards`.
- `imports` is the list of imports that changed modules should
- import. Note that rope handles duplicate imports and does not add
- the import if it already appears.
- Example #1::
- pattern ${pyobject}.get_attribute(${name})
- goal ${pyobject}[${name}]
- args pyobject: instance=rope.base.pyobjects.PyObject
- Example #2::
- pattern ${name} in ${pyobject}.get_attributes()
- goal ${name} in {pyobject}
- args pyobject: instance=rope.base.pyobjects.PyObject
- Example #3::
- pattern ${pycore}.create_module(${project}.root, ${name})
- goal generate.create_module(${project}, ${name})
- imports
- from rope.contrib import generate
- args
- pycore: type=rope.base.pycore.PyCore
- project: type=rope.base.project.Project
- Example #4::
- pattern ${pow}(${param1}, ${param2})
- goal ${param1} ** ${param2}
- args pow: name=mod.pow, exact
- Example #5::
- pattern ${inst}.longtask(${p1}, ${p2})
- goal
- ${inst}.subtask1(${p1})
- ${inst}.subtask2(${p2})
- args
- inst: type=mod.A,unsure
- """
- def __init__(self, project, pattern, goal, args=None,
- imports=None, wildcards=None):
- """Construct a restructuring
- See class pydoc for more info about the arguments.
- """
- self.pycore = project.pycore
- self.pattern = pattern
- self.goal = goal
- self.args = args
- if self.args is None:
- self.args = {}
- self.imports = imports
- if self.imports is None:
- self.imports = []
- self.wildcards = wildcards
- self.template = similarfinder.CodeTemplate(self.goal)
- def get_changes(self, checks=None, imports=None, resources=None,
- task_handle=taskhandle.NullTaskHandle()):
- """Get the changes needed by this restructuring
- `resources` can be a list of `rope.base.resources.File`\s to
- apply the restructuring on. If `None`, the restructuring will
- be applied to all python files.
- `checks` argument has been deprecated. Use the `args` argument
- of the constructor. The usage of::
- strchecks = {'obj1.type': 'mod.A', 'obj2': 'mod.B',
- 'obj3.object': 'mod.C'}
- checks = restructuring.make_checks(strchecks)
- can be replaced with::
- args = {'obj1': 'type=mod.A', 'obj2': 'name=mod.B',
- 'obj3': 'object=mod.C'}
- where obj1, obj2 and obj3 are wildcard names that appear
- in restructuring pattern.
- """
- if checks is not None:
- warnings.warn(
- 'The use of checks parameter is deprecated; '
- 'use the args parameter of the constructor instead.',
- DeprecationWarning, stacklevel=2)
- for name, value in checks.items():
- self.args[name] = similarfinder._pydefined_to_str(value)
- if imports is not None:
- warnings.warn(
- 'The use of imports parameter is deprecated; '
- 'use imports parameter of the constructor, instead.',
- DeprecationWarning, stacklevel=2)
- self.imports = imports
- changes = change.ChangeSet('Restructuring <%s> to <%s>' %
- (self.pattern, self.goal))
- if resources is not None:
- files = [resource for resource in resources
- if self.pycore.is_python_file(resource)]
- else:
- files = self.pycore.get_python_files()
- job_set = task_handle.create_jobset('Collecting Changes', len(files))
- for resource in files:
- job_set.started_job(resource.path)
- pymodule = self.pycore.resource_to_pyobject(resource)
- finder = similarfinder.SimilarFinder(pymodule,
- wildcards=self.wildcards)
- matches = list(finder.get_matches(self.pattern, self.args))
- computer = self._compute_changes(matches, pymodule)
- result = computer.get_changed()
- if result is not None:
- imported_source = self._add_imports(resource, result,
- self.imports)
- changes.add_change(change.ChangeContents(resource,
- imported_source))
- job_set.finished_job()
- return changes
- def _compute_changes(self, matches, pymodule):
- return _ChangeComputer(
- pymodule.source_code, pymodule.get_ast(),
- pymodule.lines, self.template, matches)
- def _add_imports(self, resource, source, imports):
- if not imports:
- return source
- import_infos = self._get_import_infos(resource, imports)
- pymodule = self.pycore.get_string_module(source, resource)
- imports = module_imports.ModuleImports(self.pycore, pymodule)
- for import_info in import_infos:
- imports.add_import(import_info)
- return imports.get_changed_source()
- def _get_import_infos(self, resource, imports):
- pymodule = self.pycore.get_string_module('\n'.join(imports),
- resource)
- imports = module_imports.ModuleImports(self.pycore, pymodule)
- return [imports.import_info
- for imports in imports.imports]
- def make_checks(self, string_checks):
- """Convert str to str dicts to str to PyObject dicts
- This function is here to ease writing a UI.
- """
- checks = {}
- for key, value in string_checks.items():
- is_pyname = not key.endswith('.object') and \
- not key.endswith('.type')
- evaluated = self._evaluate(value, is_pyname=is_pyname)
- if evaluated is not None:
- checks[key] = evaluated
- return checks
- def _evaluate(self, code, is_pyname=True):
- attributes = code.split('.')
- pyname = None
- if attributes[0] in ('__builtin__', '__builtins__'):
- class _BuiltinsStub(object):
- def get_attribute(self, name):
- return builtins.builtins[name]
- pyobject = _BuiltinsStub()
- else:
- pyobject = self.pycore.get_module(attributes[0])
- for attribute in attributes[1:]:
- pyname = pyobject[attribute]
- if pyname is None:
- return None
- pyobject = pyname.get_object()
- return pyname if is_pyname else pyobject
- def replace(code, pattern, goal):
- """used by other refactorings"""
- finder = similarfinder.RawSimilarFinder(code)
- matches = list(finder.get_matches(pattern))
- ast = patchedast.get_patched_ast(code)
- lines = codeanalyze.SourceLinesAdapter(code)
- template = similarfinder.CodeTemplate(goal)
- computer = _ChangeComputer(code, ast, lines, template, matches)
- result = computer.get_changed()
- if result is None:
- return code
- return result
- class _ChangeComputer(object):
- def __init__(self, code, ast, lines, goal, matches):
- self.source = code
- self.goal = goal
- self.matches = matches
- self.ast = ast
- self.lines = lines
- self.matched_asts = {}
- self._nearest_roots = {}
- if self._is_expression():
- for match in self.matches:
- self.matched_asts[match.ast] = match
- def get_changed(self):
- if self._is_expression():
- result = self._get_node_text(self.ast)
- if result == self.source:
- return None
- return result
- else:
- collector = codeanalyze.ChangeCollector(self.source)
- last_end = -1
- for match in self.matches:
- start, end = match.get_region()
- if start < last_end:
- if not self._is_expression():
- continue
- last_end = end
- replacement = self._get_matched_text(match)
- collector.add_change(start, end, replacement)
- return collector.get_changed()
- def _is_expression(self):
- return self.matches and isinstance(self.matches[0],
- similarfinder.ExpressionMatch)
- def _get_matched_text(self, match):
- mapping = {}
- for name in self.goal.get_names():
- node = match.get_ast(name)
- if node is None:
- raise similarfinder.BadNameInCheckError(
- 'Unknown name <%s>' % name)
- force = self._is_expression() and match.ast == node
- mapping[name] = self._get_node_text(node, force)
- unindented = self.goal.substitute(mapping)
- return self._auto_indent(match.get_region()[0], unindented)
- def _get_node_text(self, node, force=False):
- if not force and node in self.matched_asts:
- return self._get_matched_text(self.matched_asts[node])
- start, end = patchedast.node_region(node)
- main_text = self.source[start:end]
- collector = codeanalyze.ChangeCollector(main_text)
- for node in self._get_nearest_roots(node):
- sub_start, sub_end = patchedast.node_region(node)
- collector.add_change(sub_start - start, sub_end - start,
- self._get_node_text(node))
- result = collector.get_changed()
- if result is None:
- return main_text
- return result
- def _auto_indent(self, offset, text):
- lineno = self.lines.get_line_number(offset)
- indents = sourceutils.get_indents(self.lines, lineno)
- result = []
- for index, line in enumerate(text.splitlines(True)):
- if index != 0 and line.strip():
- result.append(' ' * indents)
- result.append(line)
- return ''.join(result)
- def _get_nearest_roots(self, node):
- if node not in self._nearest_roots:
- result = []
- for child in ast.get_child_nodes(node):
- if child in self.matched_asts:
- result.append(child)
- else:
- result.extend(self._get_nearest_roots(child))
- self._nearest_roots[node] = result
- return self._nearest_roots[node]
|