File indexing completed on 2024-05-12 03:53:40

0001 # -*- coding: utf-8 -*-
0002 #
0003 # SPDX-FileCopyrightText: 2014 Aurélien Gâteau <agateau@kde.org>
0004 #
0005 # SPDX-License-Identifier: BSD-2-Clause
0006 
0007 import logging
0008 import itertools
0009 from functools import cmp_to_key
0010 
0011 from kapidox.depdiagram.block import Block, quote
0012 from kapidox.depdiagram.framework import Framework
0013 from kapidox.depdiagram.frameworkdb import FrameworkDb
0014 
0015 __all__ = ('generate',)
0016 
0017 ROOT_NODE_ATTRS = dict(fontsize=12, shape="box")
0018 
0019 GROUP_ATTRS = dict(style="filled", fillcolor="grey95", color="grey85")
0020 
0021 # Blocks within groups
0022 OTHER_ATTRS = dict(style="filled", fillcolor="cornsilk", color="black")
0023 
0024 QT_ATTRS = dict(style="filled", fillcolor="darkseagreen1", color="black")
0025 
0026 FW_ATTRS = dict(style="filled", fillcolor="azure", color="black")
0027 
0028 # Target blocks, used with --detailed
0029 FW_TARGET_ATTRS = dict(style="filled", fillcolor="paleturquoise")
0030 
0031 # Highlight the wanted framework, used with --framework
0032 WANTED_FW_ATTRS = dict(penwidth=2)
0033 
0034 class FrameworkCmp(object):
0035     def __init__(self, db):
0036         self.db = db
0037         self.src = set(db)
0038         self.dst = []
0039 
0040     def __call__(self, fw1, fw2):
0041         if self.depends_on(fw1, fw2):
0042             return 1
0043         if self.depends_on(fw2, fw1):
0044             return -1
0045         return 0
0046 
0047     def depends_on(self, depender_fw, provider_fw):
0048         for dep_target in depender_fw.get_all_target_dependencies():
0049             if provider_fw.has_target(dep_target):
0050                 return True
0051 
0052             try:
0053                 dep_fw = self.db.get_framework_for_target(dep_target)
0054             except KeyError:
0055                 # No framework for this target, must be an external dependency,
0056                 # carry on
0057                 continue
0058             if dep_fw != depender_fw and self.depends_on(dep_fw, provider_fw):
0059                 return True
0060 
0061         return False
0062 
0063 
0064 class DotWriter(Block):
0065     def __init__(self, db, out, wanted_fw=None, detailed=False):
0066         Block.__init__(self, out)
0067         self.db = db
0068         self.detailed = detailed
0069         self.wanted_fw = wanted_fw
0070 
0071     def write(self):
0072         with self.curly_block("digraph Root") as root:
0073             root.write_list_attrs("node", **ROOT_NODE_ATTRS)
0074 
0075             other_targets = self.db.find_external_targets()
0076             qt_targets = set([x for x in other_targets if x.startswith("Qt")])
0077             other_targets.difference_update(qt_targets)
0078 
0079             if qt_targets:
0080                 with root.cluster_block("Qt", **GROUP_ATTRS) as b:
0081                     b.write_list_attrs("node", **QT_ATTRS)
0082                     b.write_nodes(qt_targets)
0083 
0084             if other_targets:
0085                 with root.cluster_block("Others", **GROUP_ATTRS) as b:
0086                     b.write_list_attrs("node", **OTHER_ATTRS)
0087                     b.write_nodes(other_targets)
0088 
0089             lst = sorted([x for x in self.db], key=lambda x: x.tier)
0090             for tier, frameworks in itertools.groupby(lst, lambda x: x.tier):
0091                 cluster_title = "Tier {}".format(tier)
0092                 with root.cluster_block(cluster_title, **GROUP_ATTRS) as tier_block:
0093                     tier_block.write_list_attrs("node", **FW_ATTRS)
0094                     # Sort frameworks within the tier to ensure frameworks which
0095                     # depend on other frameworks from that tier are listed after
0096                     # their dependees.
0097                     frameworks = list(frameworks)
0098                     for fw in sorted(frameworks, key=cmp_to_key(FrameworkCmp(self.db))):
0099                         if self.detailed:
0100                             self.write_detailed_framework(tier_block, fw)
0101                         else:
0102                             self.write_framework(tier_block, fw)
0103 
0104     def write_framework(self, tier_block, fw):
0105         if fw == self.wanted_fw:
0106             tier_block.write_list_attrs(quote(fw.name), **WANTED_FW_ATTRS)
0107         else:
0108             tier_block.write_nodes([fw.name])
0109         edges = set([])
0110         for target in fw.get_all_target_dependencies():
0111             try:
0112                 target_fw = self.db.get_framework_for_target(target)
0113                 if fw == target_fw:
0114                     continue
0115                 dep = target_fw.name
0116             except KeyError:
0117                 dep = target
0118             edges.add((fw.name, dep))
0119         for dep_fw in fw.get_extra_frameworks():
0120             edges.add((fw.name, dep_fw))
0121         for edge in edges:
0122             tier_block.writeln('"{}" -> "{}";'.format(*edge))
0123 
0124     def write_detailed_framework(self, tier_block, fw):
0125         with tier_block.cluster_block(fw.name, **FW_ATTRS) as fw_block:
0126             if fw == self.wanted_fw:
0127                 fw_block.write_attrs(**WANTED_FW_ATTRS)
0128             fw_block.write_list_attrs("node", **FW_TARGET_ATTRS)
0129             targets = sorted(fw.get_targets())
0130             fw_block.write_nodes(targets)
0131             for target in targets:
0132                 deps = fw.get_dependencies_for_target(target)
0133                 for dep in sorted(deps):
0134                     fw_block.writeln('"{}" -> "{}";'.format(target, dep))
0135 
0136 
0137 def generate(out, dot_files, framework=None, with_qt=False, detailed=False):
0138     db = FrameworkDb()
0139     db.populate(dot_files, with_qt=with_qt)
0140 
0141     if framework:
0142         wanted_fw = db.find_by_name(framework)
0143         if wanted_fw is None:
0144             logging.error("No framework named {}.".format(framework))
0145             return False
0146         db.remove_unused_frameworks(wanted_fw)
0147     else:
0148         wanted_fw = None
0149 
0150     writer = DotWriter(db, out, wanted_fw=wanted_fw, detailed=detailed)
0151     writer.write()
0152 
0153     return True
0154 
0155 # vi: ts=4 sw=4 et