|
@@ -0,0 +1,112 @@
|
|
|
|
+#!/usr/bin/env python
|
|
|
|
+"""Custom reST_ directive for plantuml_ integration.
|
|
|
|
+ Adapted from ditaa_rst plugin.
|
|
|
|
+
|
|
|
|
+.. _reST: http://docutils.sourceforge.net/rst.html
|
|
|
|
+.. _plantuml: http://plantuml.sourceforge.net/
|
|
|
|
+"""
|
|
|
|
+
|
|
|
|
+import os
|
|
|
|
+import tempfile
|
|
|
|
+from zlib import adler32
|
|
|
|
+from subprocess import Popen, PIPE
|
|
|
|
+
|
|
|
|
+from docutils.nodes import image, literal_block
|
|
|
|
+from docutils.parsers.rst import Directive, directives
|
|
|
|
+from docutils import utils, nodes
|
|
|
|
+
|
|
|
|
+from pelican import logger, signals
|
|
|
|
+
|
|
|
|
+global_siteurl = ""
|
|
|
|
+
|
|
|
|
+class PlantUML(Directive):
|
|
|
|
+ required_arguments = 0
|
|
|
|
+ optional_arguments = 0
|
|
|
|
+ has_content = True
|
|
|
|
+
|
|
|
|
+ global global_siteurl
|
|
|
|
+
|
|
|
|
+ option_spec = {
|
|
|
|
+ 'class' : directives.class_option,
|
|
|
|
+ 'alt' : directives.unchanged,
|
|
|
|
+ 'format': directives.unchanged,
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ def run(self):
|
|
|
|
+ source = self.state_machine.input_lines.source(self.lineno - self.state_machine.input_offset - 1)
|
|
|
|
+ source_dir = os.path.dirname(os.path.abspath(source))
|
|
|
|
+ source_dir = utils.relative_path(None, source_dir)
|
|
|
|
+
|
|
|
|
+ path = os.path.abspath(os.path.join('output', 'images'))
|
|
|
|
+ if not os.path.exists(path):
|
|
|
|
+ os.makedirs(path)
|
|
|
|
+
|
|
|
|
+ nodes = []
|
|
|
|
+
|
|
|
|
+ body = '\n'.join(self.content)
|
|
|
|
+ tf = tempfile.NamedTemporaryFile(delete=True)
|
|
|
|
+ tf.write('@startuml\n')
|
|
|
|
+ tf.write(body.encode('utf8'))
|
|
|
|
+ tf.write('\n@enduml')
|
|
|
|
+ tf.flush()
|
|
|
|
+
|
|
|
|
+ imgformat = self.options.get('format', 'png')
|
|
|
|
+
|
|
|
|
+ if imgformat == 'png':
|
|
|
|
+ imgext = ".png"
|
|
|
|
+ outopt = "-tpng"
|
|
|
|
+ elif imgformat == 'svg':
|
|
|
|
+ imgext = ".svg"
|
|
|
|
+ outopt = "-tsvg"
|
|
|
|
+ else:
|
|
|
|
+ logger.error("Bad uml image format: "+imgformat)
|
|
|
|
+
|
|
|
|
+ # make a name
|
|
|
|
+ name = tf.name+imgext
|
|
|
|
+
|
|
|
|
+ alt = self.options.get('alt', 'uml diagram')
|
|
|
|
+ classes = self.options.pop('class', ['uml'])
|
|
|
|
+ cmdline = ['plantuml', '-o', path, outopt, tf.name ]
|
|
|
|
+
|
|
|
|
+ try:
|
|
|
|
+ p = Popen(cmdline, stdout=PIPE, stderr=PIPE)
|
|
|
|
+ out, err = p.communicate()
|
|
|
|
+ except Exception, exc:
|
|
|
|
+ error = self.state_machine.reporter.error(
|
|
|
|
+ 'Failed to run plantuml: %s' % (exc, ),
|
|
|
|
+ literal_block(self.block_text, self.block_text),
|
|
|
|
+ line=self.lineno)
|
|
|
|
+ nodes.append(error)
|
|
|
|
+ else:
|
|
|
|
+ if p.returncode == 0:
|
|
|
|
+ # renaming output image using an hash code, just to not pullate
|
|
|
|
+ # output directory with a growing number of images
|
|
|
|
+ name = os.path.join(path, os.path.basename(name))
|
|
|
|
+ newname = os.path.join(path, "%08x" % (adler32(body) & 0xffffffff))+imgext
|
|
|
|
+
|
|
|
|
+ try: # for Windows
|
|
|
|
+ os.remove(newname)
|
|
|
|
+ except Exception, exc:
|
|
|
|
+ logger.debug('File '+newname+' does not exist, not deleted')
|
|
|
|
+
|
|
|
|
+ os.rename(name, newname)
|
|
|
|
+ url = global_siteurl + '/images/' + os.path.basename(newname)
|
|
|
|
+ imgnode = image(uri=url, classes=classes, alt=alt)
|
|
|
|
+ nodes.append(imgnode)
|
|
|
|
+ else:
|
|
|
|
+ error = self.state_machine.reporter.error(
|
|
|
|
+ 'Error in "%s" directive: %s' % (self.name, err),
|
|
|
|
+ literal_block(self.block_text, self.block_text),
|
|
|
|
+ line=self.lineno)
|
|
|
|
+ nodes.append(error)
|
|
|
|
+
|
|
|
|
+ return nodes
|
|
|
|
+
|
|
|
|
+def custom_url(generator, metadata):
|
|
|
|
+ global global_siteurl
|
|
|
|
+ global_siteurl = generator.settings['SITEURL']
|
|
|
|
+
|
|
|
|
+def register():
|
|
|
|
+ """Plugin registration."""
|
|
|
|
+ signals.article_generator_context.connect(custom_url)
|
|
|
|
+ directives.register_directive('uml', PlantUML)
|