Files
md2gost/md2gost/render.py
T
Arseny 974d49b056 [FIX] порядок TOC и монтирование картинок в Docker
- md2gost.sh: монтировать директорию входного файла (/input:ro) вместо
  одного файла — картинки по относительным путям (img/) теперь видны
  внутри контейнера; основное исправление падений make run
- render.py: вставлять TOC до следующего блока, а не после — порядок
  стал АННОТАЦИЯ -> СОДЕРЖАНИЕ -> ПЕРЕЧЕНЬ -> ВВЕДЕНИЕ (ранее заголовок
  ПЕРЕЧЕНЬ без тела оказывался перед СОДЕРЖАНИЕМ, а его тело — после)
- heading.py: не устанавливать current_heading для СОДЕРЖАНИЕ, чтобы
  не сбивать спецрежим перечня сокращений
- .gitignore: игнорировать сгенерированные report.tex/pdf и img/
2026-08-25 14:27:37 +03:00

90 lines
2.9 KiB
Python

"""Главный цикл рендера: проход по токенам и диспетчеризация.
Порядок диспетчеризации для каждого top-level токена:
1. Паттерны (registry.PATTERNS) — составные условия на набор токенов
(например «абзац из ровно одной картинки»).
2. Словарь registry.BLOCK — обработчики одиночных top-level токенов.
Контракт block-обработчика: ``handler(renderer, tok) -> str``; обработчик
обязан продвинуть ``renderer.pos`` за все потреблённые токены. Нарушение
контракта — ошибка (RuntimeError), а не тихий неверный вывод.
"""
from __future__ import annotations
from markdown_it.token import Token
from md2gost import registry
from md2gost.latex import END_DOCUMENT, PREAMBLE, render_titlepage
# Импорт обработчиков регистрирует их в реестрах.
from md2gost.handlers import ( # noqa: F401
bullet_list,
fence,
figure,
heading,
hr,
math_block,
ordered_list,
paragraph,
table,
)
TOC_HEADING = "СОДЕРЖАНИЕ"
class Renderer:
def __init__(self, tokens: list[Token], front_matter: dict[str, str]) -> None:
self.tokens = tokens
self.fm = front_matter
self.pos = 0
self.current_heading = ""
self.pending_toc = False
def render(self) -> str:
parts: list[str] = [
PREAMBLE,
render_titlepage(self.fm),
"\\setcounter{page}{2}",
]
while self.pos < len(self.tokens):
if self.pending_toc:
parts.append("\\newpage\n\\tableofcontents\n\\newpage")
self.pending_toc = False
rendered = self._dispatch()
parts.append(rendered)
# TOC в конце файла (если # СОДЕРЖАНИЕ был последним блоком)
if self.pending_toc:
parts.append("\\newpage\n\\tableofcontents\n\\newpage")
parts.append(END_DOCUMENT)
return "\n\n".join(p for p in parts if p)
def _dispatch(self) -> str:
tokens = self.tokens
pos = self.pos
tok = tokens[pos]
for match_fn, handler_fn in registry.PATTERNS:
span = match_fn(tokens, pos)
if span is not None and span > 0:
self.pos += span
return handler_fn(tokens[pos : pos + span])
handler = registry.BLOCK.get(tok.type)
if handler is None:
self.pos += 1
return ""
result = handler(self, tok)
if self.pos <= pos:
raise RuntimeError(
f"обработчик {handler.__name__} не продвинул pos "
f"для токена '{tok.type}'"
)
return result