File size: 4,354 Bytes
d1ceb73 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
import re
from ._base import DirectiveParser, BaseDirective
__all__ = ['FencedDirective']
_type_re = re.compile(r'^ *\{[a-zA-Z0-9_-]+\}')
_directive_re = re.compile(
r'\{(?P<type>[a-zA-Z0-9_-]+)\} *(?P<title>[^\n]*)(?:\n|$)'
r'(?P<options>(?:\:[a-zA-Z0-9_-]+\: *[^\n]*\n+)*)'
r'\n*(?P<text>(?:[^\n]*\n+)*)'
)
class FencedParser(DirectiveParser):
name = 'fenced_directive'
@staticmethod
def parse_type(m: re.Match):
return m.group('type')
@staticmethod
def parse_title(m: re.Match):
return m.group('title')
@staticmethod
def parse_content(m: re.Match):
return m.group('text')
class FencedDirective(BaseDirective):
"""A **fenced** style of directive looks like a fenced code block, it is
inspired by markdown-it-docutils. The syntax looks like:
.. code-block:: text
```{directive-type} title
:option-key: option value
:option-key: option value
content text here
```
To use ``FencedDirective``, developers can add it into plugin list in
the :class:`Markdown` instance:
.. code-block:: python
import mistune
from mistune.directives import FencedDirective, Admonition
md = mistune.create_markdown(plugins=[
# ...
FencedDirective([Admonition()]),
])
FencedDirective is using >= 3 backticks or curly-brackets for the fenced
syntax. Developers can change it to other characters, e.g. colon:
.. code-block:: python
directive = FencedDirective([Admonition()], ':')
And then the directive syntax would look like:
.. code-block:: text
::::{note} Nesting directives
You can nest directives by ensuring the start and end fence matching
the length. For instance, in this example, the admonition is started
with 4 colons, then it should end with 4 colons.
You can nest another admonition with other length of colons except 4.
:::{tip} Longer outermost fence
It would be better that you put longer markers for the outer fence,
and shorter markers for the inner fence. In this example, we put 4
colons outsie, and 3 colons inside.
:::
::::
:param plugins: list of directive plugins
:param markers: characters to determine the fence, default is backtick
and curly-bracket
"""
parser = FencedParser
def __init__(self, plugins, markers='`~'):
super(FencedDirective, self).__init__(plugins)
self.markers = markers
_marker_pattern = '|'.join(re.escape(c) for c in markers)
self.directive_pattern = (
r'^(?P<fenced_directive_mark>(?:' + _marker_pattern + r'){3,})'
r'\{[a-zA-Z0-9_-]+\}'
)
def _process_directive(self, block, marker, start, state):
mlen = len(marker)
cursor_start = start + len(marker)
_end_pattern = (
r'^ {0,3}' + marker[0] + '{' + str(mlen) + r',}'
r'[ \t]*(?:\n|$)'
)
_end_re = re.compile(_end_pattern, re.M)
_end_m = _end_re.search(state.src, cursor_start)
if _end_m:
text = state.src[cursor_start:_end_m.start()]
end_pos = _end_m.end()
else:
text = state.src[cursor_start:]
end_pos = state.cursor_max
m = _directive_re.match(text)
if not m:
return
self.parse_method(block, m, state)
return end_pos
def parse_directive(self, block, m, state):
marker = m.group('fenced_directive_mark')
return self._process_directive(block, marker, m.start(), state)
def parse_fenced_code(self, block, m, state):
info = m.group('fenced_3')
if not info or not _type_re.match(info):
return block.parse_fenced_code(m, state)
if state.depth() >= block.max_nested_level:
return block.parse_fenced_code(m, state)
marker = m.group('fenced_2')
return self._process_directive(block, marker, m.start(), state)
def __call__(self, md):
super(FencedDirective, self).__call__(md)
if self.markers == '`~':
md.block.register('fenced_code', None, self.parse_fenced_code)
else:
self.register_block_parser(md, 'fenced_code')
|