feat: epub2md_merge.py – Gesamtdatei aus einer epub2md-Ausgabe
Fügt die Kapiteldateien in Lesereihenfolge (manifest.json) zu einer Markdown-Datei mit dem Original-EPUB-Namen (.md) zusammen. Dabei werden: - Links zwischen Kapiteln zu Sprunglinks in der Gesamtdatei, - kollidierende Anker pro Kapitel umbenannt, - Medienpfade relativ zum Ziel umgeschrieben, - doppelte Fußnotendefinitionen entfernt, - Code nie verändert. Optionen -o, --toc, --no-frontmatter und --force; abschließender Linkcheck. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0dad1f7b2d
commit
88888e5587
1 changed files with 448 additions and 0 deletions
73
tests/test_merge.py
Normal file
73
tests/test_merge.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import re
|
||||
|
||||
import yaml
|
||||
|
||||
from conftest import PNG, Book, Doc
|
||||
from epub2md.linkcheck import anchors_in, check_links
|
||||
from epub2md.merge import main as merge_main
|
||||
|
||||
A = ('<h1 id="sec">Kapitel A</h1><p>Siehe <a href="b.xhtml#sec">B</a>, <a href="b.xhtml">Kapitel B</a>, '
|
||||
'<a href="#sec">hier</a> und Anmerkung<a epub:type="noteref" href="b.xhtml#n1">1</a>.</p>'
|
||||
'<p><img src="img/a.png" alt="Bild"/></p>'
|
||||
'<p><a href="b.xhtml"><img src="img/a.png" alt="Bildlink"/></a></p>'
|
||||
'<pre>[kein](link.md) id="sec" `code`</pre>')
|
||||
B = ('<h1 id="sec">Kapitel B</h1><p>Zurück zu <a href="a.xhtml#sec">A</a>, nochmal Anmerkung'
|
||||
'<a epub:type="noteref" href="#n1">1</a>.</p>'
|
||||
'<aside epub:type="footnote" id="n1"><p>Gemeinsame Anmerkung.</p></aside>')
|
||||
|
||||
|
||||
def book() -> Book:
|
||||
return Book(title="Merge-Test", docs=[Doc("a.xhtml", A), Doc("b.xhtml", B)], media={"img/a.png": PNG})
|
||||
|
||||
|
||||
def test_merge_complete_book(convert, tmp_path):
|
||||
run = convert(book())
|
||||
assert merge_main([str(run.out)]) == 0
|
||||
target = run.out / f"{run.report['input']['name'][:-5]}.md"
|
||||
text = target.read_text(encoding="utf-8")
|
||||
# one book-level frontmatter, no chapter frontmatter
|
||||
fm = yaml.safe_load(text.split("---\n")[1])
|
||||
assert fm["title"] == "Merge-Test" and fm["author"] == ["Erika Musterfrau"] and fm["lang"] == "de"
|
||||
assert text.count("chapter_number") == 0
|
||||
# reading order
|
||||
assert text.index("# Kapitel A") < text.index("# Kapitel B")
|
||||
# links between chapters point into the merged file; colliding id "sec" renamed in chapter B
|
||||
assert '<a id="sec"></a>' in text and '<a id="c002-sec"></a>' in text
|
||||
assert "[B](#c002-sec)" in text and "[A](#sec)" in text and "[hier](#sec)" in text
|
||||
assert "[Kapitel B](#002-kapitel-b)" in text and '<a id="002-kapitel-b"></a>' in text
|
||||
# media paths relative to the merged file, also inside image links
|
||||
assert "" in text
|
||||
assert "[](#002-kapitel-b)" in text
|
||||
# code untouched
|
||||
assert '[kein](link.md) id="sec" `code`' in text
|
||||
# footnote defined in both chapters -> only once in the merged file
|
||||
assert text.count("[^fn-001]:") == 1
|
||||
assert check_links({target.as_posix(): text}, {p.as_posix() for p in run.out.rglob("*") if p.is_file()}) == []
|
||||
prose = re.sub(r"^```.*?^```$", "", text, flags=re.M | re.S)
|
||||
ids = re.findall(r'id="([^"]+)"', prose)
|
||||
assert len(ids) == len(set(ids))
|
||||
|
||||
|
||||
def test_merge_refuses_overwrite_without_force(convert):
|
||||
run = convert(book())
|
||||
assert merge_main([str(run.out)]) == 0
|
||||
assert merge_main([str(run.out)]) == 1
|
||||
assert merge_main([str(run.out), "--force"]) == 0
|
||||
|
||||
|
||||
def test_merge_to_other_location_rewrites_media_paths(convert, tmp_path):
|
||||
run = convert(book())
|
||||
(tmp_path / "ziel").mkdir()
|
||||
target = tmp_path / "ziel" / "buch.md"
|
||||
assert merge_main([str(run.out), "-o", str(target), "--toc", "--no-frontmatter"]) == 0
|
||||
text = target.read_text(encoding="utf-8")
|
||||
assert not text.startswith("---")
|
||||
assert f"" in text
|
||||
assert "1. [Kapitel A](#001-kapitel-a)" in text
|
||||
assert "001-kapitel-a" in anchors_in(text)
|
||||
|
||||
|
||||
def test_merge_invalid_directory(tmp_path):
|
||||
assert merge_main([str(tmp_path / "gibt-es-nicht")]) == 1
|
||||
(tmp_path / "leer").mkdir()
|
||||
assert merge_main([str(tmp_path / "leer")]) == 1
|
||||
Loading…
Reference in a new issue