├── .gitignore ├── LICENSE ├── README.md ├── mdx_inline_graphviz.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015 Steffen Prince 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Markdown Inline Graphviz 2 | ======================== 3 | 4 | A Python Markdown extension that replaces inline Graphviz definitins with 5 | inline SVGs or PNGs! 6 | 7 | Why render the graphs inline? No configuration! Works with any 8 | Python-Markdown-based static site generator, such as 9 | [MkDocs](http://www.mkdocs.org/), [Pelican](http://blog.getpelican.com/), and 10 | [Nikola](https://getnikola.com/) out of the box without configuring an output 11 | directory. 12 | 13 | # Installation 14 | 15 | $ pip install markdown-inline-graphviz 16 | 17 | # Usage 18 | 19 | Activate the `inline_graphviz` extension. For example, with Mkdocs, you add a 20 | stanza to mkdocs.yml: 21 | 22 | ``` 23 | markdown_extensions: 24 | - inline_graphviz 25 | ``` 26 | 27 | To use it in your Markdown doc: 28 | 29 | ``` 30 | {% dot attack_plan.svg 31 | digraph G { 32 | rankdir=LR 33 | Earth [peripheries=2] 34 | Mars 35 | Earth -> Mars 36 | } 37 | %} 38 | ``` 39 | 40 | Supported graphviz commands: dot, neato, fdp, sfdp, twopi, circo. 41 | 42 | # Credits 43 | 44 | Inspired by [jawher/markdown-dot](https://github.com/jawher/markdown-dot), 45 | which renders the dot graph to a file instead of inline. 46 | 47 | 48 | # License 49 | 50 | [MIT License](http://www.opensource.org/licenses/mit-license.php) 51 | -------------------------------------------------------------------------------- /mdx_inline_graphviz.py: -------------------------------------------------------------------------------- 1 | """ 2 | Graphviz extensions for Markdown. 3 | Renders the output inline, eliminating the need to configure an output 4 | directory. 5 | 6 | Supports outputs types of SVG and PNG. The output will be taken from the 7 | filename specified in the tag. Example: 8 | 9 | {% dot attack_plan.svg 10 | digraph G { 11 | rankdir=LR 12 | Earth [peripheries=2] 13 | Mars 14 | Earth -> Mars 15 | } 16 | %} 17 | 18 | Requires the graphviz library (http://www.graphviz.org/) 19 | 20 | Inspired by jawher/markdown-dot (https://github.com/jawher/markdown-dot) 21 | """ 22 | 23 | import re 24 | import markdown 25 | import subprocess 26 | import base64 27 | 28 | # Global vars 29 | BLOCK_RE = re.compile( 30 | r'^\{% (?P\w+)\s+(?P[^\s]+)\s*\n(?P.*?)%}\s*$', 31 | re.MULTILINE | re.DOTALL) 32 | # Command whitelist 33 | SUPPORTED_COMMAMDS = ['dot', 'neato', 'fdp', 'sfdp', 'twopi', 'circo'] 34 | 35 | 36 | class InlineGraphvizExtension(markdown.Extension): 37 | 38 | def extendMarkdown(self, md, md_globals): 39 | """ Add InlineGraphvizPreprocessor to the Markdown instance. """ 40 | md.registerExtension(self) 41 | 42 | md.preprocessors.add('graphviz_block', 43 | InlineGraphvizPreprocessor(md), 44 | "_begin") 45 | 46 | 47 | class InlineGraphvizPreprocessor(markdown.preprocessors.Preprocessor): 48 | 49 | def __init__(self, md): 50 | super(InlineGraphvizPreprocessor, self).__init__(md) 51 | 52 | def run(self, lines): 53 | """ Match and generate dot code blocks.""" 54 | 55 | text = "\n".join(lines) 56 | while 1: 57 | m = BLOCK_RE.search(text) 58 | if m: 59 | command = m.group('command') 60 | # Whitelist command, prevent command injection. 61 | if command not in SUPPORTED_COMMAMDS: 62 | raise Exception('Command not supported: %s' % command) 63 | filename = m.group('filename') 64 | content = m.group('content') 65 | filetype = filename[filename.rfind('.')+1:] 66 | 67 | args = [command, '-T'+filetype] 68 | try: 69 | proc = subprocess.Popen( 70 | args, 71 | stdin=subprocess.PIPE, 72 | stderr=subprocess.PIPE, 73 | stdout=subprocess.PIPE) 74 | proc.stdin.write(content.encode('utf-8')) 75 | 76 | output, err = proc.communicate() 77 | 78 | if filetype == 'svg': 79 | data_url_filetype = 'svg+xml' 80 | encoding = 'utf-8' 81 | img = output.decode(encoding) 82 | 83 | if filetype == 'png': 84 | data_url_filetype = 'png' 85 | encoding = 'base64' 86 | output = base64.b64encode(output) 87 | data_path = "data:image/%s;%s,%s" % ( 88 | data_url_filetype, 89 | encoding, 90 | output) 91 | img = "![" + filename + "](" + data_path + ")" 92 | 93 | text = '%s\n%s\n%s' % ( 94 | text[:m.start()], img, text[m.end():]) 95 | 96 | except Exception as e: 97 | err = str(e) + ' : ' + str(args) 98 | return ( 99 | '
Error : ' + err + '
' 100 | '
' + content + '
').split('\n') 101 | 102 | else: 103 | break 104 | return text.split("\n") 105 | 106 | 107 | def makeExtension(*args, **kwargs): 108 | return InlineGraphvizExtension(*args, **kwargs) 109 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from setuptools import setup 4 | 5 | VERSION = '1.0' 6 | 7 | if sys.argv[-1] == 'publish': 8 | if os.system("pip freeze | grep wheel"): 9 | print("wheel not installed.\nUse `pip install wheel`.\nExiting.") 10 | sys.exit() 11 | if os.system("pip freeze | grep twine"): 12 | print("twine not installed.\nUse `pip install twine`.\nExiting.") 13 | sys.exit() 14 | os.system("python setup.py sdist bdist_wheel") 15 | os.system("twine upload dist/*") 16 | print("You probably want to also tag the version now:") 17 | print(" git tag -a {0} -m 'version {0}'".format(VERSION)) 18 | print(" git push --tags") 19 | sys.exit() 20 | 21 | setup( 22 | name="Markdown Inline Graphviz Extension", 23 | version=VERSION, 24 | py_modules=["mdx_inline_graphviz"], 25 | install_requires=['Markdown>=2.3.1'], 26 | author="Steffen Prince", 27 | author_email="steffen@sprin.io", 28 | description="Render inline graphs with Markdown and Graphviz", 29 | license="MIT", 30 | url="https://github.com/sprin/markdown-inline-graphviz", 31 | classifiers=[ 32 | 'Development Status :: 5 - Production/Stable', 33 | 34 | 'Intended Audience :: Developers', 35 | 'Topic :: Documentation', 36 | 'Topic :: Text Processing', 37 | 38 | 'License :: OSI Approved :: MIT License', 39 | 40 | # Specify the Python versions you support here. In particular, ensure 41 | # that you indicate whether you support Python 2, Python 3 or both. 42 | 'Programming Language :: Python :: 2', 43 | 'Programming Language :: Python :: 2.7', 44 | 'Programming Language :: Python :: 3', 45 | 'Programming Language :: Python :: 3.4', 46 | 'Programming Language :: Python :: 3.5', 47 | ], 48 | ) 49 | --------------------------------------------------------------------------------