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 = ('

Kapitel A

Siehe B, Kapitel B, ' 'hier und Anmerkung1.

' '

Bild

' '

Bildlink

' '
[kein](link.md) id="sec" `code`
') B = ('

Kapitel B

Zurück zu A, nochmal Anmerkung' '1.

' '') 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 '' in text and '' in text assert "[B](#c002-sec)" in text and "[A](#sec)" in text and "[hier](#sec)" in text assert "[Kapitel B](#kap-002-kapitel-b)" in text and '' in text # media paths relative to the merged file, also inside image links assert "![Bild](media/images/0001-bild.png)" in text assert "[![Bildlink](media/images/0001-bild.png)](#kap-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"![Bild](../{run.out.name}/media/images/0001-bild.png)" in text assert "1. [Kapitel A](#kap-001-kapitel-a)" in text assert "kap-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 def test_merge_shortens_iso_date(convert): from epub2md.merge import display_date assert display_date("2025-01-13T08:00:00+00:00", "de") == "13.01.2025" assert display_date("2025-01-13", "de-DE") == "13.01.2025" assert display_date("2025-01-13T08:00:00Z", "en") == "2025-01-13" assert display_date("2025", "de") == "2025" # year only / other formats stay assert display_date("Januar 2025", "de") == "Januar 2025" run = convert(book()) assert merge_main([str(run.out)]) == 0 text = (run.out / f"{run.report['input']['name'][:-5]}.md").read_text(encoding="utf-8") assert yaml.safe_load(text.split("---\n")[1])["date"] == "01.01.2024" def front_book() -> Book: docs = [Doc("cover.xhtml", '
Cover
'), Doc("titel.xhtml", "

Erika Musterfrau

Vorspann

Roman

"), Doc("impressum.xhtml", "

© 2024 Testverlag

Das Werk ist urheberrechtlich geschützt.

"), Doc("a.xhtml", "

Kapitel A

a

")] return Book(title="Vorspann", docs=docs, nav=[("Kapitel A", "a.xhtml")], media={"img/a.png": PNG}) def test_merge_places_toc_placeholder_after_front_matter(convert): run = convert(front_book()) assert merge_main([str(run.out)]) == 0 text = (run.out / f"{run.report['input']['name'][:-5]}.md").read_text(encoding="utf-8") cover, title, imprint = text.index("![Cover]"), text.index("\nRoman"), text.index("© 2024") toc, first = text.index('
'), text.index("# Kapitel A") assert cover < title < imprint < toc < first assert "epub2md-titlepage" not in text # the book has its own title page def test_merge_title_placeholder_after_cover_without_own_title_page(convert): docs = [Doc("cover.xhtml", '
Cover
'), Doc("a.xhtml", "

Kapitel A

a

")] run = convert(Book(title="Ohne Titelei", docs=docs, nav=[("Kapitel A", "a.xhtml")], media={"img/a.png": PNG})) assert merge_main([str(run.out)]) == 0 text = (run.out / f"{run.report['input']['name'][:-5]}.md").read_text(encoding="utf-8") assert text.index("![Cover]") < text.index('
') \ < text.index('
') < text.index("# Kapitel A") def test_merge_own_toc_replaces_placeholder(convert): run = convert(front_book()) assert merge_main([str(run.out), "--toc"]) == 0 text = (run.out / f"{run.report['input']['name'][:-5]}.md").read_text(encoding="utf-8") assert "epub2md-toc" not in text and text.index("© 2024") < text.index("## Inhalt") < text.index("# Kapitel A") def test_merge_front_matter_region_with_gaps(convert): """Krooss/Blake-like: short untyped pages between title page and imprint; main matter starts at '1 …'.""" docs = [Doc("cover.xhtml", '
Cover
'), Doc("titel.xhtml", "

Erika Musterfrau

Vorspann

Roman

"), Doc("auflage.xhtml", "

Vorspann

4. Auflage 2025

"), Doc("impressum.xhtml", "

© 2024 Testverlag

Das Werk ist urheberrechtlich geschützt.

"), Doc("brief.xhtml", "

Liebe Leserin, lieber Leser,

Viel Spaß.

"), Doc("k1.xhtml", "

1 Der Einstieg

a

"), Doc("k2.xhtml", "

Zwischenspiel

b

")] book = Book(title="Vorspann", docs=docs, nav=[("1 Der Einstieg", "k1.xhtml"), ("Zwischenspiel", "k2.xhtml")], media={"img/a.png": PNG}) run = convert(book) assert merge_main([str(run.out)]) == 0 text = (run.out / f"{run.report['input']['name'][:-5]}.md").read_text(encoding="utf-8") toc = text.index('
') assert text.index("© 2024") < text.index("Viel Spaß.") < toc < text.index("# 1 Der Einstieg") assert text.count("epub2md-toc") == 1 and "epub2md-titlepage" not in text