+# [ MARKDOWN WRAPPING ] #######################################################
+
+
+InlineImageInfo = namedtuple(
+ "InlineImageInfo", ["cid", "desc"], defaults=[None]
+)
+
+
+class InlineImageExtension(Extension):
+ class RelatedImageInlineProcessor(ImageInlineProcessor):
+ def __init__(self, re, md, ext):
+ super().__init__(re, md)
+ self._ext = ext
+
+ def handleMatch(self, m, data):
+ el, start, end = super().handleMatch(m, data)
+ if "src" in el.attrib:
+ src = el.attrib["src"]
+ if "://" not in src or src.startswith("file://"):
+ # We only inline local content
+ cid = self._ext.get_cid_for_image(el.attrib)
+ el.attrib["src"] = f"cid:{cid}"
+ return el, start, end
+
+ def __init__(self):
+ super().__init__()
+ self._images = OrderedDict()
+
+ def extendMarkdown(self, md):
+ md.registerExtension(self)
+ inline_image_proc = self.RelatedImageInlineProcessor(
+ IMAGE_LINK_RE, md, self
+ )
+ md.inlinePatterns.register(inline_image_proc, "image_link", 150)
+
+ def get_cid_for_image(self, attrib):
+ msgid = make_msgid()[1:-1]
+ path = attrib["src"]
+ if path.startswith("/"):
+ path = f"file://{path}"
+ self._images[path] = InlineImageInfo(
+ msgid, attrib.get("title", attrib.get("alt"))
+ )
+ return msgid
+
+ def get_images(self):
+ return self._images
+
+
+def markdown_with_inline_image_support(
+ text, *, extensions=None, extension_configs=None
+):
+ inline_image_handler = InlineImageExtension()
+ extensions = extensions or []
+ extensions.append(inline_image_handler)
+ mdwn = markdown.Markdown(
+ extensions=extensions, extension_configs=extension_configs
+ )
+ htmltext = mdwn.convert(text)
+
+ images = inline_image_handler.get_images()
+
+ def replace_image_with_cid(matchobj):
+ for m in (matchobj.group(1), f"file://{matchobj.group(1)}"):
+ if m in images:
+ return f"(cid:{images[m].cid}"
+ return matchobj.group(0)
+
+ text = re.sub(r"\(([^)\s]+)", replace_image_with_cid, text)
+ return text, htmltext, images
+
+
+# [ CSS STYLING ] #############################################################
+
+try:
+ import pynliner
+
+ _PYNLINER = True
+
+except ImportError:
+ _PYNLINER = False
+
+try:
+ from pygments.formatters import get_formatter_by_name
+
+ _CODEHILITE_CLASS = "codehilite"
+
+ _PYGMENTS_CSS = get_formatter_by_name(
+ "html", style="default"
+ ).get_style_defs(f".{_CODEHILITE_CLASS}")
+
+except ImportError:
+ _PYGMENTS_CSS = None
+
+
+def apply_styling(html, css):
+ return (
+ pynliner.Pynliner()
+ .from_string(html)
+ .with_cssString("\n".join(s for s in [_PYGMENTS_CSS, css] if s))
+ .run()
+ )
+
+