plantuml_rst.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. #!/usr/bin/env python
  2. """Custom reST_ directive for plantuml_ integration.
  3. Adapted from ditaa_rst plugin.
  4. .. _reST: http://docutils.sourceforge.net/rst.html
  5. .. _plantuml: http://plantuml.sourceforge.net/
  6. """
  7. import os
  8. import tempfile
  9. from zlib import adler32
  10. from subprocess import Popen, PIPE
  11. from docutils.nodes import image, literal_block
  12. from docutils.parsers.rst import Directive, directives
  13. from docutils import utils, nodes
  14. from pelican import logger, signals
  15. global_siteurl = ""
  16. class PlantUML(Directive):
  17. required_arguments = 0
  18. optional_arguments = 0
  19. has_content = True
  20. global global_siteurl
  21. option_spec = {
  22. 'class' : directives.class_option,
  23. 'alt' : directives.unchanged,
  24. 'format': directives.unchanged,
  25. }
  26. def run(self):
  27. source = self.state_machine.input_lines.source(self.lineno - self.state_machine.input_offset - 1)
  28. source_dir = os.path.dirname(os.path.abspath(source))
  29. source_dir = utils.relative_path(None, source_dir)
  30. path = os.path.abspath(os.path.join('output', 'images'))
  31. if not os.path.exists(path):
  32. os.makedirs(path)
  33. nodes = []
  34. body = '\n'.join(self.content)
  35. tf = tempfile.NamedTemporaryFile(delete=True)
  36. tf.write('@startuml\n')
  37. tf.write(body.encode('utf8'))
  38. tf.write('\n@enduml')
  39. tf.flush()
  40. imgformat = self.options.get('format', 'png')
  41. if imgformat == 'png':
  42. imgext = ".png"
  43. outopt = "-tpng"
  44. elif imgformat == 'svg':
  45. imgext = ".svg"
  46. outopt = "-tsvg"
  47. else:
  48. logger.error("Bad uml image format: "+imgformat)
  49. # make a name
  50. name = tf.name+imgext
  51. alt = self.options.get('alt', 'uml diagram')
  52. classes = self.options.pop('class', ['uml'])
  53. cmdline = ['plantuml', '-o', path, outopt, tf.name ]
  54. try:
  55. p = Popen(cmdline, stdout=PIPE, stderr=PIPE)
  56. out, err = p.communicate()
  57. except Exception, exc:
  58. error = self.state_machine.reporter.error(
  59. 'Failed to run plantuml: %s' % (exc, ),
  60. literal_block(self.block_text, self.block_text),
  61. line=self.lineno)
  62. nodes.append(error)
  63. else:
  64. if p.returncode == 0:
  65. # renaming output image using an hash code, just to not pullate
  66. # output directory with a growing number of images
  67. name = os.path.join(path, os.path.basename(name))
  68. newname = os.path.join(path, "%08x" % (adler32(body) & 0xffffffff))+imgext
  69. try: # for Windows
  70. os.remove(newname)
  71. except Exception, exc:
  72. logger.debug('File '+newname+' does not exist, not deleted')
  73. os.rename(name, newname)
  74. url = global_siteurl + '/images/' + os.path.basename(newname)
  75. imgnode = image(uri=url, classes=classes, alt=alt)
  76. nodes.append(imgnode)
  77. else:
  78. error = self.state_machine.reporter.error(
  79. 'Error in "%s" directive: %s' % (self.name, err),
  80. literal_block(self.block_text, self.block_text),
  81. line=self.lineno)
  82. nodes.append(error)
  83. return nodes
  84. def custom_url(generator, metadata):
  85. global global_siteurl
  86. global_siteurl = generator.settings['SITEURL']
  87. def register():
  88. """Plugin registration."""
  89. signals.article_generator_context.connect(custom_url)
  90. directives.register_directive('uml', PlantUML)