--split, --from-md, --jpeg-quality, YAML-Kopf, --title/--author
- --split teile|kapitel: Einzeldateien in einem Ordner mit index.md (Reihenfolge) und images/; Titelei und Inhaltsverzeichnis als Vorspann; YAML-Kopf je Datei (title, part, chapter, order, book, author) - --from-md DATEI|ORDNER: EPUB aus bearbeitetem Markdown bzw. Split-Ordner, Angaben aus dem YAML-Kopf; ersetzt den pandoc-Befehl aus Weg B - Markdown beginnt mit YAML-Kopf (title, author, lang, cover); Cover wird mit --images immer abgelegt - --jpeg-quality (Standard 85 statt fest 95): EPUB Band 1 3 MB statt 5 MB - --title/--author überschreiben PDF-Metadaten - Tests (tests/test_split.py), README, Bedienungsanleitung (Weg C), CLAUDE.md, TODO.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f0e0562451
commit
d52ac597fc
1 changed files with 527 additions and 103 deletions
146
tests/test_split.py
Normal file
146
tests/test_split.py
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
"""--split, --from-md, YAML-Kopf und --jpeg-quality."""
|
||||
|
||||
import zipfile
|
||||
|
||||
import pytest
|
||||
|
||||
import pdf2md as p
|
||||
from conftest import needs_pandoc
|
||||
from test_e2e import run
|
||||
|
||||
BOOK = """# Das Buch
|
||||
|
||||
## Inhaltsverzeichnis
|
||||
|
||||
Teil I
|
||||
|
||||
1 Anfang
|
||||
|
||||
## Teil I: Der Anfang
|
||||
|
||||
Einleitung zum Teil.
|
||||
|
||||
### 1 Anfang
|
||||
|
||||
Text eins.
|
||||
|
||||
```
|
||||
# kein Kapitel, nur ein Kommentar
|
||||
```
|
||||
|
||||
### 2 Weiter
|
||||
|
||||
Text zwei.
|
||||
|
||||
## Impressum
|
||||
|
||||
Alle Rechte vorbehalten.
|
||||
"""
|
||||
|
||||
|
||||
def test_frontmatter_round_trip():
|
||||
meta = {"title": "Ein \"Buch\": Teil 1", "author": "Max Muster", "chapter": 3, "leer": ""}
|
||||
text = p.make_frontmatter(meta) + "# Text\n"
|
||||
parsed, body = p.split_frontmatter(text)
|
||||
assert parsed == {"title": "Ein \"Buch\": Teil 1", "author": "Max Muster", "chapter": 3}
|
||||
assert body == "# Text\n"
|
||||
|
||||
|
||||
def test_text_without_frontmatter_is_unchanged():
|
||||
assert p.split_frontmatter("---\n\nText\n") == ({}, "---\n\nText\n")
|
||||
|
||||
|
||||
def test_slugify():
|
||||
assert p.slugify("2 Ein Brief – eine Liste – ein Vermächtnis") == "2-ein-brief-eine-liste-ein-vermaechtnis"
|
||||
assert p.slugify("Größe & Maß") == "groesse-mass"
|
||||
assert p.slugify("!!!") == "abschnitt"
|
||||
|
||||
|
||||
def test_split_by_chapters():
|
||||
sections = p.split_markdown(BOOK, "kapitel")
|
||||
assert [s["title"] for s in sections] == [
|
||||
"Vorspann", "Teil I: Der Anfang", "1 Anfang", "2 Weiter", "Impressum",
|
||||
]
|
||||
assert sections[0]["body"].startswith("# Das Buch") and "## Inhaltsverzeichnis" in sections[0]["body"]
|
||||
assert sections[2]["part"] == "Teil I: Der Anfang" and sections[2]["chapter"] == 1
|
||||
assert "# kein Kapitel" in sections[2]["body"] # Codeblock wird nicht geschnitten
|
||||
assert "".join(s["body"] for s in sections).replace("\n", "") == BOOK.replace("\n", "")
|
||||
|
||||
|
||||
def test_split_by_parts():
|
||||
sections = p.split_markdown(BOOK, "teile")
|
||||
assert [s["title"] for s in sections] == ["Vorspann", "Teil I: Der Anfang", "Impressum"]
|
||||
assert "### 2 Weiter" in sections[1]["body"]
|
||||
|
||||
|
||||
def test_write_split_and_read_back(tmp_path):
|
||||
sections = p.split_markdown(BOOK, "kapitel")
|
||||
files = p.write_split(tmp_path / "buch", sections, {"title": "Das Buch", "author": "Max"})
|
||||
assert [f.name for f in files][:3] == ["00_vorspann.md", "01_teil-i-der-anfang.md", "02_1-anfang.md"]
|
||||
meta, body, base = p.read_markdown_source(tmp_path / "buch")
|
||||
assert meta["title"] == "Das Buch" and base == tmp_path / "buch"
|
||||
assert body.replace("\n", "") == BOOK.replace("\n", "")
|
||||
head, _ = p.split_frontmatter(files[2].read_text())
|
||||
assert head == {"title": "1 Anfang", "part": "Teil I: Der Anfang", "chapter": 1,
|
||||
"order": 2, "book": "Das Buch", "author": "Max"}
|
||||
|
||||
|
||||
def test_missing_index_entry_is_reported(tmp_path):
|
||||
folder = tmp_path / "buch"
|
||||
p.write_split(folder, p.split_markdown(BOOK, "teile"), {"title": "X"})
|
||||
(folder / "01_teil-i-der-anfang.md").unlink()
|
||||
with pytest.raises(RuntimeError, match="nicht vorhanden"):
|
||||
p.read_markdown_source(folder)
|
||||
|
||||
|
||||
# --- Ende zu Ende ------------------------------------------------------------------
|
||||
|
||||
def test_cli_split_writes_folder(pdfs, tmp_path):
|
||||
run(pdfs["digital"], tmp_path / "buch.md", "--split", "kapitel", "--images")
|
||||
folder = tmp_path / "buch"
|
||||
names = sorted(f.name for f in folder.glob("*.md"))
|
||||
assert names[0] == "00_vorspann.md" and "index.md" in names
|
||||
assert any(n.endswith("_1-der-notar.md") for n in names)
|
||||
assert (folder / "images" / "cover.jpg").is_file()
|
||||
text = "".join(f.read_text() for f in folder.glob("*.md"))
|
||||
assert "](images/" in text and "_images/" not in text
|
||||
|
||||
|
||||
@needs_pandoc
|
||||
def test_from_md_folder_follows_index_order(pdfs, tmp_path):
|
||||
run(pdfs["digital"], tmp_path / "buch.md", "--split", "kapitel", "--epub")
|
||||
index = tmp_path / "buch" / "index.md"
|
||||
lines = index.read_text().split("\n")
|
||||
i = next(n for n, line in enumerate(lines) if "1-der-notar" in line)
|
||||
j = next(n for n, line in enumerate(lines) if "2-ein-brief" in line)
|
||||
lines[i], lines[j] = lines[j], lines[i]
|
||||
index.write_text("\n".join(lines))
|
||||
|
||||
run("--from-md", tmp_path / "buch", "--epub-file", tmp_path / "neu.epub")
|
||||
with zipfile.ZipFile(tmp_path / "neu.epub") as z:
|
||||
nav = z.read("EPUB/nav.xhtml").decode()
|
||||
opf = z.read("EPUB/content.opf").decode()
|
||||
assert nav.index("2 Ein Brief") < nav.index("1 Der Notar")
|
||||
assert "Erika Muster" in opf and "cover" in opf
|
||||
|
||||
|
||||
@needs_pandoc
|
||||
def test_from_md_file_uses_frontmatter_and_overrides(pdfs, tmp_path):
|
||||
run(pdfs["digital"], tmp_path / "buch.md", "--images")
|
||||
md = tmp_path / "buch.md"
|
||||
md.write_text(md.read_text().replace("Der Regen", "Der starke Regen"))
|
||||
run("--from-md", md, "--title", "Neuer Titel")
|
||||
with zipfile.ZipFile(tmp_path / "buch.epub") as z:
|
||||
opf = z.read("EPUB/content.opf").decode()
|
||||
text = "".join(z.read(n).decode() for n in z.namelist() if n.endswith(".xhtml"))
|
||||
assert ">Neuer Titel<" in opf and "Erika Muster" in opf and "cover" in opf
|
||||
assert "Der starke Regen" in text and "title:" not in text
|
||||
|
||||
|
||||
def test_jpeg_quality_changes_image_size(pdfs, tmp_path):
|
||||
def size(quality):
|
||||
out = tmp_path / f"q{quality}" / "buch.md"
|
||||
run(pdfs["digital"], out, "--images", "--jpeg-quality", str(quality))
|
||||
return sum(f.stat().st_size for f in (out.parent / "buch_images").glob("*.jpg"))
|
||||
|
||||
assert size(30) < size(95)
|
||||
Loading…
Reference in a new issue