8th day of python challenges 111-117
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
|
||||
# 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 $"
|
238
venv/lib/python3.6/site-packages/pylint/pyreverse/diadefslib.py
Normal file
238
venv/lib/python3.6/site-packages/pylint/pyreverse/diadefslib.py
Normal file
@@ -0,0 +1,238 @@
|
||||
# -*- 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@upcloud.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
|
||||
|
||||
"""handle diagram generation options for class diagram or default diagrams
|
||||
"""
|
||||
|
||||
import astroid
|
||||
|
||||
from pylint.pyreverse.diagrams import PackageDiagram, ClassDiagram
|
||||
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
|
267
venv/lib/python3.6/site-packages/pylint/pyreverse/diagrams.py
Normal file
267
venv/lib/python3.6/site-packages/pylint/pyreverse/diagrams.py
Normal file
@@ -0,0 +1,267 @@
|
||||
# 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>
|
||||
|
||||
# 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.pyreverse.utils import is_interface, FilterMixIn
|
||||
from pylint.checkers.utils import decorated_with_property
|
||||
|
||||
|
||||
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")
|
368
venv/lib/python3.6/site-packages/pylint/pyreverse/inspector.py
Normal file
368
venv/lib/python3.6/site-packages/pylint/pyreverse/inspector.py
Normal file
@@ -0,0 +1,368 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015-2018 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@upcloud.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
|
||||
|
||||
"""
|
||||
Visitor doing some postprocessing on the astroid tree.
|
||||
Try to resolve definitions (namespace) dictionary, relationship...
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
import collections
|
||||
import os
|
||||
import traceback
|
||||
|
||||
import astroid
|
||||
from astroid import bases
|
||||
from astroid import exceptions
|
||||
from astroid import manager
|
||||
from astroid import modutils
|
||||
from astroid import 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."""
|
||||
# FIXME: what if __implements__ = (MyIFace, MyParent.__implements__)...
|
||||
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:
|
||||
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:
|
||||
# TODO: don't use get_module_part,
|
||||
# missing package precedence
|
||||
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
|
||||
# XXX why is first file defining the project.path ?
|
||||
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
|
221
venv/lib/python3.6/site-packages/pylint/pyreverse/main.py
Normal file
221
venv/lib/python3.6/site-packages/pylint/pyreverse/main.py
Normal file
@@ -0,0 +1,221 @@
|
||||
# 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-2018 Claudiu Popa <pcmanticore@gmail.com>
|
||||
# Copyright (c) 2015 Ionel Cristian Maries <contact@ionelmc.ro>
|
||||
# Copyright (c) 2016 Alexander Pervakov <frost.nzcr4@jagmort.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>
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from pylint.config import ConfigurationMixIn
|
||||
from pylint.pyreverse.inspector import Linker, project_from_files
|
||||
from pylint.pyreverse.diadefslib import DiadefsHandler
|
||||
from pylint.pyreverse import writer
|
||||
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",
|
||||
),
|
||||
),
|
||||
# TODO : generate dependencies like in pylint
|
||||
# ("package-dependencies",
|
||||
# dict(short="M", action="store", metavar='<package_depth>', type='int',
|
||||
# help='show <package_depth> module dependencies beyond modules in \
|
||||
# <projects> (for the package diagram)')),
|
||||
(
|
||||
"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:])
|
221
venv/lib/python3.6/site-packages/pylint/pyreverse/utils.py
Normal file
221
venv/lib/python3.6/site-packages/pylint/pyreverse/utils.py
Normal file
@@ -0,0 +1,221 @@
|
||||
# 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-2017 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>
|
||||
|
||||
# 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
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
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 IOError:
|
||||
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("^__[A-Za-z0-9]+[A-Za-z0-9_]*__$")
|
||||
PRIVATE = re.compile("^__[_A-Za-z0-9]*[A-Za-z0-9]+_?$")
|
||||
PROTECTED = re.compile("^_[_A-Za-z0-9]*$")
|
||||
|
||||
|
||||
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("^.*Abstract.*")
|
||||
FINAL = re.compile("^[A-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 = {}
|
||||
|
||||
def visit(self, node):
|
||||
"""launch the visit starting from the given node"""
|
||||
if node in self._visited:
|
||||
return None
|
||||
self._visited[node] = 1 # FIXME: use set ?
|
||||
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.6/site-packages/pylint/pyreverse/vcgutils.py
Normal file
229
venv/lib/python3.6/site-packages/pylint/pyreverse/vcgutils.py
Normal file
@@ -0,0 +1,229 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015-2017 Claudiu Popa <pcmanticore@gmail.com>
|
||||
# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
|
||||
# Copyright (c) 2018 ssolanki <sushobhitsolanki@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
|
||||
|
||||
"""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]
|
212
venv/lib/python3.6/site-packages/pylint/pyreverse/writer.py
Normal file
212
venv/lib/python3.6/site-packages/pylint/pyreverse/writer.py
Normal file
@@ -0,0 +1,212 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 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-2017 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 ssolanki <sushobhitsolanki@gmail.com>
|
||||
# Copyright (c) 2018 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
|
||||
|
||||
"""Utilities for creating VCG and Dot diagrams"""
|
||||
|
||||
from pylint.pyreverse.utils import is_exception
|
||||
from pylint.pyreverse.vcgutils import VCGPrinter
|
||||
from pylint.graph import DotBackend
|
||||
|
||||
|
||||
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:
|
||||
label = r"%s%s()\l" % (label, func.name)
|
||||
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