Files

116 lines
4.9 KiB
Python

"""CLI: пакетная генерация документов без веб-сервера.
Примеры:
python -m certgen.gen --template classic --fio "Иванов Иван Иванович" \
--body "за успешное прохождение курса"
python -m certgen.gen --template letter --csv names.csv --outdir out
В CSV первая строка может быть заголовком (fio,title,body,date,prefix);
допускаются разделители ',' и ';'. Если колонок нет — берётся первый столбец как ФИО.
"""
from __future__ import annotations
import argparse
import csv
import json
import re
import shutil
from pathlib import Path
from . import config, store
from .service import create_certificate
def _read_csv(path: Path) -> list[dict]:
text = path.read_text(encoding="utf-8-sig")
first = text.splitlines()[0] if text.strip() else ""
delim = ";" if first.count(";") > first.count(",") else ","
rows = list(csv.reader(text.splitlines(), delimiter=delim))
rows = [r for r in rows if any(c.strip() for c in r)]
if not rows:
return []
header = [c.strip().lower() for c in rows[0]]
known = {"fio", "фио", "title", "заголовок", "body", "текст",
"date", "дата", "prefix", "префикс", "number", "номер", "email", "почта"}
has_header = bool(known & set(header))
records = []
data = rows[1:] if has_header else rows
keys = header if has_header else ["fio"]
for row in data:
rec = {}
if has_header:
for i, key in enumerate(keys):
rec[key] = row[i] if i < len(row) else ""
else:
rec["fio"] = row[0]
# нормализуем русские заголовки
for ru, en in (("фио", "fio"), ("заголовок", "title"), ("текст", "body"),
("дата", "date"), ("префикс", "prefix"), ("номер", "number"),
("почта", "email")):
if ru in rec and en not in rec:
rec[en] = rec.pop(ru)
records.append(rec)
return records
def main() -> None:
p = argparse.ArgumentParser(prog="certgen.gen", description="Пакетная генерация документов")
p.add_argument("--template", default="classic", help="slug шаблона")
p.add_argument("--fio", help="ФИО для одного документа")
p.add_argument("--email", help="email получателя документа")
p.add_argument("--title", help="заголовок")
p.add_argument("--body", help="дополнительный текст")
p.add_argument("--date", help="дата")
p.add_argument("--prefix", help="префикс номера")
p.add_argument("--number", help="особый номер из резерва 1..(START-1)")
p.add_argument("--csv", type=Path, help="CSV со списком")
p.add_argument("--names", help="список ФИО через ';'")
p.add_argument("--outdir", type=Path, help="куда скопировать готовые файлы")
p.add_argument("--base", help="базовый URL для ссылок")
p.add_argument("--json", action="store_true", help="вывести результат как JSON")
args = p.parse_args()
config.ensure_dirs()
store.init_db()
template = store.get_template(args.template)
if not template:
raise SystemExit(f"Неизвестный шаблон: {args.template}. "
f"Доступны: {[t['slug'] for t in store.list_templates()]}")
records: list[dict] = []
if args.csv:
records = _read_csv(args.csv)
elif args.names:
records = [{"fio": n.strip()} for n in args.names.split(";") if n.strip()]
elif args.fio:
records = [{"fio": args.fio}]
else:
raise SystemExit("Укажите --fio, --names или --csv")
results = []
for rec in records:
body = {"fio": rec.get("fio", "")}
for key, val in (("title", args.title), ("body", args.body),
("date", args.date), ("prefix", args.prefix),
("number", args.number), ("email", args.email)):
value = rec.get(key) or val
if value:
body[key] = value
cert = create_certificate(template, body, base=args.base)
if args.outdir:
args.outdir.mkdir(parents=True, exist_ok=True)
safe = re.sub(r"[^\w.-]+", "_", cert["number"]) or str(cert["seq"])
shutil.copyfile(cert["png_path"], args.outdir / f"{safe}.png")
shutil.copyfile(cert["pdf_path"], args.outdir / f"{safe}.pdf")
results.append(cert)
if not args.json:
print(f"№ {cert['number']} {cert['fio']} -> {cert['url']}")
if args.json:
print(json.dumps(results, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()