Spaces:
Running
Running
SilverElixir commited on
Commit ·
aa3af88
1
Parent(s): c40bb87
Convert newlines to br in rich path so lists stop collapsing
Browse files- lumen_formatting.py +8 -0
- tests/test_lumen_formatting.py +22 -4
lumen_formatting.py
CHANGED
|
@@ -578,6 +578,14 @@ def _md_to_rich_html(text: str) -> str:
|
|
| 578 |
|
| 579 |
text = text.replace(_BLOCKQUOTE_START, "<blockquote>").replace(_BLOCKQUOTE_END, "</blockquote>")
|
| 580 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 581 |
for key, (tag_level, inner) in _heads.items():
|
| 582 |
text = text.replace(key, f"<h{tag_level}>{_rich_inline(inner)}</h{tag_level}>")
|
| 583 |
for key, (header, rows) in _tables.items():
|
|
|
|
| 578 |
|
| 579 |
text = text.replace(_BLOCKQUOTE_START, "<blockquote>").replace(_BLOCKQUOTE_END, "</blockquote>")
|
| 580 |
|
| 581 |
+
# Прод 07.10.2026: в rich-сообщении голый перенос строки НЕ разрыв — Telegram
|
| 582 |
+
# склеивает абзацы, и разнесённый список приходил одной кашей (в обычном
|
| 583 |
+
# HTML-пути \n работает, поэтому баг был виден только в финале). Переносы
|
| 584 |
+
# заменяем на <br/> — ровно так их показывают доки для <blockquote>.
|
| 585 |
+
# Момент: код/ссылки/таблицы/заголовки ещё в плейсхолдерах (без \n внутри),
|
| 586 |
+
# поэтому многострочные <pre> не пострадают.
|
| 587 |
+
text = text.replace("\n", "<br/>")
|
| 588 |
+
|
| 589 |
for key, (tag_level, inner) in _heads.items():
|
| 590 |
text = text.replace(key, f"<h{tag_level}>{_rich_inline(inner)}</h{tag_level}>")
|
| 591 |
for key, (header, rows) in _tables.items():
|
tests/test_lumen_formatting.py
CHANGED
|
@@ -432,10 +432,10 @@ def test_md_to_rich_html_splits_inline_lists_end_to_end():
|
|
| 432 |
bullets = "Вот несколько хороших мелодрамм: " + " • ".join(
|
| 433 |
["фильм номер %d с тёплым и подробным описанием сюжета" % i for i in range(4)]
|
| 434 |
)
|
| 435 |
-
assert "
|
| 436 |
numbered = "1. Пункт первый с достаточным пояснением для проверки. 2. Пункт второй с достаточным пояснением для проверки. 3. Пункт третий с достаточным пояснением для проверки."
|
| 437 |
rich = lumen_formatting._md_to_rich_html(numbered)
|
| 438 |
-
assert "
|
| 439 |
|
| 440 |
|
| 441 |
def test_md_to_rich_html_scrubs_stray_latex_like_the_plain_path():
|
|
@@ -473,7 +473,10 @@ def test_render_paths_parity_on_shared_cases():
|
|
| 473 |
"список: " + " • ".join(f"пункт {i} с текстом подлиннее" for i in range(4)),
|
| 474 |
]
|
| 475 |
for case in shared:
|
| 476 |
-
|
|
|
|
|
|
|
|
|
|
| 477 |
|
| 478 |
|
| 479 |
def test_render_paths_differ_only_for_headings():
|
|
@@ -483,6 +486,21 @@ def test_render_paths_differ_only_for_headings():
|
|
| 483 |
assert lumen_formatting._md_to_rich_html("## Title") == "<h3>Title</h3>"
|
| 484 |
|
| 485 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 486 |
def test_md_to_rich_html_splits_three_bullets_prod_movies():
|
| 487 |
# Прод 26.09.2026: ответ про мелодрамы — 3 пункта склеились, rich-путь молчал.
|
| 488 |
text = (
|
|
@@ -491,7 +509,7 @@ def test_md_to_rich_html_splits_three_bullets_prod_movies():
|
|
| 491 |
"• Crazy Rich Asians (2018): вясёлая рамантычная камедыя пра кітайскую эліту, поўная колеру і музыкі."
|
| 492 |
)
|
| 493 |
assert len(text) >= 200
|
| 494 |
-
assert "
|
| 495 |
|
| 496 |
|
| 497 |
def test_md_to_html_full_pipeline_converts_bullet_list_with_bold():
|
|
|
|
| 432 |
bullets = "Вот несколько хороших мелодрамм: " + " • ".join(
|
| 433 |
["фильм номер %d с тёплым и подробным описанием сюжета" % i for i in range(4)]
|
| 434 |
)
|
| 435 |
+
assert "<br/>• " in lumen_formatting._md_to_rich_html(bullets)
|
| 436 |
numbered = "1. Пункт первый с достаточным пояснением для проверки. 2. Пункт второй с достаточным пояснением для проверки. 3. Пункт третий с достаточным пояснением для проверки."
|
| 437 |
rich = lumen_formatting._md_to_rich_html(numbered)
|
| 438 |
+
assert "<br/>2. " in rich and "<br/>3. " in rich
|
| 439 |
|
| 440 |
|
| 441 |
def test_md_to_rich_html_scrubs_stray_latex_like_the_plain_path():
|
|
|
|
| 473 |
"список: " + " • ".join(f"пункт {i} с текстом подлиннее" for i in range(4)),
|
| 474 |
]
|
| 475 |
for case in shared:
|
| 476 |
+
# Перенос строки в rich — это <br/> (прод 07.10.2026: голый \n там склеивался),
|
| 477 |
+
# в обычном HTML — \n. Поэтому сравниваем с приведением к общему виду.
|
| 478 |
+
rich = lumen_formatting._md_to_rich_html(case).replace("<br/>", "\n")
|
| 479 |
+
assert lumen_formatting._md_to_html(case) == rich, case
|
| 480 |
|
| 481 |
|
| 482 |
def test_render_paths_differ_only_for_headings():
|
|
|
|
| 486 |
assert lumen_formatting._md_to_rich_html("## Title") == "<h3>Title</h3>"
|
| 487 |
|
| 488 |
|
| 489 |
+
def test_md_to_rich_html_turns_newlines_into_br():
|
| 490 |
+
# Прод 07.10.2026: в финальном rich-сообщении разнесённый список приходил одной
|
| 491 |
+
# кашей — Telegram склеивает абзацы, голый "\n" разрывом не считается. Разрыв
|
| 492 |
+
# в rich даёт только <br/> (так же в доках показан <blockquote>). Падает на
|
| 493 |
+
# старом коде, где переносы оставались "\n" и список выглядел слипшимся.
|
| 494 |
+
b = chr(96) * 3
|
| 495 |
+
text = "список:\n• раз\n• два\n\n" + b + "python\nx = 1\ny = 2\n" + b
|
| 496 |
+
rich = lumen_formatting._md_to_rich_html(text)
|
| 497 |
+
assert "список:<br/>• раз<br/>• два<br/><br/>" in rich
|
| 498 |
+
# Переносы внутри блока кода обязаны остаться переносами.
|
| 499 |
+
assert '<pre><code class="language-python">x = 1\ny = 2</code></pre>' in rich
|
| 500 |
+
# Вне <pre> голых переносов не остаётся.
|
| 501 |
+
assert "\n" not in rich.replace("x = 1\ny = 2", "")
|
| 502 |
+
|
| 503 |
+
|
| 504 |
def test_md_to_rich_html_splits_three_bullets_prod_movies():
|
| 505 |
# Прод 26.09.2026: ответ про мелодрамы — 3 пункта склеились, rich-путь молчал.
|
| 506 |
text = (
|
|
|
|
| 509 |
"• Crazy Rich Asians (2018): вясёлая рамантычная камедыя пра кітайскую эліту, поўная колеру і музыкі."
|
| 510 |
)
|
| 511 |
assert len(text) >= 200
|
| 512 |
+
assert "<br/>• " in lumen_formatting._md_to_rich_html(text)
|
| 513 |
|
| 514 |
|
| 515 |
def test_md_to_html_full_pipeline_converts_bullet_list_with_bold():
|