BigUpdate
Python application / build (push) Has been cancelled

This commit is contained in:
Igor20264
2026-09-03 10:44:08 +03:00
parent d2da20fdb2
commit 516abe7b83
177 changed files with 40178 additions and 2 deletions
+185
View File
@@ -0,0 +1,185 @@
"""Document type profiles and preprocess helpers for MIREA TZ."""
from __future__ import annotations
import re
from dataclasses import dataclass
from datetime import datetime
DOC_TYPES = ("coursework", "practice", "vkr", "PIS_custom", "APID_coursework")
# auto — Word multilevel list numbers headings; strip digits from md text
# manual — digits stay in md (# 1 … / ## 1.1 …); Word auto-numbering disabled
HEADING_NUMBERING_MODES = ("auto", "manual")
DEFAULT_HEADING_NUMBERING = "manual"
# manual — content built by md2gost with page numbers from layout tracker
# native — Word TOC field (обновить поле при открытии в Word)
TOC_MODES = ("manual", "native")
DEFAULT_TOC_MODE = "native"
# off — одна таблица Word, пагинацию делает Word (по умолчанию; без автоподписи)
# legacy — режем по оценке высоты + «Продолжение…» с page_break_before
# caption — режем по оценке + явный PageBreak + «Продолжение…»
# soft — синоним off (автоподпись mid-page без точной вёрстки Word невозможна в DOCX)
TABLE_CONTINUATION_MODES = ("off", "legacy", "caption", "soft")
DEFAULT_TABLE_CONTINUATION = "off"
# section — Рисунок 1.1 / 2.1 (ТЗ МИРЭА); continuous — Рисунок 1, 2, 3 (ПИС)
NUMBERING_SCOPES = ("section", "continuous")
STYLE_PRESETS = ("mirea", "pis_custom")
@dataclass
class DocProfile:
name: str
min_sources: int
max_sources: int | None # None = no upper bound
sectional_biblio: bool
require_graphic_appendix: bool
max_source_age_years: int = 5
# mirea — ГОСТ/методичка МИРЭА; pis_custom — итоговый отчёт по практическим работам
style_preset: str = "mirea"
numbering_scope: str = "section"
# Structure expectations for checker
require_intro_conclusion: bool = True
require_bibliography: bool = True
require_practical_works: bool = False
# КР по «Архитектуре приложений и данных» (методичка Аншиной/Лагуновой)
require_apid_kr_structure: bool = False
PROFILES = {
"coursework": DocProfile("coursework", 5, 7, False, False),
"practice": DocProfile("practice", 5, 7, False, False),
"vkr": DocProfile("vkr", 10, None, True, True),
# Курсовая АПИД: оформление ГОСТ/Кириллина, источники 7–20 по методичке дисциплины
"APID_coursework": DocProfile(
"APID_coursework",
min_sources=7,
max_sources=20,
sectional_biblio=False,
require_graphic_appendix=False,
max_source_age_years=5,
style_preset="mirea",
numbering_scope="section",
require_intro_conclusion=True,
require_bibliography=True,
require_practical_works=False,
require_apid_kr_structure=True,
),
# Итоговый отчёт по практическим работам (чек-лист ПИС)
"PIS_custom": DocProfile(
"PIS_custom",
min_sources=0,
max_sources=None,
sectional_biblio=False,
require_graphic_appendix=False,
max_source_age_years=10,
style_preset="pis_custom",
numbering_scope="continuous",
require_intro_conclusion=False,
require_bibliography=False,
require_practical_works=True,
),
}
def get_profile(doc_type: str) -> DocProfile:
if doc_type not in PROFILES:
raise ValueError(f"Unknown document type: {doc_type}. Expected one of {DOC_TYPES}")
return PROFILES[doc_type]
# --- text autofixes ---
def fix_russian_quotes(text: str) -> str:
"""Replace "..." with «...» for Russian text segments (heuristic)."""
def repl(m):
inner = m.group(1)
if inner.lstrip().startswith("%"):
return m.group(0)
return f"«{inner}»"
return re.sub(r'"([^"\n]+)"', repl, text)
def fix_dashes(text: str) -> str:
"""Replace spaced hyphen used as dash with em dash; keep range hyphens."""
# "слово - слово" → "слово — слово"
text = re.sub(r"(\S) - (\S)", r"\1 — \2", text)
return text
def replace_emdash_with_hyphen(text: str) -> str:
"""Replace typographic em dash U+2014 with ASCII hyphen-minus."""
return text.replace("", "-")
# Runtime switch for — vs - (set from CLI / Converter)
_EMDASH_TO_HYPHEN = False
def set_emdash_to_hyphen(enabled: bool) -> None:
global _EMDASH_TO_HYPHEN
_EMDASH_TO_HYPHEN = bool(enabled)
def emdash_to_hyphen_enabled() -> bool:
return _EMDASH_TO_HYPHEN
def dash_separator() -> str:
"""Caption / prose dash: ' - ' or '' depending on CLI flag."""
return " - " if _EMDASH_TO_HYPHEN else ""
def preprocess_markdown(text: str, emdash_to_hyphen: bool | None = None) -> str:
if emdash_to_hyphen is not None:
set_emdash_to_hyphen(emdash_to_hyphen)
text = fix_russian_quotes(text)
if _EMDASH_TO_HYPHEN:
# Keep ASCII hyphens; flatten any em dashes from the source
text = replace_emdash_with_hyphen(text)
else:
text = fix_dashes(text)
text = separate_biblio_lines(text)
return text
def separate_biblio_lines(text: str) -> str:
"""Ensure each [n]: bibliography line is its own paragraph (Marko soft-breaks otherwise)."""
lines = text.splitlines(keepends=True)
out: list[str] = []
in_biblio = False
for line in lines:
stripped = line.strip()
if re.match(r"^#\s*\*?\s*СПИСОК\s+ИСПОЛЬЗОВАНН?ЫХ\s+ИСТОЧНИКОВ\s*$", stripped, re.I):
in_biblio = True
out.append(line)
continue
if in_biblio and re.match(r"^#\s+", stripped):
in_biblio = False
if in_biblio and re.match(r"^\[\d+(?:\.\d+)?\]:", stripped):
# blank line before entry if previous wasn't blank
if out and out[-1].strip():
out.append("\n")
out.append(line if line.endswith("\n") else line + "\n")
out.append("\n")
continue
out.append(line)
return "".join(out)
def find_formula_refs(text: str) -> set[str]:
"""Labels of formulas that are cited, e.g. @Формула:euler or (1.1) after %eq."""
refs = set()
for m in re.finditer(r"@Формула:(\w+)", text, re.IGNORECASE):
refs.add(m.group(1))
for m in re.finditer(r"@Formula:(\w+)", text, re.IGNORECASE):
refs.add(m.group(1))
return refs
def current_year() -> int:
return datetime.now().year