164 lines
6.0 KiB
Python
164 lines
6.0 KiB
Python
"""Оформление листинга и флаг подсветки синтаксиса.
|
||
|
||
Методичка МИРЭА, табл. 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)
|