graphviz.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. """
  2. GraphViz Tag
  3. ---------
  4. This implements a Liquid-style graphviz tag for Pelican. You can use different
  5. Graphviz programs like dot, neato, twopi etc. [1]
  6. [1] http://www.graphviz.org/
  7. Syntax
  8. ------
  9. {% graphviz
  10. <program> {
  11. <DOT code>
  12. }
  13. %}
  14. Examples
  15. --------
  16. {% graphviz
  17. dot {
  18. digraph graphname {
  19. a -> b -> c;
  20. b -> d;
  21. }
  22. }
  23. %}
  24. {% graphviz
  25. twopi {
  26. <code goes here>
  27. }
  28. %}
  29. {% graphviz
  30. neato {
  31. <code goes here>
  32. }
  33. %}
  34. ...
  35. Output
  36. ------
  37. <span class="graphviz" style="text-align: center;"><img src="data:image/png;base64,_BASE64_IMAGE DATA_/></span>
  38. """
  39. import base64
  40. import re
  41. from .mdx_liquid_tags import LiquidTags
  42. SYNTAX = '{% dot graphviz [program] [dot code] %}'
  43. DOT_BLOCK_RE = re.compile(r'^\s*(?P<program>\w+)\s*\{\s*(?P<code>.*\})\s*\}$', re.MULTILINE | re.DOTALL)
  44. def run_graphviz(program, code, options=[], format='png'):
  45. """ Runs graphviz programs and returns image data
  46. Copied from https://github.com/tkf/ipython-hierarchymagic/blob/master/hierarchymagic.py
  47. """
  48. import os
  49. from subprocess import Popen, PIPE
  50. dot_args = [program] + options + ['-T', format]
  51. if os.name == 'nt':
  52. # Avoid opening shell window.
  53. # * https://github.com/tkf/ipython-hierarchymagic/issues/1
  54. # * http://stackoverflow.com/a/2935727/727827
  55. p = Popen(dot_args, stdout=PIPE, stdin=PIPE, stderr=PIPE, creationflags=0x08000000)
  56. else:
  57. p = Popen(dot_args, stdout=PIPE, stdin=PIPE, stderr=PIPE)
  58. wentwrong = False
  59. try:
  60. # Graphviz may close standard input when an error occurs,
  61. # resulting in a broken pipe on communicate()
  62. stdout, stderr = p.communicate(code.encode('utf-8'))
  63. except (OSError, IOError) as err:
  64. if err.errno != EPIPE:
  65. raise
  66. wentwrong = True
  67. except IOError as err:
  68. if err.errno != EINVAL:
  69. raise
  70. wentwrong = True
  71. if wentwrong:
  72. # in this case, read the standard output and standard error streams
  73. # directly, to get the error message(s)
  74. stdout, stderr = p.stdout.read(), p.stderr.read()
  75. p.wait()
  76. if p.returncode != 0:
  77. raise RuntimeError('dot exited with error:\n[stderr]\n{0}'.format(stderr.decode('utf-8')))
  78. return stdout
  79. @LiquidTags.register('graphviz')
  80. def graphviz_parser(preprocessor, tag, markup):
  81. """ Simple Graphviz parser """
  82. # Parse the markup string
  83. m = DOT_BLOCK_RE.search(markup)
  84. if m:
  85. # Get program and DOT code
  86. code = m.group('code')
  87. program = m.group('program').strip()
  88. # Run specified program with our markup
  89. output = run_graphviz(program, code)
  90. # Return Base64 encoded image
  91. return '<span class="graphviz" style="text-align: center;"><img src="data:image/png;base64,%s"></span>' % base64.b64encode(output).decode('utf-8')
  92. else:
  93. raise ValueError('Error processing input. '
  94. 'Expected syntax: {0}'.format(SYNTAX))
  95. #----------------------------------------------------------------------
  96. # This import allows image tag to be a Pelican plugin
  97. from .liquid_tags import register