31
32
The graph is inserted as a PNG+image map into HTML and a PDF in
34
:copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
35
:copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
36
:copyright: Copyright 2010-2011 by the PySide team.
35
37
:license: BSD, see LICENSE for details.
48
49
from docutils import nodes
49
50
from docutils.parsers.rst import directives
51
from sphinx.roles import xfileref_role
52
52
from sphinx.ext.graphviz import render_dot_html, render_dot_latex
53
53
from sphinx.util.compat import Directive
68
68
from all the way to the root "object", and then is able to generate a
69
69
graphviz dot graph from them.
71
def __init__(self, class_names, currmodule, show_builtins=False):
71
def __init__(self, class_names, currmodule, show_builtins=False, parts=0):
73
73
*class_names* is a list of child classes to show bases from.
78
78
self.class_names = class_names
79
self.classes = self._import_classes(class_names, currmodule)
80
self.all_classes = self._all_classes(self.classes)
81
if len(self.all_classes) == 0:
79
classes = self._import_classes(class_names, currmodule)
80
self.class_info = self._class_info(classes, show_builtins, parts)
81
if not self.class_info:
82
82
raise InheritanceException('No classes found for '
83
83
'inheritance diagram')
84
self.show_builtins = show_builtins
86
85
def _import_class_or_module(self, name, currmodule):
91
90
path, base = class_sig_re.match(name).groups()
91
except (AttributeError, ValueError):
93
92
raise InheritanceException('Invalid class or module %r specified '
94
93
'for inheritance diagram' % name)
99
98
# two possibilities: either it is a module, then import it
101
module = __import__(fullname)
102
101
todoc = sys.modules[fullname]
103
102
except ImportError:
104
# else it is a class, then import the module
107
# try the current module
110
raise InheritanceException(
111
'Could not import class %r specified for '
112
'inheritance diagram' % base)
114
module = __import__(path)
115
todoc = getattr(sys.modules[path], base)
104
__import__(currmodule)
105
todoc = sys.modules[currmodule]
106
for attr in name.split('.'):
107
todoc = getattr(todoc, attr)
116
108
except (ImportError, AttributeError):
117
109
raise InheritanceException(
118
110
'Could not import class or module %r specified for '
119
'inheritance diagram' % (path + '.' + base))
111
'inheritance diagram' % (currmodule + '.' + name))
121
113
# If a class, just return it
122
114
if inspect.isclass(todoc):
131
123
'not a class or module' % name)
133
125
def _import_classes(self, class_names, currmodule):
135
Import a list of classes.
126
"""Import a list of classes."""
138
128
for name in class_names:
139
129
classes.extend(self._import_class_or_module(name, currmodule))
142
def _all_classes(self, classes):
144
Return a list of all classes that are ancestors of *classes*.
132
def _class_info(self, classes, show_builtins, parts):
133
"""Return name and bases for all classes that are ancestors of
136
*parts* gives the number of dotted name parts that is removed from the
137
displayed node names.
140
builtins = __builtins__.values()
148
142
def recurse(cls):
149
all_classes[cls] = None
150
for c in cls.__bases__:
151
if c not in all_classes and c.__name__ != "BaseWrapper":
143
if not show_builtins and cls in builtins:
146
nodename = self.class_name(cls, parts)
147
fullname = self.class_name(cls, 0)
150
all_classes[cls] = (nodename, fullname, baselist)
151
for base in cls.__bases__:
152
if not show_builtins and base in builtins:
154
if base.__name__ == "Object" and base.__module__ == "Shiboken":
156
baselist.append(self.class_name(base, parts))
157
if base not in all_classes:
154
160
for cls in classes:
157
return all_classes.keys()
163
return all_classes.values()
159
165
def class_name(self, cls, parts=0):
161
Given a class object, return a fully-qualified name. This
162
works for things I've tested in matplotlib so far, but may not
163
be completely general.
166
"""Given a class object, return a fully-qualified name.
168
This works for things I've tested in matplotlib so far, but may not be
165
171
module = cls.__module__
166
172
if module == '__builtin__':
177
183
Get all of the class names involved in the graph.
179
return [self.class_name(x) for x in self.all_classes]
185
return [fullname for (_, fullname, _) in self.class_info]
181
187
# These are the default attrs for graphviz
182
188
default_graph_attrs = {
202
208
def _format_graph_attrs(self, attrs):
203
209
return ''.join(['%s=%s;\n' % x for x in attrs.items()])
205
def generate_dot(self, name, parts=0, urls={}, env=None,
211
def generate_dot(self, name, urls={}, env=None,
206
212
graph_attrs={}, node_attrs={}, edge_attrs={}):
208
214
Generate a graphviz dot graph from the classes that
230
236
res.append('digraph %s {\n' % name)
231
237
res.append(self._format_graph_attrs(g_attrs))
233
for cls in self.all_classes:
234
if not self.show_builtins and cls in __builtins__.values():
237
name = self.class_name(cls, parts)
239
for name, fullname, bases in self.class_info:
240
241
this_node_attrs = n_attrs.copy()
241
url = urls.get(self.class_name(cls))
242
url = urls.get(fullname)
242
243
if url is not None:
243
244
this_node_attrs['URL'] = '"%s"' % url
244
245
res.append(' "%s" [%s];\n' %
245
246
(name, self._format_node_attrs(this_node_attrs)))
247
248
# Write the edges
248
for base in cls.__bases__:
249
if base.__name__ == "BaseWrapper":
251
if not self.show_builtins and base in __builtins__.values():
254
base_name = self.class_name(base, parts)
249
for base_name in bases:
255
250
res.append(' "%s" -> "%s" [%s];\n' %
256
251
(base_name, name,
257
252
self._format_node_attrs(e_attrs)))
283
278
node.document = self.state.document
284
279
env = self.state.document.settings.env
285
280
class_names = self.arguments[0].split()
281
class_role = env.get_domain('py').role('class')
282
# Store the original content for use as a hash
283
node['parts'] = self.options.get('parts', 0)
284
node['content'] = ', '.join(class_names)
287
286
# Create a graph starting with the list of classes
289
graph = InheritanceGraph(class_names, env.currmodule)
288
graph = InheritanceGraph(
289
class_names, env.temp_data.get('py:module'),
290
291
except InheritanceException, err:
291
292
return [node.document.reporter.warning(err.args[0],
292
293
line=self.lineno)]
296
297
# references to real URLs later. These nodes will eventually be
297
298
# removed from the doctree after we're done with them.
298
299
for name in graph.get_all_class_names():
299
refnodes, x = xfileref_role(
300
refnodes, x = class_role(
300
301
'class', ':class:`%s`' % name, name, 0, self.state)
301
302
node.extend(refnodes)
302
303
# Store the graph object so we can use it to generate the
304
305
node['graph'] = graph
305
# Store the original content for use as a hash
306
node['parts'] = self.options.get('parts', 0)
307
node['content'] = ', '.join(class_names)
331
328
elif child.get('refid') is not None:
332
329
urls[child['reftitle']] = '#' + child.get('refid')
334
dotcode = graph.generate_dot(name, parts, urls, env=self.builder.env)
331
dotcode = graph.generate_dot(name, urls, env=self.builder.env)
335
332
render_dot_html(self, node, dotcode, [], 'inheritance', 'inheritance',
336
333
alt='Inheritance diagram of ' + node['content'])
337
334
raise nodes.SkipNode
342
339
Output the graph for LaTeX. This will insert a PDF.
344
341
graph = node['graph']
345
parts = node['parts']
347
343
graph_hash = get_graph_hash(node)
348
344
name = 'inheritance%s' % graph_hash
350
dotcode = graph.generate_dot(name, parts, env=self.builder.env,
346
dotcode = graph.generate_dot(name, env=self.builder.env,
351
347
graph_attrs={'size': '"6.0,6.0"'})
352
348
render_dot_latex(self, node, dotcode, [], 'inheritance')
353
349
raise nodes.SkipNode
363
359
inheritance_diagram,
364
360
latex=(latex_visit_inheritance_diagram, None),
365
361
html=(html_visit_inheritance_diagram, None),
367
364
app.add_directive('inheritance-diagram', InheritanceDiagram)
368
365
app.add_config_value('inheritance_graph_attrs', {}, False),
369
366
app.add_config_value('inheritance_node_attrs', {}, False),