From f4ffa5dabe18f5eb9d1cbacfdb79dec5a8bf8040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=BE=E6=A5=9A=E7=AC=91?= Date: Wed, 23 Sep 2026 00:38:17 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20FIX:=20Restore=20rules=20when=20?= =?UTF-8?q?reset=5Frules=20exits=20with=20an=20exception?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + docs/using.md | 1 + markdown_it/main.py | 12 +++++++----- tests/test_api/test_main.py | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 040ff138..b13b5049 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* šŸ› Restore temporary rule changes when an exception exits `MarkdownIt.reset_rules()`. * ✨ Add `--enable-tables` to the CLI for file, standard input and interactive parsing in [#422](https://github.com/executablebooks/markdown-it-py/pull/422) * šŸ› Fix CLI interactive mode joining input lines with an extra newline, which split every line into its own paragraph and broke hard line breaks, in [#172](https://github.com/executablebooks/markdown-it-py/issues/172) * šŸ› Fix trimming and splitting with the Python whitespace set instead of the CommonMark one, which dropped U+001C–U+001F and U+0085 from paragraphs, headings, table cells and fence info strings and let distinct reference labels resolve each other, in [#418](https://github.com/executablebooks/markdown-it-py/pull/418), thanks to [@Nexory](https://github.com/Nexory) diff --git a/docs/using.md b/docs/using.md index aae3e858..5811208a 100644 --- a/docs/using.md +++ b/docs/using.md @@ -102,6 +102,7 @@ md.enable(["list", "emphasis"]).render("- __*emphasise this*__") ``` You can temporarily modify rules with the `reset_rules` context manager. +The previous rule configuration is restored when the context exits, including when an exception is raised. ```{jupyter-execute} with md.reset_rules(): diff --git a/markdown_it/main.py b/markdown_it/main.py index 050a3b87..2c5eed9a 100644 --- a/markdown_it/main.py +++ b/markdown_it/main.py @@ -216,11 +216,13 @@ def disable( def reset_rules(self) -> Generator[None, None, None]: """A context manager, that will reset the current enabled rules on exit.""" chain_rules = self.get_active_rules() - yield - for chain, rules in chain_rules.items(): - if chain != "inline2": - self[chain].ruler.enableOnly(rules) - self.inline.ruler2.enableOnly(chain_rules["inline2"]) + try: + yield + finally: + for chain, rules in chain_rules.items(): + if chain != "inline2": + self[chain].ruler.enableOnly(rules) + self.inline.ruler2.enableOnly(chain_rules["inline2"]) def add_render_rule( self, name: str, function: Callable[..., Any], fmt: str = "html" diff --git a/tests/test_api/test_main.py b/tests/test_api/test_main.py index c84e33de..574d8b7c 100644 --- a/tests/test_api/test_main.py +++ b/tests/test_api/test_main.py @@ -174,6 +174,41 @@ def test_reset(): } +def test_reset_after_exception() -> None: + """Restore all rule chains without swallowing the original exception.""" + md = MarkdownIt("zero") + original_rules = md.get_active_rules() + error = RuntimeError("rendering failed") + + with pytest.raises(RuntimeError) as exc_info, md.reset_rules(): + md.enable(["heading", "emphasis"]) + md.disable("text_join") + assert md.render("# *heading*") == "

heading

\n" + raise error + + assert exc_info.value is error + assert md.get_active_rules() == original_rules + assert md.render("# *heading*") == "

# *heading*

\n" + + +def test_nested_reset_after_exception() -> None: + """An inner failure restores the outer context's temporary configuration.""" + md = MarkdownIt() + original_rules = md.get_active_rules() + + with md.reset_rules(): + md.disable("heading") + outer_rules = md.get_active_rules() + with pytest.raises(RuntimeError), md.reset_rules(): + md.disable("emphasis") + raise RuntimeError("inner rendering failed") + assert md.get_active_rules() == outer_rules + assert md.render("# *heading*") == "

# heading

\n" + + assert md.get_active_rules() == original_rules + assert md.render("# *heading*") == "

heading

\n" + + def test_parseInline(): md = MarkdownIt() tokens = md.parseInline("abc\n\n> xyz")