mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-12-29 00:41:45 +01:00
361 lines
12 KiB
Python
361 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright (c) 2015-2020 Claudiu Popa <pcmanticore@gmail.com>
|
|
# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
|
|
# Copyright (c) 2018 ssolanki <sushobhitsolanki@gmail.com>
|
|
# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
|
|
# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
|
|
# Copyright (c) 2019 Pierre Sassoulas <pierre.sassoulas@gmail.com>
|
|
# Copyright (c) 2020 Anthony Sottile <asottile@umich.edu>
|
|
|
|
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
# For details: https://github.com/PyCQA/pylint/blob/master/COPYING
|
|
|
|
"""
|
|
Visitor doing some postprocessing on the astroid tree.
|
|
Try to resolve definitions (namespace) dictionary, relationship...
|
|
"""
|
|
import collections
|
|
import os
|
|
import traceback
|
|
|
|
import astroid
|
|
from astroid import bases, exceptions, manager, modutils, node_classes
|
|
|
|
from pylint.pyreverse import utils
|
|
|
|
|
|
def _iface_hdlr(_):
|
|
"""Handler used by interfaces to handle suspicious interface nodes."""
|
|
return True
|
|
|
|
|
|
def _astroid_wrapper(func, modname):
|
|
print("parsing %s..." % modname)
|
|
try:
|
|
return func(modname)
|
|
except exceptions.AstroidBuildingException as exc:
|
|
print(exc)
|
|
except Exception as exc: # pylint: disable=broad-except
|
|
traceback.print_exc()
|
|
|
|
|
|
def interfaces(node, herited=True, handler_func=_iface_hdlr):
|
|
"""Return an iterator on interfaces implemented by the given class node."""
|
|
try:
|
|
implements = bases.Instance(node).getattr("__implements__")[0]
|
|
except exceptions.NotFoundError:
|
|
return
|
|
if not herited and implements.frame() is not node:
|
|
return
|
|
found = set()
|
|
missing = False
|
|
for iface in node_classes.unpack_infer(implements):
|
|
if iface is astroid.Uninferable:
|
|
missing = True
|
|
continue
|
|
if iface not in found and handler_func(iface):
|
|
found.add(iface)
|
|
yield iface
|
|
if missing:
|
|
raise exceptions.InferenceError()
|
|
|
|
|
|
class IdGeneratorMixIn:
|
|
"""Mixin adding the ability to generate integer uid."""
|
|
|
|
def __init__(self, start_value=0):
|
|
self.id_count = start_value
|
|
|
|
def init_counter(self, start_value=0):
|
|
"""init the id counter
|
|
"""
|
|
self.id_count = start_value
|
|
|
|
def generate_id(self):
|
|
"""generate a new identifier
|
|
"""
|
|
self.id_count += 1
|
|
return self.id_count
|
|
|
|
|
|
class Linker(IdGeneratorMixIn, utils.LocalsVisitor):
|
|
"""Walk on the project tree and resolve relationships.
|
|
|
|
According to options the following attributes may be
|
|
added to visited nodes:
|
|
|
|
* uid,
|
|
a unique identifier for the node (on astroid.Project, astroid.Module,
|
|
astroid.Class and astroid.locals_type). Only if the linker
|
|
has been instantiated with tag=True parameter (False by default).
|
|
|
|
* Function
|
|
a mapping from locals names to their bounded value, which may be a
|
|
constant like a string or an integer, or an astroid node
|
|
(on astroid.Module, astroid.Class and astroid.Function).
|
|
|
|
* instance_attrs_type
|
|
as locals_type but for klass member attributes (only on astroid.Class)
|
|
|
|
* implements,
|
|
list of implemented interface _objects_ (only on astroid.Class nodes)
|
|
"""
|
|
|
|
def __init__(self, project, inherited_interfaces=0, tag=False):
|
|
IdGeneratorMixIn.__init__(self)
|
|
utils.LocalsVisitor.__init__(self)
|
|
# take inherited interface in consideration or not
|
|
self.inherited_interfaces = inherited_interfaces
|
|
# tag nodes or not
|
|
self.tag = tag
|
|
# visited project
|
|
self.project = project
|
|
|
|
def visit_project(self, node):
|
|
"""visit a pyreverse.utils.Project node
|
|
|
|
* optionally tag the node with a unique id
|
|
"""
|
|
if self.tag:
|
|
node.uid = self.generate_id()
|
|
for module in node.modules:
|
|
self.visit(module)
|
|
|
|
def visit_package(self, node):
|
|
"""visit an astroid.Package node
|
|
|
|
* optionally tag the node with a unique id
|
|
"""
|
|
if self.tag:
|
|
node.uid = self.generate_id()
|
|
for subelmt in node.values():
|
|
self.visit(subelmt)
|
|
|
|
def visit_module(self, node):
|
|
"""visit an astroid.Module node
|
|
|
|
* set the locals_type mapping
|
|
* set the depends mapping
|
|
* optionally tag the node with a unique id
|
|
"""
|
|
if hasattr(node, "locals_type"):
|
|
return
|
|
node.locals_type = collections.defaultdict(list)
|
|
node.depends = []
|
|
if self.tag:
|
|
node.uid = self.generate_id()
|
|
|
|
def visit_classdef(self, node):
|
|
"""visit an astroid.Class node
|
|
|
|
* set the locals_type and instance_attrs_type mappings
|
|
* set the implements list and build it
|
|
* optionally tag the node with a unique id
|
|
"""
|
|
if hasattr(node, "locals_type"):
|
|
return
|
|
node.locals_type = collections.defaultdict(list)
|
|
if self.tag:
|
|
node.uid = self.generate_id()
|
|
# resolve ancestors
|
|
for baseobj in node.ancestors(recurs=False):
|
|
specializations = getattr(baseobj, "specializations", [])
|
|
specializations.append(node)
|
|
baseobj.specializations = specializations
|
|
# resolve instance attributes
|
|
node.instance_attrs_type = collections.defaultdict(list)
|
|
for assignattrs in node.instance_attrs.values():
|
|
for assignattr in assignattrs:
|
|
if not isinstance(assignattr, astroid.Unknown):
|
|
self.handle_assignattr_type(assignattr, node)
|
|
# resolve implemented interface
|
|
try:
|
|
node.implements = list(interfaces(node, self.inherited_interfaces))
|
|
except astroid.InferenceError:
|
|
node.implements = ()
|
|
|
|
def visit_functiondef(self, node):
|
|
"""visit an astroid.Function node
|
|
|
|
* set the locals_type mapping
|
|
* optionally tag the node with a unique id
|
|
"""
|
|
if hasattr(node, "locals_type"):
|
|
return
|
|
node.locals_type = collections.defaultdict(list)
|
|
if self.tag:
|
|
node.uid = self.generate_id()
|
|
|
|
link_project = visit_project
|
|
link_module = visit_module
|
|
link_class = visit_classdef
|
|
link_function = visit_functiondef
|
|
|
|
def visit_assignname(self, node):
|
|
"""visit an astroid.AssignName node
|
|
|
|
handle locals_type
|
|
"""
|
|
# avoid double parsing done by different Linkers.visit
|
|
# running over the same project:
|
|
if hasattr(node, "_handled"):
|
|
return
|
|
node._handled = True
|
|
if node.name in node.frame():
|
|
frame = node.frame()
|
|
else:
|
|
# the name has been defined as 'global' in the frame and belongs
|
|
# there.
|
|
frame = node.root()
|
|
try:
|
|
if not hasattr(frame, "locals_type"):
|
|
# If the frame doesn't have a locals_type yet,
|
|
# it means it wasn't yet visited. Visit it now
|
|
# to add what's missing from it.
|
|
if isinstance(frame, astroid.ClassDef):
|
|
self.visit_classdef(frame)
|
|
elif isinstance(frame, astroid.FunctionDef):
|
|
self.visit_functiondef(frame)
|
|
else:
|
|
self.visit_module(frame)
|
|
|
|
current = frame.locals_type[node.name]
|
|
values = set(node.infer())
|
|
frame.locals_type[node.name] = list(set(current) | values)
|
|
except astroid.InferenceError:
|
|
pass
|
|
|
|
@staticmethod
|
|
def handle_assignattr_type(node, parent):
|
|
"""handle an astroid.assignattr node
|
|
|
|
handle instance_attrs_type
|
|
"""
|
|
try:
|
|
values = set(node.infer())
|
|
current = set(parent.instance_attrs_type[node.attrname])
|
|
parent.instance_attrs_type[node.attrname] = list(current | values)
|
|
except astroid.InferenceError:
|
|
pass
|
|
|
|
def visit_import(self, node):
|
|
"""visit an astroid.Import node
|
|
|
|
resolve module dependencies
|
|
"""
|
|
context_file = node.root().file
|
|
for name in node.names:
|
|
relative = modutils.is_relative(name[0], context_file)
|
|
self._imported_module(node, name[0], relative)
|
|
|
|
def visit_importfrom(self, node):
|
|
"""visit an astroid.ImportFrom node
|
|
|
|
resolve module dependencies
|
|
"""
|
|
basename = node.modname
|
|
context_file = node.root().file
|
|
if context_file is not None:
|
|
relative = modutils.is_relative(basename, context_file)
|
|
else:
|
|
relative = False
|
|
for name in node.names:
|
|
if name[0] == "*":
|
|
continue
|
|
# analyze dependencies
|
|
fullname = "%s.%s" % (basename, name[0])
|
|
if fullname.find(".") > -1:
|
|
try:
|
|
fullname = modutils.get_module_part(fullname, context_file)
|
|
except ImportError:
|
|
continue
|
|
if fullname != basename:
|
|
self._imported_module(node, fullname, relative)
|
|
|
|
def compute_module(self, context_name, mod_path):
|
|
"""return true if the module should be added to dependencies"""
|
|
package_dir = os.path.dirname(self.project.path)
|
|
if context_name == mod_path:
|
|
return 0
|
|
if modutils.is_standard_module(mod_path, (package_dir,)):
|
|
return 1
|
|
return 0
|
|
|
|
def _imported_module(self, node, mod_path, relative):
|
|
"""Notify an imported module, used to analyze dependencies"""
|
|
module = node.root()
|
|
context_name = module.name
|
|
if relative:
|
|
mod_path = "%s.%s" % (".".join(context_name.split(".")[:-1]), mod_path)
|
|
if self.compute_module(context_name, mod_path):
|
|
# handle dependencies
|
|
if not hasattr(module, "depends"):
|
|
module.depends = []
|
|
mod_paths = module.depends
|
|
if mod_path not in mod_paths:
|
|
mod_paths.append(mod_path)
|
|
|
|
|
|
class Project:
|
|
"""a project handle a set of modules / packages"""
|
|
|
|
def __init__(self, name=""):
|
|
self.name = name
|
|
self.path = None
|
|
self.modules = []
|
|
self.locals = {}
|
|
self.__getitem__ = self.locals.__getitem__
|
|
self.__iter__ = self.locals.__iter__
|
|
self.values = self.locals.values
|
|
self.keys = self.locals.keys
|
|
self.items = self.locals.items
|
|
|
|
def add_module(self, node):
|
|
self.locals[node.name] = node
|
|
self.modules.append(node)
|
|
|
|
def get_module(self, name):
|
|
return self.locals[name]
|
|
|
|
def get_children(self):
|
|
return self.modules
|
|
|
|
def __repr__(self):
|
|
return "<Project %r at %s (%s modules)>" % (
|
|
self.name,
|
|
id(self),
|
|
len(self.modules),
|
|
)
|
|
|
|
|
|
def project_from_files(
|
|
files, func_wrapper=_astroid_wrapper, project_name="no name", black_list=("CVS",)
|
|
):
|
|
"""return a Project from a list of files or modules"""
|
|
# build the project representation
|
|
astroid_manager = manager.AstroidManager()
|
|
project = Project(project_name)
|
|
for something in files:
|
|
if not os.path.exists(something):
|
|
fpath = modutils.file_from_modpath(something.split("."))
|
|
elif os.path.isdir(something):
|
|
fpath = os.path.join(something, "__init__.py")
|
|
else:
|
|
fpath = something
|
|
ast = func_wrapper(astroid_manager.ast_from_file, fpath)
|
|
if ast is None:
|
|
continue
|
|
project.path = project.path or ast.file
|
|
project.add_module(ast)
|
|
base_name = ast.name
|
|
# recurse in package except if __init__ was explicitly given
|
|
if ast.package and something.find("__init__") == -1:
|
|
# recurse on others packages / modules if this is a package
|
|
for fpath in modutils.get_module_files(
|
|
os.path.dirname(ast.file), black_list
|
|
):
|
|
ast = func_wrapper(astroid_manager.ast_from_file, fpath)
|
|
if ast is None or ast.name == base_name:
|
|
continue
|
|
project.add_module(ast)
|
|
return project
|