Building Project Files
هذا الالتزام موجود في:
@@ -0,0 +1,35 @@
|
||||
from typing import List
|
||||
|
||||
from ._base import BaseDirective, DirectiveParser, DirectivePlugin
|
||||
from ._fenced import FencedDirective
|
||||
from ._rst import RSTDirective
|
||||
from .admonition import Admonition
|
||||
from .image import Figure, Image
|
||||
from .include import Include
|
||||
from .toc import TableOfContents
|
||||
|
||||
|
||||
class RstDirective(RSTDirective): # pragma: no cover
|
||||
def __init__(self, plugins: List[DirectivePlugin]) -> None:
|
||||
super(RstDirective, self).__init__(plugins)
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"'RstDirective' is deprecated, please use 'RSTDirective' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"DirectiveParser",
|
||||
"BaseDirective",
|
||||
"DirectivePlugin",
|
||||
"RSTDirective",
|
||||
"FencedDirective",
|
||||
"Admonition",
|
||||
"TableOfContents",
|
||||
"Include",
|
||||
"Image",
|
||||
"Figure",
|
||||
]
|
ملف ثنائي غير معروض.
ملف ثنائي غير معروض.
ملف ثنائي غير معروض.
ملف ثنائي غير معروض.
ملف ثنائي غير معروض.
ملف ثنائي غير معروض.
ملف ثنائي غير معروض.
ملف ثنائي غير معروض.
162
venv/lib/python3.12/site-packages/mistune/directives/_base.py
Normal file
162
venv/lib/python3.12/site-packages/mistune/directives/_base.py
Normal file
@@ -0,0 +1,162 @@
|
||||
import re
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
Match,
|
||||
Optional,
|
||||
Tuple,
|
||||
Type,
|
||||
Union,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..block_parser import BlockParser
|
||||
from ..core import BlockState
|
||||
from ..markdown import Markdown
|
||||
|
||||
|
||||
class DirectiveParser(ABCMeta):
|
||||
name = "directive"
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def parse_type(m: Match[str]) -> str:
|
||||
raise NotImplementedError()
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def parse_title(m: Match[str]) -> str:
|
||||
raise NotImplementedError()
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def parse_content(m: Match[str]) -> str:
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
def parse_tokens(cls, block: "BlockParser", text: str, state: "BlockState") -> Iterable[Dict[str, Any]]:
|
||||
if state.depth() >= block.max_nested_level - 1 and cls.name in block.rules:
|
||||
rules = list(block.rules)
|
||||
rules.remove(cls.name)
|
||||
else:
|
||||
rules = block.rules
|
||||
child = state.child_state(text)
|
||||
block.parse(child, rules)
|
||||
return child.tokens
|
||||
|
||||
@staticmethod
|
||||
def parse_options(m: Match[str]) -> List[Tuple[str, str]]:
|
||||
text = m.group("options")
|
||||
if not text.strip():
|
||||
return []
|
||||
|
||||
options = []
|
||||
for line in re.split(r"\n+", text):
|
||||
line = line.strip()[1:]
|
||||
if not line:
|
||||
continue
|
||||
i = line.find(":")
|
||||
k = line[:i]
|
||||
v = line[i + 1 :].strip()
|
||||
options.append((k, v))
|
||||
return options
|
||||
|
||||
|
||||
class BaseDirective(metaclass=ABCMeta):
|
||||
parser: Type[DirectiveParser]
|
||||
directive_pattern: Optional[str] = None
|
||||
|
||||
def __init__(self, plugins: List["DirectivePlugin"]):
|
||||
self._methods: Dict[
|
||||
str,
|
||||
Callable[
|
||||
["BlockParser", Match[str], "BlockState"],
|
||||
Union[Dict[str, Any], List[Dict[str, Any]]],
|
||||
],
|
||||
] = {}
|
||||
self.__plugins = plugins
|
||||
|
||||
def register(
|
||||
self,
|
||||
name: str,
|
||||
fn: Callable[
|
||||
["BlockParser", Match[str], "BlockState"],
|
||||
Union[Dict[str, Any], List[Dict[str, Any]]],
|
||||
],
|
||||
) -> None:
|
||||
self._methods[name] = fn
|
||||
|
||||
def parse_method(
|
||||
self, block: "BlockParser", m: Match[str], state: "BlockState"
|
||||
) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
|
||||
_type = self.parser.parse_type(m)
|
||||
method = self._methods.get(_type)
|
||||
if method:
|
||||
try:
|
||||
token = method(block, m, state)
|
||||
except ValueError as e:
|
||||
token = {"type": "block_error", "raw": str(e)}
|
||||
else:
|
||||
text = m.group(0)
|
||||
token = {
|
||||
"type": "block_error",
|
||||
"raw": text,
|
||||
}
|
||||
|
||||
if isinstance(token, list):
|
||||
for tok in token:
|
||||
state.append_token(tok)
|
||||
else:
|
||||
state.append_token(token)
|
||||
return token
|
||||
|
||||
@abstractmethod
|
||||
def parse_directive(self, block: "BlockParser", m: Match[str], state: "BlockState") -> Optional[int]:
|
||||
raise NotImplementedError()
|
||||
|
||||
def register_block_parser(self, md: "Markdown", before: Optional[str] = None) -> None:
|
||||
md.block.register(
|
||||
self.parser.name,
|
||||
self.directive_pattern,
|
||||
self.parse_directive,
|
||||
before=before,
|
||||
)
|
||||
|
||||
def __call__(self, markdown: "Markdown") -> None:
|
||||
for plugin in self.__plugins:
|
||||
plugin.parser = self.parser
|
||||
plugin(self, markdown)
|
||||
|
||||
|
||||
class DirectivePlugin:
|
||||
parser: Type[DirectiveParser]
|
||||
|
||||
def __init__(self) -> None: ...
|
||||
|
||||
def parse_options(self, m: Match[str]) -> List[Tuple[str, str]]:
|
||||
return self.parser.parse_options(m)
|
||||
|
||||
def parse_type(self, m: Match[str]) -> str:
|
||||
return self.parser.parse_type(m)
|
||||
|
||||
def parse_title(self, m: Match[str]) -> str:
|
||||
return self.parser.parse_title(m)
|
||||
|
||||
def parse_content(self, m: Match[str]) -> str:
|
||||
return self.parser.parse_content(m)
|
||||
|
||||
def parse_tokens(self, block: "BlockParser", text: str, state: "BlockState") -> Iterable[Dict[str, Any]]:
|
||||
return self.parser.parse_tokens(block, text, state)
|
||||
|
||||
def parse(
|
||||
self, block: "BlockParser", m: Match[str], state: "BlockState"
|
||||
) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
|
||||
raise NotImplementedError()
|
||||
|
||||
def __call__(self, directive: BaseDirective, md: "Markdown") -> None:
|
||||
raise NotImplementedError()
|
151
venv/lib/python3.12/site-packages/mistune/directives/_fenced.py
Normal file
151
venv/lib/python3.12/site-packages/mistune/directives/_fenced.py
Normal file
@@ -0,0 +1,151 @@
|
||||
import re
|
||||
from typing import TYPE_CHECKING, List, Match, Optional
|
||||
|
||||
from ._base import BaseDirective, DirectiveParser, DirectivePlugin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..block_parser import BlockParser
|
||||
from ..core import BlockState
|
||||
from ..markdown import Markdown
|
||||
|
||||
|
||||
__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: Match[str]) -> str:
|
||||
return m.group("type")
|
||||
|
||||
@staticmethod
|
||||
def parse_title(m: Match[str]) -> str:
|
||||
return m.group("title")
|
||||
|
||||
@staticmethod
|
||||
def parse_content(m: Match[str]) -> str:
|
||||
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: List[DirectivePlugin], markers: str = "`~") -> None:
|
||||
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: "BlockParser", marker: str, start: int, state: "BlockState") -> Optional[int]:
|
||||
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 None
|
||||
|
||||
self.parse_method(block, m, state)
|
||||
return end_pos
|
||||
|
||||
def parse_directive(self, block: "BlockParser", m: Match[str], state: "BlockState") -> Optional[int]:
|
||||
marker = m.group("fenced_directive_mark")
|
||||
return self._process_directive(block, marker, m.start(), state)
|
||||
|
||||
def parse_fenced_code(self, block: "BlockParser", m: Match[str], state: "BlockState") -> Optional[int]:
|
||||
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: "Markdown") -> None:
|
||||
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")
|
81
venv/lib/python3.12/site-packages/mistune/directives/_rst.py
Normal file
81
venv/lib/python3.12/site-packages/mistune/directives/_rst.py
Normal file
@@ -0,0 +1,81 @@
|
||||
import re
|
||||
from typing import TYPE_CHECKING, Match, Optional
|
||||
|
||||
from ._base import BaseDirective, DirectiveParser
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..block_parser import BlockParser
|
||||
from ..core import BlockState
|
||||
from ..markdown import Markdown
|
||||
|
||||
__all__ = ["RSTDirective"]
|
||||
|
||||
|
||||
_directive_re = re.compile(
|
||||
r"\.\.( +)(?P<type>[a-zA-Z0-9_-]+)\:\: *(?P<title>[^\n]*)(?:\n|$)"
|
||||
r"(?P<options>(?: \1 {0,3}\:[a-zA-Z0-9_-]+\: *[^\n]*\n+)*)"
|
||||
r"\n*(?P<text>(?: \1 {0,3}[^\n]*\n+)*)"
|
||||
)
|
||||
|
||||
|
||||
class RSTParser(DirectiveParser):
|
||||
name = "rst_directive"
|
||||
|
||||
@staticmethod
|
||||
def parse_type(m: Match[str]) -> str:
|
||||
return m.group("type")
|
||||
|
||||
@staticmethod
|
||||
def parse_title(m: Match[str]) -> str:
|
||||
return m.group("title")
|
||||
|
||||
@staticmethod
|
||||
def parse_content(m: Match[str]) -> str:
|
||||
full_content = m.group(0)
|
||||
text = m.group("text")
|
||||
pretext = full_content[: -len(text)]
|
||||
leading = len(m.group(1)) + 2
|
||||
return "\n".join(line[leading:] for line in text.splitlines()) + "\n"
|
||||
|
||||
|
||||
class RSTDirective(BaseDirective):
|
||||
"""A RST style of directive syntax is inspired by reStructuredText.
|
||||
The syntax is very powerful that you can define a lot of custom
|
||||
features on your own. The syntax looks like:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
.. directive-type:: directive value
|
||||
:option-key: option value
|
||||
:option-key: option value
|
||||
|
||||
content text here
|
||||
|
||||
To use ``RSTDirective``, developers can add it into plugin list in
|
||||
the :class:`Markdown` instance:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import mistune
|
||||
from mistune.directives import RSTDirective, Admonition
|
||||
|
||||
md = mistune.create_markdown(plugins=[
|
||||
# ...
|
||||
RSTDirective([Admonition()]),
|
||||
])
|
||||
"""
|
||||
|
||||
parser = RSTParser
|
||||
directive_pattern = r"^\.\. +[a-zA-Z0-9_-]+\:\:"
|
||||
|
||||
def parse_directive(self, block: "BlockParser", m: Match[str], state: "BlockState") -> Optional[int]:
|
||||
m2 = _directive_re.match(state.src, state.cursor)
|
||||
if not m2:
|
||||
return None
|
||||
|
||||
self.parse_method(block, m2, state)
|
||||
return m2.end()
|
||||
|
||||
def __call__(self, markdown: "Markdown") -> None:
|
||||
super(RSTDirective, self).__call__(markdown)
|
||||
self.register_block_parser(markdown)
|
@@ -0,0 +1,76 @@
|
||||
from typing import TYPE_CHECKING, Any, Dict, Match
|
||||
|
||||
from ._base import BaseDirective, DirectivePlugin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..block_parser import BlockParser
|
||||
from ..core import BlockState
|
||||
from ..markdown import Markdown
|
||||
|
||||
|
||||
class Admonition(DirectivePlugin):
|
||||
SUPPORTED_NAMES = {
|
||||
"attention",
|
||||
"caution",
|
||||
"danger",
|
||||
"error",
|
||||
"hint",
|
||||
"important",
|
||||
"note",
|
||||
"tip",
|
||||
"warning",
|
||||
}
|
||||
|
||||
def parse(self, block: "BlockParser", m: Match[str], state: "BlockState") -> Dict[str, Any]:
|
||||
name = self.parse_type(m)
|
||||
attrs = {"name": name}
|
||||
options = dict(self.parse_options(m))
|
||||
if "class" in options:
|
||||
attrs["class"] = options["class"]
|
||||
|
||||
title = self.parse_title(m)
|
||||
if not title:
|
||||
title = name.capitalize()
|
||||
|
||||
content = self.parse_content(m)
|
||||
children = [
|
||||
{
|
||||
"type": "admonition_title",
|
||||
"text": title,
|
||||
},
|
||||
{
|
||||
"type": "admonition_content",
|
||||
"children": self.parse_tokens(block, content, state),
|
||||
},
|
||||
]
|
||||
return {
|
||||
"type": "admonition",
|
||||
"children": children,
|
||||
"attrs": attrs,
|
||||
}
|
||||
|
||||
def __call__(self, directive: "BaseDirective", md: "Markdown") -> None:
|
||||
for name in self.SUPPORTED_NAMES:
|
||||
directive.register(name, self.parse)
|
||||
|
||||
assert md.renderer is not None
|
||||
if md.renderer.NAME == "html":
|
||||
md.renderer.register("admonition", render_admonition)
|
||||
md.renderer.register("admonition_title", render_admonition_title)
|
||||
md.renderer.register("admonition_content", render_admonition_content)
|
||||
|
||||
|
||||
def render_admonition(self: Any, text: str, name: str, **attrs: Any) -> str:
|
||||
html = '<section class="admonition ' + name
|
||||
_cls = attrs.get("class")
|
||||
if _cls:
|
||||
html += " " + _cls
|
||||
return html + '">\n' + text + "</section>\n"
|
||||
|
||||
|
||||
def render_admonition_title(self: Any, text: str) -> str:
|
||||
return '<p class="admonition-title">' + text + "</p>\n"
|
||||
|
||||
|
||||
def render_admonition_content(self: Any, text: str) -> str:
|
||||
return text
|
176
venv/lib/python3.12/site-packages/mistune/directives/image.py
Normal file
176
venv/lib/python3.12/site-packages/mistune/directives/image.py
Normal file
@@ -0,0 +1,176 @@
|
||||
import re
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Match, Optional
|
||||
|
||||
from ..util import escape as escape_text
|
||||
from ..util import escape_url
|
||||
from ._base import BaseDirective, DirectivePlugin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..block_parser import BlockParser
|
||||
from ..core import BlockState
|
||||
from ..markdown import Markdown
|
||||
from ..renderers.html import HTMLRenderer
|
||||
|
||||
__all__ = ["Image", "Figure"]
|
||||
|
||||
_num_re = re.compile(r"^\d+(?:\.\d*)?")
|
||||
_allowed_aligns = ["top", "middle", "bottom", "left", "center", "right"]
|
||||
|
||||
|
||||
def _parse_attrs(options: Dict[str, Any]) -> Dict[str, Any]:
|
||||
attrs = {}
|
||||
if "alt" in options:
|
||||
attrs["alt"] = options["alt"]
|
||||
|
||||
# validate align
|
||||
align = options.get("align")
|
||||
if align and align in _allowed_aligns:
|
||||
attrs["align"] = align
|
||||
|
||||
height = options.get("height")
|
||||
width = options.get("width")
|
||||
if height and _num_re.match(height):
|
||||
attrs["height"] = height
|
||||
if width and _num_re.match(width):
|
||||
attrs["width"] = width
|
||||
if "target" in options:
|
||||
attrs["target"] = escape_url(options["target"])
|
||||
return attrs
|
||||
|
||||
|
||||
class Image(DirectivePlugin):
|
||||
NAME = "image"
|
||||
|
||||
def parse(self, block: "BlockParser", m: Match[str], state: "BlockState") -> Dict[str, Any]:
|
||||
options = dict(self.parse_options(m))
|
||||
attrs = _parse_attrs(options)
|
||||
attrs["src"] = self.parse_title(m)
|
||||
return {"type": "block_image", "attrs": attrs}
|
||||
|
||||
def __call__(self, directive: "BaseDirective", md: "Markdown") -> None:
|
||||
directive.register(self.NAME, self.parse)
|
||||
assert md.renderer is not None
|
||||
if md.renderer.NAME == "html":
|
||||
md.renderer.register("block_image", render_block_image)
|
||||
|
||||
|
||||
def render_block_image(
|
||||
self: "HTMLRenderer",
|
||||
src: str,
|
||||
alt: Optional[str] = None,
|
||||
width: Optional[str] = None,
|
||||
height: Optional[str] = None,
|
||||
**attrs: Any,
|
||||
) -> str:
|
||||
img = '<img src="' + escape_text(src) + '"'
|
||||
style = ""
|
||||
if alt:
|
||||
img += ' alt="' + escape_text(alt) + '"'
|
||||
if width:
|
||||
if width.isdigit():
|
||||
img += ' width="' + width + '"'
|
||||
else:
|
||||
style += "width:" + width + ";"
|
||||
if height:
|
||||
if height.isdigit():
|
||||
img += ' height="' + height + '"'
|
||||
else:
|
||||
style += "height:" + height + ";"
|
||||
if style:
|
||||
img += ' style="' + escape_text(style) + '"'
|
||||
|
||||
img += " />"
|
||||
|
||||
_cls = "block-image"
|
||||
align = attrs.get("align")
|
||||
if align:
|
||||
_cls += " align-" + align
|
||||
|
||||
target = attrs.get("target")
|
||||
if target:
|
||||
href = self.safe_url(target)
|
||||
outer = '<a class="' + _cls + '" href="' + href + '">'
|
||||
return outer + img + "</a>\n"
|
||||
else:
|
||||
return '<div class="' + _cls + '">' + img + "</div>\n"
|
||||
|
||||
|
||||
class Figure(DirectivePlugin):
|
||||
NAME = "figure"
|
||||
|
||||
def parse_directive_content(
|
||||
self, block: "BlockParser", m: Match[str], state: "BlockState"
|
||||
) -> Optional[List[Dict[str, Any]]]:
|
||||
content = self.parse_content(m)
|
||||
if not content:
|
||||
return None
|
||||
|
||||
tokens = list(self.parse_tokens(block, content, state))
|
||||
caption = tokens[0]
|
||||
if caption["type"] == "paragraph":
|
||||
caption["type"] = "figcaption"
|
||||
children = [caption]
|
||||
if len(tokens) > 1:
|
||||
children.append({"type": "legend", "children": tokens[1:]})
|
||||
return children
|
||||
return None
|
||||
|
||||
def parse(self, block: "BlockParser", m: Match[str], state: "BlockState") -> Dict[str, Any]:
|
||||
options = dict(self.parse_options(m))
|
||||
image_attrs = _parse_attrs(options)
|
||||
image_attrs["src"] = self.parse_title(m)
|
||||
|
||||
align = image_attrs.pop("align", None)
|
||||
fig_attrs = {}
|
||||
if align:
|
||||
fig_attrs["align"] = align
|
||||
for k in ["figwidth", "figclass"]:
|
||||
if k in options:
|
||||
fig_attrs[k] = options[k]
|
||||
|
||||
children = [{"type": "block_image", "attrs": image_attrs}]
|
||||
content = self.parse_directive_content(block, m, state)
|
||||
if content:
|
||||
children.extend(content)
|
||||
return {
|
||||
"type": "figure",
|
||||
"attrs": fig_attrs,
|
||||
"children": children,
|
||||
}
|
||||
|
||||
def __call__(self, directive: "BaseDirective", md: "Markdown") -> None:
|
||||
directive.register(self.NAME, self.parse)
|
||||
|
||||
assert md.renderer is not None
|
||||
if md.renderer.NAME == "html":
|
||||
md.renderer.register("figure", render_figure)
|
||||
md.renderer.register("block_image", render_block_image)
|
||||
md.renderer.register("figcaption", render_figcaption)
|
||||
md.renderer.register("legend", render_legend)
|
||||
|
||||
|
||||
def render_figure(
|
||||
self: Any,
|
||||
text: str,
|
||||
align: Optional[str] = None,
|
||||
figwidth: Optional[str] = None,
|
||||
figclass: Optional[str] = None,
|
||||
) -> str:
|
||||
_cls = "figure"
|
||||
if align:
|
||||
_cls += " align-" + align
|
||||
if figclass:
|
||||
_cls += " " + figclass
|
||||
|
||||
html = '<figure class="' + _cls + '"'
|
||||
if figwidth:
|
||||
html += ' style="width:' + figwidth + '"'
|
||||
return html + ">\n" + text + "</figure>\n"
|
||||
|
||||
|
||||
def render_figcaption(self: Any, text: str) -> str:
|
||||
return "<figcaption>" + text + "</figcaption>\n"
|
||||
|
||||
|
||||
def render_legend(self: Any, text: str) -> str:
|
||||
return '<div class="legend">\n' + text + "</div>\n"
|
@@ -0,0 +1,73 @@
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Match, Union
|
||||
|
||||
from ._base import BaseDirective, DirectivePlugin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..block_parser import BlockParser
|
||||
from ..core import BaseRenderer, BlockState
|
||||
from ..markdown import Markdown
|
||||
|
||||
|
||||
class Include(DirectivePlugin):
|
||||
def parse(
|
||||
self, block: "BlockParser", m: Match[str], state: "BlockState"
|
||||
) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
|
||||
source_file = state.env.get("__file__")
|
||||
if not source_file:
|
||||
return {"type": "block_error", "raw": "Missing source file"}
|
||||
|
||||
encoding = "utf-8"
|
||||
options = self.parse_options(m)
|
||||
if options:
|
||||
attrs = dict(options)
|
||||
if "encoding" in attrs:
|
||||
encoding = attrs["encoding"]
|
||||
else:
|
||||
attrs = {}
|
||||
|
||||
relpath = self.parse_title(m)
|
||||
dest = os.path.join(os.path.dirname(source_file), relpath)
|
||||
dest = os.path.normpath(dest)
|
||||
|
||||
if dest == source_file:
|
||||
return {
|
||||
"type": "block_error",
|
||||
"raw": "Could not include self: " + relpath,
|
||||
}
|
||||
|
||||
if not os.path.isfile(dest):
|
||||
return {
|
||||
"type": "block_error",
|
||||
"raw": "Could not find file: " + relpath,
|
||||
}
|
||||
|
||||
with open(dest, "rb") as f:
|
||||
content = f.read().decode(encoding)
|
||||
|
||||
ext = os.path.splitext(relpath)[1]
|
||||
if ext in {".md", ".markdown", ".mkd"}:
|
||||
new_state = block.state_cls()
|
||||
new_state.env["__file__"] = dest
|
||||
new_state.process(content)
|
||||
block.parse(new_state)
|
||||
return new_state.tokens
|
||||
|
||||
elif ext in {".html", ".xhtml", ".htm"}:
|
||||
return {"type": "block_html", "raw": content}
|
||||
|
||||
attrs["filepath"] = dest
|
||||
return {
|
||||
"type": "include",
|
||||
"raw": content,
|
||||
"attrs": attrs,
|
||||
}
|
||||
|
||||
def __call__(self, directive: BaseDirective, md: "Markdown") -> None:
|
||||
directive.register("include", self.parse)
|
||||
if md.renderer and md.renderer.NAME == "html":
|
||||
md.renderer.register("include", render_html_include)
|
||||
|
||||
|
||||
def render_html_include(renderer: "BaseRenderer", text: str, **attrs: Any) -> str:
|
||||
return '<pre class="directive-include">\n' + text + "</pre>\n"
|
111
venv/lib/python3.12/site-packages/mistune/directives/toc.py
Normal file
111
venv/lib/python3.12/site-packages/mistune/directives/toc.py
Normal file
@@ -0,0 +1,111 @@
|
||||
"""
|
||||
TOC directive
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
The TOC directive syntax looks like::
|
||||
|
||||
.. toc:: Title
|
||||
:min-level: 1
|
||||
:max-level: 3
|
||||
|
||||
"Title", "min-level", and "max-level" option can be empty. "min-level"
|
||||
and "max-level" are integers >= 1 and <= 6, which define the allowed
|
||||
heading levels writers want to include in the table of contents.
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Dict, Match
|
||||
|
||||
from ..toc import normalize_toc_item, render_toc_ul
|
||||
from ._base import BaseDirective, DirectivePlugin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..block_parser import BlockParser
|
||||
from ..core import BaseRenderer, BlockState
|
||||
from ..markdown import Markdown
|
||||
|
||||
|
||||
class TableOfContents(DirectivePlugin):
|
||||
def __init__(self, min_level: int = 1, max_level: int = 3) -> None:
|
||||
self.min_level = min_level
|
||||
self.max_level = max_level
|
||||
|
||||
def generate_heading_id(self, token: Dict[str, Any], index: int) -> str:
|
||||
return "toc_" + str(index + 1)
|
||||
|
||||
def parse(self, block: "BlockParser", m: Match[str], state: "BlockState") -> Dict[str, Any]:
|
||||
title = self.parse_title(m)
|
||||
options = self.parse_options(m)
|
||||
if options:
|
||||
d_options = dict(options)
|
||||
collapse = "collapse" in d_options
|
||||
min_level = _normalize_level(d_options, "min-level", self.min_level)
|
||||
max_level = _normalize_level(d_options, "max-level", self.max_level)
|
||||
if min_level < self.min_level:
|
||||
raise ValueError(f'"min-level" option MUST be >= {self.min_level}')
|
||||
if max_level > self.max_level:
|
||||
raise ValueError(f'"max-level" option MUST be <= {self.max_level}')
|
||||
if min_level > max_level:
|
||||
raise ValueError('"min-level" option MUST be less than "max-level" option')
|
||||
else:
|
||||
collapse = False
|
||||
min_level = self.min_level
|
||||
max_level = self.max_level
|
||||
|
||||
attrs = {
|
||||
"min_level": min_level,
|
||||
"max_level": max_level,
|
||||
"collapse": collapse,
|
||||
}
|
||||
return {"type": "toc", "text": title or "", "attrs": attrs}
|
||||
|
||||
def toc_hook(self, md: "Markdown", state: "BlockState") -> None:
|
||||
sections = []
|
||||
headings = []
|
||||
|
||||
for tok in state.tokens:
|
||||
if tok["type"] == "toc":
|
||||
sections.append(tok)
|
||||
elif tok["type"] == "heading":
|
||||
headings.append(tok)
|
||||
|
||||
if sections:
|
||||
toc_items = []
|
||||
# adding ID for each heading
|
||||
for i, tok in enumerate(headings):
|
||||
tok["attrs"]["id"] = self.generate_heading_id(tok, i)
|
||||
toc_items.append(normalize_toc_item(md, tok))
|
||||
|
||||
for sec in sections:
|
||||
_min = sec["attrs"]["min_level"]
|
||||
_max = sec["attrs"]["max_level"]
|
||||
toc = [item for item in toc_items if _min <= item[0] <= _max]
|
||||
sec["attrs"]["toc"] = toc
|
||||
|
||||
def __call__(self, directive: BaseDirective, md: "Markdown") -> None:
|
||||
if md.renderer and md.renderer.NAME == "html":
|
||||
# only works with HTML renderer
|
||||
directive.register("toc", self.parse)
|
||||
md.before_render_hooks.append(self.toc_hook)
|
||||
md.renderer.register("toc", render_html_toc)
|
||||
|
||||
|
||||
def render_html_toc(renderer: "BaseRenderer", title: str, collapse: bool = False, **attrs: Any) -> str:
|
||||
if not title:
|
||||
title = "Table of Contents"
|
||||
content = render_toc_ul(attrs["toc"])
|
||||
|
||||
html = '<details class="toc"'
|
||||
if not collapse:
|
||||
html += " open"
|
||||
html += ">\n<summary>" + title + "</summary>\n"
|
||||
return html + content + "</details>\n"
|
||||
|
||||
|
||||
def _normalize_level(options: Dict[str, Any], name: str, default: Any) -> Any:
|
||||
level = options.get(name)
|
||||
if not level:
|
||||
return default
|
||||
try:
|
||||
return int(level)
|
||||
except (ValueError, TypeError):
|
||||
raise ValueError(f'"{name}" option MUST be integer')
|
المرجع في مشكلة جديدة
حظر مستخدم