Brian S. Stephan
502adac89c
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
55 lines
1.9 KiB
Python
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)
|