1 # -*- coding: utf-8 -*-
6 Allow graphviz-formatted graphs to be included in Sphinx-generated
9 :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
10 :license: BSD, see LICENSE for details.
18 from subprocess import Popen, PIPE
20 from hashlib import sha1 as sha
24 from docutils import nodes
26 from sphinx.errors import SphinxError
27 from sphinx.util import ensuredir
28 from sphinx.util.compat import Directive
31 mapname_re = re.compile(r'<map id="(.*?)"')
34 class GraphvizError(SphinxError):
35 category = 'Graphviz error'
38 class graphviz(nodes.General, nodes.Element):
42 class Graphviz(Directive):
44 Directive to insert arbitrary dot markup.
47 required_arguments = 0
48 optional_arguments = 0
49 final_argument_whitespace = False
54 node['code'] = '\n'.join(self.content)
59 class GraphvizSimple(Directive):
61 Directive to insert arbitrary dot markup.
64 required_arguments = 1
65 optional_arguments = 0
66 final_argument_whitespace = False
71 node['code'] = '%s %s {\n%s\n}\n' % \
72 (self.name, self.arguments[0], '\n'.join(self.content))
77 def render_dot(self, code, options, format, prefix='graphviz'):
79 Render graphviz code into a PNG or PDF output file.
81 hashkey = code.encode('utf-8') + str(options) + \
82 str(self.builder.config.graphviz_dot_args)
83 fname = '%s-%s.%s' % (prefix, sha(hashkey).hexdigest(), format)
84 if hasattr(self.builder, 'imgpath'):
86 relfn = posixpath.join(self.builder.imgpath, fname)
87 outfn = path.join(self.builder.outdir, '_images', fname)
91 outfn = path.join(self.builder.outdir, fname)
93 if path.isfile(outfn):
96 if hasattr(self.builder, '_graphviz_warned_dot') or \
97 hasattr(self.builder, '_graphviz_warned_ps2pdf'):
100 ensuredir(path.dirname(outfn))
102 dot_args = [self.builder.config.graphviz_dot]
103 dot_args.extend(self.builder.config.graphviz_dot_args)
104 dot_args.extend(options)
105 dot_args.extend(['-T' + format, '-o' + outfn])
107 dot_args.extend(['-Tcmapx', '-o%s.map' % outfn])
109 p = Popen(dot_args, stdout=PIPE, stdin=PIPE, stderr=PIPE)
111 if err.errno != 2: # No such file or directory
113 self.builder.warn('dot command %r cannot be run (needed for graphviz '
114 'output), check the graphviz_dot setting' %
115 self.builder.config.graphviz_dot)
116 self.builder._graphviz_warned_dot = True
118 # graphviz expects UTF-8 by default
119 if isinstance(code, unicode):
120 code = code.encode('utf-8')
121 stdout, stderr = p.communicate(code)
122 if p.returncode != 0:
123 raise GraphvizError('dot exited with error:\n[stderr]\n%s\n'
124 '[stdout]\n%s' % (stderr, stdout))
128 def render_dot_html(self, node, code, options, prefix='graphviz', imgcls=None):
130 fname, outfn = render_dot(self, code, options, 'png', prefix)
131 except GraphvizError, exc:
132 self.builder.warn('dot code %r: ' % code + str(exc))
135 self.body.append(self.starttag(node, 'p', CLASS='graphviz'))
137 self.body.append(self.encode(code))
139 mapfile = open(outfn + '.map', 'rb')
141 imgmap = mapfile.readlines()
144 imgcss = imgcls and 'class="%s"' % imgcls or ''
146 # nothing in image map (the lines are <map> and </map>)
147 self.body.append('<img src="%s" alt="%s" %s/>\n' %
148 (fname, self.encode(code).strip(), imgcss))
150 # has a map: get the name of the map and connect the parts
151 mapname = mapname_re.match(imgmap[0]).group(1)
152 self.body.append('<img src="%s" alt="%s" usemap="#%s" %s/>\n' %
153 (fname, self.encode(code).strip(),
155 self.body.extend(imgmap)
156 self.body.append('</p>\n')
160 def html_visit_graphviz(self, node):
161 render_dot_html(self, node, node['code'], node['options'])
164 def render_dot_latex(self, node, code, options, prefix='graphviz'):
166 fname, outfn = render_dot(self, code, options, 'pdf', prefix)
167 except GraphvizError, exc:
168 self.builder.warn('dot code %r: ' % code + str(exc))
171 if fname is not None:
172 self.body.append('\\includegraphics[]{%s}' % fname)
176 def latex_visit_graphviz(self, node):
177 render_dot_latex(self, node, node['code'], node['options'])
180 app.add_node(graphviz,
181 html=(html_visit_graphviz, None),
182 latex=(latex_visit_graphviz, None))
183 app.add_directive('graphviz', Graphviz)
184 app.add_directive('graph', GraphvizSimple)
185 app.add_directive('digraph', GraphvizSimple)
186 app.add_config_value('graphviz_dot', 'dot', 'html')
187 app.add_config_value('graphviz_dot_args', [], 'html')