Building Project Files

هذا الالتزام موجود في:
ahmedgamalyousef
2025-09-27 23:34:33 +03:00
التزام bb279b263f
1831 ملفات معدلة مع 330295 إضافات و0 حذوفات

عرض الملف

@@ -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",
]

عرض الملف

@@ -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()

عرض الملف

@@ -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")

عرض الملف

@@ -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

عرض الملف

@@ -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"

عرض الملف

@@ -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')