| Index: tools/telemetry/third_party/rope/rope/base/pycore.py
|
| diff --git a/tools/telemetry/third_party/rope/rope/base/pycore.py b/tools/telemetry/third_party/rope/rope/base/pycore.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..c4c1195a4d7ef5ee471655a4e5d97bb2b5836d3d
|
| --- /dev/null
|
| +++ b/tools/telemetry/third_party/rope/rope/base/pycore.py
|
| @@ -0,0 +1,346 @@
|
| +import bisect
|
| +import difflib
|
| +import sys
|
| +import warnings
|
| +
|
| +import rope.base.libutils
|
| +import rope.base.resourceobserver
|
| +import rope.base.resources
|
| +import rope.base.oi.doa
|
| +import rope.base.oi.objectinfo
|
| +import rope.base.oi.soa
|
| +from rope.base import builtins
|
| +from rope.base import exceptions
|
| +from rope.base import stdmods
|
| +from rope.base import taskhandle
|
| +from rope.base import utils
|
| +from rope.base.exceptions import ModuleNotFoundError
|
| +from rope.base.pyobjectsdef import PyModule, PyPackage
|
| +
|
| +
|
| +class PyCore(object):
|
| +
|
| + def __init__(self, project):
|
| + self.project = project
|
| + self._init_resource_observer()
|
| + self.cache_observers = []
|
| + self.module_cache = _ModuleCache(self)
|
| + self.extension_cache = _ExtensionCache(self)
|
| + self.object_info = rope.base.oi.objectinfo.ObjectInfoManager(project)
|
| + self._init_python_files()
|
| + self._init_automatic_soa()
|
| +
|
| + def _init_python_files(self):
|
| + self.python_matcher = None
|
| + patterns = self.project.prefs.get('python_files', None)
|
| + if patterns is not None:
|
| + self.python_matcher = rope.base.resources._ResourceMatcher()
|
| + self.python_matcher.set_patterns(patterns)
|
| +
|
| + def _init_resource_observer(self):
|
| + callback = self._invalidate_resource_cache
|
| + observer = rope.base.resourceobserver.ResourceObserver(
|
| + changed=callback, moved=callback, removed=callback)
|
| + self.observer = \
|
| + rope.base.resourceobserver.FilteredResourceObserver(observer)
|
| + self.project.add_observer(self.observer)
|
| +
|
| + def _init_automatic_soa(self):
|
| + if not self.automatic_soa:
|
| + return
|
| + callback = self._file_changed_for_soa
|
| + observer = rope.base.resourceobserver.ResourceObserver(
|
| + changed=callback, moved=callback, removed=callback)
|
| + self.project.add_observer(observer)
|
| +
|
| + @property
|
| + def automatic_soa(self):
|
| + auto_soa = self.project.prefs.get('automatic_soi', None)
|
| + return self.project.prefs.get('automatic_soa', auto_soa)
|
| +
|
| + def _file_changed_for_soa(self, resource, new_resource=None):
|
| + old_contents = self.project.history.\
|
| + contents_before_current_change(resource)
|
| + if old_contents is not None:
|
| + perform_soa_on_changed_scopes(self.project, resource, old_contents)
|
| +
|
| + def is_python_file(self, resource):
|
| + if resource.is_folder():
|
| + return False
|
| + if self.python_matcher is None:
|
| + return resource.name.endswith('.py')
|
| + return self.python_matcher.does_match(resource)
|
| +
|
| + @utils.deprecated('Use `project.get_module` instead')
|
| + def get_module(self, name, folder=None):
|
| + """Returns a `PyObject` if the module was found."""
|
| + return self.project.get_module(name, folder)
|
| +
|
| + def _builtin_submodules(self, modname):
|
| + result = {}
|
| + for extension in self.extension_modules:
|
| + if extension.startswith(modname + '.'):
|
| + name = extension[len(modname) + 1:]
|
| + if '.' not in name:
|
| + result[name] = self.builtin_module(extension)
|
| + return result
|
| +
|
| + def builtin_module(self, name):
|
| + return self.extension_cache.get_pymodule(name)
|
| +
|
| + @utils.deprecated('Use `project.get_relative_module` instead')
|
| + def get_relative_module(self, name, folder, level):
|
| + return self.project.get_relative_module(name, folder, level)
|
| +
|
| + @utils.deprecated('Use `libutils.get_string_module` instead')
|
| + def get_string_module(self, code, resource=None, force_errors=False):
|
| + """Returns a `PyObject` object for the given code
|
| +
|
| + If `force_errors` is `True`, `exceptions.ModuleSyntaxError` is
|
| + raised if module has syntax errors. This overrides
|
| + ``ignore_syntax_errors`` project config.
|
| +
|
| + """
|
| + return PyModule(self, code, resource, force_errors=force_errors)
|
| +
|
| + @utils.deprecated('Use `libutils.get_string_scope` instead')
|
| + def get_string_scope(self, code, resource=None):
|
| + """Returns a `Scope` object for the given code"""
|
| + return rope.base.libutils.get_string_scope(code, resource)
|
| +
|
| + def _invalidate_resource_cache(self, resource, new_resource=None):
|
| + for observer in self.cache_observers:
|
| + observer(resource)
|
| +
|
| + @utils.deprecated('Use `project.get_python_path_folders` instead')
|
| + def get_python_path_folders(self):
|
| + return self.project.get_python_path_folders()
|
| +
|
| + @utils.deprecated('Use `project.find_module` instead')
|
| + def find_module(self, modname, folder=None):
|
| + """Returns a resource corresponding to the given module
|
| +
|
| + returns None if it can not be found
|
| + """
|
| + return self.project.find_module(modname, folder)
|
| +
|
| + @utils.deprecated('Use `project.find_relative_module` instead')
|
| + def find_relative_module(self, modname, folder, level):
|
| + return self.project.find_relative_module(modname, folder, level)
|
| +
|
| + # INFO: It was decided not to cache source folders, since:
|
| + # - Does not take much time when the root folder contains
|
| + # packages, that is most of the time
|
| + # - We need a separate resource observer; `self.observer`
|
| + # does not get notified about module and folder creations
|
| + @utils.deprecated('Use `project.get_source_folders` instead')
|
| + def get_source_folders(self):
|
| + """Returns project source folders"""
|
| + return self.project.get_source_folders()
|
| +
|
| + def resource_to_pyobject(self, resource, force_errors=False):
|
| + return self.module_cache.get_pymodule(resource, force_errors)
|
| +
|
| + @utils.deprecated('Use `project.get_python_files` instead')
|
| + def get_python_files(self):
|
| + """Returns all python files available in the project"""
|
| + return self.project.get_python_files()
|
| +
|
| + def _is_package(self, folder):
|
| + if folder.has_child('__init__.py') and \
|
| + not folder.get_child('__init__.py').is_folder():
|
| + return True
|
| + else:
|
| + return False
|
| +
|
| + def _find_source_folders(self, folder):
|
| + for resource in folder.get_folders():
|
| + if self._is_package(resource):
|
| + return [folder]
|
| + result = []
|
| + for resource in folder.get_files():
|
| + if resource.name.endswith('.py'):
|
| + result.append(folder)
|
| + break
|
| + for resource in folder.get_folders():
|
| + result.extend(self._find_source_folders(resource))
|
| + return result
|
| +
|
| + def run_module(self, resource, args=None, stdin=None, stdout=None):
|
| + """Run `resource` module
|
| +
|
| + Returns a `rope.base.oi.doa.PythonFileRunner` object for
|
| + controlling the process.
|
| +
|
| + """
|
| + perform_doa = self.project.prefs.get('perform_doi', True)
|
| + perform_doa = self.project.prefs.get('perform_doa', perform_doa)
|
| + receiver = self.object_info.doa_data_received
|
| + if not perform_doa:
|
| + receiver = None
|
| + runner = rope.base.oi.doa.PythonFileRunner(
|
| + self, resource, args, stdin, stdout, receiver)
|
| + runner.add_finishing_observer(self.module_cache.forget_all_data)
|
| + runner.run()
|
| + return runner
|
| +
|
| + def analyze_module(self, resource, should_analyze=lambda py: True,
|
| + search_subscopes=lambda py: True, followed_calls=None):
|
| + """Analyze `resource` module for static object inference
|
| +
|
| + This function forces rope to analyze this module to collect
|
| + information about function calls. `should_analyze` is a
|
| + function that is called with a `PyDefinedObject` argument. If
|
| + it returns `True` the element is analyzed. If it is `None` or
|
| + returns `False` the element is not analyzed.
|
| +
|
| + `search_subscopes` is like `should_analyze`; The difference is
|
| + that if it returns `False` the sub-scopes are all ignored.
|
| + That is it is assumed that `should_analyze` returns `False`
|
| + for all of its subscopes.
|
| +
|
| + `followed_calls` override the value of ``soa_followed_calls``
|
| + project config.
|
| + """
|
| + if followed_calls is None:
|
| + followed_calls = self.project.prefs.get('soa_followed_calls', 0)
|
| + pymodule = self.resource_to_pyobject(resource)
|
| + self.module_cache.forget_all_data()
|
| + rope.base.oi.soa.analyze_module(
|
| + self, pymodule, should_analyze, search_subscopes, followed_calls)
|
| +
|
| + def get_classes(self, task_handle=taskhandle.NullTaskHandle()):
|
| + warnings.warn('`PyCore.get_classes()` is deprecated',
|
| + DeprecationWarning, stacklevel=2)
|
| + return []
|
| +
|
| + def __str__(self):
|
| + return str(self.module_cache) + str(self.object_info)
|
| +
|
| + @utils.deprecated('Use `libutils.modname` instead')
|
| + def modname(self, resource):
|
| + return rope.base.libutils.modname(resource)
|
| +
|
| + @property
|
| + @utils.cacheit
|
| + def extension_modules(self):
|
| + result = set(self.project.prefs.get('extension_modules', []))
|
| + if self.project.prefs.get('import_dynload_stdmods', False):
|
| + result.update(stdmods.dynload_modules())
|
| + return result
|
| +
|
| +
|
| +class _ModuleCache(object):
|
| +
|
| + def __init__(self, pycore):
|
| + self.pycore = pycore
|
| + self.module_map = {}
|
| + self.pycore.cache_observers.append(self._invalidate_resource)
|
| + self.observer = self.pycore.observer
|
| +
|
| + def _invalidate_resource(self, resource):
|
| + if resource in self.module_map:
|
| + self.forget_all_data()
|
| + self.observer.remove_resource(resource)
|
| + del self.module_map[resource]
|
| +
|
| + def get_pymodule(self, resource, force_errors=False):
|
| + if resource in self.module_map:
|
| + return self.module_map[resource]
|
| + if resource.is_folder():
|
| + result = PyPackage(self.pycore, resource,
|
| + force_errors=force_errors)
|
| + else:
|
| + result = PyModule(self.pycore, resource=resource,
|
| + force_errors=force_errors)
|
| + if result.has_errors:
|
| + return result
|
| + self.module_map[resource] = result
|
| + self.observer.add_resource(resource)
|
| + return result
|
| +
|
| + def forget_all_data(self):
|
| + for pymodule in self.module_map.values():
|
| + pymodule._forget_concluded_data()
|
| +
|
| + def __str__(self):
|
| + return 'PyCore caches %d PyModules\n' % len(self.module_map)
|
| +
|
| +
|
| +class _ExtensionCache(object):
|
| +
|
| + def __init__(self, pycore):
|
| + self.pycore = pycore
|
| + self.extensions = {}
|
| +
|
| + def get_pymodule(self, name):
|
| + if name == '__builtin__':
|
| + return builtins.builtins
|
| + allowed = self.pycore.extension_modules
|
| + if name not in self.extensions and name in allowed:
|
| + self.extensions[name] = builtins.BuiltinModule(name, self.pycore)
|
| + return self.extensions.get(name)
|
| +
|
| +
|
| +def perform_soa_on_changed_scopes(project, resource, old_contents):
|
| + pycore = project.pycore
|
| + if resource.exists() and pycore.is_python_file(resource):
|
| + try:
|
| + new_contents = resource.read()
|
| + # detecting changes in new_contents relative to old_contents
|
| + detector = _TextChangeDetector(new_contents, old_contents)
|
| +
|
| + def search_subscopes(pydefined):
|
| + scope = pydefined.get_scope()
|
| + return detector.is_changed(scope.get_start(), scope.get_end())
|
| +
|
| + def should_analyze(pydefined):
|
| + scope = pydefined.get_scope()
|
| + start = scope.get_start()
|
| + end = scope.get_end()
|
| + return detector.consume_changes(start, end)
|
| + pycore.analyze_module(resource, should_analyze, search_subscopes)
|
| + except exceptions.ModuleSyntaxError:
|
| + pass
|
| +
|
| +
|
| +class _TextChangeDetector(object):
|
| +
|
| + def __init__(self, old, new):
|
| + self.old = old
|
| + self.new = new
|
| + self._set_diffs()
|
| +
|
| + def _set_diffs(self):
|
| + differ = difflib.Differ()
|
| + self.lines = []
|
| + lineno = 0
|
| + for line in differ.compare(self.old.splitlines(True),
|
| + self.new.splitlines(True)):
|
| + if line.startswith(' '):
|
| + lineno += 1
|
| + elif line.startswith('-'):
|
| + lineno += 1
|
| + self.lines.append(lineno)
|
| +
|
| + def is_changed(self, start, end):
|
| + """Tell whether any of start till end lines have changed
|
| +
|
| + The end points are inclusive and indices start from 1.
|
| + """
|
| + left, right = self._get_changed(start, end)
|
| + if left < right:
|
| + return True
|
| + return False
|
| +
|
| + def consume_changes(self, start, end):
|
| + """Clear the changed status of lines from start till end"""
|
| + left, right = self._get_changed(start, end)
|
| + if left < right:
|
| + del self.lines[left:right]
|
| + return left < right
|
| +
|
| + def _get_changed(self, start, end):
|
| + left = bisect.bisect_left(self.lines, start)
|
| + right = bisect.bisect_right(self.lines, end)
|
| + return left, right
|
|
|