diff --git a/docs/src/markdown/extensions/blocks/index.md b/docs/src/markdown/extensions/blocks/index.md index 74451422f..c9c88df6a 100644 --- a/docs/src/markdown/extensions/blocks/index.md +++ b/docs/src/markdown/extensions/blocks/index.md @@ -131,6 +131,46 @@ Indented content should always be separated from the block header by one empty l option block. /// +### Alternative syntax for options + +Some formatters tends to remove the leading four spaces before the additional option. In that case, an alternative syntax is possible by replacing the spaces by a single `@` : + +/// tab | Alternative syntax +```text title="Admonition" +/// admonition | Some title +@type: warning + +Some content +/// +``` + +//// html | div.result +///// admonition | Some title +@type: warning + +Some content +///// +//// +/// + +/// tab | Normal syntax +```text title="Admonition" +/// admonition | Some title + type: warning + +Some content +/// +``` + +//// html | div.result +///// admonition | Some title + type: warning + +Some content +///// +//// +/// + ## Nesting Generic blocks can be nested as long as the block fence differs in number of leading tokens. This is similar to how diff --git a/pymdownx/blocks/__init__.py b/pymdownx/blocks/__init__.py index 616f697c0..0727df92b 100644 --- a/pymdownx/blocks/__init__.py +++ b/pymdownx/blocks/__init__.py @@ -41,7 +41,7 @@ ) RE_INDENT_YAML_LINE = re.compile(r'(?m)^(?:[ ]{4,}(?!\s).*?(?:\n|$))+') - +RE_ALTERNATE_YAML_LINE = re.compile(r'(?m)^(?:@ *)(.*?)(?:\n|$)+') class BlockEntry: """Track Block entries.""" @@ -197,6 +197,7 @@ def __init__(self, parser: BlockParser, md: Markdown) -> None: self.start = RE_START self.end = RE_END self.yaml_line = RE_INDENT_YAML_LINE + self.yaml_line_alternate = RE_ALTERNATE_YAML_LINE def detab_by_length(self, text: str, length: int) -> tuple[str, str]: """Remove a tab from the front of each line of the given text.""" @@ -322,6 +323,12 @@ def split_header(self, block: str, length: int) -> tuple[dict[str, Any] | None, blocks.insert(0, end) block = block[:m.start(0)] + # Convert alternative yaml-ish header + m = self.yaml_line_alternate.match(block) + if m is not None: + block = ' ' * 4 + m.group(1) + + # Extract header m = self.yaml_line.match(block) if m is not None: config = textwrap.dedent(m.group(0)) diff --git a/tests/test_extensions/test_blocks/test_general_blocks_alternate.py b/tests/test_extensions/test_blocks/test_general_blocks_alternate.py new file mode 100644 index 000000000..a7b8e6113 --- /dev/null +++ b/tests/test_extensions/test_blocks/test_general_blocks_alternate.py @@ -0,0 +1,174 @@ +"""Using the "tab" Blocks extension, test general cases for Blocks for the alternate attribute syntax.""" +from ... import util +import unittest +from pymdownx.blocks import block +import markdown + +class TestBlockUndefinedOptionAlternate(util.MdCase): + """Test Blocks with undefined options.""" + + extension = ['pymdownx.blocks.html', 'pymdownx.blocks.definition'] + + def test_undefined_option(self): + """An undefined option will cause the block parsing to fail.""" + + self.check_markdown( + R''' + /// html | div + @option: whatever + + content + /// + ''', + ''' +

/// html | div + @option: whatever

+

content + ///

+ ''', + True + ) + + def test_bad_option(self): + """An undefined option will cause the block parsing to fail.""" + + self.check_markdown( + R''' + /// html | div + @attrs: whatever + + content + /// + ''', + ''' +

/// html | div + @attrs: whatever

+

content + ///

+ ''', + True + ) + + def test_no_arg(self): + """Test no options.""" + + self.check_markdown( + R''' + /// html + @attrs: whatever + + content + /// + ''', + ''' +

/// html + @attrs: whatever

+

content + ///

+ ''', + True + ) + + def test_too_many_args(self): + """Test too many options.""" + + self.check_markdown( + R''' + /// define + @option: whatever + + content + /// + ''', + ''' +

/// define + @option: whatever

+

content + ///

+ ''', + True + ) + + def test_commented_frontmatter(self): + """Test commented frontmatter.""" + + self.check_markdown( + R''' + /// html | div + # @attrs: {class: test} + + content + /// + ''', + ''' +
+

content

+
+ ''', + True + ) + + +class TestAttributesAlternate(util.MdCase): + """Test Blocks tab cases.""" + + extension = ['pymdownx.blocks.admonition'] + + def test_attributes(self): + """Test attributes.""" + + self.check_markdown( + R''' + /// admonition | Title + @attrs: {class: some classes, id: an-id, name: some value} + + content + /// + ''', + ''' +
+

Title

+

content

+
+ ''', + True + ) + + def test_attributes_with_spaces(self): + """Test attributes.""" + + self.check_markdown( + R''' + /// admonition | Title + @ attrs: {class: some classes, id: an-id, name: some value} + + content + /// + ''', + ''' +
+

Title

+

content

+
+ ''', + True + ) + + def test_bad_attributes(self): + """Test no attributes.""" + + self.check_markdown( + R''' + /// admonition | Title + @attrs: {'+': 'value'} + content + /// + ''', + ''' +

/// admonition | Title + @attrs: {'+': 'value'} + content + ///

+ ''', + True + ) \ No newline at end of file