New `graphviz` extension to embed graphviz graphs.

This commit is contained in:
Georg Brandl 2009-03-04 23:52:56 +01:00
parent 1a22255080
commit 75637a5d6a
5 changed files with 264 additions and 0 deletions

View File

@ -6,6 +6,7 @@ Substantial parts of the templates were written by Armin Ronacher
Other contributors, listed alphabetically, are:
* Daniel Bültmann -- todo extension
* Charles Duffy -- original graphviz extension
* Josip Dzolonga -- coverage builder
* Horst Gutmann -- internationalization support
* Martin Hans -- autodoc improvements

View File

@ -137,6 +137,8 @@ New features added
* Extensions and API:
- New ``graphviz`` extension to embed graphviz graphs.
- Autodoc now has a reusable Python API, which can be used to
create custom types of objects to auto-document (e.g. Zope
interfaces). See also ``Sphinx.add_autodocumenter()``.

77
doc/ext/graphviz.rst Normal file
View File

@ -0,0 +1,77 @@
.. highlight:: rest
The Graphviz extension
======================
.. module:: sphinx.ext.graphviz
:synopsis: Support for Graphviz graphs.
.. versionadded:: 0.6
This extension allows you to embed `Graphviz <http://graphviz.org/>`_ graphs in
your documents.
It adds these directives:
.. directive:: graphviz
Directive to embed graphviz code. The input code for ``dot`` is given as the
content. For example::
.. graphviz::
digraph foo {
"bar" -> "baz";
}
In HTML output, the code will be rendered to a PNG image. In LaTeX output,
the code will be rendered to an embeddable PDF file.
.. directive:: graph
Directive for embedding a single undirected graph. The name is given as a
directive argument, the contents of the graph are the directive content.
This is a convenience directive to generate ``graph <name> { <content> }``.
For example::
.. graph:: foo
"bar" -- "baz";
.. directive:: digraph
Directive for embedding a single directed graph. The name is given as a
directive argument, the contents of the graph are the directive content.
This is a convenience directive to generate ``digraph <name> { <content> }``.
For example::
.. digraph:: foo
"bar" -> "baz" -> "quux";
There are also these new config values:
.. confval:: graphviz_dot
The command name with which to invoke ``dot``. The default is ``'dot'``; you
may need to set this to a full path if ``dot`` is not in the executable
search path.
Since this setting is not portable from system to system, it is normally not
useful to set it in ``conf.py``; rather, giving it on the
:program:`sphinx-build` command line via the :option:`-D` option should be
preferable, like this::
sphinx-build -b html -D graphviz_dot=C:\graphviz\bin\dot.exe . _build/html
.. confval:: graphviz_dot_args
Additional command-line arguments to give to dot, as a list. The default is
an empty list. This is the right place to set global graph, node or edge
attributes via dot's ``-G``, ``-N`` and ``-E`` options.

View File

@ -44,6 +44,8 @@ These extensions are built in and can be activated by respective entries in the
ext/doctest
ext/intersphinx
ext/math
ext/graphviz
ext/inheritance
ext/refcounting
ext/ifconfig
ext/coverage

182
sphinx/ext/graphviz.py Normal file
View File

@ -0,0 +1,182 @@
# -*- coding: utf-8 -*-
"""
sphinx.ext.graphviz
~~~~~~~~~~~~~~~~~~~
Allow graphviz-formatted graphs to be included in Sphinx-generated
documents inline.
:copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import os
import re
import sys
import posixpath
from os import path
from subprocess import Popen, PIPE
try:
from hashlib import sha1 as sha
except ImportError:
from sha import sha
from docutils import nodes
from sphinx.errors import SphinxError
from sphinx.util import ensuredir
from sphinx.util.compat import Directive
mapname_re = re.compile(r'<map id="(.*?)"')
class GraphvizError(SphinxError):
category = 'Graphviz error'
class graphviz(nodes.General, nodes.Element):
pass
class Graphviz(Directive):
"""
Directive to insert arbitrary dot markup.
"""
has_content = True
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = False
option_spec = {}
def run(self):
node = graphviz()
node['code'] = '\n'.join(self.content)
node['options'] = []
return [node]
class GraphvizSimple(Directive):
"""
Directive to insert arbitrary dot markup.
"""
has_content = True
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = False
option_spec = {}
def run(self):
node = graphviz()
node['code'] = '%s %s {\n%s\n}\n' % \
(self.name, self.arguments[0], '\n'.join(self.content))
node['options'] = []
return [node]
def render_dot(self, code, options, format, prefix='graphviz'):
"""
Render graphviz code into a PNG or PDF output file.
"""
hashkey = code.encode('utf-8') + str(options) + \
str(self.builder.config.graphviz_dot_args)
fname = '%s-%s.%s' % (prefix, sha(hashkey).hexdigest(), format)
if hasattr(self.builder, 'imgpath'):
# HTML
relfn = posixpath.join(self.builder.imgpath, fname)
outfn = path.join(self.builder.outdir, '_images', fname)
else:
# LaTeX
relfn = fname
outfn = path.join(self.builder.outdir, fname)
if path.isfile(outfn):
return relfn
if hasattr(self.builder, '_graphviz_warned_dot') or \
hasattr(self.builder, '_graphviz_warned_ps2pdf'):
return None
ensuredir(path.dirname(outfn))
dot_args = [self.builder.config.graphviz_dot]
dot_args.extend(self.builder.config.graphviz_dot_args)
dot_args.extend(options)
dot_args.extend(['-T' + format, '-o' + outfn])
if format == 'png':
dot_args.extend(['-Tcmapx', '-o%s.map' % outfn])
try:
p = Popen(dot_args, stdout=PIPE, stdin=PIPE, stderr=PIPE)
except OSError, err:
if err.errno != 2: # No such file or directory
raise
self.builder.warn('dot command %r cannot be run (needed for graphviz '
'output), check the graphviz_dot setting' %
self.builder.config.graphviz_dot)
self.builder._graphviz_warned_dot = True
return None
stdout, stderr = p.communicate(code)
if p.returncode != 0:
raise GraphvizError('dot exited with error:\n[stderr]\n%s\n'
'[stdout]\n%s' % (stderr, stdout))
return relfn
def render_dot_html(self, node, code, options, prefix='graphviz', imgcls=None):
try:
fname = render_dot(self, code, options, 'png', prefix)
except GraphvizError, exc:
self.builder.warn('dot code %r: ' % code + str(exc))
raise nodes.SkipNode
self.body.append(self.starttag(node, 'p', CLASS='graphviz'))
if fname is None:
self.body.append(self.encode(code))
else:
mapfile = open(path.join(self.builder.outdir, fname) + '.map', 'rb')
imgmap = mapfile.readlines()
mapfile.close()
imgcss = imgcls and 'class="%s"' % imgcls or ''
if len(imgmap) == 2:
# nothing in image map (the lines are <map> and </map>)
self.body.append('<img src="%s" alt="%s" %s/>\n' %
(fname, self.encode(code).strip(), imgcss))
else:
# has a map: get the name of the map and connect the parts
mapname = mapname_re.match(imgmap[0]).group(1)
self.body.append('<img src="%s" alt="%s" usemap="#%s" %s/>\n' %
(fname, self.encode(code).strip(),
mapname, imgcss))
self.body.extend(imgmap)
self.body.append('</p>\n')
raise nodes.SkipNode
def html_visit_graphviz(self, node):
render_dot_html(self, node, node['code'], node['options'])
def render_dot_latex(self, node, code, options, prefix='graphviz'):
try:
fname = render_dot(self, code, options, 'pdf', prefix)
except GraphvizError, exc:
self.builder.warn('dot code %r: ' % code + str(exc))
raise nodes.SkipNode
if fname is not None:
self.body.append('\\includegraphics{%s}' % fname)
raise nodes.SkipNode
def latex_visit_graphviz(self, node):
render_dot_latex(self, node, node['code'], node['options'])
def setup(app):
app.add_node(graphviz,
html=(html_visit_graphviz, None),
latex=(latex_visit_graphviz, None))
app.add_directive('graphviz', Graphviz)
app.add_directive('graph', GraphvizSimple)
app.add_directive('digraph', GraphvizSimple)
app.add_config_value('graphviz_dot', 'dot', 'html')
app.add_config_value('graphviz_dot_args', [], 'html')