Skip to content
Merged
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 marko/ext/gfm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
elements.Table,
elements.TableRow,
elements.TableCell,
elements.Alert,
],
renderer_mixins=[renderer.GFMRendererMixin],
)
Expand Down
23 changes: 23 additions & 0 deletions marko/ext/gfm/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,26 @@ def __init__(self, text: str) -> None:
self.inline_body = text.strip().replace("\\|", "|")
self.header = False
self.align: str | None = None


class Alert(block.Quote):
"""Alert block element: block quote with a header like WARNING, NOTE, TIP, IMPORTANT, or CAUTION."""

priority = block.Quote.priority + 1

@classmethod
def match(cls, source):
return source.expect_re(r" {,3}>\s*\[\!(WARNING|NOTE|TIP|IMPORTANT|CAUTION)\]")

@classmethod
def parse(cls, source):
alert_type = source.match.group(1)
source.next_line(require_prefix=False)
source.consume()
state = cls(alert_type)
with source.under_state(state):
state.children = source.parser.parse_source(source)
return state

def __init__(self, alert_type):
self.alert_type = alert_type
21 changes: 21 additions & 0 deletions marko/ext/gfm/renderer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# mypy: disable-error-code="no-redef"
from __future__ import annotations

import re

from marko.helpers import render_dispatch
Expand Down Expand Up @@ -104,3 +106,22 @@ def render_url(self, element):
@render_url.dispatch(MarkdownRenderer)
def render_url(self, element):
return element.dest

@render_dispatch(HTMLRenderer)
def render_alert(self, element):
header = self.escape_html(element.alert_type)
children = self.render_children(element)
return (
f'<blockquote class="alert alert-{element.alert_type.lower()}">\n'
f"<p>{header.title()}</p>\n{children}</blockquote>\n"
)

@render_alert.dispatch(MarkdownRenderer)
def render_alert(self, element):
lines: list[str] = []
lines.append(self._prefix + f"> [!{element.alert_type}]\n")
with self.container("> ", "> "):
for child in element.children:
lines.append(self.render(child))
self._prefix = self._second_prefix
return "".join(lines)
3 changes: 3 additions & 0 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@ def test_gfm_markdown_renderer(self):
| Apple | $1 |
| Orange | $2 |

> [!CAUTION]
> Upcoming bugs

[^1]: go to https://example.com
"""
)
Expand Down
28 changes: 28 additions & 0 deletions tests/test_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,31 @@ def test_codehilite_options(self):
def test_render_code_block_with_extra(self):
content = '```python filename="test.py"\nprint("hello")\n```'
assert '<span class="filename">test.py</span>' in self.markdown(content)


class TestGFMAlert:
def setup_method(self):
from marko import Markdown
from marko.ast_renderer import ASTRenderer
from marko.ext.gfm import GFM

self.md_ast = Markdown(renderer=ASTRenderer, extensions=[GFM])
self.md_html = Markdown(extensions=[GFM])

def test_alert_ast(self):
text = "> [!WARNING]\n> Foo bar\n> Bar\n"
ast = self.md_ast(text)
admon = ast["children"][0]
assert admon["element"] == "alert"
assert admon["alert_type"] == "WARNING"
inner = admon["children"][0]["children"]
assert inner[0]["children"] == "Foo bar"
assert inner[1]["element"] == "line_break"
assert inner[2]["children"] == "Bar"

def test_alert_html(self):
text = "> [!WARNING]\n> Foo bar\n> Bar\n"
html = self.md_html(text)
assert '<blockquote class="alert alert-warning">' in html
assert "<p>Warning</p>" in html
assert "<p>Foo bar\nBar</p>" in html
Loading