diff --git a/README.md b/README.md index c568dda..bfeb047 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ docker build -t md2gost . - Изображения (см. ниже) - Аббревиатуры (раздел "Перечень принятых сокращений") - Ссылки, жирный, курсив +- Пометка правок `==текст==` — жёлтое выделение для рецензента ### Изображения diff --git a/md2gost/handlers/inline.py b/md2gost/handlers/inline.py index d1b5aff..a32988d 100644 --- a/md2gost/handlers/inline.py +++ b/md2gost/handlers/inline.py @@ -81,9 +81,49 @@ def bind_initials(text: str) -> str: return text +# ==текст== — пометка правки для рецензента. Разметки такого рода в +# CommonMark нет, поэтому она разбирается здесь, на уровне текстовых узлов: +# внутрь листингов и инлайн-кода регулярное выражение не попадает, и «==» +# в исходниках на C++ остаётся сравнением. Границы выделения примыкают к +# тексту вплотную — как у прочих парных знаков разметки. +_MARK_RE = re.compile(r"==(?=\S)(.+?)(?<=\S)==") + + +def render_text(text: str) -> str: + return split_compounds(bind_initials(escape_latex(text))) + + +def highlight_word(word: str) -> str: + """Слово в жёлтой плашке, составное — по плашке на каждую часть. + + Плашка неразрывна, поэтому шорткат переноса «"=» внутри неё не работает + и «аппаратно-программный» целиком вылезает за поле. Дефис получает свою + плашку, а точка переноса ставится после неё. + """ + parts = [part for part in word.split("-") if part] + return "\\hlw{-}\\allowbreak ".join(f"\\hlw{{{part}}}" for part in parts) + + +def highlight(text: str) -> str: + """Жёлтая плашка на каждом слове. + + Одной плашкой на всё выделение строка не разорвётся по его середине, + а выделять приходится и целые предложения. + """ + escaped = bind_initials(escape_latex(text)) + return " ".join(highlight_word(word) for word in escaped.split(" ") if word) + + @inline("text") def _text(tok: Token) -> str: - return split_compounds(bind_initials(escape_latex(tok.content))) + parts: list[str] = [] + end = 0 + for match in _MARK_RE.finditer(tok.content): + parts.append(render_text(tok.content[end : match.start()])) + parts.append(highlight(match.group(1))) + end = match.end() + parts.append(render_text(tok.content[end:])) + return "".join(parts) @inline("strong_open") diff --git a/tests/test_render.py b/tests/test_render.py index 52e17b4..e63a1ed 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -37,6 +37,30 @@ def test_unnumbered_heading() -> None: assert "ВВЕДЕНИЕ" in tex +def test_mark_highlights_every_word() -> None: + tex = render("Обычный ==правленый текст== дальше.\n") + assert "\\hlw{правленый} \\hlw{текст}" in tex + assert "Обычный " in tex + assert " дальше." in tex + + +def test_mark_splits_compound_word_into_separate_boxes() -> None: + # Плашка неразрывна: составное слово одним боксом вылезает за поле. + tex = render("==аппаратно-программный комплекс==\n") + assert "\\hlw{аппаратно}\\hlw{-}\\allowbreak \\hlw{программный}" in tex + + +def test_mark_in_heading_reaches_the_contents() -> None: + tex = render("## ==Контейнеризация серверного приложения==\n") + assert "\\subsection{\\hlw{Контейнеризация}" in tex + + +def test_comparison_in_listing_is_not_a_mark() -> None: + tex = body(render("```cpp\nif (a==b && c==d) return;\n```\n")) + assert "\\hlw" not in tex + assert "a==b" in tex + + def test_list_punctuation() -> None: tex = render("- один\n- два\n- три\n") assert " \\item один;" in tex