from collections import namedtuple, OrderedDict
from markdown.extensions import Extension
from markdown.blockprocessors import BlockProcessor
-from markdown.inlinepatterns import ImageInlineProcessor, IMAGE_LINK_RE
+from markdown.inlinepatterns import (
+ SimpleTextInlineProcessor,
+ ImageInlineProcessor,
+ IMAGE_LINK_RE,
+)
from email.utils import make_msgid
from urllib import request
class File:
-
class Op(enum.Enum):
R = enum.auto()
W = enum.auto()
if content and not re.search(r"[r+]", mode):
raise RuntimeError("Cannot specify content without read mode")
- self._cache = {
- File.Op.R: [content] if content else [],
- File.Op.W: []
- }
+ self._cache = {File.Op.R: [content] if content else [], File.Op.W: []}
self._lastop = None
self._mode = mode
self._kwargs = kwargs
return self._file.read()
def write(self, s, *, cache=True):
-
if self._lastop == File.Op.R:
try:
self._file.seek(0)
)
+# [ FORMAT=FLOWED HANDLING ] ##################################################
+
+
+class FormatFlowedNewlineExtension(Extension):
+ FFNL_RE = r"(?!\S)(\s)\n"
+
+ def extendMarkdown(self, md):
+ ffnl = SimpleTextInlineProcessor(self.FFNL_RE)
+ md.inlinePatterns.register(ffnl, "ffnl", 125)
+
+
# [ QUOTE HANDLING ] ##########################################################
class QuoteToAdmonitionExtension(Extension):
- class EmailQuoteBlockProcessor(BlockProcessor):
+ class BlockProcessor(BlockProcessor):
RE = re.compile(r"(?:^|\n)>\s*(.*)")
def __init__(self, parser):
admonition[0].set("class", "admonition-title")
with self.disable():
- self.parser.parseChunk(
- admonition, "\n".join(quotelines)
- )
+ self.parser.parseChunk(admonition, "\n".join(quotelines))
@contextmanager
def disable(self):
def extendMarkdown(self, md):
md.registerExtension(self)
- email_quote_proc = self.EmailQuoteBlockProcessor(md.parser)
+ email_quote_proc = self.BlockProcessor(md.parser)
md.parser.blockprocessors.register(email_quote_proc, "emailquote", 25)
] = _CODEHILITE_CLASS
extensions = extensions or []
+ extensions.append(FormatFlowedNewlineExtension())
extensions.append(QuoteToAdmonitionExtension())
draft = draft_f.read()
with (
File() as draft_f,
File(mode="w") as cmd_f,
- File(content=css) as css_f
+ File(content=css) as css_f,
):
do_massage(
draft_f=draft_f,
"This is the plain-text version",
)
htmlsig = "HTML Signature from {path} but as a string"
- html = (
- f'<div id="signature"><p>{htmlsig.format(path=fakepath2)}</p></div>'
- )
+ html = f'<div id="signature"><p>{htmlsig.format(path=fakepath2)}</p></div>'
sig_f = fakefilefactory(fakepath2, content=html)
== mailparts[-2]
)
+ @pytest.mark.converter
+ def test_converter_format_flowed_with_nl2br(
+ self, fakepath, fakefilefactory
+ ):
+ mailparts = (
+ "This is format=flowed text ",
+ "with spaces at the end ",
+ "and there ought be no newlines.",
+ "",
+ "[link](https://example.org) ",
+ "and text.",
+ "",
+ "[link text ",
+ "broken up](https://example.org).",
+ "",
+ "This is on a new line with a hard break ",
+ "due to the double space",
+ )
+ with fakefilefactory(
+ fakepath, content="\n".join(mailparts)
+ ) as draft_f:
+ convert_markdown_to_html(
+ draft_f, extensions=["nl2br"], filefactory=fakefilefactory
+ )
+
+ soup = bs4.BeautifulSoup(
+ fakefilefactory[fakepath.with_suffix(".html")].read(),
+ "html.parser",
+ )
+ import ipdb
+
+ p = soup.p.extract().text
+ assert "".join(mailparts[0:3]) == p
+ p = ''.join(map(str, soup.p.extract().contents))
+ assert p == '<a href="https://example.org">link</a> and text.'
+ p = ''.join(map(str, soup.p.extract().contents))
+ assert (
+ p == '<a href="https://example.org">link text broken up</a>.'
+ )
+
@pytest.mark.fileio
def test_file_class_contextmanager(self, const1, monkeypatch):
state = dict(o=False, c=False)