From db14d775e5a9ac0bf3bdfd0f4f9e28e96cdafe2f Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Wed, 13 May 2026 10:29:20 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=8C=20Stop=20directive-option=20parsin?= =?UTF-8?q?g=20at=20colon=20fences?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In order to determine a directive option block, we look for all lines at the top of the directive that begin with `:`. However, this failed for a nested colon fence like: ``` ::::{note} :class: abc :::{note} content ::: :::: ``` This adds a check to terminate the option block when a line starts with 3 or more colons. --- myst_parser/parsers/directives.py | 5 ++++- tests/test_renderers/test_parse_directives.py | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/myst_parser/parsers/directives.py b/myst_parser/parsers/directives.py index d2512831..4f4f7ae3 100644 --- a/myst_parser/parsers/directives.py +++ b/myst_parser/parsers/directives.py @@ -192,7 +192,10 @@ def _parse_directive_options( content_lines = content.splitlines() yaml_lines = [] while content_lines: - if not content_lines[0].lstrip().startswith(":"): + stripped = content_lines[0].lstrip() + # Stop at lines that don't start with a colon or have 3+ colons, which are colon fences + # (e.g. nested directives like `::::{other}`) + if not stripped.startswith(":") or stripped.startswith(":::"): break yaml_lines.append(content_lines.pop(0).lstrip()[1:]) options_block = "\n".join(yaml_lines) diff --git a/tests/test_renderers/test_parse_directives.py b/tests/test_renderers/test_parse_directives.py index 3813cc97..36eba61b 100644 --- a/tests/test_renderers/test_parse_directives.py +++ b/tests/test_renderers/test_parse_directives.py @@ -120,3 +120,10 @@ def test_additional_options(): ) assert len(result.warnings) == 1 assert "Unknown option" in result.warnings[0].msg + + +def test_colon_options_stop_at_colon_fence(): + """Options parsing should stop when encountering a colon fence (3+ colons).""" + result = parse_directive_text(Note, "", ":class: xxx\n::::{other}\ncontent\n::::") + assert result.options == {"class": ["xxx"]} + assert result.body == ["::::{other}", "content", "::::"]