# -*- coding: utf-8 -*- """ sphinxcontrib.plantuml ~~~~~~~~~~~~~~~~~~~~~~ Embed PlantUML diagrams on your documentation. :copyright: Copyright 2010 by Yuya Nishihara . :license: BSD, see LICENSE for details. """ import os, subprocess try: from hashlib import sha1 except ImportError: # Python<2.5 from sha import sha as sha1 from docutils import nodes #+DV #from docutils.parsers.rst.directives.images import Image #-DV from docutils.parsers.rst import directives from sphinx.errors import SphinxError from sphinx.util.compat import Directive from sphinx.util.osutil import ensuredir, ENOENT class PlantUmlError(SphinxError): pass class plantuml(nodes.General, nodes.Element): pass class UmlDirective(Directive): #class UmlDirective(Image): """Directive to insert PlantUML markup Example:: .. uml:: :alt: Alice and Bob Alice -> Bob: Hello Alice <- Bob: Hi """ has_content = True option_spec = {'alt': directives.unchanged} #option_spec = Image.option_spec.copy() #required_arguments = 0 def run(self): node = plantuml() node['uml'] = '\n'.join(self.content) node['alt'] = self.options.get('alt', None) return [node] # DV - this should be generating the image and returning an image node # rather than creating a new type of node. Then customization for downstream # renderers can leverage existing support for image rendering rather # than having to customize output for each one. #refname, outfname = self._generate_name('\n'.join(self.content)) #self.options['uri'] = refname #node = nodes.image(self.block_text, self.options) #return [node] # def _generate_name(self, content): # key = sha1(content.encode('utf-8')).hexdigest() # fname = 'plantuml-%s.png' % key # imgpath = getattr(self.builder, 'imgpath', None) # if imgpath: # return ('/'.join((self.builder.imgpath, fname)), # os.path.join(self.builder.outdir, '_images', fname)) # else: # return fname, os.path.join(self.builder.outdir, fname) # # # def _generate_plantuml_args(self): # if isinstance(self.builder.config.plantuml, basestring): # args = self.builder.config.plantuml # else: # args = list(self.builder.config.plantuml) # args.extend('-pipe -charset utf-8'.split()) # return args # # # def _render_plantuml(self, refname, outfname, content): # if os.path.exists(outfname): # return refname # don't regenerate # ensuredir(os.path.dirname(outfname)) # f = open(outfname, 'wb') # try: # try: # p = subprocess.Popen(self._generate_plantuml_args(self), stdout=f, # stdin=subprocess.PIPE, stderr=subprocess.PIPE) # except OSError, err: # if err.errno != ENOENT: # raise # raise PlantUmlError('plantuml command %r cannot be run' # % self.builder.config.plantuml) # serr = p.communicate( content.encode('utf-8') )[1] # if p.returncode != 0: # raise PlantUmlError('error while running plantuml\n\n' + serr) # return refname # finally: # f.close() def generate_name(self, node): key = sha1(node['uml'].encode('utf-8')).hexdigest() fname = 'plantuml-%s.png' % key imgpath = getattr(self.builder, 'imgpath', None) if imgpath: return ('/'.join((self.builder.imgpath, fname)), os.path.join(self.builder.outdir, '_images', fname)) else: return fname, os.path.join(self.builder.outdir, fname) def generate_plantuml_args(self): if isinstance(self.builder.config.plantuml, basestring): args = self.builder.config.plantuml else: args = list(self.builder.config.plantuml) args.extend('-pipe -charset utf-8'.split()) return args def render_plantuml(self, node): refname, outfname = generate_name(self, node) if os.path.exists(outfname): return refname # don't regenerate ensuredir(os.path.dirname(outfname)) f = open(outfname, 'wb') try: try: p = subprocess.Popen(generate_plantuml_args(self), stdout=f, stdin=subprocess.PIPE, stderr=subprocess.PIPE) except OSError, err: if err.errno != ENOENT: raise raise PlantUmlError('plantuml command %r cannot be run' % self.builder.config.plantuml) serr = p.communicate(node['uml'].encode('utf-8'))[1] if p.returncode != 0: raise PlantUmlError('error while running plantuml\n\n' + serr) return refname finally: f.close() def html_visit_plantuml(self, node): try: refname = render_plantuml(self, node) except PlantUmlError, err: self.builder.warn(str(err)) raise nodes.SkipNode self.body.append(self.starttag(node, 'p', CLASS='plantuml')) self.body.append('%s\n' % (self.encode(refname), self.encode(node['alt'] or node['uml']))) self.body.append('

\n') raise nodes.SkipNode def latex_visit_plantuml(self, node): try: refname = render_plantuml(self, node) except PlantUmlError, err: self.builder.warn(str(err)) raise nodes.SkipNode self.body.append('\\includegraphics{%s}' % self.encode(refname)) raise nodes.SkipNode def setup(app): app.add_node(plantuml, html=(html_visit_plantuml, None), latex=(latex_visit_plantuml, None)) app.add_directive('uml', UmlDirective) app.add_config_value('plantuml', 'plantuml', 'html')