Removed the Requirement to Install Python and NodeJS (Now Bundled with Borealis)
This commit is contained in:
314
Dependencies/Python/Lib/pyclbr.py
vendored
Normal file
314
Dependencies/Python/Lib/pyclbr.py
vendored
Normal file
@ -0,0 +1,314 @@
|
||||
"""Parse a Python module and describe its classes and functions.
|
||||
|
||||
Parse enough of a Python file to recognize imports and class and
|
||||
function definitions, and to find out the superclasses of a class.
|
||||
|
||||
The interface consists of a single function:
|
||||
readmodule_ex(module, path=None)
|
||||
where module is the name of a Python module, and path is an optional
|
||||
list of directories where the module is to be searched. If present,
|
||||
path is prepended to the system search path sys.path. The return value
|
||||
is a dictionary. The keys of the dictionary are the names of the
|
||||
classes and functions defined in the module (including classes that are
|
||||
defined via the from XXX import YYY construct). The values are
|
||||
instances of classes Class and Function. One special key/value pair is
|
||||
present for packages: the key '__path__' has a list as its value which
|
||||
contains the package search path.
|
||||
|
||||
Classes and Functions have a common superclass: _Object. Every instance
|
||||
has the following attributes:
|
||||
module -- name of the module;
|
||||
name -- name of the object;
|
||||
file -- file in which the object is defined;
|
||||
lineno -- line in the file where the object's definition starts;
|
||||
end_lineno -- line in the file where the object's definition ends;
|
||||
parent -- parent of this object, if any;
|
||||
children -- nested objects contained in this object.
|
||||
The 'children' attribute is a dictionary mapping names to objects.
|
||||
|
||||
Instances of Function describe functions with the attributes from _Object,
|
||||
plus the following:
|
||||
is_async -- if a function is defined with an 'async' prefix
|
||||
|
||||
Instances of Class describe classes with the attributes from _Object,
|
||||
plus the following:
|
||||
super -- list of super classes (Class instances if possible);
|
||||
methods -- mapping of method names to beginning line numbers.
|
||||
If the name of a super class is not recognized, the corresponding
|
||||
entry in the list of super classes is not a class instance but a
|
||||
string giving the name of the super class. Since import statements
|
||||
are recognized and imported modules are scanned as well, this
|
||||
shouldn't happen often.
|
||||
"""
|
||||
|
||||
import ast
|
||||
import sys
|
||||
import importlib.util
|
||||
|
||||
__all__ = ["readmodule", "readmodule_ex", "Class", "Function"]
|
||||
|
||||
_modules = {} # Initialize cache of modules we've seen.
|
||||
|
||||
|
||||
class _Object:
|
||||
"Information about Python class or function."
|
||||
def __init__(self, module, name, file, lineno, end_lineno, parent):
|
||||
self.module = module
|
||||
self.name = name
|
||||
self.file = file
|
||||
self.lineno = lineno
|
||||
self.end_lineno = end_lineno
|
||||
self.parent = parent
|
||||
self.children = {}
|
||||
if parent is not None:
|
||||
parent.children[name] = self
|
||||
|
||||
|
||||
# Odd Function and Class signatures are for back-compatibility.
|
||||
class Function(_Object):
|
||||
"Information about a Python function, including methods."
|
||||
def __init__(self, module, name, file, lineno,
|
||||
parent=None, is_async=False, *, end_lineno=None):
|
||||
super().__init__(module, name, file, lineno, end_lineno, parent)
|
||||
self.is_async = is_async
|
||||
if isinstance(parent, Class):
|
||||
parent.methods[name] = lineno
|
||||
|
||||
|
||||
class Class(_Object):
|
||||
"Information about a Python class."
|
||||
def __init__(self, module, name, super_, file, lineno,
|
||||
parent=None, *, end_lineno=None):
|
||||
super().__init__(module, name, file, lineno, end_lineno, parent)
|
||||
self.super = super_ or []
|
||||
self.methods = {}
|
||||
|
||||
|
||||
# These 2 functions are used in these tests
|
||||
# Lib/test/test_pyclbr, Lib/idlelib/idle_test/test_browser.py
|
||||
def _nest_function(ob, func_name, lineno, end_lineno, is_async=False):
|
||||
"Return a Function after nesting within ob."
|
||||
return Function(ob.module, func_name, ob.file, lineno,
|
||||
parent=ob, is_async=is_async, end_lineno=end_lineno)
|
||||
|
||||
def _nest_class(ob, class_name, lineno, end_lineno, super=None):
|
||||
"Return a Class after nesting within ob."
|
||||
return Class(ob.module, class_name, super, ob.file, lineno,
|
||||
parent=ob, end_lineno=end_lineno)
|
||||
|
||||
|
||||
def readmodule(module, path=None):
|
||||
"""Return Class objects for the top-level classes in module.
|
||||
|
||||
This is the original interface, before Functions were added.
|
||||
"""
|
||||
|
||||
res = {}
|
||||
for key, value in _readmodule(module, path or []).items():
|
||||
if isinstance(value, Class):
|
||||
res[key] = value
|
||||
return res
|
||||
|
||||
def readmodule_ex(module, path=None):
|
||||
"""Return a dictionary with all functions and classes in module.
|
||||
|
||||
Search for module in PATH + sys.path.
|
||||
If possible, include imported superclasses.
|
||||
Do this by reading source, without importing (and executing) it.
|
||||
"""
|
||||
return _readmodule(module, path or [])
|
||||
|
||||
|
||||
def _readmodule(module, path, inpackage=None):
|
||||
"""Do the hard work for readmodule[_ex].
|
||||
|
||||
If inpackage is given, it must be the dotted name of the package in
|
||||
which we are searching for a submodule, and then PATH must be the
|
||||
package search path; otherwise, we are searching for a top-level
|
||||
module, and path is combined with sys.path.
|
||||
"""
|
||||
# Compute the full module name (prepending inpackage if set).
|
||||
if inpackage is not None:
|
||||
fullmodule = "%s.%s" % (inpackage, module)
|
||||
else:
|
||||
fullmodule = module
|
||||
|
||||
# Check in the cache.
|
||||
if fullmodule in _modules:
|
||||
return _modules[fullmodule]
|
||||
|
||||
# Initialize the dict for this module's contents.
|
||||
tree = {}
|
||||
|
||||
# Check if it is a built-in module; we don't do much for these.
|
||||
if module in sys.builtin_module_names and inpackage is None:
|
||||
_modules[module] = tree
|
||||
return tree
|
||||
|
||||
# Check for a dotted module name.
|
||||
i = module.rfind('.')
|
||||
if i >= 0:
|
||||
package = module[:i]
|
||||
submodule = module[i+1:]
|
||||
parent = _readmodule(package, path, inpackage)
|
||||
if inpackage is not None:
|
||||
package = "%s.%s" % (inpackage, package)
|
||||
if not '__path__' in parent:
|
||||
raise ImportError('No package named {}'.format(package))
|
||||
return _readmodule(submodule, parent['__path__'], package)
|
||||
|
||||
# Search the path for the module.
|
||||
f = None
|
||||
if inpackage is not None:
|
||||
search_path = path
|
||||
else:
|
||||
search_path = path + sys.path
|
||||
spec = importlib.util._find_spec_from_path(fullmodule, search_path)
|
||||
if spec is None:
|
||||
raise ModuleNotFoundError(f"no module named {fullmodule!r}", name=fullmodule)
|
||||
_modules[fullmodule] = tree
|
||||
# Is module a package?
|
||||
if spec.submodule_search_locations is not None:
|
||||
tree['__path__'] = spec.submodule_search_locations
|
||||
try:
|
||||
source = spec.loader.get_source(fullmodule)
|
||||
except (AttributeError, ImportError):
|
||||
# If module is not Python source, we cannot do anything.
|
||||
return tree
|
||||
else:
|
||||
if source is None:
|
||||
return tree
|
||||
|
||||
fname = spec.loader.get_filename(fullmodule)
|
||||
return _create_tree(fullmodule, path, fname, source, tree, inpackage)
|
||||
|
||||
|
||||
class _ModuleBrowser(ast.NodeVisitor):
|
||||
def __init__(self, module, path, file, tree, inpackage):
|
||||
self.path = path
|
||||
self.tree = tree
|
||||
self.file = file
|
||||
self.module = module
|
||||
self.inpackage = inpackage
|
||||
self.stack = []
|
||||
|
||||
def visit_ClassDef(self, node):
|
||||
bases = []
|
||||
for base in node.bases:
|
||||
name = ast.unparse(base)
|
||||
if name in self.tree:
|
||||
# We know this super class.
|
||||
bases.append(self.tree[name])
|
||||
elif len(names := name.split(".")) > 1:
|
||||
# Super class form is module.class:
|
||||
# look in module for class.
|
||||
*_, module, class_ = names
|
||||
if module in _modules:
|
||||
bases.append(_modules[module].get(class_, name))
|
||||
else:
|
||||
bases.append(name)
|
||||
|
||||
parent = self.stack[-1] if self.stack else None
|
||||
class_ = Class(self.module, node.name, bases, self.file, node.lineno,
|
||||
parent=parent, end_lineno=node.end_lineno)
|
||||
if parent is None:
|
||||
self.tree[node.name] = class_
|
||||
self.stack.append(class_)
|
||||
self.generic_visit(node)
|
||||
self.stack.pop()
|
||||
|
||||
def visit_FunctionDef(self, node, *, is_async=False):
|
||||
parent = self.stack[-1] if self.stack else None
|
||||
function = Function(self.module, node.name, self.file, node.lineno,
|
||||
parent, is_async, end_lineno=node.end_lineno)
|
||||
if parent is None:
|
||||
self.tree[node.name] = function
|
||||
self.stack.append(function)
|
||||
self.generic_visit(node)
|
||||
self.stack.pop()
|
||||
|
||||
def visit_AsyncFunctionDef(self, node):
|
||||
self.visit_FunctionDef(node, is_async=True)
|
||||
|
||||
def visit_Import(self, node):
|
||||
if node.col_offset != 0:
|
||||
return
|
||||
|
||||
for module in node.names:
|
||||
try:
|
||||
try:
|
||||
_readmodule(module.name, self.path, self.inpackage)
|
||||
except ImportError:
|
||||
_readmodule(module.name, [])
|
||||
except (ImportError, SyntaxError):
|
||||
# If we can't find or parse the imported module,
|
||||
# too bad -- don't die here.
|
||||
continue
|
||||
|
||||
def visit_ImportFrom(self, node):
|
||||
if node.col_offset != 0:
|
||||
return
|
||||
try:
|
||||
module = "." * node.level
|
||||
if node.module:
|
||||
module += node.module
|
||||
module = _readmodule(module, self.path, self.inpackage)
|
||||
except (ImportError, SyntaxError):
|
||||
return
|
||||
|
||||
for name in node.names:
|
||||
if name.name in module:
|
||||
self.tree[name.asname or name.name] = module[name.name]
|
||||
elif name.name == "*":
|
||||
for import_name, import_value in module.items():
|
||||
if import_name.startswith("_"):
|
||||
continue
|
||||
self.tree[import_name] = import_value
|
||||
|
||||
|
||||
def _create_tree(fullmodule, path, fname, source, tree, inpackage):
|
||||
mbrowser = _ModuleBrowser(fullmodule, path, fname, tree, inpackage)
|
||||
mbrowser.visit(ast.parse(source))
|
||||
return mbrowser.tree
|
||||
|
||||
|
||||
def _main():
|
||||
"Print module output (default this file) for quick visual check."
|
||||
import os
|
||||
try:
|
||||
mod = sys.argv[1]
|
||||
except:
|
||||
mod = __file__
|
||||
if os.path.exists(mod):
|
||||
path = [os.path.dirname(mod)]
|
||||
mod = os.path.basename(mod)
|
||||
if mod.lower().endswith(".py"):
|
||||
mod = mod[:-3]
|
||||
else:
|
||||
path = []
|
||||
tree = readmodule_ex(mod, path)
|
||||
lineno_key = lambda a: getattr(a, 'lineno', 0)
|
||||
objs = sorted(tree.values(), key=lineno_key, reverse=True)
|
||||
indent_level = 2
|
||||
while objs:
|
||||
obj = objs.pop()
|
||||
if isinstance(obj, list):
|
||||
# Value is a __path__ key.
|
||||
continue
|
||||
if not hasattr(obj, 'indent'):
|
||||
obj.indent = 0
|
||||
|
||||
if isinstance(obj, _Object):
|
||||
new_objs = sorted(obj.children.values(),
|
||||
key=lineno_key, reverse=True)
|
||||
for ob in new_objs:
|
||||
ob.indent = obj.indent + indent_level
|
||||
objs.extend(new_objs)
|
||||
if isinstance(obj, Class):
|
||||
print("{}class {} {} {}"
|
||||
.format(' ' * obj.indent, obj.name, obj.super, obj.lineno))
|
||||
elif isinstance(obj, Function):
|
||||
print("{}def {} {}".format(' ' * obj.indent, obj.name, obj.lineno))
|
||||
|
||||
if __name__ == "__main__":
|
||||
_main()
|
Reference in New Issue
Block a user