summaryrefslogtreecommitdiff
path: root/sphinx/ext/graphviz.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/ext/graphviz.py')
-rw-r--r--sphinx/ext/graphviz.py187
1 files changed, 187 insertions, 0 deletions
diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py
new file mode 100644
index 00000000..ea7b206d
--- /dev/null
+++ b/sphinx/ext/graphviz.py
@@ -0,0 +1,187 @@
+# -*- 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, outfn
+
+ 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
+ # graphviz expects UTF-8 by default
+ if isinstance(code, unicode):
+ code = code.encode('utf-8')
+ 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, outfn
+
+
+def render_dot_html(self, node, code, options, prefix='graphviz', imgcls=None):
+ try:
+ fname, outfn = 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(outfn + '.map', 'rb')
+ try:
+ imgmap = mapfile.readlines()
+ finally:
+ 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')