[ADD] титульный лист по бланку задания на практику
This commit is contained in:
@@ -109,7 +109,7 @@ PREAMBLE = r"""\documentclass[a4paper, 14pt]{extarticle}
|
|||||||
|
|
||||||
\newcommand{\ptpfieldnote}[4][4.5cm]{%
|
\newcommand{\ptpfieldnote}[4][4.5cm]{%
|
||||||
\ptpfield[#1]{#2}{#3}%
|
\ptpfield[#1]{#2}{#3}%
|
||||||
\par\vspace{-0.7em}%
|
\par\vspace{-0.45em}%
|
||||||
\hspace*{#1}\makebox[\dimexpr\textwidth-#1\relax][c]{\footnotesize\itshape #4}\par}
|
\hspace*{#1}\makebox[\dimexpr\textwidth-#1\relax][c]{\footnotesize\itshape #4}\par}
|
||||||
|
|
||||||
\newcommand{\ptpfieldpar}[3][4.5cm]{%
|
\newcommand{\ptpfieldpar}[3][4.5cm]{%
|
||||||
|
|||||||
@@ -1,5 +1,142 @@
|
|||||||
|
"""Титульный лист по бланку «ЗАДАНИЕ на прохождение учебной практики».
|
||||||
|
|
||||||
|
Бланк идёт отдельным листом и вкладывается в отчёт, поэтому документ состоит
|
||||||
|
из одного этого листа. Текст пунктов задания в модуле не хранится: у каждого
|
||||||
|
студента он свой и приходит из front matter ключами ``пункт_N``.
|
||||||
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
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:
|
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)
|
source = inspect.getsource(ptp_report)
|
||||||
for forbidden in ("Посевин", "Коновалов", "Камалетдинов", "ИУ9-2"):
|
for forbidden in ("Посевин", "Коновалов", "Камалетдинов", "ИУ9-2"):
|
||||||
assert forbidden not in source
|
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