[ADD] титульный лист по бланку задания на практику

This commit is contained in:
Arseny
2026-09-02 03:29:54 +03:00
parent 6aed7f186d
commit fab9e19962
3 changed files with 217 additions and 2 deletions
+1 -1
View File
@@ -109,7 +109,7 @@ PREAMBLE = r"""\documentclass[a4paper, 14pt]{extarticle}
\newcommand{\ptpfieldnote}[4][4.5cm]{%
\ptpfield[#1]{#2}{#3}%
\par\vspace{-0.7em}%
\par\vspace{-0.45em}%
\hspace*{#1}\makebox[\dimexpr\textwidth-#1\relax][c]{\footnotesize\itshape #4}\par}
\newcommand{\ptpfieldpar}[3][4.5cm]{%
+138 -1
View File
@@ -1,5 +1,142 @@
"""Титульный лист по бланку «ЗАДАНИЕ на прохождение учебной практики».
Бланк идёт отдельным листом и вкладывается в отчёт, поэтому документ состоит
из одного этого листа. Текст пунктов задания в модуле не хранится: у каждого
студента он свой и приходит из front matter ключами ``пункт_N``.
"""
from __future__ import annotations
import re
from md2gost.handlers.inline import escape_latex
from md2gost.latex.titlepages._common import field, ministry_lines
_ITEM_RE = re.compile(r"^пункт_(\d+)$")
def task_items(fm: dict[str, str]) -> list[str]:
"""Пункты задания из ключей ``пункт_N``, отсортированные по номеру."""
items = [
(int(match.group(1)), value)
for key, value in fm.items()
if (match := _ITEM_RE.match(key)) and value.strip()
]
if not items:
raise ValueError(
"front matter: у макета 'ptp-task' нет ни одного пункта задания; "
"добавьте поля 'пункт_1', 'пункт_2', ..."
)
return [value for _, value in sorted(items)]
def _items_tex(fm: dict[str, str], team_line: str) -> str:
"""Нумерованный список пунктов; под пунктом с двоеточием — строка команды."""
lines: list[str] = []
for text in task_items(fm):
lines.append(f" \\item {escape_latex(text)}")
if text.rstrip().endswith(":"):
lines.append(
" \\par\\vspace{0.5ex}"
f"\\noindent\\makebox[\\textwidth][c]{{\\itshape {team_line}}}"
"\\par\\vspace{-0.6em}"
"\\noindent\\rule{\\textwidth}{0.4pt}\\par"
)
return "\n".join(lines)
def render(fm: dict[str, str]) -> str:
raise NotImplementedError
department = field(fm, "кафедра")
dep_index = field(fm, "кафедра_индекс", "ИУ9")
student_full = field(fm, "студент_полностью") or field(fm, "студент")
group = field(fm, "группа")
company = field(fm, "предприятие")
team = field(fm, "команда")
repo = field(fm, "хранилище")
advisor = field(fm, "руководитель")
day = field(fm, "дата_выдачи_день")
month = field(fm, "дата_выдачи_месяц")
year = field(fm, "дата_выдачи_год", "2026")
team_line = f"{team} (хранилище {repo})" if repo else team
student_line = f"{student_full}, {group}" if group else student_full
return (
"\\begin{titlepage}\n"
"\\thispagestyle{empty}\n"
"\\setstretch{1.0}\n"
# center/flushright добавляют \\topsep и \\parskip сверху и снизу;
# на бланке из-за этого не остаётся места под подписи.
"\\setlength{\\parskip}{0pt}\n"
"\\setlength{\\topsep}{0pt}\n"
"\\setlength{\\partopsep}{0pt}\n"
"\n"
"% --- Шапка без эмблемы: в бланке задания её нет ---\n"
"\\vspace*{-52pt}\n"
"{\\small\\bfseries\n"
f"{ministry_lines(fm)}}}\n"
"\n"
"% --- Полосы ---\n"
"\\vspace{1ex}\n"
"\\noindent\\rule{\\textwidth}{2.3pt}\n"
"\n"
"\\vspace*{-18pt}\n"
"\\noindent\\rule{\\textwidth}{0.4pt}\n"
"\n"
"% --- Кафедра ---\n"
"\\vspace{1ex}\n"
"\\begin{center}\n"
f"Кафедра <<\\underline{{{department}}}>> (\\underline{{{dep_index}}})\n"
"\\end{center}\n"
"\n"
"% --- Утверждаю ---\n"
"\\vspace{0.5ex}\n"
"\\begin{flushright}\n"
"УТВЕРЖДАЮ \\\\[0.4ex]\n"
"Заведующий кафедрой \\rule{3cm}{0.4pt} \\\\[1ex]\n"
"\\rule{3.2cm}{0.4pt} \\quad \\rule{4cm}{0.4pt} \\\\[1ex]\n"
"<<\\,\\rule{1cm}{0.4pt}\\,>> \\rule{3cm}{0.4pt} 20\\,\\rule{0.8cm}{0.4pt} г.\n"
"\\end{flushright}\n"
"\n"
"% --- Заголовок ---\n"
"\\vspace{0.5ex}\n"
"\\begin{center}\n"
"{\\large\\bfseries З\\,А\\\\,А\\,Н\\\\,Е} \\\\[0.4ex]\n"
"{\\bfseries на прохождение учебной практики}\n"
"\\end{center}\n"
"\n"
"% --- Поля ---\n"
"\\vspace{1ex}\n"
f"\\ptpfield[3.3cm]{{на предприятии}}{{{company}}}\n"
"\n"
"\\vspace{0.8ex}\n"
f"\\ptpfieldnote[2.2cm]{{Студент}}{{{student_line}}}"
"{(фамилия, имя, отчество; инициалы; индекс группы)}\n"
"\n"
"% --- Пункты задания ---\n"
"\\vspace{1.2ex}\n"
"\\noindent Во время прохождения учебной практики студент должен:\n"
# Пункты набираются мельче основного кегля — как в бланке кафедры,
# иначе полный список не помещается на лист вместе с подписями.
"{\\small\n"
"\\begin{enumerate}[wide=0pt, labelindent=0pt, labelwidth=0pt,\n"
" label=\\arabic*., itemsep=0.2ex, topsep=0.3ex, parsep=0pt]\n"
f"{_items_tex(fm, team_line)}\n"
"\\end{enumerate}}\n"
"\n"
"% --- Дата выдачи ---\n"
"\\vspace{1ex}\n"
"\\noindent Дата выдачи задания "
f"<<\\,\\underline{{~{day}~}}\\,>> \\underline{{~{month}~}} "
f"\\underline{{~{year}~}} г.\n"
"\n"
"% --- Подписи ---\n"
"\\vspace{0.6em}\n"
f"{{\\setstretch{{1.3}}\\ptpsig{{\\bfseries Руководитель практики от кафедры}}"
f"{{{advisor}}}{{(Фамилия И.О.)}}}}\n"
"\n"
"\\vspace{0.5em}\n"
"{\\setstretch{1.3}\\ptpsig{\\bfseries Студент}{}{(Фамилия И.О.)}}\n"
"\n"
"\\end{titlepage}"
)
+78
View File
@@ -231,3 +231,81 @@ def test_ptp_report_has_no_hardcoded_names() -> None:
source = inspect.getsource(ptp_report)
for forbidden in ("Посевин", "Коновалов", "Камалетдинов", "ИУ9-2"):
assert forbidden not in source
PTP_TASK_FM = (
"титульник: ptp-task\n"
"кафедра: Теоретическая информатика и компьютерные технологии\n"
"кафедра_индекс: ИУ9\n"
"студент: Булдаков А. С.\n"
"студент_полностью: Булдаков Арсений Сергеевич\n"
"группа: ИУ9-22Б\n"
"предприятие: МГТУ имени Н. Э. Баумана\n"
"команда: Система мониторинга радиоэфира\n"
"хранилище: https://monitor.yss.su/IU9/radio-serv\n"
"руководитель: Посевин Д. П.\n"
"дата_выдачи_день: 29\n"
"дата_выдачи_месяц: июня\n"
"дата_выдачи_год: 2026\n"
"пункт_1: Изучить принципы построения систем пеленгации.\n"
"пункт_2: Получить задание в составе команды:\n"
"пункт_10: Написать отчёт.\n"
)
def test_ptp_task_renders_items_in_numeric_order() -> None:
"""Пункт 10 идёт после пункта 2, а не между 1 и 2 как при сортировке строк."""
tex = render_fm(PTP_TASK_FM)
first = tex.index("Изучить принципы построения систем пеленгации.")
second = tex.index("Получить задание в составе команды:")
third = tex.index("Написать отчёт.")
assert first < second < third
def test_ptp_task_requires_items() -> None:
with pytest.raises(ValueError, match="пункт_1"):
render_fm("титульник: ptp-task\nстудент: Булдаков А. С.\n")
def test_ptp_task_has_blank_furniture() -> None:
tex = render_fm(PTP_TASK_FM)
for fragment in (
"УТВЕРЖДАЮ",
"Заведующий кафедрой",
"З\\,А\\\\,А\\,Н\\\\,Е",
"на прохождение учебной практики",
"Дата выдачи задания",
"Руководитель практики от кафедры",
):
assert fragment in tex
def test_ptp_task_student_line_carries_group() -> None:
tex = render_fm(PTP_TASK_FM)
assert "Булдаков Арсений Сергеевич, ИУ9-22Б" in tex
def test_ptp_task_team_line_follows_item_with_colon() -> None:
tex = render_fm(PTP_TASK_FM)
colon_item = tex.index("Получить задание в составе команды:")
team = tex.index("Система мониторинга радиоэфира (хранилище")
next_item = tex.index("Написать отчёт.")
assert colon_item < team < next_item
def test_ptp_task_has_no_emblem() -> None:
"""В бланке задания эмблемы нет."""
tex = render_fm(PTP_TASK_FM)
titlepage = tex[tex.index("\\begin{titlepage}") : tex.index("\\end{titlepage}")]
assert "emblem" not in titlepage
def test_ptp_task_has_no_hardcoded_assignment_text() -> None:
"""Пункты задания и имена приходят только из front matter."""
import inspect
from md2gost.latex.titlepages import ptp_task
source = inspect.getsource(ptp_task)
for forbidden in ("Web", "2D-игра", "GitFlic", "Javascript", "Посевин", "Коновалов"):
assert forbidden not in source