incorporeal-cms/incorporealcms/mdx/pydot.py
Brian S. Stephan 502adac89c
make the pydot syntax backwards compatible with code fences
without this, the code fence parser was getting thrown off if you didn't
have the pydot extension loaded, which was thwarting backwards
compatibility. this makes the pydot bits look like an attribute to the
vanilla parser, so at least then the vanilla markdown renders as
intended
2022-04-19 08:41:59 -05:00

55 lines
1.9 KiB
Python

"""Serve dot diagrams inline."""
import base64
import logging
import re
import markdown
import pydot
logger = logging.getLogger(__name__)
class InlinePydot(markdown.Extension):
"""Wrap the markdown prepcoressor."""
def extendMarkdown(self, md):
"""Add InlinePydotPreprocessor to the Markdown instance."""
md.preprocessors.register(InlinePydotPreprocessor(md), 'dot_block', 100)
class InlinePydotPreprocessor(markdown.preprocessors.Preprocessor):
"""Identify dot codeblocks and run them through pydot."""
BLOCK_RE = re.compile(r'~~~{\s+pydot:(?P<filename>[^\s]+)\s+}\n(?P<content>.*?)~~~', re.DOTALL)
def run(self, lines):
"""Match and generate diagrams from dot code blocks."""
text = '\n'.join(lines)
out = text
for block_match in self.BLOCK_RE.finditer(text):
filename = block_match.group(1)
dot_string = block_match.group(2)
logger.debug("matched markdown block: %s", dot_string)
logger.debug("match start/end: %s/%s", block_match.start(), block_match.end())
# use pydot to turn the text into pydot
graphs = pydot.graph_from_dot_data(dot_string)
if not graphs:
logger.debug("some kind of issue with parsed 'dot' %s", dot_string)
raise ValueError("error parsing dot text!")
# encode the image and provide as an inline image in markdown
encoded_image = base64.b64encode(graphs[0].create_png()).decode('ascii')
data_path = f'data:image/png;base64,{encoded_image}'
inline_image = f'![{filename}]({data_path})'
# replace the image in the output markdown
out = out.replace(block_match.group(0), inline_image)
return out.split('\n')
def makeExtension(*args, **kwargs):
"""Provide the extension to the markdown extension loader."""
return InlinePydot(*args, **kwargs)