Building Project Files

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

عرض الملف

@@ -0,0 +1,50 @@
from importlib import import_module
from typing import TYPE_CHECKING, Dict, Protocol, Union, cast
if TYPE_CHECKING:
from ..markdown import Markdown
_plugins = {
"speedup": "mistune.plugins.speedup.speedup",
"strikethrough": "mistune.plugins.formatting.strikethrough",
"mark": "mistune.plugins.formatting.mark",
"insert": "mistune.plugins.formatting.insert",
"superscript": "mistune.plugins.formatting.superscript",
"subscript": "mistune.plugins.formatting.subscript",
"footnotes": "mistune.plugins.footnotes.footnotes",
"table": "mistune.plugins.table.table",
"url": "mistune.plugins.url.url",
"abbr": "mistune.plugins.abbr.abbr",
"def_list": "mistune.plugins.def_list.def_list",
"math": "mistune.plugins.math.math",
"ruby": "mistune.plugins.ruby.ruby",
"task_lists": "mistune.plugins.task_lists.task_lists",
"spoiler": "mistune.plugins.spoiler.spoiler",
}
class Plugin(Protocol):
def __call__(self, md: "Markdown") -> None: ...
_cached_modules: Dict[str, Plugin] = {}
PluginRef = Union[str, Plugin] # reference to register a plugin
def import_plugin(name: PluginRef) -> Plugin:
if callable(name):
return name
if name in _cached_modules:
return _cached_modules[name]
if name in _plugins:
module_path, func_name = _plugins[name].rsplit(".", 1)
else:
module_path, func_name = name.rsplit(".", 1)
module = import_module(module_path)
plugin = cast(Plugin, getattr(module, func_name))
_cached_modules[name] = plugin
return plugin

عرض الملف

@@ -0,0 +1,109 @@
import re
import types
from typing import TYPE_CHECKING, Match
from ..helpers import PREVENT_BACKSLASH
from ..util import escape
if TYPE_CHECKING:
from ..block_parser import BlockParser
from ..core import BaseRenderer, BlockState, InlineState
from ..inline_parser import InlineParser
from ..markdown import Markdown
__all__ = ["abbr"]
# https://michelf.ca/projects/php-markdown/extra/#abbr
REF_ABBR = (
r"^ {0,3}\*\[(?P<abbr_key>[^\]]+)" + PREVENT_BACKSLASH + r"\]:"
r"(?P<abbr_text>(?:[ \t]*\n(?: {3,}|\t)[^\n]+)|(?:[^\n]*))$"
)
def parse_ref_abbr(block: "BlockParser", m: Match[str], state: "BlockState") -> int:
ref = state.env.get("ref_abbrs")
if not ref:
ref = {}
key = m.group("abbr_key")
text = m.group("abbr_text")
ref[key] = text.strip()
state.env["ref_abbrs"] = ref
# abbr definition can split paragraph
state.append_token({"type": "blank_line"})
return m.end() + 1
def process_text(inline: "InlineParser", text: str, state: "InlineState") -> None:
ref = state.env.get("ref_abbrs")
if not ref:
return state.append_token({"type": "text", "raw": text})
if state.tokens:
last = state.tokens[-1]
if last["type"] == "text":
state.tokens.pop()
text = last["raw"] + text
abbrs_re = state.env.get("abbrs_re")
if not abbrs_re:
abbrs_re = re.compile(r"|".join(re.escape(k) for k in ref.keys()))
state.env["abbrs_re"] = abbrs_re
pos = 0
while pos < len(text):
m = abbrs_re.search(text, pos)
if not m:
break
end_pos = m.start()
if end_pos > pos:
hole = text[pos:end_pos]
state.append_token({"type": "text", "raw": hole})
label = m.group(0)
state.append_token(
{"type": "abbr", "children": [{"type": "text", "raw": label}], "attrs": {"title": ref[label]}}
)
pos = m.end()
if pos == 0:
# special case, just pure text
state.append_token({"type": "text", "raw": text})
elif pos < len(text):
state.append_token({"type": "text", "raw": text[pos:]})
def render_abbr(renderer: "BaseRenderer", text: str, title: str) -> str:
if not title:
return "<abbr>" + text + "</abbr>"
return '<abbr title="' + escape(title) + '">' + text + "</abbr>"
def abbr(md: "Markdown") -> None:
"""A mistune plugin to support abbreviations, spec defined at
https://michelf.ca/projects/php-markdown/extra/#abbr
Here is an example:
.. code-block:: text
The HTML specification
is maintained by the W3C.
*[HTML]: Hyper Text Markup Language
*[W3C]: World Wide Web Consortium
It will be converted into HTML:
.. code-block:: html
The <abbr title="Hyper Text Markup Language">HTML</abbr> specification
is maintained by the <abbr title="World Wide Web Consortium">W3C</abbr>.
:param md: Markdown instance
"""
md.block.register("ref_abbr", REF_ABBR, parse_ref_abbr, before="paragraph")
# replace process_text
md.inline.process_text = types.MethodType(process_text, md.inline) # type: ignore[method-assign]
if md.renderer and md.renderer.NAME == "html":
md.renderer.register("abbr", render_abbr)

عرض الملف

@@ -0,0 +1,145 @@
import re
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Match
from ..util import strip_end
if TYPE_CHECKING:
from ..block_parser import BlockParser
from ..core import BaseRenderer, BlockState
from ..markdown import Markdown
__all__ = ["def_list"]
# https://michelf.ca/projects/php-markdown/extra/#def-list
DEF_PATTERN = (
r"^(?P<def_list_head>(?:[^\n]+\n)+?)"
r"\n?(?:"
r"\:[ \t]+.*\n"
r"(?:[^\n]+\n)*" # lazy continue line
r"(?:(?:[ \t]*\n)*[ \t]+[^\n]+\n)*"
r"(?:[ \t]*\n)*"
r")+"
)
DEF_RE = re.compile(DEF_PATTERN, re.M)
DD_START_RE = re.compile(r"^:[ \t]+", re.M)
TRIM_RE = re.compile(r"^ {0,4}", re.M)
HAS_BLANK_LINE_RE = re.compile(r"\n[ \t]*\n$")
def parse_def_list(block: "BlockParser", m: Match[str], state: "BlockState") -> int:
pos = m.end()
children = list(_parse_def_item(block, m))
m2 = DEF_RE.match(state.src, pos)
while m2:
children.extend(list(_parse_def_item(block, m2)))
pos = m2.end()
m2 = DEF_RE.match(state.src, pos)
state.append_token(
{
"type": "def_list",
"children": children,
}
)
return pos
def _parse_def_item(block: "BlockParser", m: Match[str]) -> Iterable[Dict[str, Any]]:
head = m.group("def_list_head")
for line in head.splitlines():
yield {
"type": "def_list_head",
"text": line,
}
src = m.group(0)
end = len(head)
m2 = DD_START_RE.search(src, end)
assert m2 is not None
start = m2.start()
prev_blank_line = src[end:start] == "\n"
while m2:
m2 = DD_START_RE.search(src, start + 1)
if not m2:
break
end = m2.start()
text = src[start:end].replace(":", " ", 1)
children = _process_text(block, text, prev_blank_line)
prev_blank_line = bool(HAS_BLANK_LINE_RE.search(text))
yield {
"type": "def_list_item",
"children": children,
}
start = end
text = src[start:].replace(":", " ", 1)
children = _process_text(block, text, prev_blank_line)
yield {
"type": "def_list_item",
"children": children,
}
def _process_text(block: "BlockParser", text: str, loose: bool) -> List[Any]:
text = TRIM_RE.sub("", text)
state = block.state_cls()
state.process(strip_end(text))
# use default list rules
block.parse(state, block.list_rules)
tokens = state.tokens
if not loose and len(tokens) == 1 and tokens[0]["type"] == "paragraph":
tokens[0]["type"] = "block_text"
return tokens
def render_def_list(renderer: "BaseRenderer", text: str) -> str:
return "<dl>\n" + text + "</dl>\n"
def render_def_list_head(renderer: "BaseRenderer", text: str) -> str:
return "<dt>" + text + "</dt>\n"
def render_def_list_item(renderer: "BaseRenderer", text: str) -> str:
return "<dd>" + text + "</dd>\n"
def def_list(md: "Markdown") -> None:
"""A mistune plugin to support def list, spec defined at
https://michelf.ca/projects/php-markdown/extra/#def-list
Here is an example:
.. code-block:: text
Apple
: Pomaceous fruit of plants of the genus Malus in
the family Rosaceae.
Orange
: The fruit of an evergreen tree of the genus Citrus.
It will be converted into HTML:
.. code-block:: html
<dl>
<dt>Apple</dt>
<dd>Pomaceous fruit of plants of the genus Malus in
the family Rosaceae.</dd>
<dt>Orange</dt>
<dd>The fruit of an evergreen tree of the genus Citrus.</dd>
</dl>
:param md: Markdown instance
"""
md.block.register("def_list", DEF_PATTERN, parse_def_list, before="paragraph")
if md.renderer and md.renderer.NAME == "html":
md.renderer.register("def_list", render_def_list)
md.renderer.register("def_list_head", render_def_list_head)
md.renderer.register("def_list_item", render_def_list_item)

عرض الملف

@@ -0,0 +1,154 @@
import re
from typing import TYPE_CHECKING, Any, Dict, List, Match, Union
from ..core import BlockState
from ..helpers import LINK_LABEL
from ..util import unikey
if TYPE_CHECKING:
from ..block_parser import BlockParser
from ..core import BaseRenderer, InlineState
from ..inline_parser import InlineParser
from ..markdown import Markdown
__all__ = ["footnotes"]
_PARAGRAPH_SPLIT = re.compile(r"\n{2,}")
# https://michelf.ca/projects/php-markdown/extra/#footnotes
REF_FOOTNOTE = (
r"^(?P<footnote_lead> {0,3})"
r"\[\^(?P<footnote_key>" + LINK_LABEL + r")]:[ \t]"
r"(?P<footnote_text>[^\n]*(?:\n+|$)"
r"(?:(?P=footnote_lead) {1,3}(?! )[^\n]*\n+)*"
r")"
)
INLINE_FOOTNOTE = r"\[\^(?P<footnote_key>" + LINK_LABEL + r")\]"
def parse_inline_footnote(inline: "InlineParser", m: Match[str], state: "InlineState") -> int:
key = unikey(m.group("footnote_key"))
ref = state.env.get("ref_footnotes")
if ref and key in ref:
notes = state.env.get("footnotes")
if not notes:
notes = []
if key not in notes:
notes.append(key)
state.env["footnotes"] = notes
state.append_token({"type": "footnote_ref", "raw": key, "attrs": {"index": notes.index(key) + 1}})
else:
state.append_token({"type": "text", "raw": m.group(0)})
return m.end()
def parse_ref_footnote(block: "BlockParser", m: Match[str], state: BlockState) -> int:
ref = state.env.get("ref_footnotes")
if not ref:
ref = {}
key = unikey(m.group("footnote_key"))
if key not in ref:
ref[key] = m.group("footnote_text")
state.env["ref_footnotes"] = ref
return m.end()
def parse_footnote_item(block: "BlockParser", key: str, index: int, state: BlockState) -> Dict[str, Any]:
ref = state.env.get("ref_footnotes")
if not ref:
raise ValueError("Missing 'ref_footnotes'.")
text = ref[key]
lines = text.splitlines()
second_line = None
for second_line in lines[1:]:
if second_line:
break
if second_line:
spaces = len(second_line) - len(second_line.lstrip())
pattern = re.compile(r"^ {" + str(spaces) + r",}", flags=re.M)
text = pattern.sub("", text).strip()
items = _PARAGRAPH_SPLIT.split(text)
children = [{"type": "paragraph", "text": s} for s in items]
else:
text = text.strip()
children = [{"type": "paragraph", "text": text}]
return {"type": "footnote_item", "children": children, "attrs": {"key": key, "index": index}}
def md_footnotes_hook(
md: "Markdown", result: Union[str, List[Dict[str, Any]]], state: BlockState
) -> Union[str, List[Dict[str, Any]]]:
notes = state.env.get("footnotes")
if not notes:
return result
children = [parse_footnote_item(md.block, k, i + 1, state) for i, k in enumerate(notes)]
state = BlockState()
state.tokens = [{"type": "footnotes", "children": children}]
output = md.render_state(state)
return result + output # type: ignore[operator]
def render_footnote_ref(renderer: "BaseRenderer", key: str, index: int) -> str:
i = str(index)
html = '<sup class="footnote-ref" id="fnref-' + i + '">'
return html + '<a href="#fn-' + i + '">' + i + "</a></sup>"
def render_footnotes(renderer: "BaseRenderer", text: str) -> str:
return '<section class="footnotes">\n<ol>\n' + text + "</ol>\n</section>\n"
def render_footnote_item(renderer: "BaseRenderer", text: str, key: str, index: int) -> str:
i = str(index)
back = '<a href="#fnref-' + i + '" class="footnote">&#8617;</a>'
text = text.rstrip()[:-4] + back + "</p>"
return '<li id="fn-' + i + '">' + text + "</li>\n"
def footnotes(md: "Markdown") -> None:
"""A mistune plugin to support footnotes, spec defined at
https://michelf.ca/projects/php-markdown/extra/#footnotes
Here is an example:
.. code-block:: text
That's some text with a footnote.[^1]
[^1]: And that's the footnote.
It will be converted into HTML:
.. code-block:: html
<p>That's some text with a footnote.<sup class="footnote-ref" id="fnref-1"><a href="#fn-1">1</a></sup></p>
<section class="footnotes">
<ol>
<li id="fn-1"><p>And that's the footnote.<a href="#fnref-1" class="footnote">&#8617;</a></p></li>
</ol>
</section>
:param md: Markdown instance
"""
md.inline.register(
"footnote",
INLINE_FOOTNOTE,
parse_inline_footnote,
before="link",
)
md.block.register(
"ref_footnote",
REF_FOOTNOTE,
parse_ref_footnote,
before="ref_link",
)
md.after_render_hooks.append(md_footnotes_hook)
if md.renderer and md.renderer.NAME == "html":
md.renderer.register("footnote_ref", render_footnote_ref)
md.renderer.register("footnote_item", render_footnote_item)
md.renderer.register("footnotes", render_footnotes)

عرض الملف

@@ -0,0 +1,183 @@
import re
from typing import TYPE_CHECKING, Match, Optional, Pattern
from ..helpers import PREVENT_BACKSLASH
if TYPE_CHECKING:
from ..core import BaseRenderer, InlineState
from ..inline_parser import InlineParser
from ..markdown import Markdown
__all__ = ["strikethrough", "mark", "insert", "superscript", "subscript"]
_STRIKE_END = re.compile(r"(?:" + PREVENT_BACKSLASH + r"\\~|[^\s~])~~(?!~)")
_MARK_END = re.compile(r"(?:" + PREVENT_BACKSLASH + r"\\=|[^\s=])==(?!=)")
_INSERT_END = re.compile(r"(?:" + PREVENT_BACKSLASH + r"\\\^|[^\s^])\^\^(?!\^)")
SUPERSCRIPT_PATTERN = r"\^(?:" + PREVENT_BACKSLASH + r"\\\^|\S|\\ )+?\^"
SUBSCRIPT_PATTERN = r"~(?:" + PREVENT_BACKSLASH + r"\\~|\S|\\ )+?~"
def parse_strikethrough(inline: "InlineParser", m: Match[str], state: "InlineState") -> Optional[int]:
return _parse_to_end(inline, m, state, "strikethrough", _STRIKE_END)
def render_strikethrough(renderer: "BaseRenderer", text: str) -> str:
return "<del>" + text + "</del>"
def parse_mark(inline: "InlineParser", m: Match[str], state: "InlineState") -> Optional[int]:
return _parse_to_end(inline, m, state, "mark", _MARK_END)
def render_mark(renderer: "BaseRenderer", text: str) -> str:
return "<mark>" + text + "</mark>"
def parse_insert(inline: "InlineParser", m: Match[str], state: "InlineState") -> Optional[int]:
return _parse_to_end(inline, m, state, "insert", _INSERT_END)
def render_insert(renderer: "BaseRenderer", text: str) -> str:
return "<ins>" + text + "</ins>"
def parse_superscript(inline: "InlineParser", m: Match[str], state: "InlineState") -> int:
return _parse_script(inline, m, state, "superscript")
def render_superscript(renderer: "BaseRenderer", text: str) -> str:
return "<sup>" + text + "</sup>"
def parse_subscript(inline: "InlineParser", m: Match[str], state: "InlineState") -> int:
return _parse_script(inline, m, state, "subscript")
def render_subscript(renderer: "BaseRenderer", text: str) -> str:
return "<sub>" + text + "</sub>"
def _parse_to_end(
inline: "InlineParser",
m: Match[str],
state: "InlineState",
tok_type: str,
end_pattern: Pattern[str],
) -> Optional[int]:
pos = m.end()
m1 = end_pattern.search(state.src, pos)
if not m1:
return None
end_pos = m1.end()
text = state.src[pos : end_pos - 2]
new_state = state.copy()
new_state.src = text
children = inline.render(new_state)
state.append_token({"type": tok_type, "children": children})
return end_pos
def _parse_script(inline: "InlineParser", m: Match[str], state: "InlineState", tok_type: str) -> int:
text = m.group(0)
new_state = state.copy()
new_state.src = text[1:-1].replace("\\ ", " ")
children = inline.render(new_state)
state.append_token({"type": tok_type, "children": children})
return m.end()
def strikethrough(md: "Markdown") -> None:
"""A mistune plugin to support strikethrough. Spec defined by
GitHub flavored Markdown and commonly used by many parsers:
.. code-block:: text
~~This was mistaken text~~
It will be converted into HTML:
.. code-block:: html
<del>This was mistaken text</del>
:param md: Markdown instance
"""
md.inline.register(
"strikethrough",
r"~~(?=[^\s~])",
parse_strikethrough,
before="link",
)
if md.renderer and md.renderer.NAME == "html":
md.renderer.register("strikethrough", render_strikethrough)
def mark(md: "Markdown") -> None:
"""A mistune plugin to add ``<mark>`` tag. Spec defined at
https://facelessuser.github.io/pymdown-extensions/extensions/mark/:
.. code-block:: text
==mark me== ==mark \\=\\= equal==
:param md: Markdown instance
"""
md.inline.register(
"mark",
r"==(?=[^\s=])",
parse_mark,
before="link",
)
if md.renderer and md.renderer.NAME == "html":
md.renderer.register("mark", render_mark)
def insert(md: "Markdown") -> None:
"""A mistune plugin to add ``<ins>`` tag. Spec defined at
https://facelessuser.github.io/pymdown-extensions/extensions/caret/#insert:
.. code-block:: text
^^insert me^^
:param md: Markdown instance
"""
md.inline.register(
"insert",
r"\^\^(?=[^\s\^])",
parse_insert,
before="link",
)
if md.renderer and md.renderer.NAME == "html":
md.renderer.register("insert", render_insert)
def superscript(md: "Markdown") -> None:
"""A mistune plugin to add ``<sup>`` tag. Spec defined at
https://pandoc.org/MANUAL.html#superscripts-and-subscripts:
.. code-block:: text
2^10^ is 1024.
:param md: Markdown instance
"""
md.inline.register("superscript", SUPERSCRIPT_PATTERN, parse_superscript, before="linebreak")
if md.renderer and md.renderer.NAME == "html":
md.renderer.register("superscript", render_superscript)
def subscript(md: "Markdown") -> None:
"""A mistune plugin to add ``<sub>`` tag. Spec defined at
https://pandoc.org/MANUAL.html#superscripts-and-subscripts:
.. code-block:: text
H~2~O is a liquid.
:param md: Markdown instance
"""
md.inline.register("subscript", SUBSCRIPT_PATTERN, parse_subscript, before="linebreak")
if md.renderer and md.renderer.NAME == "html":
md.renderer.register("subscript", render_subscript)

عرض الملف

@@ -0,0 +1,65 @@
from typing import TYPE_CHECKING, Match
if TYPE_CHECKING:
from ..block_parser import BlockParser
from ..core import BaseRenderer, BlockState, InlineState
from ..inline_parser import InlineParser
from ..markdown import Markdown
__all__ = ["math", "math_in_quote", "math_in_list"]
BLOCK_MATH_PATTERN = r"^ {0,3}\$\$[ \t]*\n(?P<math_text>[\s\S]+?)\n\$\$[ \t]*$"
INLINE_MATH_PATTERN = r"\$(?!\s)(?P<math_text>.+?)(?!\s)\$"
def parse_block_math(block: "BlockParser", m: Match[str], state: "BlockState") -> int:
text = m.group("math_text")
state.append_token({"type": "block_math", "raw": text})
return m.end() + 1
def parse_inline_math(inline: "InlineParser", m: Match[str], state: "InlineState") -> int:
text = m.group("math_text")
state.append_token({"type": "inline_math", "raw": text})
return m.end()
def render_block_math(renderer: "BaseRenderer", text: str) -> str:
return '<div class="math">$$\n' + text + "\n$$</div>\n"
def render_inline_math(renderer: "BaseRenderer", text: str) -> str:
return r'<span class="math">\(' + text + r"\)</span>"
def math(md: "Markdown") -> None:
"""A mistune plugin to support math. The syntax is used
by many markdown extensions:
.. code-block:: text
Block math is surrounded by $$:
$$
f(a)=f(b)
$$
Inline math is surrounded by `$`, such as $f(a)=f(b)$
:param md: Markdown instance
"""
md.block.register("block_math", BLOCK_MATH_PATTERN, parse_block_math, before="list")
md.inline.register("inline_math", INLINE_MATH_PATTERN, parse_inline_math, before="link")
if md.renderer and md.renderer.NAME == "html":
md.renderer.register("block_math", render_block_math)
md.renderer.register("inline_math", render_inline_math)
def math_in_quote(md: "Markdown") -> None:
"""Enable block math plugin in block quote."""
md.block.insert_rule(md.block.block_quote_rules, "block_math", before="list")
def math_in_list(md: "Markdown") -> None:
"""Enable block math plugin in list."""
md.block.insert_rule(md.block.list_rules, "block_math", before="list")

عرض الملف

@@ -0,0 +1,112 @@
import re
from typing import TYPE_CHECKING, Any, Dict, List, Match, Optional
from ..helpers import parse_link, parse_link_label
from ..util import unikey
if TYPE_CHECKING:
from ..core import BaseRenderer, InlineState
from ..inline_parser import InlineParser
from ..markdown import Markdown
RUBY_PATTERN = r"\[(?:\w+\([\w ]+\))+\]"
_ruby_re = re.compile(RUBY_PATTERN)
def parse_ruby(inline: "InlineParser", m: Match[str], state: "InlineState") -> int:
text = m.group(0)[1:-2]
items = text.split(")")
tokens = []
for item in items:
rb, rt = item.split("(")
tokens.append({"type": "ruby", "raw": rb, "attrs": {"rt": rt}})
end_pos = m.end()
next_match = _ruby_re.match(state.src, end_pos)
if next_match:
for tok in tokens:
state.append_token(tok)
return parse_ruby(inline, next_match, state)
# repeat link logic
if end_pos < len(state.src):
link_pos = _parse_ruby_link(inline, state, end_pos, tokens)
if link_pos:
return link_pos
for tok in tokens:
state.append_token(tok)
return end_pos
def _parse_ruby_link(
inline: "InlineParser", state: "InlineState", pos: int, tokens: List[Dict[str, Any]]
) -> Optional[int]:
c = state.src[pos]
if c == "(":
# standard link [text](<url> "title")
attrs, link_pos = parse_link(state.src, pos + 1)
if link_pos:
state.append_token(
{
"type": "link",
"children": tokens,
"attrs": attrs,
}
)
return link_pos
elif c == "[":
# standard ref link [text][label]
label, link_pos = parse_link_label(state.src, pos + 1)
if label and link_pos:
ref_links = state.env["ref_links"]
key = unikey(label)
env = ref_links.get(key)
if env:
attrs = {"url": env["url"], "title": env.get("title")}
state.append_token(
{
"type": "link",
"children": tokens,
"attrs": attrs,
}
)
else:
for tok in tokens:
state.append_token(tok)
state.append_token(
{
"type": "text",
"raw": "[" + label + "]",
}
)
return link_pos
return None
def render_ruby(renderer: "BaseRenderer", text: str, rt: str) -> str:
return "<ruby>" + text + "<rt>" + rt + "</rt></ruby>"
def ruby(md: "Markdown") -> None:
"""A mistune plugin to support ``<ruby>`` tag. The syntax is defined
at https://lepture.com/en/2022/markdown-ruby-markup:
.. code-block:: text
[漢字(ㄏㄢˋㄗˋ)]
[漢(ㄏㄢˋ)字(ㄗˋ)]
[漢字(ㄏㄢˋㄗˋ)][link]
[漢字(ㄏㄢˋㄗˋ)](/url "title")
[link]: /url "title"
:param md: Markdown instance
"""
md.inline.register("ruby", RUBY_PATTERN, parse_ruby, before="link")
if md.renderer and md.renderer.NAME == "html":
md.renderer.register("ruby", render_ruby)

عرض الملف

@@ -0,0 +1,50 @@
import re
import string
from typing import TYPE_CHECKING, Match
if TYPE_CHECKING:
from ..block_parser import BlockParser
from ..core import BlockState, InlineState
from ..inline_parser import InlineParser
from ..markdown import Markdown
# because mismatch is too slow, add parsers for paragraph and text
HARD_LINEBREAK_RE = re.compile(r" *\n\s*")
PARAGRAPH = (
# start with none punctuation, not number, not whitespace
r"(?:^[^\s\d" + re.escape(string.punctuation) + r"][^\n]*\n)+"
)
__all__ = ["speedup"]
def parse_text(inline: "InlineParser", m: Match[str], state: "InlineState") -> int:
text = m.group(0)
text = HARD_LINEBREAK_RE.sub("\n", text)
inline.process_text(text, state)
return m.end()
def parse_paragraph(block: "BlockParser", m: Match[str], state: "BlockState") -> int:
text = m.group(0)
state.add_paragraph(text)
return m.end()
def speedup(md: "Markdown") -> None:
"""Increase the speed of parsing paragraph and inline text."""
md.block.register("paragraph", PARAGRAPH, parse_paragraph)
punc = r"\\><!\[_*`~\^\$="
text_pattern = r"[\s\S]+?(?=[" + punc + r"]|"
if "url_link" in md.inline.rules:
text_pattern += "https?:|"
if md.inline.hard_wrap:
text_pattern += r" *\n|"
else:
text_pattern += r" {2,}\n|"
text_pattern += r"$)"
md.inline.register("text", text_pattern, parse_text)

عرض الملف

@@ -0,0 +1,87 @@
import re
from typing import TYPE_CHECKING, Match
if TYPE_CHECKING:
from ..block_parser import BlockParser
from ..core import BaseRenderer, BlockState, InlineState
from ..inline_parser import InlineParser
from ..markdown import Markdown
__all__ = ["spoiler"]
_BLOCK_SPOILER_START = re.compile(r"^ {0,3}! ?", re.M)
_BLOCK_SPOILER_MATCH = re.compile(r"^( {0,3}![^\n]*\n)+$")
INLINE_SPOILER_PATTERN = r">!\s*(?P<spoiler_text>.+?)\s*!<"
def parse_block_spoiler(block: "BlockParser", m: Match[str], state: "BlockState") -> int:
text, end_pos = block.extract_block_quote(m, state)
if not text.endswith("\n"):
# ensure it endswith \n to make sure
# _BLOCK_SPOILER_MATCH.match works
text += "\n"
depth = state.depth()
if not depth and _BLOCK_SPOILER_MATCH.match(text):
text = _BLOCK_SPOILER_START.sub("", text)
tok_type = "block_spoiler"
else:
tok_type = "block_quote"
# scan children state
child = state.child_state(text)
if state.depth() >= block.max_nested_level - 1:
rules = list(block.block_quote_rules)
rules.remove("block_quote")
else:
rules = block.block_quote_rules
block.parse(child, rules)
token = {"type": tok_type, "children": child.tokens}
if end_pos:
state.prepend_token(token)
return end_pos
state.append_token(token)
return state.cursor
def parse_inline_spoiler(inline: "InlineParser", m: Match[str], state: "InlineState") -> int:
text = m.group("spoiler_text")
new_state = state.copy()
new_state.src = text
children = inline.render(new_state)
state.append_token({"type": "inline_spoiler", "children": children})
return m.end()
def render_block_spoiler(renderer: "BaseRenderer", text: str) -> str:
return '<div class="spoiler">\n' + text + "</div>\n"
def render_inline_spoiler(renderer: "BaseRenderer", text: str) -> str:
return '<span class="spoiler">' + text + "</span>"
def spoiler(md: "Markdown") -> None:
"""A mistune plugin to support block and inline spoiler. The
syntax is inspired by stackexchange:
.. code-block:: text
Block level spoiler looks like block quote, but with `>!`:
>! this is spoiler
>!
>! the content will be hidden
Inline spoiler is surrounded by `>!` and `!<`, such as >! hide me !<.
:param md: Markdown instance
"""
# reset block quote parser with block spoiler parser
md.block.register("block_quote", None, parse_block_spoiler)
md.inline.register("inline_spoiler", INLINE_SPOILER_PATTERN, parse_inline_spoiler)
if md.renderer and md.renderer.NAME == "html":
md.renderer.register("block_spoiler", render_block_spoiler)
md.renderer.register("inline_spoiler", render_inline_spoiler)

عرض الملف

@@ -0,0 +1,189 @@
import re
from typing import (
TYPE_CHECKING,
Any,
Dict,
List,
Match,
Optional,
Tuple,
Union,
)
from ..helpers import PREVENT_BACKSLASH
if TYPE_CHECKING:
from ..block_parser import BlockParser
from ..core import BaseRenderer, BlockState
from ..markdown import Markdown
# https://michelf.ca/projects/php-markdown/extra/#table
__all__ = ["table", "table_in_quote", "table_in_list"]
TABLE_PATTERN = (
r"^ {0,3}\|(?P<table_head>.+)\|[ \t]*\n"
r" {0,3}\|(?P<table_align> *[-:]+[-| :]*)\|[ \t]*\n"
r"(?P<table_body>(?: {0,3}\|.*\|[ \t]*(?:\n|$))*)\n*"
)
NP_TABLE_PATTERN = (
r"^ {0,3}(?P<nptable_head>\S.*\|.*)\n"
r" {0,3}(?P<nptable_align>[-:]+ *\|[-| :]*)\n"
r"(?P<nptable_body>(?:.*\|.*(?:\n|$))*)\n*"
)
TABLE_CELL = re.compile(r"^ {0,3}\|(.+)\|[ \t]*$")
CELL_SPLIT = re.compile(r" *" + PREVENT_BACKSLASH + r"\| *")
ALIGN_CENTER = re.compile(r"^ *:-+: *$")
ALIGN_LEFT = re.compile(r"^ *:-+ *$")
ALIGN_RIGHT = re.compile(r"^ *-+: *$")
def parse_table(block: "BlockParser", m: Match[str], state: "BlockState") -> Optional[int]:
pos = m.end()
header = m.group("table_head")
align = m.group("table_align")
thead, aligns = _process_thead(header, align)
if not thead:
return None
assert aligns is not None
rows = []
body = m.group("table_body")
for text in body.splitlines():
m2 = TABLE_CELL.match(text)
if not m2: # pragma: no cover
return None
row = _process_row(m2.group(1), aligns)
if not row:
return None
rows.append(row)
children = [thead, {"type": "table_body", "children": rows}]
state.append_token({"type": "table", "children": children})
return pos
def parse_nptable(block: "BlockParser", m: Match[str], state: "BlockState") -> Optional[int]:
header = m.group("nptable_head")
align = m.group("nptable_align")
thead, aligns = _process_thead(header, align)
if not thead:
return None
assert aligns is not None
rows = []
body = m.group("nptable_body")
for text in body.splitlines():
row = _process_row(text, aligns)
if not row:
return None
rows.append(row)
children = [thead, {"type": "table_body", "children": rows}]
state.append_token({"type": "table", "children": children})
return m.end()
def _process_thead(header: str, align: str) -> Union[Tuple[None, None], Tuple[Dict[str, Any], List[str]]]:
headers = CELL_SPLIT.split(header)
aligns = CELL_SPLIT.split(align)
if len(headers) != len(aligns):
return None, None
for i, v in enumerate(aligns):
if ALIGN_CENTER.match(v):
aligns[i] = "center"
elif ALIGN_LEFT.match(v):
aligns[i] = "left"
elif ALIGN_RIGHT.match(v):
aligns[i] = "right"
else:
aligns[i] = None
children = [
{"type": "table_cell", "text": text.strip(), "attrs": {"align": aligns[i], "head": True}}
for i, text in enumerate(headers)
]
thead = {"type": "table_head", "children": children}
return thead, aligns
def _process_row(text: str, aligns: List[str]) -> Optional[Dict[str, Any]]:
cells = CELL_SPLIT.split(text)
if len(cells) != len(aligns):
return None
children = [
{"type": "table_cell", "text": text.strip(), "attrs": {"align": aligns[i], "head": False}}
for i, text in enumerate(cells)
]
return {"type": "table_row", "children": children}
def render_table(renderer: "BaseRenderer", text: str) -> str:
return "<table>\n" + text + "</table>\n"
def render_table_head(renderer: "BaseRenderer", text: str) -> str:
return "<thead>\n<tr>\n" + text + "</tr>\n</thead>\n"
def render_table_body(renderer: "BaseRenderer", text: str) -> str:
return "<tbody>\n" + text + "</tbody>\n"
def render_table_row(renderer: "BaseRenderer", text: str) -> str:
return "<tr>\n" + text + "</tr>\n"
def render_table_cell(renderer: "BaseRenderer", text: str, align: Optional[str] = None, head: bool = False) -> str:
if head:
tag = "th"
else:
tag = "td"
html = " <" + tag
if align:
html += ' style="text-align:' + align + '"'
return html + ">" + text + "</" + tag + ">\n"
def table(md: "Markdown") -> None:
"""A mistune plugin to support table, spec defined at
https://michelf.ca/projects/php-markdown/extra/#table
Here is an example:
.. code-block:: text
First Header | Second Header
------------- | -------------
Content Cell | Content Cell
Content Cell | Content Cell
:param md: Markdown instance
"""
md.block.register("table", TABLE_PATTERN, parse_table, before="paragraph")
md.block.register("nptable", NP_TABLE_PATTERN, parse_nptable, before="paragraph")
if md.renderer and md.renderer.NAME == "html":
md.renderer.register("table", render_table)
md.renderer.register("table_head", render_table_head)
md.renderer.register("table_body", render_table_body)
md.renderer.register("table_row", render_table_row)
md.renderer.register("table_cell", render_table_cell)
def table_in_quote(md: "Markdown") -> None:
"""Enable table plugin in block quotes."""
md.block.insert_rule(md.block.block_quote_rules, "table", before="paragraph")
md.block.insert_rule(md.block.block_quote_rules, "nptable", before="paragraph")
def table_in_list(md: "Markdown") -> None:
"""Enable table plugin in list."""
md.block.insert_rule(md.block.list_rules, "table", before="paragraph")
md.block.insert_rule(md.block.list_rules, "nptable", before="paragraph")

عرض الملف

@@ -0,0 +1,69 @@
import re
from typing import TYPE_CHECKING, Any, Dict, Iterable
if TYPE_CHECKING:
from ..core import BaseRenderer, BlockState
from ..markdown import Markdown
__all__ = ["task_lists"]
TASK_LIST_ITEM = re.compile(r"^(\[[ xX]\])\s+")
def task_lists_hook(md: "Markdown", state: "BlockState") -> Iterable[Dict[str, Any]]:
return _rewrite_all_list_items(state.tokens)
def render_task_list_item(renderer: "BaseRenderer", text: str, checked: bool = False) -> str:
checkbox = '<input class="task-list-item-checkbox" type="checkbox" disabled'
if checked:
checkbox += " checked/>"
else:
checkbox += "/>"
if text.startswith("<p>"):
text = text.replace("<p>", "<p>" + checkbox, 1)
else:
text = checkbox + text
return '<li class="task-list-item">' + text + "</li>\n"
def task_lists(md: "Markdown") -> None:
"""A mistune plugin to support task lists. Spec defined by
GitHub flavored Markdown and commonly used by many parsers:
.. code-block:: text
- [ ] unchecked task
- [x] checked task
:param md: Markdown instance
"""
md.before_render_hooks.append(task_lists_hook)
if md.renderer and md.renderer.NAME == "html":
md.renderer.register("task_list_item", render_task_list_item)
def _rewrite_all_list_items(tokens: Iterable[Dict[str, Any]]) -> Iterable[Dict[str, Any]]:
for tok in tokens:
if tok["type"] == "list_item":
_rewrite_list_item(tok)
if "children" in tok:
_rewrite_all_list_items(tok["children"])
return tokens
def _rewrite_list_item(tok: Dict[str, Any]) -> None:
children = tok["children"]
if children:
first_child = children[0]
text = first_child.get("text", "")
m = TASK_LIST_ITEM.match(text)
if m:
mark = m.group(1)
first_child["text"] = text[m.end() :]
tok["type"] = "task_list_item"
tok["attrs"] = {"checked": mark != "[ ]"}

عرض الملف

@@ -0,0 +1,32 @@
from typing import TYPE_CHECKING, Match
from ..util import escape_url
if TYPE_CHECKING:
from ..core import InlineState
from ..inline_parser import InlineParser
from ..markdown import Markdown
__all__ = ["url"]
URL_LINK_PATTERN = r"""https?:\/\/[^\s<]+[^<.,:;"')\]\s]"""
def parse_url_link(inline: "InlineParser", m: Match[str], state: "InlineState") -> int:
text = m.group(0)
pos = m.end()
if state.in_link:
inline.process_text(text, state)
return pos
state.append_token(
{
"type": "link",
"children": [{"type": "text", "raw": text}],
"attrs": {"url": escape_url(text)},
}
)
return pos
def url(md: "Markdown") -> None:
md.inline.register("url_link", URL_LINK_PATTERN, parse_url_link)