[ADD] титульный лист по бланку задания на практику
This commit is contained in:
@@ -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]{%
|
||||
|
||||
@@ -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}"
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user