mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-11-26 17:05:31 +01:00
241 lines
8.5 KiB
Python
241 lines
8.5 KiB
Python
|
# -*- 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, 2020 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
|