aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/doc/inheritance_graph.py
blob: 00e0ac4867414cd3f228aa830ae6463aa52b3aea (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

import sys

from import_inheritance import (get_inheritance_entries_by_import)
from json_inheritance import (is_inheritance_from_json_enabled,
                              get_inheritance_entries_from_json)


TEST_DRIVER_USAGE = """Usage: inheritance_graph.py [module] [class]

Example:
python inheritance_graph.py  PySide6.QtWidgets PySide6.QtWidgets.QWizard
"""


def format_dict(d):
    """Format the URL dict for error message."""
    result = '{'
    n = 0
    for k, v in d.items():
        n += 1
        if n > 10:
            result += "..."
            break
        if n > 1:
            result += ", "
        result += f'"{k}": "{v}"'
    result += '}'
    return result


class InheritanceGraph(object):
    """
    Given a list of classes, determines the set of classes that they inherit
    from all the way to the root "object", and then is able to generate a
    graphviz dot graph from them.
    """
    def __init__(self, class_names, currmodule, builtins=None, show_builtins=False, parts=0):
        """
        *class_names* is a list of child classes to show bases from.

        If *show_builtins* is True, then Python builtins will be shown
        in the graph.
        """
        self.class_names = class_names
        if is_inheritance_from_json_enabled():
            self.class_info = get_inheritance_entries_from_json(class_names)
        else:
            self.class_info = get_inheritance_entries_by_import(class_names,
                                                                currmodule,
                                                                builtins,
                                                                show_builtins,
                                                                parts)

    def get_all_class_names(self):
        """
        Get all of the class names involved in the graph.
        """
        return [fullname for (_, fullname, _) in self.class_info]

    # These are the default attrs for graphviz
    default_graph_attrs = {
        'rankdir': 'LR',
        'size': '"8.0, 12.0"',
    }
    default_node_attrs = {
        'shape': 'box',
        'fontsize': 10,
        'height': 0.25,
        'fontname': '"Vera Sans, DejaVu Sans, Liberation Sans, '
                    'Arial, Helvetica, sans"',
        'style': '"setlinewidth(0.5)"',
    }
    default_edge_attrs = {
        'arrowsize': 0.5,
        'style': '"setlinewidth(0.5)"',
    }

    def _format_node_attrs(self, attrs):
        return ','.join([f'{x[0]}={x[1]}' for x in attrs.items()])

    def _format_graph_attrs(self, attrs):
        return ''.join([f"{x[0]}={x[1]};\n" for x in attrs.items()])

    def generate_dot(self, name, urls={}, env=None,
                     graph_attrs={}, node_attrs={}, edge_attrs={}):
        """
        Generate a graphviz dot graph from the classes that
        were passed in to __init__.

        *name* is the name of the graph.

        *urls* is a dictionary mapping class names to HTTP URLs.

        *graph_attrs*, *node_attrs*, *edge_attrs* are dictionaries containing
        key/value pairs to pass on as graphviz properties.
        """
        g_attrs = self.default_graph_attrs.copy()
        n_attrs = self.default_node_attrs.copy()
        e_attrs = self.default_edge_attrs.copy()
        g_attrs.update(graph_attrs)
        n_attrs.update(node_attrs)
        e_attrs.update(edge_attrs)
        if env:
            g_attrs.update(env.config.inheritance_graph_attrs)
            n_attrs.update(env.config.inheritance_node_attrs)
            e_attrs.update(env.config.inheritance_edge_attrs)

        res = []
        res.append(f'digraph {name} {{\n')
        res.append(self._format_graph_attrs(g_attrs))

        for name, fullname, bases in self.class_info:
            # Write the node
            this_node_attrs = n_attrs.copy()
            url = urls.get(fullname)
            if url is not None:
                this_node_attrs['URL'] = f'"{url}"'
                this_node_attrs['target'] = '"_top"'  # Browser target frame attribute (same page)
            else:
                urls_str = format_dict(urls)
                print(f'inheritance_graph.py: No URL found for {name} ({fullname}) in {urls_str}.',
                      file=sys.stderr)
            attribute = self._format_node_attrs(this_node_attrs)
            res.append(f'  "{name}" [{attribute}];\n')

            # Write the edges
            for base_name in bases:
                attribute = self._format_node_attrs(e_attrs)
                res.append(f'  "{base_name}" -> "{name}" [{attribute}];\n')
        res.append('}\n')
        return ''.join(res)


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(TEST_DRIVER_USAGE)
        sys.exit(-1)
    module = sys.argv[1]
    class_names = sys.argv[2:]
    graph = InheritanceGraph(class_names, module)
    dot = graph.generate_dot("test")
    print(dot)