Files
md2gost/md2gost/main.py
T

153 lines
5.6 KiB
Python

import argparse
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
import md2gost
from md2gost.latex.log import STRICT_KINDS, format_diagnostics, parse_log
from md2gost.parser import Parser, iter_image_sources
from md2gost.render import Renderer
def _report_latex_log(log_path: Path) -> bool:
"""Печатает диагностику сборки. Возвращает True при серьёзных дефектах.
Журнал живёт во временном каталоге и исчезает вместе с ним, поэтому всё,
о чём сообщил xelatex, нужно вывести наружу здесь — иначе при запуске
в контейнере узнать о дефектах вёрстки неоткуда.
"""
if not log_path.exists():
return False
diags = parse_log(log_path.read_text(errors="replace"))
summary = format_diagnostics(diags)
if summary:
print(summary, file=sys.stderr)
return any(d.kind in STRICT_KINDS for d in diags)
def run_xelatex(tex_path: Path, output_dir: Path) -> bool:
result = subprocess.run(
[
"xelatex",
"-interaction=nonstopmode",
"-output-directory",
str(output_dir),
str(tex_path),
],
# Относительные пути картинок резолвятся от CWD, поэтому компилируем
# из каталога, куда _copy_assets сложил все изображения.
cwd=str(output_dir),
capture_output=True,
text=True,
)
pdf_path = output_dir / tex_path.with_suffix(".pdf").name
return pdf_path.exists() and result.returncode == 0
def _find_emblem(input_dir: Path) -> Path | None:
candidates = [
input_dir / "emblem.png",
Path(md2gost.__file__).parent / "emblem.png",
]
return next((p for p in candidates if p.is_file()), None)
def _copy_assets(parser: Parser, input_dir: Path, cache_dir: Path) -> None:
"""Копирует эмблему и все картинки документа в каталог компиляции."""
emblem = _find_emblem(input_dir)
if emblem is None:
print(
"предупреждение: emblem.png не найден — титульный лист без эмблемы",
file=sys.stderr,
)
else:
shutil.copy2(emblem, cache_dir / "emblem.png")
for src in iter_image_sources(parser.tokens):
src_path = input_dir / src
if not src_path.is_file():
print(
f"предупреждение: изображение не найдено: {src_path}", file=sys.stderr
)
continue
dest = cache_dir / src
dest.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src_path, dest)
def main() -> None:
cli = argparse.ArgumentParser(
prog="md2gost",
description="Markdown -> LaTeX конвертер по ГОСТ Р 7.0.5-2008",
)
cli.add_argument("input", type=Path, help="путь к .md файлу")
cli.add_argument(
"-o",
"--output",
type=Path,
default=Path("."),
help="выходная директория (по умолчанию текущая)",
)
cli.add_argument(
"--no-pdf",
action="store_true",
help="только .tex, без компиляции в PDF",
)
cli.add_argument(
"--strict",
action="store_true",
help="выйти с ошибкой, если xelatex сообщил о предупреждениях",
)
args = cli.parse_args()
if not args.input.exists():
print(f"ошибка: {args.input} не найден", file=sys.stderr)
sys.exit(1)
args.output.mkdir(parents=True, exist_ok=True)
md_text = args.input.read_text(encoding="utf-8")
parser = Parser(md_text)
renderer = Renderer(parser.tokens, parser.front_matter)
latex = renderer.render()
with tempfile.TemporaryDirectory(prefix="md2gost-") as cache:
cache_dir = Path(cache)
tex_name = args.input.with_suffix(".tex").name
tex_path = cache_dir / tex_name
tex_path.write_text(latex, encoding="utf-8")
_copy_assets(parser, args.input.parent, cache_dir)
final_tex = args.output / tex_name
shutil.copy2(tex_path, final_tex)
print(f"-> {final_tex}")
if not args.no_pdf:
log_path = cache_dir / tex_path.with_suffix(".log").name
pdf_name = args.input.with_suffix(".pdf").name
for attempt in (1, 2):
if not run_xelatex(tex_path, cache_dir):
stage = "" if attempt == 1 else " (2-й проход)"
print(f"ошибка: xelatex{stage} не собрал PDF", file=sys.stderr)
_report_latex_log(log_path)
sys.exit(1)
final_pdf = args.output / pdf_name
shutil.copy2(cache_dir / pdf_name, final_pdf)
print(f"-> {final_pdf}")
# Разбирается журнал второго прохода: после первого ссылки и номера
# страниц ещё не разложены, и часть предупреждений ложная.
has_defects = _report_latex_log(log_path)
if has_defects and args.strict:
print("ошибка: --strict, а вёрстка содержит дефекты", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()