Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions docs/using.md
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
12 changes: 7 additions & 5 deletions markdown_it/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
35 changes: 35 additions & 0 deletions tests/test_api/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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*") == "<h1><em>heading</em></h1>\n"
raise error

assert exc_info.value is error
assert md.get_active_rules() == original_rules
assert md.render("# *heading*") == "<p># *heading*</p>\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*") == "<p># <em>heading</em></p>\n"

assert md.get_active_rules() == original_rules
assert md.render("# *heading*") == "<h1><em>heading</em></h1>\n"


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