"""Оформление листинга и флаг подсветки синтаксиса. Методичка МИРЭА, табл. 4.1 (`_method_extract.txt`, строки 716-737), блок «Содержание листинга»: шрифт Courier New, начертание Обычный, размер 10 пт, цвет текста Авто (чёрный), выравнивание по левому краю, отступы слева/справа 0 см, первая строка 0 см, интервал перед и после 0 мм, междустрочный одинарный. Отсюда два независимых требования: * с выключенной подсветкой (дефолт) содержимое листинга не должно нести никакого прямого форматирования на уровне run'а; * если пользователь всё же включил `--syntax-highlighting`, флаг обязан работать честно — курсив из стиля Pygments должен доезжать до XML, а checker обязан предупредить об отклонении от табл. 4.1. """ import os import unittest from contextlib import contextmanager import docx from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING from docx.oxml.ns import qn from docx.shared import Pt from md2gost import package_dir from md2gost.checker import check_markdown from md2gost.renderable.caption import CaptionInfo from md2gost.renderable.listing import Listing from md2gost.styles import apply_mirea_styles CODE = '# комментарий\nprint("hello")\n' MD_WITH_LISTING = """# *СОДЕРЖАНИЕ [TOC] # Раздел %code1 Пример кода ```python print(1) ``` См. @Листинг:code1. """ @contextmanager def _syntax_highlighting(enabled: bool): previous = os.environ.get("SYNTAX_HIGHLIGHTING") if enabled: os.environ["SYNTAX_HIGHLIGHTING"] = "1" else: os.environ.pop("SYNTAX_HIGHLIGHTING", None) try: yield finally: if previous is None: os.environ.pop("SYNTAX_HIGHLIGHTING", None) else: os.environ["SYNTAX_HIGHLIGHTING"] = previous class _ListingTestCase(unittest.TestCase): def setUp(self) -> None: self._document = docx.Document(os.path.join(package_dir(), "Template.docx")) self._document._body.clear_content() apply_mirea_styles(self._document) def _listing(self, highlighting: bool) -> Listing: listing = Listing(self._document._body, "python", CaptionInfo("code1", "Пример")) with _syntax_highlighting(highlighting): listing.set_text(CODE) return listing @staticmethod def _runs(listing: Listing): for paragraph in listing.paragraphs: for run in paragraph._docx_paragraph.runs: yield run class TestListingWithoutHighlighting(_ListingTestCase): """Табл. 4.1: содержимое листинга — Обычный, чёрный, Courier New 10 пт.""" def test_runs_carry_no_direct_formatting(self): listing = self._listing(highlighting=False) runs = list(self._runs(listing)) self.assertTrue(runs) for run in runs: rPr = run._element.find(qn("w:rPr")) if rPr is None: continue self.assertIsNone(rPr.find(qn("w:b")), run.text) self.assertIsNone(rPr.find(qn("w:i")), run.text) self.assertIsNone(rPr.find(qn("w:color")), run.text) def test_code_style_matches_table_4_1(self): style = self._document.styles["Code"] self.assertEqual("Courier New", style.font.name) self.assertEqual(Pt(10), style.font.size) self.assertFalse(style.font.bold) self.assertFalse(style.font.italic) pf = style.paragraph_format self.assertEqual(WD_ALIGN_PARAGRAPH.LEFT, pf.alignment) self.assertAlmostEqual(0, pf.first_line_indent.cm, places=3) self.assertAlmostEqual(0, pf.left_indent.cm, places=3) self.assertAlmostEqual(0, pf.space_before.mm, places=3) self.assertAlmostEqual(0, pf.space_after.mm, places=3) self.assertEqual(WD_LINE_SPACING.SINGLE, pf.line_spacing_rule) class TestListingWithHighlighting(_ListingTestCase): """Включённый флаг обязан реально давать курсив там, где его задаёт Pygments.""" def test_italic_token_reaches_xml(self): listing = self._listing(highlighting=True) italic_runs = [run for run in self._runs(listing) if run.italic] self.assertTrue( italic_runs, "ни один run не получил курсив, хотя стиль Pygments задаёт italic " "для Token.Comment", ) self.assertTrue( any("комментарий" in run.text for run in italic_runs), [run.text for run in italic_runs], ) class TestSyntaxHighlightingChecker(unittest.TestCase): """Подсветка противоречит табл. 4.1 — checker обязан об этом сказать.""" def test_no_issue_when_flag_is_off(self): issues = check_markdown(MD_WITH_LISTING, "coursework") self.assertEqual( [], [i for i in issues if i.id == "listing.syntax_highlighting"] ) def test_warning_when_flag_is_on(self): issues = check_markdown( MD_WITH_LISTING, "coursework", syntax_highlighting=True ) found = [i for i in issues if i.id == "listing.syntax_highlighting"] self.assertEqual(1, len(found)) self.assertEqual("warning", found[0].severity) def test_error_under_strict(self): issues = check_markdown( MD_WITH_LISTING, "coursework", syntax_highlighting=True, strict=True ) found = [i for i in issues if i.id == "listing.syntax_highlighting"] self.assertEqual(1, len(found)) self.assertEqual("error", found[0].severity)