X-Git-Url: https://git.madduck.net/etc/neomutt.git/blobdiff_plain/9e23b25c95a897b7986281631c6f22ad69e3bc69..da64868d7919227705aa219bf25905b73e154b76:/.config/neomutt/buildmimetree.py?ds=sidebyside diff --git a/.config/neomutt/buildmimetree.py b/.config/neomutt/buildmimetree.py index 2ad671a..1ff0cb0 100755 --- a/.config/neomutt/buildmimetree.py +++ b/.config/neomutt/buildmimetree.py @@ -28,6 +28,9 @@ # - Pynliner, provides --css-file and thus inline styling of HTML output # - Pygments, then syntax highlighting for fenced code is enabled # +# Running tests: +# pytest -x buildmimetree.py +# # Latest version: # https://git.madduck.net/etc/neomutt.git/blob_plain/HEAD:/.config/neomutt/buildmimetree.py # @@ -47,6 +50,7 @@ import bs4 import xml.etree.ElementTree as etree import io import enum +from contextlib import contextmanager from collections import namedtuple, OrderedDict from markdown.extensions import Extension from markdown.blockprocessors import BlockProcessor @@ -231,10 +235,6 @@ class File: if cache and self._cache[File.Op.R]: return self._get_cache(File.Op.R) - if not self._file: - with self as f: - return f.read(cache=cache) - if self._lastop == File.Op.W: try: self._file.seek(0) @@ -251,10 +251,6 @@ class File: def write(self, s, *, cache=True): - if not self._file: - with self as f: - return f.write(s, cache=cache) - if self._lastop == File.Op.R: try: self._file.seek(0) @@ -490,8 +486,12 @@ class QuoteToAdmonitionExtension(Extension): def __init__(self, parser): super().__init__(parser) self._title = None + self._disable = False def test(self, parent, blocks): + if self._disable: + return False + if markdown.util.nearing_recursion_limit(): return False @@ -527,9 +527,16 @@ class QuoteToAdmonitionExtension(Extension): self.parser.parseChunk(admonition, self._title) admonition[0].set("class", "admonition-title") - self.parser.parseChunk( - admonition, "\n".join(self.clean(line) for line in quotelines) - ) + with self.disable(): + self.parser.parseChunk( + admonition, "\n".join(quotelines) + ) + + @contextmanager + def disable(self): + self._disable = True + yield True + self._disable = False @classmethod def clean(klass, line): @@ -644,7 +651,9 @@ def extract_signature(text, *, filefactory=FileFactory()): path = pathlib.Path(re.split(r" +", lines.pop(0), maxsplit=1)[1]) textsig = "\n".join(lines) - sig_input = filefactory(path.expanduser()).read() + with filefactory(path.expanduser()) as sig_f: + sig_input = sig_f.read() + soup = bs4.BeautifulSoup(sig_input, "html.parser") style = str(soup.style.extract()) if soup.style else "" @@ -958,14 +967,6 @@ def do_massage( # variable used to identify the command file we're currently writing # to. cmds = MuttCommands(cmd_f, debug=debug_commands) - cmds.cmd('set editor="$my_editor"') - cmds.cmd('set edit_headers="$my_edit_headers"') - cmds.cmd("unset my_editor") - cmds.cmd("unset my_edit_headers") - - # let's flush those commands, as there'll be a lot of pushes from now - # on, which need to be run in reverse order - cmds.flush() extensions = extensions.split(",") if extensions else [] tree = converter( @@ -1111,6 +1112,10 @@ def do_massage( except AttributeError: filename = "pytest_internal_file" cmds.cmd(f"source 'rm -f {filename}|'") + cmds.cmd('set editor="$my_editor"') + cmds.cmd('set edit_headers="$my_edit_headers"') + cmds.cmd("unset my_editor") + cmds.cmd("unset my_edit_headers") cmds.cmd("unset my_mdwn_postprocess_cmd_file") cmds.flush() @@ -1405,14 +1410,14 @@ try: ) lines = cmd_f.read().splitlines() - assert '="$my_editor"' in lines.pop(0) - assert '="$my_edit_headers"' in lines.pop(0) - assert "unset my_editor" == lines.pop(0) - assert "unset my_edit_headers" == lines.pop(0) assert "send-message" in lines.pop(0) assert "update-encoding" in lines.pop(0) assert "first-entry" in lines.pop(0) assert "source 'rm -f " in lines.pop(0) + assert '="$my_editor"' in lines.pop(0) + assert '="$my_edit_headers"' in lines.pop(0) + assert "unset my_editor" == lines.pop(0) + assert "unset my_edit_headers" == lines.pop(0) assert "unset my_mdwn_postprocess_cmd_file" == lines.pop(0) @pytest.mark.massage @@ -1429,7 +1434,7 @@ try: max_other_attachments=max_attachments, converter=converter, ) - lines = cmd_f.read().splitlines()[4:-2] + lines = cmd_f.read().splitlines()[:-6] assert "first-entry" in lines.pop() assert "update-encoding" in lines.pop() @@ -1471,7 +1476,7 @@ try: cmd_f=cmd_f, converter=converter, ) - lines = cmd_f.read().splitlines()[4:-2] + lines = cmd_f.read().splitlines()[:-6] assert "first-entry" in lines.pop() assert "update-encoding" in lines.pop() @@ -1541,10 +1546,10 @@ try: @pytest.mark.converter def test_converter_tree_basic(self, fakepath, const1, fakefilefactory): - draft_f = fakefilefactory(fakepath, content=const1) - tree = convert_markdown_to_html( - draft_f, filefactory=fakefilefactory - ) + with fakefilefactory(fakepath, content=const1) as draft_f: + tree = convert_markdown_to_html( + draft_f, filefactory=fakefilefactory + ) assert tree.subtype == "alternative" assert len(tree.children) == 2 @@ -1558,8 +1563,8 @@ try: def test_converter_writes( self, fakepath, fakefilefactory, const1, monkeypatch ): - draft_f = fakefilefactory(fakepath, content=const1) - convert_markdown_to_html(draft_f, filefactory=fakefilefactory) + with fakefilefactory(fakepath, content=const1) as draft_f: + convert_markdown_to_html(draft_f, filefactory=fakefilefactory) html = fakefilefactory.pop() assert fakepath.with_suffix(".html") == html[0] @@ -1753,7 +1758,6 @@ try: @pytest.mark.styling def test_massage_styling_to_converter(self): css = "p { color:red }" - css_f = File(content=css) css_applied = [] def converter(draft_f, css_f, **kwargs): @@ -1761,12 +1765,17 @@ try: css_applied.append(css) return Part("text", "plain", draft_f.path, orig=True) - do_massage( - draft_f=File(), - cmd_f=File(), - css_f=css_f, - converter=converter, - ) + with ( + File() as draft_f, + File(mode="w") as cmd_f, + File(content=css) as css_f + ): + do_massage( + draft_f=draft_f, + cmd_f=cmd_f, + css_f=css_f, + converter=converter, + ) assert css_applied[0] == css @pytest.mark.converter @@ -1835,11 +1844,10 @@ try: assert htmlsig == sigconst.format(path=fakepath) @pytest.mark.sig - def test_signature_extraction_file_not_found(self, const1): - path = pathlib.Path("/does/not/exist") + def test_signature_extraction_file_not_found(self, fakepath, const1): with pytest.raises(FileNotFoundError): origtext, textsig, htmlsig = extract_signature( - f"{const1}{EMAIL_SIG_SEP}{HTML_SIG_MARKER}{path}\n{const1}" + f"{const1}{EMAIL_SIG_SEP}{HTML_SIG_MARKER}{fakepath}\n{const1}" ) @pytest.mark.imgproc @@ -2044,6 +2052,29 @@ try: p = quote.p.extract() assert p.contents[1].name == "strong" + @pytest.mark.converter + def test_converter_attribution_to_admonition_with_blockquote( + self, fakepath, fakefilefactory + ): + mailparts = ( + "Regarding whatever", + "> blockquote line1", + "> blockquote line2", + "> ", + "> new para with **bold** text", + ) + with fakefilefactory( + fakepath, content="\n".join(mailparts) + ) as draft_f: + convert_markdown_to_html(draft_f, filefactory=fakefilefactory) + + soup = bs4.BeautifulSoup( + fakefilefactory[fakepath.with_suffix(".html")].read(), + "html.parser", + ) + quote = soup.select_one("div.admonition.quote") + assert quote.blockquote + @pytest.mark.converter def test_converter_attribution_to_admonition_multiple( self, fakepath, fakefilefactory @@ -2113,6 +2144,11 @@ try: f.write(const1, cache=False) assert f.read(cache=False) == const1 + @pytest.mark.fileio + def test_file_class_path_no_exists(self, fakepath): + with pytest.raises(FileNotFoundError): + File(fakepath, mode="r").open() + @pytest.mark.fileio def test_file_class_cache(self, tmp_path, const1, const2): path = tmp_path / "file"