diff --git a/md2gost/latex/__init__.py b/md2gost/latex/__init__.py index 1494130..f85c61c 100644 --- a/md2gost/latex/__init__.py +++ b/md2gost/latex/__init__.py @@ -1,4 +1,4 @@ from md2gost.latex.preamble import END_DOCUMENT, PREAMBLE -from md2gost.latex.titlepage import render_titlepage +from md2gost.latex.titlepages import TITLEPAGES, render_titlepage -__all__ = ["END_DOCUMENT", "PREAMBLE", "render_titlepage"] +__all__ = ["END_DOCUMENT", "PREAMBLE", "TITLEPAGES", "render_titlepage"] diff --git a/md2gost/latex/titlepages/__init__.py b/md2gost/latex/titlepages/__init__.py new file mode 100644 index 0000000..dc9f97a --- /dev/null +++ b/md2gost/latex/titlepages/__init__.py @@ -0,0 +1,32 @@ +"""Реестр макетов титульного листа. + +Макет выбирается полем front matter ``титульник``; по умолчанию ``default`` — +исторический макет, поэтому существующие документы продолжают собираться +без изменений. +""" + +from __future__ import annotations + +from typing import Callable + +from md2gost.latex.titlepages import default, ptp_report, ptp_task + +TITLEPAGES: dict[str, Callable[[dict[str, str]], str]] = { + "default": default.render, + "ptp-report": ptp_report.render, + "ptp-task": ptp_task.render, +} + +__all__ = ["TITLEPAGES", "render_titlepage"] + + +def render_titlepage(fm: dict[str, str]) -> str: + name = fm.get("титульник", "default") + try: + layout = TITLEPAGES[name] + except KeyError: + available = ", ".join(sorted(TITLEPAGES)) + raise ValueError( + f"front matter: неизвестный титульник '{name}'; доступны: {available}" + ) from None + return layout(fm) diff --git a/md2gost/latex/titlepages/_common.py b/md2gost/latex/titlepages/_common.py new file mode 100644 index 0000000..3f50533 --- /dev/null +++ b/md2gost/latex/titlepages/_common.py @@ -0,0 +1,39 @@ +"""Общие части титульных листов: доступ к полям и шапка по бланку кафедры.""" + +from __future__ import annotations + +from md2gost.handlers.inline import escape_latex + +MINISTRY_DEFAULTS = { + "министерство": "Министерство науки и высшего образования Российской Федерации", + "вуз_тип": "Федеральное государственное автономное образовательное учреждение", + "вуз_тип2": "высшего образования", + "вуз": "Московский государственный технический университет", + "вуз_имени": "имени Н.Э. Баумана", + "вуз_статус": "национальный исследовательский университет", + "вуз_аббревиатура": "МГТУ им. Н.Э. Баумана", +} + + +def field(fm: dict[str, str], key: str, default: str = "") -> str: + """Значение поля front matter, экранированное для LaTeX.""" + return escape_latex(fm.get(key, default)) + + +def ministry_lines(fm: dict[str, str]) -> str: + """Семь центрированных строк шапки в точности по бланку кафедры. + + Отличается от макета ``default`` разбивкой строк: закрывающая кавычка стоит + после статуса вуза, а скобки закрыты. + """ + get = lambda key: fm.get(key, MINISTRY_DEFAULTS[key]) # noqa: E731 + lines = [ + get("министерство"), + get("вуз_тип"), + get("вуз_тип2"), + f"<<{get('вуз')}", + get("вуз_имени"), + f"({get('вуз_статус')})>>", + f"({get('вуз_аббревиатура')})", + ] + return "\n".join(f"\\vspace*{{-0.7ex}}\n\\centerline{{{line}}}" for line in lines) diff --git a/md2gost/latex/titlepage.py b/md2gost/latex/titlepages/default.py similarity index 93% rename from md2gost/latex/titlepage.py rename to md2gost/latex/titlepages/default.py index 0493727..d0c6991 100644 --- a/md2gost/latex/titlepage.py +++ b/md2gost/latex/titlepages/default.py @@ -1,9 +1,15 @@ +"""Исторический макет титульного листа. + +Перенесён без изменений: вывод обязан совпадать байт в байт с прежним, +иначе сломаются уже сданные документы. +""" + from __future__ import annotations from md2gost.handlers.inline import escape_latex -def render_titlepage(fm: dict[str, str]) -> str: +def render(fm: dict[str, str]) -> str: required = {"название": "заголовок работы", "тип": "тип работы"} for key, desc in required.items(): if key not in fm: diff --git a/md2gost/latex/titlepages/ptp_report.py b/md2gost/latex/titlepages/ptp_report.py new file mode 100644 index 0000000..be92f23 --- /dev/null +++ b/md2gost/latex/titlepages/ptp_report.py @@ -0,0 +1,5 @@ +from __future__ import annotations + + +def render(fm: dict[str, str]) -> str: + raise NotImplementedError diff --git a/md2gost/latex/titlepages/ptp_task.py b/md2gost/latex/titlepages/ptp_task.py new file mode 100644 index 0000000..be92f23 --- /dev/null +++ b/md2gost/latex/titlepages/ptp_task.py @@ -0,0 +1,5 @@ +from __future__ import annotations + + +def render(fm: dict[str, str]) -> str: + raise NotImplementedError diff --git a/tests/test_render.py b/tests/test_render.py index f1e27bb..139ef85 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -1,3 +1,6 @@ +import pytest + +from md2gost.latex.titlepages import TITLEPAGES from md2gost.parser import Parser from md2gost.render import Renderer @@ -9,6 +12,12 @@ def render(md: str) -> str: return Renderer(parser.tokens, parser.front_matter).render() +def render_fm(extra: str, body: str = "Текст.\n") -> str: + head = f"---\nназвание: Тестовая работа\nтип: Отчёт\n{extra}---\n\n" + parser = Parser(head + body) + return Renderer(parser.tokens, parser.front_matter).render() + + def test_heading_numbering() -> None: tex = render("# Исследовательская часть\n\n## Обзор\n") assert "\\section{Исследовательская часть}" in tex @@ -139,3 +148,18 @@ def _table_block(tex: str) -> str: start = tex.index("\\begin{table}") end = tex.index("\\end{table}") + len("\\end{table}") return tex[start:end] + + +def test_titlepage_default_is_used_when_field_absent() -> None: + tex = render("Текст.\n") + assert "\\begin{titlepage}" in tex + assert "ОТЧЁТ ПО УЧЕБНОЙ ПРАКТИКЕ" not in tex + + +def test_titlepage_registry_lists_three_layouts() -> None: + assert set(TITLEPAGES) == {"default", "ptp-report", "ptp-task"} + + +def test_titlepage_unknown_name_raises() -> None: + with pytest.raises(ValueError, match="ptp-report"): + render_fm("титульник: несуществующий\n")