extract_audio.py sarea peste lectiile fara video. Gasita una in curriculum-ul accesibil (Modul 3 #9 - expirari de optiuni, witching, 0DTE), cu 4847 de caractere de continut real. Era numarata gresit ca acoperita. Plus: - render_html.py: index grupat pe module cu titlul H1 al fiecarei pagini - render_html.py: garda pe liniile goale din <svg> (rup blocul HTML tacut) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MtSTyTmt6AbCL9j5ajEDm1
91 lines
3.2 KiB
Python
91 lines
3.2 KiB
Python
"""
|
|
Render every summaries/**/*.md to a sibling .html — mobile-readable (viewport
|
|
meta + real font size), served alongside the .md via `tailscale serve`.
|
|
Also (re)writes summaries/index.html linking every rendered page.
|
|
Run after adding/editing any summary.
|
|
"""
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import markdown
|
|
|
|
ROOT = Path(__file__).parent / "summaries"
|
|
|
|
PAGE_TEMPLATE = """<!DOCTYPE html>
|
|
<html lang="ro">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{title}</title>
|
|
<style>
|
|
body {{ max-width: 720px; margin: 0 auto; padding: 16px 20px 60px;
|
|
font: 18px/1.55 -apple-system, Segoe UI, Roboto, sans-serif;
|
|
color: #1a1a1a; }}
|
|
h1 {{ font-size: 1.4em; }}
|
|
h2 {{ font-size: 1.15em; margin-top: 1.6em; border-top: 1px solid #ddd; padding-top: .8em; }}
|
|
code {{ font-size: .95em; background: #f0f0f0; padding: .1em .3em; border-radius: 3px; }}
|
|
a {{ color: #1a5fb4; }}
|
|
hr {{ border: none; border-top: 1px solid #ddd; margin: 2em 0; }}
|
|
em {{ color: #555; }}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
{body}
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
|
|
def check_svgs(md_path: Path, text: str) -> None:
|
|
"""O linie goală în interiorul unui <svg> face python-markdown să rupă
|
|
blocul HTML în paragrafe, iar browserul aruncă restul elementelor —
|
|
diagrama se randează pe jumătate, fără nicio eroare. S-a întâmplat deja
|
|
de două ori; mai bine crapă aici decât să publici o diagramă stricată."""
|
|
for m in re.finditer(r"<svg .*?</svg>", text, re.S):
|
|
if re.search(r"\n[ \t]*\n", m.group(0)):
|
|
line = text[: m.start()].count("\n") + 1
|
|
raise SystemExit(f"{md_path}:{line}: linie goală în interiorul <svg> — markdown va rupe blocul")
|
|
|
|
|
|
def render_file(md_path: Path) -> tuple[Path, str]:
|
|
text = md_path.read_text(encoding="utf-8")
|
|
check_svgs(md_path, text)
|
|
body = markdown.markdown(text, extensions=["extra", "toc"])
|
|
title = text.splitlines()[0].lstrip("# ").strip() if text else md_path.stem
|
|
html_path = md_path.with_suffix(".html")
|
|
html_path.write_text(PAGE_TEMPLATE.format(title=title, body=body), encoding="utf-8")
|
|
return html_path, title
|
|
|
|
|
|
def write_index(pages: list[tuple[Path, str]]):
|
|
"""Grupat pe module, cu titlul H1 al fiecărei pagini — numele fișierului
|
|
(o dată, un număr) nu spune nimic despre ce e înăuntru."""
|
|
sections = []
|
|
current = None
|
|
for path, title in sorted(pages):
|
|
if path.parent.name != current:
|
|
if current is not None:
|
|
sections.append("</ul>")
|
|
current = path.parent.name
|
|
sections.append(f"<h2>{current.replace('_', ' ')}</h2><ul>")
|
|
sections.append(f'<li><a href="{path.relative_to(ROOT)}">{title}</a></li>')
|
|
if current is not None:
|
|
sections.append("</ul>")
|
|
(ROOT / "index.html").write_text(
|
|
PAGE_TEMPLATE.format(title="ATM — Sumarizări",
|
|
body="<h1>ATM — Sumarizări</h1>" + "".join(sections)),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def main():
|
|
md_files = sorted(ROOT.glob("*/*.md"))
|
|
pages = [render_file(f) for f in md_files]
|
|
write_index(pages)
|
|
print(f"Rendered {len(pages)} page(s) + index.html")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|