Initial commit
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
# 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
|
||||
|
||||
"""
|
||||
pyreverse.extensions
|
||||
"""
|
||||
|
||||
__revision__ = "$Id $"
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
240
venv/lib/python3.8/site-packages/pylint/pyreverse/diadefslib.py
Normal file
240
venv/lib/python3.8/site-packages/pylint/pyreverse/diadefslib.py
Normal file
@@ -0,0 +1,240 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2006, 2008-2010, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
|
||||
# Copyright (c) 2014 Brett Cannon <brett@python.org>
|
||||
# Copyright (c) 2014 Arun Persaud <arun@nubati.net>
|
||||
# Copyright (c) 2015-2018 Claudiu Popa <pcmanticore@gmail.com>
|
||||
# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
|
||||
# Copyright (c) 2015 Ionel Cristian Maries <contact@ionelmc.ro>
|
||||
# Copyright (c) 2016 Ashley Whetter <ashley@awhetter.co.uk>
|
||||
# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
|
||||
# Copyright (c) 2018 ssolanki <sushobhitsolanki@gmail.com>
|
||||
# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com>
|
||||
# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
|
||||
# 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
|
||||
|
||||
"""handle diagram generation options for class diagram or default diagrams
|
||||
"""
|
||||
|
||||
import astroid
|
||||
|
||||
from pylint.pyreverse.diagrams import ClassDiagram, PackageDiagram
|
||||
from pylint.pyreverse.utils import LocalsVisitor
|
||||
|
||||
BUILTINS_NAME = "builtins"
|
||||
|
||||
# diagram generators ##########################################################
|
||||
|
||||
|
||||
class DiaDefGenerator:
|
||||
"""handle diagram generation options"""
|
||||
|
||||
def __init__(self, linker, handler):
|
||||
"""common Diagram Handler initialization"""
|
||||
self.config = handler.config
|
||||
self._set_default_options()
|
||||
self.linker = linker
|
||||
self.classdiagram = None # defined by subclasses
|
||||
|
||||
def get_title(self, node):
|
||||
"""get title for objects"""
|
||||
title = node.name
|
||||
if self.module_names:
|
||||
title = "%s.%s" % (node.root().name, title)
|
||||
return title
|
||||
|
||||
def _set_option(self, option):
|
||||
"""activate some options if not explicitly deactivated"""
|
||||
# if we have a class diagram, we want more information by default;
|
||||
# so if the option is None, we return True
|
||||
if option is None:
|
||||
return bool(self.config.classes)
|
||||
return option
|
||||
|
||||
def _set_default_options(self):
|
||||
"""set different default options with _default dictionary"""
|
||||
self.module_names = self._set_option(self.config.module_names)
|
||||
all_ancestors = self._set_option(self.config.all_ancestors)
|
||||
all_associated = self._set_option(self.config.all_associated)
|
||||
anc_level, association_level = (0, 0)
|
||||
if all_ancestors:
|
||||
anc_level = -1
|
||||
if all_associated:
|
||||
association_level = -1
|
||||
if self.config.show_ancestors is not None:
|
||||
anc_level = self.config.show_ancestors
|
||||
if self.config.show_associated is not None:
|
||||
association_level = self.config.show_associated
|
||||
self.anc_level, self.association_level = anc_level, association_level
|
||||
|
||||
def _get_levels(self):
|
||||
"""help function for search levels"""
|
||||
return self.anc_level, self.association_level
|
||||
|
||||
def show_node(self, node):
|
||||
"""true if builtins and not show_builtins"""
|
||||
if self.config.show_builtin:
|
||||
return True
|
||||
return node.root().name != BUILTINS_NAME
|
||||
|
||||
def add_class(self, node):
|
||||
"""visit one class and add it to diagram"""
|
||||
self.linker.visit(node)
|
||||
self.classdiagram.add_object(self.get_title(node), node)
|
||||
|
||||
def get_ancestors(self, node, level):
|
||||
"""return ancestor nodes of a class node"""
|
||||
if level == 0:
|
||||
return
|
||||
for ancestor in node.ancestors(recurs=False):
|
||||
if not self.show_node(ancestor):
|
||||
continue
|
||||
yield ancestor
|
||||
|
||||
def get_associated(self, klass_node, level):
|
||||
"""return associated nodes of a class node"""
|
||||
if level == 0:
|
||||
return
|
||||
for association_nodes in list(klass_node.instance_attrs_type.values()) + list(
|
||||
klass_node.locals_type.values()
|
||||
):
|
||||
for node in association_nodes:
|
||||
if isinstance(node, astroid.Instance):
|
||||
node = node._proxied
|
||||
if not (isinstance(node, astroid.ClassDef) and self.show_node(node)):
|
||||
continue
|
||||
yield node
|
||||
|
||||
def extract_classes(self, klass_node, anc_level, association_level):
|
||||
"""extract recursively classes related to klass_node"""
|
||||
if self.classdiagram.has_node(klass_node) or not self.show_node(klass_node):
|
||||
return
|
||||
self.add_class(klass_node)
|
||||
|
||||
for ancestor in self.get_ancestors(klass_node, anc_level):
|
||||
self.extract_classes(ancestor, anc_level - 1, association_level)
|
||||
|
||||
for node in self.get_associated(klass_node, association_level):
|
||||
self.extract_classes(node, anc_level, association_level - 1)
|
||||
|
||||
|
||||
class DefaultDiadefGenerator(LocalsVisitor, DiaDefGenerator):
|
||||
"""generate minimum diagram definition for the project :
|
||||
|
||||
* a package diagram including project's modules
|
||||
* a class diagram including project's classes
|
||||
"""
|
||||
|
||||
def __init__(self, linker, handler):
|
||||
DiaDefGenerator.__init__(self, linker, handler)
|
||||
LocalsVisitor.__init__(self)
|
||||
|
||||
def visit_project(self, node):
|
||||
"""visit a pyreverse.utils.Project node
|
||||
|
||||
create a diagram definition for packages
|
||||
"""
|
||||
mode = self.config.mode
|
||||
if len(node.modules) > 1:
|
||||
self.pkgdiagram = PackageDiagram("packages %s" % node.name, mode)
|
||||
else:
|
||||
self.pkgdiagram = None
|
||||
self.classdiagram = ClassDiagram("classes %s" % node.name, mode)
|
||||
|
||||
def leave_project(self, node): # pylint: disable=unused-argument
|
||||
"""leave the pyreverse.utils.Project node
|
||||
|
||||
return the generated diagram definition
|
||||
"""
|
||||
if self.pkgdiagram:
|
||||
return self.pkgdiagram, self.classdiagram
|
||||
return (self.classdiagram,)
|
||||
|
||||
def visit_module(self, node):
|
||||
"""visit an astroid.Module node
|
||||
|
||||
add this class to the package diagram definition
|
||||
"""
|
||||
if self.pkgdiagram:
|
||||
self.linker.visit(node)
|
||||
self.pkgdiagram.add_object(node.name, node)
|
||||
|
||||
def visit_classdef(self, node):
|
||||
"""visit an astroid.Class node
|
||||
|
||||
add this class to the class diagram definition
|
||||
"""
|
||||
anc_level, association_level = self._get_levels()
|
||||
self.extract_classes(node, anc_level, association_level)
|
||||
|
||||
def visit_importfrom(self, node):
|
||||
"""visit astroid.ImportFrom and catch modules for package diagram
|
||||
"""
|
||||
if self.pkgdiagram:
|
||||
self.pkgdiagram.add_from_depend(node, node.modname)
|
||||
|
||||
|
||||
class ClassDiadefGenerator(DiaDefGenerator):
|
||||
"""generate a class diagram definition including all classes related to a
|
||||
given class
|
||||
"""
|
||||
|
||||
def __init__(self, linker, handler):
|
||||
DiaDefGenerator.__init__(self, linker, handler)
|
||||
|
||||
def class_diagram(self, project, klass):
|
||||
"""return a class diagram definition for the given klass and its
|
||||
related klasses
|
||||
"""
|
||||
|
||||
self.classdiagram = ClassDiagram(klass, self.config.mode)
|
||||
if len(project.modules) > 1:
|
||||
module, klass = klass.rsplit(".", 1)
|
||||
module = project.get_module(module)
|
||||
else:
|
||||
module = project.modules[0]
|
||||
klass = klass.split(".")[-1]
|
||||
klass = next(module.ilookup(klass))
|
||||
|
||||
anc_level, association_level = self._get_levels()
|
||||
self.extract_classes(klass, anc_level, association_level)
|
||||
return self.classdiagram
|
||||
|
||||
|
||||
# diagram handler #############################################################
|
||||
|
||||
|
||||
class DiadefsHandler:
|
||||
"""handle diagram definitions :
|
||||
|
||||
get it from user (i.e. xml files) or generate them
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
|
||||
def get_diadefs(self, project, linker):
|
||||
"""Get the diagrams configuration data
|
||||
|
||||
:param project:The pyreverse project
|
||||
:type project: pyreverse.utils.Project
|
||||
:param linker: The linker
|
||||
:type linker: pyreverse.inspector.Linker(IdGeneratorMixIn, LocalsVisitor)
|
||||
|
||||
:returns: The list of diagram definitions
|
||||
:rtype: list(:class:`pylint.pyreverse.diagrams.ClassDiagram`)
|
||||
"""
|
||||
|
||||
# read and interpret diagram definitions (Diadefs)
|
||||
diagrams = []
|
||||
generator = ClassDiadefGenerator(linker, self)
|
||||
for klass in self.config.classes:
|
||||
diagrams.append(generator.class_diagram(project, klass))
|
||||
if not diagrams:
|
||||
diagrams = DefaultDiadefGenerator(linker, self).visit(project)
|
||||
for diagram in diagrams:
|
||||
diagram.extract_relationships()
|
||||
return diagrams
|
||||
269
venv/lib/python3.8/site-packages/pylint/pyreverse/diagrams.py
Normal file
269
venv/lib/python3.8/site-packages/pylint/pyreverse/diagrams.py
Normal file
@@ -0,0 +1,269 @@
|
||||
# Copyright (c) 2006, 2008-2010, 2012-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
|
||||
# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com>
|
||||
# Copyright (c) 2014 Brett Cannon <brett@python.org>
|
||||
# Copyright (c) 2014 Arun Persaud <arun@nubati.net>
|
||||
# Copyright (c) 2015 Ionel Cristian Maries <contact@ionelmc.ro>
|
||||
# Copyright (c) 2018 ssolanki <sushobhitsolanki@gmail.com>
|
||||
# Copyright (c) 2019 Pierre Sassoulas <pierre.sassoulas@gmail.com>
|
||||
|
||||
# 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
|
||||
|
||||
"""diagram objects
|
||||
"""
|
||||
|
||||
import astroid
|
||||
|
||||
from pylint.checkers.utils import decorated_with_property
|
||||
from pylint.pyreverse.utils import FilterMixIn, is_interface
|
||||
|
||||
|
||||
class Figure:
|
||||
"""base class for counter handling"""
|
||||
|
||||
|
||||
class Relationship(Figure):
|
||||
"""a relation ship from an object in the diagram to another
|
||||
"""
|
||||
|
||||
def __init__(self, from_object, to_object, relation_type, name=None):
|
||||
Figure.__init__(self)
|
||||
self.from_object = from_object
|
||||
self.to_object = to_object
|
||||
self.type = relation_type
|
||||
self.name = name
|
||||
|
||||
|
||||
class DiagramEntity(Figure):
|
||||
"""a diagram object, i.e. a label associated to an astroid node
|
||||
"""
|
||||
|
||||
def __init__(self, title="No name", node=None):
|
||||
Figure.__init__(self)
|
||||
self.title = title
|
||||
self.node = node
|
||||
|
||||
|
||||
class ClassDiagram(Figure, FilterMixIn):
|
||||
"""main class diagram handling
|
||||
"""
|
||||
|
||||
TYPE = "class"
|
||||
|
||||
def __init__(self, title, mode):
|
||||
FilterMixIn.__init__(self, mode)
|
||||
Figure.__init__(self)
|
||||
self.title = title
|
||||
self.objects = []
|
||||
self.relationships = {}
|
||||
self._nodes = {}
|
||||
self.depends = []
|
||||
|
||||
def get_relationships(self, role):
|
||||
# sorted to get predictable (hence testable) results
|
||||
return sorted(
|
||||
self.relationships.get(role, ()),
|
||||
key=lambda x: (x.from_object.fig_id, x.to_object.fig_id),
|
||||
)
|
||||
|
||||
def add_relationship(self, from_object, to_object, relation_type, name=None):
|
||||
"""create a relation ship
|
||||
"""
|
||||
rel = Relationship(from_object, to_object, relation_type, name)
|
||||
self.relationships.setdefault(relation_type, []).append(rel)
|
||||
|
||||
def get_relationship(self, from_object, relation_type):
|
||||
"""return a relation ship or None
|
||||
"""
|
||||
for rel in self.relationships.get(relation_type, ()):
|
||||
if rel.from_object is from_object:
|
||||
return rel
|
||||
raise KeyError(relation_type)
|
||||
|
||||
def get_attrs(self, node):
|
||||
"""return visible attributes, possibly with class name"""
|
||||
attrs = []
|
||||
properties = [
|
||||
(n, m)
|
||||
for n, m in node.items()
|
||||
if isinstance(m, astroid.FunctionDef) and decorated_with_property(m)
|
||||
]
|
||||
for node_name, associated_nodes in (
|
||||
list(node.instance_attrs_type.items())
|
||||
+ list(node.locals_type.items())
|
||||
+ properties
|
||||
):
|
||||
if not self.show_attr(node_name):
|
||||
continue
|
||||
names = self.class_names(associated_nodes)
|
||||
if names:
|
||||
node_name = "%s : %s" % (node_name, ", ".join(names))
|
||||
attrs.append(node_name)
|
||||
return sorted(attrs)
|
||||
|
||||
def get_methods(self, node):
|
||||
"""return visible methods"""
|
||||
methods = [
|
||||
m
|
||||
for m in node.values()
|
||||
if isinstance(m, astroid.FunctionDef)
|
||||
and not decorated_with_property(m)
|
||||
and self.show_attr(m.name)
|
||||
]
|
||||
return sorted(methods, key=lambda n: n.name)
|
||||
|
||||
def add_object(self, title, node):
|
||||
"""create a diagram object
|
||||
"""
|
||||
assert node not in self._nodes
|
||||
ent = DiagramEntity(title, node)
|
||||
self._nodes[node] = ent
|
||||
self.objects.append(ent)
|
||||
|
||||
def class_names(self, nodes):
|
||||
"""return class names if needed in diagram"""
|
||||
names = []
|
||||
for node in nodes:
|
||||
if isinstance(node, astroid.Instance):
|
||||
node = node._proxied
|
||||
if (
|
||||
isinstance(node, astroid.ClassDef)
|
||||
and hasattr(node, "name")
|
||||
and not self.has_node(node)
|
||||
):
|
||||
if node.name not in names:
|
||||
node_name = node.name
|
||||
names.append(node_name)
|
||||
return names
|
||||
|
||||
def nodes(self):
|
||||
"""return the list of underlying nodes
|
||||
"""
|
||||
return self._nodes.keys()
|
||||
|
||||
def has_node(self, node):
|
||||
"""return true if the given node is included in the diagram
|
||||
"""
|
||||
return node in self._nodes
|
||||
|
||||
def object_from_node(self, node):
|
||||
"""return the diagram object mapped to node
|
||||
"""
|
||||
return self._nodes[node]
|
||||
|
||||
def classes(self):
|
||||
"""return all class nodes in the diagram"""
|
||||
return [o for o in self.objects if isinstance(o.node, astroid.ClassDef)]
|
||||
|
||||
def classe(self, name):
|
||||
"""return a class by its name, raise KeyError if not found
|
||||
"""
|
||||
for klass in self.classes():
|
||||
if klass.node.name == name:
|
||||
return klass
|
||||
raise KeyError(name)
|
||||
|
||||
def extract_relationships(self):
|
||||
"""extract relation ships between nodes in the diagram
|
||||
"""
|
||||
for obj in self.classes():
|
||||
node = obj.node
|
||||
obj.attrs = self.get_attrs(node)
|
||||
obj.methods = self.get_methods(node)
|
||||
# shape
|
||||
if is_interface(node):
|
||||
obj.shape = "interface"
|
||||
else:
|
||||
obj.shape = "class"
|
||||
# inheritance link
|
||||
for par_node in node.ancestors(recurs=False):
|
||||
try:
|
||||
par_obj = self.object_from_node(par_node)
|
||||
self.add_relationship(obj, par_obj, "specialization")
|
||||
except KeyError:
|
||||
continue
|
||||
# implements link
|
||||
for impl_node in node.implements:
|
||||
try:
|
||||
impl_obj = self.object_from_node(impl_node)
|
||||
self.add_relationship(obj, impl_obj, "implements")
|
||||
except KeyError:
|
||||
continue
|
||||
# associations link
|
||||
for name, values in list(node.instance_attrs_type.items()) + list(
|
||||
node.locals_type.items()
|
||||
):
|
||||
for value in values:
|
||||
if value is astroid.Uninferable:
|
||||
continue
|
||||
if isinstance(value, astroid.Instance):
|
||||
value = value._proxied
|
||||
try:
|
||||
associated_obj = self.object_from_node(value)
|
||||
self.add_relationship(associated_obj, obj, "association", name)
|
||||
except KeyError:
|
||||
continue
|
||||
|
||||
|
||||
class PackageDiagram(ClassDiagram):
|
||||
"""package diagram handling
|
||||
"""
|
||||
|
||||
TYPE = "package"
|
||||
|
||||
def modules(self):
|
||||
"""return all module nodes in the diagram"""
|
||||
return [o for o in self.objects if isinstance(o.node, astroid.Module)]
|
||||
|
||||
def module(self, name):
|
||||
"""return a module by its name, raise KeyError if not found
|
||||
"""
|
||||
for mod in self.modules():
|
||||
if mod.node.name == name:
|
||||
return mod
|
||||
raise KeyError(name)
|
||||
|
||||
def get_module(self, name, node):
|
||||
"""return a module by its name, looking also for relative imports;
|
||||
raise KeyError if not found
|
||||
"""
|
||||
for mod in self.modules():
|
||||
mod_name = mod.node.name
|
||||
if mod_name == name:
|
||||
return mod
|
||||
# search for fullname of relative import modules
|
||||
package = node.root().name
|
||||
if mod_name == "%s.%s" % (package, name):
|
||||
return mod
|
||||
if mod_name == "%s.%s" % (package.rsplit(".", 1)[0], name):
|
||||
return mod
|
||||
raise KeyError(name)
|
||||
|
||||
def add_from_depend(self, node, from_module):
|
||||
"""add dependencies created by from-imports
|
||||
"""
|
||||
mod_name = node.root().name
|
||||
obj = self.module(mod_name)
|
||||
if from_module not in obj.node.depends:
|
||||
obj.node.depends.append(from_module)
|
||||
|
||||
def extract_relationships(self):
|
||||
"""extract relation ships between nodes in the diagram
|
||||
"""
|
||||
ClassDiagram.extract_relationships(self)
|
||||
for obj in self.classes():
|
||||
# ownership
|
||||
try:
|
||||
mod = self.object_from_node(obj.node.root())
|
||||
self.add_relationship(obj, mod, "ownership")
|
||||
except KeyError:
|
||||
continue
|
||||
for obj in self.modules():
|
||||
obj.shape = "package"
|
||||
# dependencies
|
||||
for dep_name in obj.node.depends:
|
||||
try:
|
||||
dep = self.get_module(dep_name, obj.node)
|
||||
except KeyError:
|
||||
continue
|
||||
self.add_relationship(obj, dep, "depends")
|
||||
361
venv/lib/python3.8/site-packages/pylint/pyreverse/inspector.py
Normal file
361
venv/lib/python3.8/site-packages/pylint/pyreverse/inspector.py
Normal file
@@ -0,0 +1,361 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015-2019 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
|
||||
217
venv/lib/python3.8/site-packages/pylint/pyreverse/main.py
Normal file
217
venv/lib/python3.8/site-packages/pylint/pyreverse/main.py
Normal file
@@ -0,0 +1,217 @@
|
||||
# Copyright (c) 2008-2010, 2012-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
|
||||
# Copyright (c) 2014 Brett Cannon <brett@python.org>
|
||||
# Copyright (c) 2014 Arun Persaud <arun@nubati.net>
|
||||
# Copyright (c) 2015-2019 Claudiu Popa <pcmanticore@gmail.com>
|
||||
# Copyright (c) 2015 Ionel Cristian Maries <contact@ionelmc.ro>
|
||||
# Copyright (c) 2016 Alexander Pervakov <frost.nzcr4@jagmort.com>
|
||||
# Copyright (c) 2018 ssolanki <sushobhitsolanki@gmail.com>
|
||||
# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
|
||||
# Copyright (c) 2019 Pierre Sassoulas <pierre.sassoulas@gmail.com>
|
||||
|
||||
# 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
|
||||
|
||||
"""
|
||||
%prog [options] <packages>
|
||||
|
||||
create UML diagrams for classes and modules in <packages>
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from pylint.config import ConfigurationMixIn
|
||||
from pylint.pyreverse import writer
|
||||
from pylint.pyreverse.diadefslib import DiadefsHandler
|
||||
from pylint.pyreverse.inspector import Linker, project_from_files
|
||||
from pylint.pyreverse.utils import insert_default_options
|
||||
|
||||
OPTIONS = (
|
||||
(
|
||||
"filter-mode",
|
||||
dict(
|
||||
short="f",
|
||||
default="PUB_ONLY",
|
||||
dest="mode",
|
||||
type="string",
|
||||
action="store",
|
||||
metavar="<mode>",
|
||||
help="""filter attributes and functions according to
|
||||
<mode>. Correct modes are :
|
||||
'PUB_ONLY' filter all non public attributes
|
||||
[DEFAULT], equivalent to PRIVATE+SPECIAL_A
|
||||
'ALL' no filter
|
||||
'SPECIAL' filter Python special functions
|
||||
except constructor
|
||||
'OTHER' filter protected and private
|
||||
attributes""",
|
||||
),
|
||||
),
|
||||
(
|
||||
"class",
|
||||
dict(
|
||||
short="c",
|
||||
action="append",
|
||||
metavar="<class>",
|
||||
dest="classes",
|
||||
default=[],
|
||||
help="create a class diagram with all classes related to <class>;\
|
||||
this uses by default the options -ASmy",
|
||||
),
|
||||
),
|
||||
(
|
||||
"show-ancestors",
|
||||
dict(
|
||||
short="a",
|
||||
action="store",
|
||||
metavar="<ancestor>",
|
||||
type="int",
|
||||
help="show <ancestor> generations of ancestor classes not in <projects>",
|
||||
),
|
||||
),
|
||||
(
|
||||
"all-ancestors",
|
||||
dict(
|
||||
short="A",
|
||||
default=None,
|
||||
help="show all ancestors off all classes in <projects>",
|
||||
),
|
||||
),
|
||||
(
|
||||
"show-associated",
|
||||
dict(
|
||||
short="s",
|
||||
action="store",
|
||||
metavar="<association_level>",
|
||||
type="int",
|
||||
help="show <association_level> levels of associated classes not in <projects>",
|
||||
),
|
||||
),
|
||||
(
|
||||
"all-associated",
|
||||
dict(
|
||||
short="S",
|
||||
default=None,
|
||||
help="show recursively all associated off all associated classes",
|
||||
),
|
||||
),
|
||||
(
|
||||
"show-builtin",
|
||||
dict(
|
||||
short="b",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="include builtin objects in representation of classes",
|
||||
),
|
||||
),
|
||||
(
|
||||
"module-names",
|
||||
dict(
|
||||
short="m",
|
||||
default=None,
|
||||
type="yn",
|
||||
metavar="[yn]",
|
||||
help="include module name in representation of classes",
|
||||
),
|
||||
),
|
||||
(
|
||||
"only-classnames",
|
||||
dict(
|
||||
short="k",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="don't show attributes and methods in the class boxes; \
|
||||
this disables -f values",
|
||||
),
|
||||
),
|
||||
(
|
||||
"output",
|
||||
dict(
|
||||
short="o",
|
||||
dest="output_format",
|
||||
action="store",
|
||||
default="dot",
|
||||
metavar="<format>",
|
||||
help="create a *.<format> output file if format available.",
|
||||
),
|
||||
),
|
||||
(
|
||||
"ignore",
|
||||
{
|
||||
"type": "csv",
|
||||
"metavar": "<file[,file...]>",
|
||||
"dest": "black_list",
|
||||
"default": ("CVS",),
|
||||
"help": "Add files or directories to the blacklist. They "
|
||||
"should be base names, not paths.",
|
||||
},
|
||||
),
|
||||
(
|
||||
"project",
|
||||
{
|
||||
"default": "",
|
||||
"type": "string",
|
||||
"short": "p",
|
||||
"metavar": "<project name>",
|
||||
"help": "set the project name.",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _check_graphviz_available(output_format):
|
||||
"""check if we need graphviz for different output format"""
|
||||
try:
|
||||
subprocess.call(["dot", "-V"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
except OSError:
|
||||
print(
|
||||
"The output format '%s' is currently not available.\n"
|
||||
"Please install 'Graphviz' to have other output formats "
|
||||
"than 'dot' or 'vcg'." % output_format
|
||||
)
|
||||
sys.exit(32)
|
||||
|
||||
|
||||
class Run(ConfigurationMixIn):
|
||||
"""base class providing common behaviour for pyreverse commands"""
|
||||
|
||||
options = OPTIONS # type: ignore
|
||||
|
||||
def __init__(self, args):
|
||||
ConfigurationMixIn.__init__(self, usage=__doc__)
|
||||
insert_default_options()
|
||||
args = self.load_command_line_configuration()
|
||||
if self.config.output_format not in ("dot", "vcg"):
|
||||
_check_graphviz_available(self.config.output_format)
|
||||
|
||||
sys.exit(self.run(args))
|
||||
|
||||
def run(self, args):
|
||||
"""checking arguments and run project"""
|
||||
if not args:
|
||||
print(self.help())
|
||||
return 1
|
||||
# insert current working directory to the python path to recognize
|
||||
# dependencies to local modules even if cwd is not in the PYTHONPATH
|
||||
sys.path.insert(0, os.getcwd())
|
||||
try:
|
||||
project = project_from_files(
|
||||
args,
|
||||
project_name=self.config.project,
|
||||
black_list=self.config.black_list,
|
||||
)
|
||||
linker = Linker(project, tag=True)
|
||||
handler = DiadefsHandler(self.config)
|
||||
diadefs = handler.get_diadefs(project, linker)
|
||||
finally:
|
||||
sys.path.pop(0)
|
||||
|
||||
if self.config.output_format == "vcg":
|
||||
writer.VCGWriter(self.config).write(diadefs)
|
||||
else:
|
||||
writer.DotWriter(self.config).write(diadefs)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
Run(sys.argv[1:])
|
||||
223
venv/lib/python3.8/site-packages/pylint/pyreverse/utils.py
Normal file
223
venv/lib/python3.8/site-packages/pylint/pyreverse/utils.py
Normal file
@@ -0,0 +1,223 @@
|
||||
# Copyright (c) 2006, 2008, 2010, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
|
||||
# Copyright (c) 2014 Brett Cannon <brett@python.org>
|
||||
# Copyright (c) 2014 Arun Persaud <arun@nubati.net>
|
||||
# Copyright (c) 2015-2019 Claudiu Popa <pcmanticore@gmail.com>
|
||||
# Copyright (c) 2015 Ionel Cristian Maries <contact@ionelmc.ro>
|
||||
# Copyright (c) 2017 hippo91 <guillaume.peillex@gmail.com>
|
||||
# Copyright (c) 2018 ssolanki <sushobhitsolanki@gmail.com>
|
||||
# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
|
||||
# Copyright (c) 2020 Anthony Sottile <asottile@umich.edu>
|
||||
# Copyright (c) 2020 bernie gray <bfgray3@users.noreply.github.com>
|
||||
|
||||
# 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
|
||||
|
||||
"""
|
||||
generic classes/functions for pyreverse core/extensions
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
########### pyreverse option utils ##############################
|
||||
|
||||
|
||||
RCFILE = ".pyreverserc"
|
||||
|
||||
|
||||
def get_default_options():
|
||||
"""
|
||||
Read config file and return list of options
|
||||
"""
|
||||
options = []
|
||||
home = os.environ.get("HOME", "")
|
||||
if home:
|
||||
rcfile = os.path.join(home, RCFILE)
|
||||
try:
|
||||
options = open(rcfile).read().split()
|
||||
except OSError:
|
||||
pass # ignore if no config file found
|
||||
return options
|
||||
|
||||
|
||||
def insert_default_options():
|
||||
"""insert default options to sys.argv
|
||||
"""
|
||||
options = get_default_options()
|
||||
options.reverse()
|
||||
for arg in options:
|
||||
sys.argv.insert(1, arg)
|
||||
|
||||
|
||||
# astroid utilities ###########################################################
|
||||
|
||||
SPECIAL = re.compile(r"^__[^\W_]+\w*__$")
|
||||
PRIVATE = re.compile(r"^__\w*[^\W_]+_?$")
|
||||
PROTECTED = re.compile(r"^_\w*$")
|
||||
|
||||
|
||||
def get_visibility(name):
|
||||
"""return the visibility from a name: public, protected, private or special
|
||||
"""
|
||||
if SPECIAL.match(name):
|
||||
visibility = "special"
|
||||
elif PRIVATE.match(name):
|
||||
visibility = "private"
|
||||
elif PROTECTED.match(name):
|
||||
visibility = "protected"
|
||||
|
||||
else:
|
||||
visibility = "public"
|
||||
return visibility
|
||||
|
||||
|
||||
ABSTRACT = re.compile(r"^.*Abstract.*")
|
||||
FINAL = re.compile(r"^[^\W\da-z]*$")
|
||||
|
||||
|
||||
def is_abstract(node):
|
||||
"""return true if the given class node correspond to an abstract class
|
||||
definition
|
||||
"""
|
||||
return ABSTRACT.match(node.name)
|
||||
|
||||
|
||||
def is_final(node):
|
||||
"""return true if the given class/function node correspond to final
|
||||
definition
|
||||
"""
|
||||
return FINAL.match(node.name)
|
||||
|
||||
|
||||
def is_interface(node):
|
||||
# bw compat
|
||||
return node.type == "interface"
|
||||
|
||||
|
||||
def is_exception(node):
|
||||
# bw compat
|
||||
return node.type == "exception"
|
||||
|
||||
|
||||
# Helpers #####################################################################
|
||||
|
||||
_CONSTRUCTOR = 1
|
||||
_SPECIAL = 2
|
||||
_PROTECTED = 4
|
||||
_PRIVATE = 8
|
||||
MODES = {
|
||||
"ALL": 0,
|
||||
"PUB_ONLY": _SPECIAL + _PROTECTED + _PRIVATE,
|
||||
"SPECIAL": _SPECIAL,
|
||||
"OTHER": _PROTECTED + _PRIVATE,
|
||||
}
|
||||
VIS_MOD = {
|
||||
"special": _SPECIAL,
|
||||
"protected": _PROTECTED,
|
||||
"private": _PRIVATE,
|
||||
"public": 0,
|
||||
}
|
||||
|
||||
|
||||
class FilterMixIn:
|
||||
"""filter nodes according to a mode and nodes' visibility
|
||||
"""
|
||||
|
||||
def __init__(self, mode):
|
||||
"init filter modes"
|
||||
__mode = 0
|
||||
for nummod in mode.split("+"):
|
||||
try:
|
||||
__mode += MODES[nummod]
|
||||
except KeyError as ex:
|
||||
print("Unknown filter mode %s" % ex, file=sys.stderr)
|
||||
self.__mode = __mode
|
||||
|
||||
def show_attr(self, node):
|
||||
"""return true if the node should be treated
|
||||
"""
|
||||
visibility = get_visibility(getattr(node, "name", node))
|
||||
return not self.__mode & VIS_MOD[visibility]
|
||||
|
||||
|
||||
class ASTWalker:
|
||||
"""a walker visiting a tree in preorder, calling on the handler:
|
||||
|
||||
* visit_<class name> on entering a node, where class name is the class of
|
||||
the node in lower case
|
||||
|
||||
* leave_<class name> on leaving a node, where class name is the class of
|
||||
the node in lower case
|
||||
"""
|
||||
|
||||
def __init__(self, handler):
|
||||
self.handler = handler
|
||||
self._cache = {}
|
||||
|
||||
def walk(self, node, _done=None):
|
||||
"""walk on the tree from <node>, getting callbacks from handler"""
|
||||
if _done is None:
|
||||
_done = set()
|
||||
if node in _done:
|
||||
raise AssertionError((id(node), node, node.parent))
|
||||
_done.add(node)
|
||||
self.visit(node)
|
||||
for child_node in node.get_children():
|
||||
assert child_node is not node
|
||||
self.walk(child_node, _done)
|
||||
self.leave(node)
|
||||
assert node.parent is not node
|
||||
|
||||
def get_callbacks(self, node):
|
||||
"""get callbacks from handler for the visited node"""
|
||||
klass = node.__class__
|
||||
methods = self._cache.get(klass)
|
||||
if methods is None:
|
||||
handler = self.handler
|
||||
kid = klass.__name__.lower()
|
||||
e_method = getattr(
|
||||
handler, "visit_%s" % kid, getattr(handler, "visit_default", None)
|
||||
)
|
||||
l_method = getattr(
|
||||
handler, "leave_%s" % kid, getattr(handler, "leave_default", None)
|
||||
)
|
||||
self._cache[klass] = (e_method, l_method)
|
||||
else:
|
||||
e_method, l_method = methods
|
||||
return e_method, l_method
|
||||
|
||||
def visit(self, node):
|
||||
"""walk on the tree from <node>, getting callbacks from handler"""
|
||||
method = self.get_callbacks(node)[0]
|
||||
if method is not None:
|
||||
method(node)
|
||||
|
||||
def leave(self, node):
|
||||
"""walk on the tree from <node>, getting callbacks from handler"""
|
||||
method = self.get_callbacks(node)[1]
|
||||
if method is not None:
|
||||
method(node)
|
||||
|
||||
|
||||
class LocalsVisitor(ASTWalker):
|
||||
"""visit a project by traversing the locals dictionary"""
|
||||
|
||||
def __init__(self):
|
||||
ASTWalker.__init__(self, self)
|
||||
self._visited = set()
|
||||
|
||||
def visit(self, node):
|
||||
"""launch the visit starting from the given node"""
|
||||
if node in self._visited:
|
||||
return None
|
||||
|
||||
self._visited.add(node)
|
||||
methods = self.get_callbacks(node)
|
||||
if methods[0] is not None:
|
||||
methods[0](node)
|
||||
if hasattr(node, "locals"): # skip Instance and other proxy
|
||||
for local_node in node.values():
|
||||
self.visit(local_node)
|
||||
if methods[1] is not None:
|
||||
return methods[1](node)
|
||||
return None
|
||||
229
venv/lib/python3.8/site-packages/pylint/pyreverse/vcgutils.py
Normal file
229
venv/lib/python3.8/site-packages/pylint/pyreverse/vcgutils.py
Normal file
@@ -0,0 +1,229 @@
|
||||
# Copyright (c) 2015-2018 Claudiu Popa <pcmanticore@gmail.com>
|
||||
# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
|
||||
# Copyright (c) 2018 ssolanki <sushobhitsolanki@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
|
||||
|
||||
"""Functions to generate files readable with Georg Sander's vcg
|
||||
(Visualization of Compiler Graphs).
|
||||
|
||||
You can download vcg at http://rw4.cs.uni-sb.de/~sander/html/gshome.html
|
||||
Note that vcg exists as a debian package.
|
||||
|
||||
See vcg's documentation for explanation about the different values that
|
||||
maybe used for the functions parameters.
|
||||
"""
|
||||
|
||||
ATTRS_VAL = {
|
||||
"algos": (
|
||||
"dfs",
|
||||
"tree",
|
||||
"minbackward",
|
||||
"left_to_right",
|
||||
"right_to_left",
|
||||
"top_to_bottom",
|
||||
"bottom_to_top",
|
||||
"maxdepth",
|
||||
"maxdepthslow",
|
||||
"mindepth",
|
||||
"mindepthslow",
|
||||
"mindegree",
|
||||
"minindegree",
|
||||
"minoutdegree",
|
||||
"maxdegree",
|
||||
"maxindegree",
|
||||
"maxoutdegree",
|
||||
),
|
||||
"booleans": ("yes", "no"),
|
||||
"colors": (
|
||||
"black",
|
||||
"white",
|
||||
"blue",
|
||||
"red",
|
||||
"green",
|
||||
"yellow",
|
||||
"magenta",
|
||||
"lightgrey",
|
||||
"cyan",
|
||||
"darkgrey",
|
||||
"darkblue",
|
||||
"darkred",
|
||||
"darkgreen",
|
||||
"darkyellow",
|
||||
"darkmagenta",
|
||||
"darkcyan",
|
||||
"gold",
|
||||
"lightblue",
|
||||
"lightred",
|
||||
"lightgreen",
|
||||
"lightyellow",
|
||||
"lightmagenta",
|
||||
"lightcyan",
|
||||
"lilac",
|
||||
"turquoise",
|
||||
"aquamarine",
|
||||
"khaki",
|
||||
"purple",
|
||||
"yellowgreen",
|
||||
"pink",
|
||||
"orange",
|
||||
"orchid",
|
||||
),
|
||||
"shapes": ("box", "ellipse", "rhomb", "triangle"),
|
||||
"textmodes": ("center", "left_justify", "right_justify"),
|
||||
"arrowstyles": ("solid", "line", "none"),
|
||||
"linestyles": ("continuous", "dashed", "dotted", "invisible"),
|
||||
}
|
||||
|
||||
# meaning of possible values:
|
||||
# O -> string
|
||||
# 1 -> int
|
||||
# list -> value in list
|
||||
GRAPH_ATTRS = {
|
||||
"title": 0,
|
||||
"label": 0,
|
||||
"color": ATTRS_VAL["colors"],
|
||||
"textcolor": ATTRS_VAL["colors"],
|
||||
"bordercolor": ATTRS_VAL["colors"],
|
||||
"width": 1,
|
||||
"height": 1,
|
||||
"borderwidth": 1,
|
||||
"textmode": ATTRS_VAL["textmodes"],
|
||||
"shape": ATTRS_VAL["shapes"],
|
||||
"shrink": 1,
|
||||
"stretch": 1,
|
||||
"orientation": ATTRS_VAL["algos"],
|
||||
"vertical_order": 1,
|
||||
"horizontal_order": 1,
|
||||
"xspace": 1,
|
||||
"yspace": 1,
|
||||
"layoutalgorithm": ATTRS_VAL["algos"],
|
||||
"late_edge_labels": ATTRS_VAL["booleans"],
|
||||
"display_edge_labels": ATTRS_VAL["booleans"],
|
||||
"dirty_edge_labels": ATTRS_VAL["booleans"],
|
||||
"finetuning": ATTRS_VAL["booleans"],
|
||||
"manhattan_edges": ATTRS_VAL["booleans"],
|
||||
"smanhattan_edges": ATTRS_VAL["booleans"],
|
||||
"port_sharing": ATTRS_VAL["booleans"],
|
||||
"edges": ATTRS_VAL["booleans"],
|
||||
"nodes": ATTRS_VAL["booleans"],
|
||||
"splines": ATTRS_VAL["booleans"],
|
||||
}
|
||||
NODE_ATTRS = {
|
||||
"title": 0,
|
||||
"label": 0,
|
||||
"color": ATTRS_VAL["colors"],
|
||||
"textcolor": ATTRS_VAL["colors"],
|
||||
"bordercolor": ATTRS_VAL["colors"],
|
||||
"width": 1,
|
||||
"height": 1,
|
||||
"borderwidth": 1,
|
||||
"textmode": ATTRS_VAL["textmodes"],
|
||||
"shape": ATTRS_VAL["shapes"],
|
||||
"shrink": 1,
|
||||
"stretch": 1,
|
||||
"vertical_order": 1,
|
||||
"horizontal_order": 1,
|
||||
}
|
||||
EDGE_ATTRS = {
|
||||
"sourcename": 0,
|
||||
"targetname": 0,
|
||||
"label": 0,
|
||||
"linestyle": ATTRS_VAL["linestyles"],
|
||||
"class": 1,
|
||||
"thickness": 0,
|
||||
"color": ATTRS_VAL["colors"],
|
||||
"textcolor": ATTRS_VAL["colors"],
|
||||
"arrowcolor": ATTRS_VAL["colors"],
|
||||
"backarrowcolor": ATTRS_VAL["colors"],
|
||||
"arrowsize": 1,
|
||||
"backarrowsize": 1,
|
||||
"arrowstyle": ATTRS_VAL["arrowstyles"],
|
||||
"backarrowstyle": ATTRS_VAL["arrowstyles"],
|
||||
"textmode": ATTRS_VAL["textmodes"],
|
||||
"priority": 1,
|
||||
"anchor": 1,
|
||||
"horizontal_order": 1,
|
||||
}
|
||||
|
||||
|
||||
# Misc utilities ###############################################################
|
||||
|
||||
|
||||
class VCGPrinter:
|
||||
"""A vcg graph writer.
|
||||
"""
|
||||
|
||||
def __init__(self, output_stream):
|
||||
self._stream = output_stream
|
||||
self._indent = ""
|
||||
|
||||
def open_graph(self, **args):
|
||||
"""open a vcg graph
|
||||
"""
|
||||
self._stream.write("%sgraph:{\n" % self._indent)
|
||||
self._inc_indent()
|
||||
self._write_attributes(GRAPH_ATTRS, **args)
|
||||
|
||||
def close_graph(self):
|
||||
"""close a vcg graph
|
||||
"""
|
||||
self._dec_indent()
|
||||
self._stream.write("%s}\n" % self._indent)
|
||||
|
||||
def node(self, title, **args):
|
||||
"""draw a node
|
||||
"""
|
||||
self._stream.write('%snode: {title:"%s"' % (self._indent, title))
|
||||
self._write_attributes(NODE_ATTRS, **args)
|
||||
self._stream.write("}\n")
|
||||
|
||||
def edge(self, from_node, to_node, edge_type="", **args):
|
||||
"""draw an edge from a node to another.
|
||||
"""
|
||||
self._stream.write(
|
||||
'%s%sedge: {sourcename:"%s" targetname:"%s"'
|
||||
% (self._indent, edge_type, from_node, to_node)
|
||||
)
|
||||
self._write_attributes(EDGE_ATTRS, **args)
|
||||
self._stream.write("}\n")
|
||||
|
||||
# private ##################################################################
|
||||
|
||||
def _write_attributes(self, attributes_dict, **args):
|
||||
"""write graph, node or edge attributes
|
||||
"""
|
||||
for key, value in args.items():
|
||||
try:
|
||||
_type = attributes_dict[key]
|
||||
except KeyError:
|
||||
raise Exception(
|
||||
"""no such attribute %s
|
||||
possible attributes are %s"""
|
||||
% (key, attributes_dict.keys())
|
||||
)
|
||||
|
||||
if not _type:
|
||||
self._stream.write('%s%s:"%s"\n' % (self._indent, key, value))
|
||||
elif _type == 1:
|
||||
self._stream.write("%s%s:%s\n" % (self._indent, key, int(value)))
|
||||
elif value in _type:
|
||||
self._stream.write("%s%s:%s\n" % (self._indent, key, value))
|
||||
else:
|
||||
raise Exception(
|
||||
"""value %s isn\'t correct for attribute %s
|
||||
correct values are %s"""
|
||||
% (value, key, _type)
|
||||
)
|
||||
|
||||
def _inc_indent(self):
|
||||
"""increment indentation
|
||||
"""
|
||||
self._indent = " %s" % self._indent
|
||||
|
||||
def _dec_indent(self):
|
||||
"""decrement indentation
|
||||
"""
|
||||
self._indent = self._indent[:-2]
|
||||
217
venv/lib/python3.8/site-packages/pylint/pyreverse/writer.py
Normal file
217
venv/lib/python3.8/site-packages/pylint/pyreverse/writer.py
Normal file
@@ -0,0 +1,217 @@
|
||||
# Copyright (c) 2008-2010, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
|
||||
# Copyright (c) 2014 Arun Persaud <arun@nubati.net>
|
||||
# Copyright (c) 2015-2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
|
||||
# Copyright (c) 2015 Mike Frysinger <vapier@gentoo.org>
|
||||
# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
|
||||
# Copyright (c) 2015 Ionel Cristian Maries <contact@ionelmc.ro>
|
||||
# Copyright (c) 2018, 2020 Anthony Sottile <asottile@umich.edu>
|
||||
# Copyright (c) 2018 ssolanki <sushobhitsolanki@gmail.com>
|
||||
# Copyright (c) 2019 Pierre Sassoulas <pierre.sassoulas@gmail.com>
|
||||
# Copyright (c) 2019 Kylian <development@goudcode.nl>
|
||||
|
||||
# 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
|
||||
|
||||
"""Utilities for creating VCG and Dot diagrams"""
|
||||
|
||||
from pylint.graph import DotBackend
|
||||
from pylint.pyreverse.utils import is_exception
|
||||
from pylint.pyreverse.vcgutils import VCGPrinter
|
||||
|
||||
|
||||
class DiagramWriter:
|
||||
"""base class for writing project diagrams
|
||||
"""
|
||||
|
||||
def __init__(self, config, styles):
|
||||
self.config = config
|
||||
self.pkg_edges, self.inh_edges, self.imp_edges, self.association_edges = styles
|
||||
self.printer = None # defined in set_printer
|
||||
|
||||
def write(self, diadefs):
|
||||
"""write files for <project> according to <diadefs>
|
||||
"""
|
||||
for diagram in diadefs:
|
||||
basename = diagram.title.strip().replace(" ", "_")
|
||||
file_name = "%s.%s" % (basename, self.config.output_format)
|
||||
self.set_printer(file_name, basename)
|
||||
if diagram.TYPE == "class":
|
||||
self.write_classes(diagram)
|
||||
else:
|
||||
self.write_packages(diagram)
|
||||
self.close_graph()
|
||||
|
||||
def write_packages(self, diagram):
|
||||
"""write a package diagram"""
|
||||
# sorted to get predictable (hence testable) results
|
||||
for i, obj in enumerate(sorted(diagram.modules(), key=lambda x: x.title)):
|
||||
self.printer.emit_node(i, label=self.get_title(obj), shape="box")
|
||||
obj.fig_id = i
|
||||
# package dependencies
|
||||
for rel in diagram.get_relationships("depends"):
|
||||
self.printer.emit_edge(
|
||||
rel.from_object.fig_id, rel.to_object.fig_id, **self.pkg_edges
|
||||
)
|
||||
|
||||
def write_classes(self, diagram):
|
||||
"""write a class diagram"""
|
||||
# sorted to get predictable (hence testable) results
|
||||
for i, obj in enumerate(sorted(diagram.objects, key=lambda x: x.title)):
|
||||
self.printer.emit_node(i, **self.get_values(obj))
|
||||
obj.fig_id = i
|
||||
# inheritance links
|
||||
for rel in diagram.get_relationships("specialization"):
|
||||
self.printer.emit_edge(
|
||||
rel.from_object.fig_id, rel.to_object.fig_id, **self.inh_edges
|
||||
)
|
||||
# implementation links
|
||||
for rel in diagram.get_relationships("implements"):
|
||||
self.printer.emit_edge(
|
||||
rel.from_object.fig_id, rel.to_object.fig_id, **self.imp_edges
|
||||
)
|
||||
# generate associations
|
||||
for rel in diagram.get_relationships("association"):
|
||||
self.printer.emit_edge(
|
||||
rel.from_object.fig_id,
|
||||
rel.to_object.fig_id,
|
||||
label=rel.name,
|
||||
**self.association_edges
|
||||
)
|
||||
|
||||
def set_printer(self, file_name, basename):
|
||||
"""set printer"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_title(self, obj):
|
||||
"""get project title"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_values(self, obj):
|
||||
"""get label and shape for classes."""
|
||||
raise NotImplementedError
|
||||
|
||||
def close_graph(self):
|
||||
"""finalize the graph"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class DotWriter(DiagramWriter):
|
||||
"""write dot graphs from a diagram definition and a project
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
styles = [
|
||||
dict(arrowtail="none", arrowhead="open"),
|
||||
dict(arrowtail="none", arrowhead="empty"),
|
||||
dict(arrowtail="node", arrowhead="empty", style="dashed"),
|
||||
dict(
|
||||
fontcolor="green", arrowtail="none", arrowhead="diamond", style="solid"
|
||||
),
|
||||
]
|
||||
DiagramWriter.__init__(self, config, styles)
|
||||
|
||||
def set_printer(self, file_name, basename):
|
||||
"""initialize DotWriter and add options for layout.
|
||||
"""
|
||||
layout = dict(rankdir="BT")
|
||||
self.printer = DotBackend(basename, additional_param=layout)
|
||||
self.file_name = file_name
|
||||
|
||||
def get_title(self, obj):
|
||||
"""get project title"""
|
||||
return obj.title
|
||||
|
||||
def get_values(self, obj):
|
||||
"""get label and shape for classes.
|
||||
|
||||
The label contains all attributes and methods
|
||||
"""
|
||||
label = obj.title
|
||||
if obj.shape == "interface":
|
||||
label = "«interface»\\n%s" % label
|
||||
if not self.config.only_classnames:
|
||||
label = r"%s|%s\l|" % (label, r"\l".join(obj.attrs))
|
||||
for func in obj.methods:
|
||||
if func.args.args:
|
||||
args = [arg.name for arg in func.args.args if arg.name != "self"]
|
||||
else:
|
||||
args = []
|
||||
label = r"%s%s(%s)\l" % (label, func.name, ", ".join(args))
|
||||
label = "{%s}" % label
|
||||
if is_exception(obj.node):
|
||||
return dict(fontcolor="red", label=label, shape="record")
|
||||
return dict(label=label, shape="record")
|
||||
|
||||
def close_graph(self):
|
||||
"""print the dot graph into <file_name>"""
|
||||
self.printer.generate(self.file_name)
|
||||
|
||||
|
||||
class VCGWriter(DiagramWriter):
|
||||
"""write vcg graphs from a diagram definition and a project
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
styles = [
|
||||
dict(arrowstyle="solid", backarrowstyle="none", backarrowsize=0),
|
||||
dict(arrowstyle="solid", backarrowstyle="none", backarrowsize=10),
|
||||
dict(
|
||||
arrowstyle="solid",
|
||||
backarrowstyle="none",
|
||||
linestyle="dotted",
|
||||
backarrowsize=10,
|
||||
),
|
||||
dict(arrowstyle="solid", backarrowstyle="none", textcolor="green"),
|
||||
]
|
||||
DiagramWriter.__init__(self, config, styles)
|
||||
|
||||
def set_printer(self, file_name, basename):
|
||||
"""initialize VCGWriter for a UML graph"""
|
||||
self.graph_file = open(file_name, "w+")
|
||||
self.printer = VCGPrinter(self.graph_file)
|
||||
self.printer.open_graph(
|
||||
title=basename,
|
||||
layoutalgorithm="dfs",
|
||||
late_edge_labels="yes",
|
||||
port_sharing="no",
|
||||
manhattan_edges="yes",
|
||||
)
|
||||
self.printer.emit_node = self.printer.node
|
||||
self.printer.emit_edge = self.printer.edge
|
||||
|
||||
def get_title(self, obj):
|
||||
"""get project title in vcg format"""
|
||||
return r"\fb%s\fn" % obj.title
|
||||
|
||||
def get_values(self, obj):
|
||||
"""get label and shape for classes.
|
||||
|
||||
The label contains all attributes and methods
|
||||
"""
|
||||
if is_exception(obj.node):
|
||||
label = r"\fb\f09%s\fn" % obj.title
|
||||
else:
|
||||
label = r"\fb%s\fn" % obj.title
|
||||
if obj.shape == "interface":
|
||||
shape = "ellipse"
|
||||
else:
|
||||
shape = "box"
|
||||
if not self.config.only_classnames:
|
||||
attrs = obj.attrs
|
||||
methods = [func.name for func in obj.methods]
|
||||
# box width for UML like diagram
|
||||
maxlen = max(len(name) for name in [obj.title] + methods + attrs)
|
||||
line = "_" * (maxlen + 2)
|
||||
label = r"%s\n\f%s" % (label, line)
|
||||
for attr in attrs:
|
||||
label = r"%s\n\f08%s" % (label, attr)
|
||||
if attrs:
|
||||
label = r"%s\n\f%s" % (label, line)
|
||||
for func in methods:
|
||||
label = r"%s\n\f10%s()" % (label, func)
|
||||
return dict(label=label, shape=shape)
|
||||
|
||||
def close_graph(self):
|
||||
"""close graph and file"""
|
||||
self.printer.close_graph()
|
||||
self.graph_file.close()
|
||||
Reference in New Issue
Block a user