diff options
Diffstat (limited to 'venv/Lib/site-packages/pylint/pyreverse')
16 files changed, 1747 insertions, 0 deletions
diff --git a/venv/Lib/site-packages/pylint/pyreverse/__init__.py b/venv/Lib/site-packages/pylint/pyreverse/__init__.py new file mode 100644 index 0000000..9ca1da5 --- /dev/null +++ b/venv/Lib/site-packages/pylint/pyreverse/__init__.py @@ -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 $" diff --git a/venv/Lib/site-packages/pylint/pyreverse/__pycache__/__init__.cpython-37.pyc b/venv/Lib/site-packages/pylint/pyreverse/__pycache__/__init__.cpython-37.pyc Binary files differnew file mode 100644 index 0000000..6054dd9 --- /dev/null +++ b/venv/Lib/site-packages/pylint/pyreverse/__pycache__/__init__.cpython-37.pyc diff --git a/venv/Lib/site-packages/pylint/pyreverse/__pycache__/diadefslib.cpython-37.pyc b/venv/Lib/site-packages/pylint/pyreverse/__pycache__/diadefslib.cpython-37.pyc Binary files differnew file mode 100644 index 0000000..64bdd6b --- /dev/null +++ b/venv/Lib/site-packages/pylint/pyreverse/__pycache__/diadefslib.cpython-37.pyc diff --git a/venv/Lib/site-packages/pylint/pyreverse/__pycache__/diagrams.cpython-37.pyc b/venv/Lib/site-packages/pylint/pyreverse/__pycache__/diagrams.cpython-37.pyc Binary files differnew file mode 100644 index 0000000..cd5a663 --- /dev/null +++ b/venv/Lib/site-packages/pylint/pyreverse/__pycache__/diagrams.cpython-37.pyc diff --git a/venv/Lib/site-packages/pylint/pyreverse/__pycache__/inspector.cpython-37.pyc b/venv/Lib/site-packages/pylint/pyreverse/__pycache__/inspector.cpython-37.pyc Binary files differnew file mode 100644 index 0000000..0bcfb4d --- /dev/null +++ b/venv/Lib/site-packages/pylint/pyreverse/__pycache__/inspector.cpython-37.pyc diff --git a/venv/Lib/site-packages/pylint/pyreverse/__pycache__/main.cpython-37.pyc b/venv/Lib/site-packages/pylint/pyreverse/__pycache__/main.cpython-37.pyc Binary files differnew file mode 100644 index 0000000..c8f9398 --- /dev/null +++ b/venv/Lib/site-packages/pylint/pyreverse/__pycache__/main.cpython-37.pyc diff --git a/venv/Lib/site-packages/pylint/pyreverse/__pycache__/utils.cpython-37.pyc b/venv/Lib/site-packages/pylint/pyreverse/__pycache__/utils.cpython-37.pyc Binary files differnew file mode 100644 index 0000000..1711f15 --- /dev/null +++ b/venv/Lib/site-packages/pylint/pyreverse/__pycache__/utils.cpython-37.pyc diff --git a/venv/Lib/site-packages/pylint/pyreverse/__pycache__/vcgutils.cpython-37.pyc b/venv/Lib/site-packages/pylint/pyreverse/__pycache__/vcgutils.cpython-37.pyc Binary files differnew file mode 100644 index 0000000..f1a93f5 --- /dev/null +++ b/venv/Lib/site-packages/pylint/pyreverse/__pycache__/vcgutils.cpython-37.pyc diff --git a/venv/Lib/site-packages/pylint/pyreverse/__pycache__/writer.cpython-37.pyc b/venv/Lib/site-packages/pylint/pyreverse/__pycache__/writer.cpython-37.pyc Binary files differnew file mode 100644 index 0000000..a0ac15c --- /dev/null +++ b/venv/Lib/site-packages/pylint/pyreverse/__pycache__/writer.cpython-37.pyc diff --git a/venv/Lib/site-packages/pylint/pyreverse/diadefslib.py b/venv/Lib/site-packages/pylint/pyreverse/diadefslib.py new file mode 100644 index 0000000..de4e9fd --- /dev/null +++ b/venv/Lib/site-packages/pylint/pyreverse/diadefslib.py @@ -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 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 diff --git a/venv/Lib/site-packages/pylint/pyreverse/diagrams.py b/venv/Lib/site-packages/pylint/pyreverse/diagrams.py new file mode 100644 index 0000000..b53b845 --- /dev/null +++ b/venv/Lib/site-packages/pylint/pyreverse/diagrams.py @@ -0,0 +1,268 @@ +# 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.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") diff --git a/venv/Lib/site-packages/pylint/pyreverse/inspector.py b/venv/Lib/site-packages/pylint/pyreverse/inspector.py new file mode 100644 index 0000000..702b108 --- /dev/null +++ b/venv/Lib/site-packages/pylint/pyreverse/inspector.py @@ -0,0 +1,357 @@ +# -*- 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... +""" +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: + 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 diff --git a/venv/Lib/site-packages/pylint/pyreverse/main.py b/venv/Lib/site-packages/pylint/pyreverse/main.py new file mode 100644 index 0000000..652b954 --- /dev/null +++ b/venv/Lib/site-packages/pylint/pyreverse/main.py @@ -0,0 +1,214 @@ +# 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> +""" +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:]) diff --git a/venv/Lib/site-packages/pylint/pyreverse/utils.py b/venv/Lib/site-packages/pylint/pyreverse/utils.py new file mode 100644 index 0000000..5a1e7e2 --- /dev/null +++ b/venv/Lib/site-packages/pylint/pyreverse/utils.py @@ -0,0 +1,220 @@ +# 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 +""" +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 = 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 diff --git a/venv/Lib/site-packages/pylint/pyreverse/vcgutils.py b/venv/Lib/site-packages/pylint/pyreverse/vcgutils.py new file mode 100644 index 0000000..89c6911 --- /dev/null +++ b/venv/Lib/site-packages/pylint/pyreverse/vcgutils.py @@ -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] diff --git a/venv/Lib/site-packages/pylint/pyreverse/writer.py b/venv/Lib/site-packages/pylint/pyreverse/writer.py new file mode 100644 index 0000000..609b1ef --- /dev/null +++ b/venv/Lib/site-packages/pylint/pyreverse/writer.py @@ -0,0 +1,213 @@ +# -*- 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.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: + args = [arg.name for arg in func.args.args if arg.name != "self"] + 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() |