feat: caiete experiențiale M1-M5 + prompt reutilizabil + ghid facilitare + md_to_pdf
Adaugă al 4-lea nivel de sumarizare: caiete de facilitator cu corelații
cross-disciplinare (IFS, CNV, attachment theory, Brené Brown, neuroștiință),
exerciții cu script complet, și ghiduri de aplicare cu secvențe de cadrare
pentru 4 contexte (grup NLP, cercetași, CNV, self-coaching).
Fișiere noi (în summaries/, ne-tracked de .gitignore):
- 5 caiete: MODUL{1-5}_{CONCEPT}.md
- 5 aplicări: MODUL{1-5}_{CONCEPT}_APLICARI.md
- 5 cross-modul: INDEX_EXERCITII, HARTA_CONEXIUNI, GLOSAR_CREDINTE,
GHID_FACILITARE, METAFORE_POVESTI
Tracked:
- PROMPT_EXPERIENTIAL.md: prompt reutilizabil pentru M6
- TODOS.md: instrucțiuni complete pentru M6 (run.bat 6 + sumarizări + caiet)
- md_to_pdf.py: convertor markdown→PDF pentru summaries
- PROCES_SUMARIZARE.md: documentație proces sumarizare standard
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
239
md_to_pdf.py
Normal file
239
md_to_pdf.py
Normal file
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Convert Markdown summaries to print-friendly PDFs."""
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
import sys
|
||||
|
||||
import markdown2
|
||||
from weasyprint import HTML
|
||||
|
||||
SUMMARIES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "summaries")
|
||||
PDF_DIR = os.path.join(SUMMARIES_DIR, "pdf")
|
||||
|
||||
CSS = """
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 2cm;
|
||||
@bottom-right {
|
||||
content: counter(page);
|
||||
font-size: 9pt;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Segoe UI", "Noto Sans", "DejaVu Sans", sans-serif;
|
||||
font-size: 11pt;
|
||||
line-height: 1.5;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 18pt;
|
||||
font-weight: bold;
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.3em;
|
||||
border-bottom: 1.5pt solid #333;
|
||||
padding-bottom: 0.2em;
|
||||
page-break-before: auto;
|
||||
}
|
||||
|
||||
h1:first-of-type {
|
||||
page-break-before: avoid;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 14pt;
|
||||
font-weight: bold;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 0.3em;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 12pt;
|
||||
font-weight: bold;
|
||||
margin-top: 0.8em;
|
||||
margin-bottom: 0.2em;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0.4em 0;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin: 0.3em 0;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 0.15em 0;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 0.5pt solid #999;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 0.5em 0;
|
||||
font-size: 10pt;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #e8e8e8;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
padding: 5pt 8pt;
|
||||
border: 0.5pt solid #bbb;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 4pt 8pt;
|
||||
border: 0.5pt solid #ccc;
|
||||
}
|
||||
|
||||
tr:nth-child(even) td {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #f5f5f5;
|
||||
border: 0.5pt solid #ccc;
|
||||
border-radius: 3pt;
|
||||
padding: 8pt 10pt;
|
||||
font-family: "Consolas", "DejaVu Sans Mono", monospace;
|
||||
font-size: 9pt;
|
||||
line-height: 1.35;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: "Consolas", "DejaVu Sans Mono", monospace;
|
||||
font-size: 9.5pt;
|
||||
background-color: #f0f0f0;
|
||||
padding: 1pt 3pt;
|
||||
border-radius: 2pt;
|
||||
}
|
||||
|
||||
pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 2pt solid #999;
|
||||
margin: 0.5em 0;
|
||||
padding: 0.3em 0 0.3em 1em;
|
||||
color: #333;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def md_to_pdf(md_path, pdf_path):
|
||||
"""Convert a single Markdown file to PDF."""
|
||||
with open(md_path, "r", encoding="utf-8") as f:
|
||||
md_text = f.read()
|
||||
|
||||
html_body = markdown2.markdown(
|
||||
md_text,
|
||||
extras=["tables", "fenced-code-blocks", "header-ids", "break-on-newline"],
|
||||
)
|
||||
|
||||
html_doc = f"""<!DOCTYPE html>
|
||||
<html lang="ro">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>{CSS}</style>
|
||||
</head>
|
||||
<body>
|
||||
{html_body}
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
HTML(string=html_doc).write_pdf(pdf_path)
|
||||
print(f" {os.path.basename(md_path)} -> {os.path.basename(pdf_path)}")
|
||||
|
||||
|
||||
def find_files(modules=None):
|
||||
"""Find MODUL*_*.md files, optionally filtered by module numbers."""
|
||||
pattern = os.path.join(SUMMARIES_DIR, "MODUL*_*.md")
|
||||
files = sorted(glob.glob(pattern))
|
||||
|
||||
if modules:
|
||||
filtered = []
|
||||
for f in files:
|
||||
basename = os.path.basename(f)
|
||||
# Extract module number from MODUL{N}_...
|
||||
try:
|
||||
num = int(basename.split("_")[0].replace("MODUL", ""))
|
||||
if num in modules:
|
||||
filtered.append(f)
|
||||
except ValueError:
|
||||
continue
|
||||
files = filtered
|
||||
|
||||
return files
|
||||
|
||||
|
||||
def parse_modules(spec):
|
||||
"""Parse module spec like '1-3' or '2,4,5' into a set of ints."""
|
||||
modules = set()
|
||||
for part in spec.split(","):
|
||||
part = part.strip()
|
||||
if "-" in part:
|
||||
start, end = part.split("-", 1)
|
||||
modules.update(range(int(start), int(end) + 1))
|
||||
else:
|
||||
modules.add(int(part))
|
||||
return modules
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Convert MD summaries to PDF")
|
||||
parser.add_argument("files", nargs="*", help="Specific MD files to convert")
|
||||
parser.add_argument(
|
||||
"--modules", "-m", help="Module filter, e.g. '1-3' or '2,4,5'"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
os.makedirs(PDF_DIR, exist_ok=True)
|
||||
|
||||
if args.files:
|
||||
md_files = [os.path.abspath(f) for f in args.files]
|
||||
else:
|
||||
modules = parse_modules(args.modules) if args.modules else None
|
||||
md_files = find_files(modules)
|
||||
|
||||
if not md_files:
|
||||
print("No MD files found to convert.")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Converting {len(md_files)} file(s) to PDF...")
|
||||
for md_path in md_files:
|
||||
basename = os.path.splitext(os.path.basename(md_path))[0]
|
||||
pdf_path = os.path.join(PDF_DIR, basename + ".pdf")
|
||||
md_to_pdf(md_path, pdf_path)
|
||||
|
||||
print(f"Done. PDFs saved to {PDF_DIR}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user