summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorg Brandl <georg@python.org>2009-03-04 23:52:56 +0100
committerGeorg Brandl <georg@python.org>2009-03-04 23:52:56 +0100
commit2c004ae5097979e06029a7ce0df6fad6301c3874 (patch)
treefbbc316dc5e6800c0fc2f58a2d6af1547013e7a8
parente444166923eb4e5cc16d15f5ccfe84adf9d01ba4 (diff)
downloadsphinx-2c004ae5097979e06029a7ce0df6fad6301c3874.tar.gz
New ``graphviz`` extension to embed graphviz graphs.
-rw-r--r--AUTHORS1
-rw-r--r--CHANGES2
-rw-r--r--doc/ext/graphviz.rst77
-rw-r--r--doc/extensions.rst2
-rw-r--r--sphinx/ext/graphviz.py182
5 files changed, 264 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
index 0d8afda5..d4bc729f 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -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
diff --git a/CHANGES b/CHANGES
index 4ca63c28..44152b1d 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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()``.
diff --git a/doc/ext/graphviz.rst b/doc/ext/graphviz.rst
new file mode 100644
index 00000000..1d4ed807
--- /dev/null
+++ b/doc/ext/graphviz.rst
@@ -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.
diff --git a/doc/extensions.rst b/doc/extensions.rst
index 12c82da5..21ba0fd8 100644
--- a/doc/extensions.rst
+++ b/doc/extensions.rst
@@ -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
diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py
new file mode 100644
index 00000000..084797b7
--- /dev/null
+++ b/sphinx/ext/graphviz.py
@@ -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')