import re from conftest import Book, Doc PY = """def read_book(path): if path: return path.read_text(encoding="utf-8") # <utf-8> \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'

Code

{PY}
' 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("<", "<").replace(">", ">") assert "<" not in code def test_shell_code_with_pipes_backslashes_and_dollar(convert): shell = "$ cat datei.txt | grep -v '^#' \\\n | sort && echo \"$HOME\" > out.txt" body = f"

Shell

{shell}
" 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 = "

T

echo ```nested``` and `x`
" 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 = ('

C

#include <stdio.h>

' 'int main(void) {
printf("Hallo\\n");
}
') run = convert(Book(docs=[Doc("c.xhtml", body)])) (_, lang, code), = fenced_blocks(run.chapter("001")) assert lang == "c" assert code == '#include \n\nint main(void) {\n printf("Hallo\\n");\n} ' def test_listing_div_with_span_pre(convert): body = ('

K

def f(x):\n' ' return 2 * x\n\ndef g(x):\n return x
') 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 = '

L

1a = 1
2b = 2
' 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 = ('

I

Nutze a*b_c, x | y und `tick` ' 'sowie Strg.

') 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 "Strg" in text def test_prose_is_not_forced_into_code(convert): body = "

P

Ein normaler Absatz mit Leerzeichen.

" 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", "

U

foo bar
")])) assert run.report["code_blocks"]["languages"] == {"(keine)": 1} def test_code_language_option_fills_only_unlabeled_blocks(convert): body = "

C

x = compute()
#include <stdio.h>\nint main(void) { return 0; }
" 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", "

C

x
")]), "--code-language", "quatsch") assert run.rc == 1 assert "quatsch" in run.stderr def test_print_wrap_markers_are_joined(convert): body = ("

W

    return (s * (s -\n    ↪ distance(a, b)) *\n        s)\n"
            "x = ['a', 'b',\n↪ 'c']
") run = convert(Book(docs=[Doc("c.xhtml", body)])) (_, _, code), = fenced_blocks(run.chapter("001")) assert code == " return (s * (s - distance(a, b)) *\n s)\nx = ['a', 'b', 'c']" item = run.report["code_blocks"]["items"][0] assert item["wrap_lines_joined"] == 2 def test_print_wrap_markers_can_be_kept(convert): body = "

W

a = (1,\n↪ 2)
" run = convert(Book(docs=[Doc("c.xhtml", body)]), "--keep-wrap-markers") (_, _, code), = fenced_blocks(run.chapter("001")) assert code == "a = (1,\n↪ 2)" def test_printed_line_numbers_do_not_hide_the_language(convert): listing = ("00 // Kapitel1/hallo.c\n01 #include <stdio.h>\n \n02 int main(void) {\n" "03 printf(\"Hallo\\n\");\n04 return 0;\n05 }") run = convert(Book(docs=[Doc("c.xhtml", f"

N

{listing}
")])) (_, lang, code), = fenced_blocks(run.chapter("001")) assert lang == "c" assert code.startswith("00 // Kapitel1/hallo.c\n01 #include ") # numbers stay in the block def test_python_is_recognized_by_parsing(convert): blocks = ["from collections import ChainMap\n\nchain = ChainMap({}, {})\nprint(chain)", "zahl = -1\nif zahl < 0:\n print(\"negativ\")", ">>> 2 + 3\n5", "name: epub2md\nversion: 1", # YAML is valid Python syntax, but no Python code "[1, 4, 9]", # program output "x = compute()"] # Python, but too weak without book context body = "

P

" + "".join(f"
{b}
" for b in blocks) run = convert(Book(docs=[Doc("c.xhtml", body)])) langs = [lang for _, lang, _ in fenced_blocks(run.chapter("001"))] assert langs == ["python", "python", "python", "", "", ""] def test_weak_signals_follow_the_book_language(convert): c_listing = "#include <stdio.h>\nint main(void) { return 0; }" snippets = ["char ch = 65; // 'A'", "if (a || b) {\n x();\n}", "int remove(const char *pathname);", "struct node {\n int value;\n};", "Ergebnis: 12345", "|00003.14|", "gcc -Wall -o prog prog.c", "Dezimal: 65; Zeichen: A"] body = "

C

" + "".join(f"
{c_listing}
" for _ in range(5)) body += "".join(f"
{s}
" for s in snippets) run = convert(Book(docs=[Doc("c.xhtml", body)])) langs = [lang for _, lang, _ in fenced_blocks(run.chapter("001"))][5:] assert langs == ["c", "c", "c", "c", "", "", "", ""] by = [item["detected_by"] for item in run.report["code_blocks"]["items"]][5:9] assert by == ["book-context"] * 4 def test_python_book_labels_assignments_but_not_output(convert): py = "def f(x):\n return x" snippets = ["foo = decorate(foo)", "random.sample(population, k)", "[x * x for x in range(5)]", "True", "('A', 'B', 'C')", "Hallo Welt", "4\n3\nnohtyP", "Jane\n42\nPerson(name='Jane')"] body = "

P

" + "".join(f"
{py}
" for _ in range(5)) body += "".join(f"
{s}
" for s in snippets) run = convert(Book(docs=[Doc("c.xhtml", body)])) langs = [lang for _, lang, _ in fenced_blocks(run.chapter("001"))][5:] assert langs == ["python", "python", "python", "", "", "", "", ""] def test_mixed_book_gets_no_context_labels(convert): body = "

M

" + "
def f(x):\n    return x
" * 3 body += "
#include <stdio.h>\nint main(void) { return 0; }
" * 3 + "
x = compute();
" run = convert(Book(docs=[Doc("c.xhtml", body)])) assert fenced_blocks(run.chapter("001"))[-1][1] == ""