epub2md/tests/test_codeblocks.py
dschlueter cc36372329 feat: Option --code-language für Codeblöcke ohne erkannte Sprache
Weist Blöcken ohne erkannte Sprache ein vom Nutzer gewähltes Label zu.
Aliase wie py werden normalisiert, unbekannte Werte ergeben Exit 1.
Erkannte Labels haben Vorrang; im Bericht detected_by "option"
(QA-Befund R1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:01:07 +02:00

103 lines
4.3 KiB
Python

import re
from conftest import Book, Doc
PY = """def read_book(path):
if path:
return path.read_text(encoding="utf-8") # &lt;utf-8&gt;
\treturn None"""
def fenced_blocks(text: str) -> list[tuple[str, str]]:
return re.findall(r"^(`{3,})([\w+-]*)\n(.*?)\n\1$", text, re.M | re.S)
def test_python_indentation_and_special_chars(convert):
body = f'<h1>Code</h1><pre><code class="language-python">{PY}</code></pre>'
run = convert(Book(docs=[Doc("c.xhtml", body)]))
blocks = fenced_blocks(run.chapter("001"))
assert len(blocks) == 1
fence, lang, code = blocks[0]
assert lang == "python"
assert code == PY.replace("&lt;", "<").replace("&gt;", ">")
assert "&lt;" not in code
def test_shell_code_with_pipes_backslashes_and_dollar(convert):
shell = "$ cat datei.txt | grep -v '^#' \\\n | sort &amp;&amp; echo \"$HOME\" > out.txt"
body = f"<h1>Shell</h1><pre>{shell}</pre>"
run = convert(Book(docs=[Doc("c.xhtml", body)]))
(_, lang, code), = fenced_blocks(run.chapter("001"))
assert code == "$ cat datei.txt | grep -v '^#' \\\n | sort && echo \"$HOME\" > out.txt"
assert lang == "" # no reliable language signal -> no label
def test_backticks_in_code_get_longer_fence(convert):
body = "<h1>T</h1><pre>echo ```nested``` and `x`</pre>"
run = convert(Book(docs=[Doc("c.xhtml", body)]))
(fence, _, code), = fenced_blocks(run.chapter("001"))
assert fence == "````"
assert code == "echo ```nested``` and `x`"
def test_br_lines_and_highlight_spans_in_pre(convert):
body = ('<h1>C</h1><div class="listing"><pre><span class="kw">#include</span> &lt;stdio.h&gt;<br/><br/>'
'int main(void) {<br/> printf("Hallo\\n");<br/>} </pre></div>')
run = convert(Book(docs=[Doc("c.xhtml", body)]))
(_, lang, code), = fenced_blocks(run.chapter("001"))
assert lang == "c"
assert code == '#include <stdio.h>\n\nint main(void) {\n printf("Hallo\\n");\n} '
def test_listing_div_with_span_pre(convert):
body = ('<h1>K</h1><div class="divUnProgramlisting"><span class="pre"><strong>def</strong> f(x):\n'
' <strong>return</strong> 2 * x\n\n<strong>def</strong> g(x):\n <strong>return</strong> x</span></div>')
run = convert(Book(docs=[Doc("c.xhtml", body)]))
(_, lang, code), = fenced_blocks(run.chapter("001"))
assert lang == "python"
assert code == "def f(x):\n return 2 * x\n\ndef g(x):\n return x"
def test_line_numbers_are_removed(convert):
body = '<h1>L</h1><pre><span class="lineno">1</span>a = 1<br/><span class="lineno">2</span>b = 2</pre>'
run = convert(Book(docs=[Doc("c.xhtml", body)]))
(_, _, code), = fenced_blocks(run.chapter("001"))
assert code == "a = 1\nb = 2"
def test_inline_code_variants(convert):
body = ('<h1>I</h1><p>Nutze <code>a*b_c</code>, <span class="tt">x | y</span> und <code>`tick`</code> '
'sowie <kbd>Strg</kbd>.</p>')
run = convert(Book(docs=[Doc("c.xhtml", body)]))
text = run.chapter("001")
assert "`a*b_c`" in text
assert "`x | y`" in text
assert "`` `tick` ``" in text
assert "<kbd>Strg</kbd>" in text
def test_prose_is_not_forced_into_code(convert):
body = "<h1>P</h1><p>Ein normaler Absatz mit Leerzeichen.</p>"
run = convert(Book(docs=[Doc("c.xhtml", body)]))
assert "```" not in run.chapter("001")
assert "Ein normaler Absatz mit Leerzeichen." in run.chapter("001")
def test_unlabeled_code_is_reported(convert):
run = convert(Book(docs=[Doc("c.xhtml", "<h1>U</h1><pre>foo bar</pre>")]))
assert run.report["code_blocks"]["languages"] == {"(keine)": 1}
def test_code_language_option_fills_only_unlabeled_blocks(convert):
body = "<h1>C</h1><pre>x = compute()</pre><pre>#include &lt;stdio.h&gt;\nint main(void) { return 0; }</pre>"
run = convert(Book(docs=[Doc("c.xhtml", body)]), "--code-language", "py")
langs = [lang for _, lang, _ in fenced_blocks(run.chapter("001"))]
assert langs == ["python", "c"]
by = {item["language"]: item["detected_by"] for item in run.report["code_blocks"]["items"]}
assert by == {"python": "option", "c": "content"}
def test_code_language_option_rejects_unknown_values(convert):
run = convert(Book(docs=[Doc("c.xhtml", "<h1>C</h1><pre>x</pre>")]), "--code-language", "quatsch")
assert run.rc == 1
assert "quatsch" in run.stderr