Skip to content
Open
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
40 changes: 40 additions & 0 deletions docs/src/markdown/extensions/blocks/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 8 additions & 1 deletion pymdownx/blocks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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))
Expand Down
174 changes: 174 additions & 0 deletions tests/test_extensions/test_blocks/test_general_blocks_alternate.py
Original file line number Diff line number Diff line change
@@ -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
///
''',
'''
<p>/// html | div
@option: whatever</p>
<p>content
///</p>
''',
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
///
''',
'''
<p>/// html | div
@attrs: whatever</p>
<p>content
///</p>
''',
True
)

def test_no_arg(self):
"""Test no options."""

self.check_markdown(
R'''
/// html
@attrs: whatever

content
///
''',
'''
<p>/// html
@attrs: whatever</p>
<p>content
///</p>
''',
True
)

def test_too_many_args(self):
"""Test too many options."""

self.check_markdown(
R'''
/// define
@option: whatever

content
///
''',
'''
<p>/// define
@option: whatever</p>
<p>content
///</p>
''',
True
)

def test_commented_frontmatter(self):
"""Test commented frontmatter."""

self.check_markdown(
R'''
/// html | div
# @attrs: {class: test}

content
///
''',
'''
<div>
<p>content</p>
</div>
''',
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
///
''',
'''
<div class="admonition some classes" id="an-id" name="some value">
<p class="admonition-title">Title</p>
<p>content</p>
</div>
''',
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
///
''',
'''
<div class="admonition some classes" id="an-id" name="some value">
<p class="admonition-title">Title</p>
<p>content</p>
</div>
''',
True
)

def test_bad_attributes(self):
"""Test no attributes."""

self.check_markdown(
R'''
/// admonition | Title
@attrs: {'+': 'value'}
content
///
''',
'''
<p>/// admonition | Title
@attrs: {'+': 'value'}
content
///</p>
''',
True
)