Upload folder using huggingface_hub (part 6)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +3 -0
- venv/lib/python3.12/site-packages/markdown_it/common/html_blocks.py +69 -0
- venv/lib/python3.12/site-packages/markdown_it/common/html_re.py +39 -0
- venv/lib/python3.12/site-packages/markdown_it/common/normalize_url.py +81 -0
- venv/lib/python3.12/site-packages/markdown_it/common/utils.py +313 -0
- venv/lib/python3.12/site-packages/markdown_it/helpers/__init__.py +6 -0
- venv/lib/python3.12/site-packages/markdown_it/helpers/__pycache__/__init__.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/helpers/__pycache__/parse_link_destination.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/helpers/__pycache__/parse_link_label.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/helpers/__pycache__/parse_link_title.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/helpers/parse_link_destination.py +83 -0
- venv/lib/python3.12/site-packages/markdown_it/helpers/parse_link_label.py +44 -0
- venv/lib/python3.12/site-packages/markdown_it/helpers/parse_link_title.py +75 -0
- venv/lib/python3.12/site-packages/markdown_it/main.py +351 -0
- venv/lib/python3.12/site-packages/markdown_it/parser_block.py +113 -0
- venv/lib/python3.12/site-packages/markdown_it/parser_core.py +46 -0
- venv/lib/python3.12/site-packages/markdown_it/parser_inline.py +215 -0
- venv/lib/python3.12/site-packages/markdown_it/port.yaml +48 -0
- venv/lib/python3.12/site-packages/markdown_it/presets/__init__.py +48 -0
- venv/lib/python3.12/site-packages/markdown_it/presets/__pycache__/__init__.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/presets/__pycache__/commonmark.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/presets/__pycache__/default.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/presets/__pycache__/zero.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/presets/commonmark.py +75 -0
- venv/lib/python3.12/site-packages/markdown_it/presets/default.py +36 -0
- venv/lib/python3.12/site-packages/markdown_it/presets/zero.py +44 -0
- venv/lib/python3.12/site-packages/markdown_it/py.typed +1 -0
- venv/lib/python3.12/site-packages/markdown_it/renderer.py +356 -0
- venv/lib/python3.12/site-packages/markdown_it/ruler.py +275 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/__init__.py +28 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/__init__.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/blockquote.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/code.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/fence.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/heading.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/hr.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/html_block.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/lheading.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/list.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/paragraph.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/reference.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/state_block.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/table.cpython-312.pyc +0 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/blockquote.py +368 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/code.py +36 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/fence.py +146 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/heading.py +69 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/hr.py +56 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/html_block.py +90 -0
- venv/lib/python3.12/site-packages/markdown_it/rules_block/lheading.py +86 -0
.gitattributes
CHANGED
|
@@ -64,3 +64,6 @@ venv/lib/python3.12/site-packages/huggingface_hub/inference/__pycache__/_client.
|
|
| 64 |
venv/lib/python3.12/site-packages/huggingface_hub/inference/_generated/__pycache__/_async_client.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
|
| 65 |
venv/lib/python3.12/site-packages/jinja2/__pycache__/compiler.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
|
| 66 |
venv/lib/python3.12/site-packages/joblib/test/__pycache__/test_parallel.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
venv/lib/python3.12/site-packages/huggingface_hub/inference/_generated/__pycache__/_async_client.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
|
| 65 |
venv/lib/python3.12/site-packages/jinja2/__pycache__/compiler.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
|
| 66 |
venv/lib/python3.12/site-packages/joblib/test/__pycache__/test_parallel.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
|
| 67 |
+
venv/lib/python3.12/site-packages/mpmath/__pycache__/function_docs.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
|
| 68 |
+
venv/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_fp.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
|
| 69 |
+
venv/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_functions2.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
|
venv/lib/python3.12/site-packages/markdown_it/common/html_blocks.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""List of valid html blocks names, according to commonmark spec
|
| 2 |
+
http://jgm.github.io/CommonMark/spec.html#html-blocks
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
# see https://spec.commonmark.org/0.31.2/#html-blocks
|
| 6 |
+
block_names = [
|
| 7 |
+
"address",
|
| 8 |
+
"article",
|
| 9 |
+
"aside",
|
| 10 |
+
"base",
|
| 11 |
+
"basefont",
|
| 12 |
+
"blockquote",
|
| 13 |
+
"body",
|
| 14 |
+
"caption",
|
| 15 |
+
"center",
|
| 16 |
+
"col",
|
| 17 |
+
"colgroup",
|
| 18 |
+
"dd",
|
| 19 |
+
"details",
|
| 20 |
+
"dialog",
|
| 21 |
+
"dir",
|
| 22 |
+
"div",
|
| 23 |
+
"dl",
|
| 24 |
+
"dt",
|
| 25 |
+
"fieldset",
|
| 26 |
+
"figcaption",
|
| 27 |
+
"figure",
|
| 28 |
+
"footer",
|
| 29 |
+
"form",
|
| 30 |
+
"frame",
|
| 31 |
+
"frameset",
|
| 32 |
+
"h1",
|
| 33 |
+
"h2",
|
| 34 |
+
"h3",
|
| 35 |
+
"h4",
|
| 36 |
+
"h5",
|
| 37 |
+
"h6",
|
| 38 |
+
"head",
|
| 39 |
+
"header",
|
| 40 |
+
"hr",
|
| 41 |
+
"html",
|
| 42 |
+
"iframe",
|
| 43 |
+
"legend",
|
| 44 |
+
"li",
|
| 45 |
+
"link",
|
| 46 |
+
"main",
|
| 47 |
+
"menu",
|
| 48 |
+
"menuitem",
|
| 49 |
+
"nav",
|
| 50 |
+
"noframes",
|
| 51 |
+
"ol",
|
| 52 |
+
"optgroup",
|
| 53 |
+
"option",
|
| 54 |
+
"p",
|
| 55 |
+
"param",
|
| 56 |
+
"search",
|
| 57 |
+
"section",
|
| 58 |
+
"summary",
|
| 59 |
+
"table",
|
| 60 |
+
"tbody",
|
| 61 |
+
"td",
|
| 62 |
+
"tfoot",
|
| 63 |
+
"th",
|
| 64 |
+
"thead",
|
| 65 |
+
"title",
|
| 66 |
+
"tr",
|
| 67 |
+
"track",
|
| 68 |
+
"ul",
|
| 69 |
+
]
|
venv/lib/python3.12/site-packages/markdown_it/common/html_re.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Regexps to match html elements"""
|
| 2 |
+
|
| 3 |
+
import re
|
| 4 |
+
|
| 5 |
+
attr_name = "[a-zA-Z_:][a-zA-Z0-9:._-]*"
|
| 6 |
+
|
| 7 |
+
unquoted = "[^\"'=<>`\\x00-\\x20]+"
|
| 8 |
+
single_quoted = "'[^']*'"
|
| 9 |
+
double_quoted = '"[^"]*"'
|
| 10 |
+
|
| 11 |
+
attr_value = "(?:" + unquoted + "|" + single_quoted + "|" + double_quoted + ")"
|
| 12 |
+
|
| 13 |
+
attribute = "(?:\\s+" + attr_name + "(?:\\s*=\\s*" + attr_value + ")?)"
|
| 14 |
+
|
| 15 |
+
open_tag = "<[A-Za-z][A-Za-z0-9\\-]*" + attribute + "*\\s*\\/?>"
|
| 16 |
+
|
| 17 |
+
close_tag = "<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>"
|
| 18 |
+
comment = "<!---?>|<!--(?:[^-]|-[^-]|--[^>])*-->"
|
| 19 |
+
processing = "<[?][\\s\\S]*?[?]>"
|
| 20 |
+
declaration = "<![A-Za-z][^>]*>"
|
| 21 |
+
cdata = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>"
|
| 22 |
+
|
| 23 |
+
HTML_TAG_RE = re.compile(
|
| 24 |
+
"^(?:"
|
| 25 |
+
+ open_tag
|
| 26 |
+
+ "|"
|
| 27 |
+
+ close_tag
|
| 28 |
+
+ "|"
|
| 29 |
+
+ comment
|
| 30 |
+
+ "|"
|
| 31 |
+
+ processing
|
| 32 |
+
+ "|"
|
| 33 |
+
+ declaration
|
| 34 |
+
+ "|"
|
| 35 |
+
+ cdata
|
| 36 |
+
+ ")"
|
| 37 |
+
)
|
| 38 |
+
HTML_OPEN_CLOSE_TAG_STR = "^(?:" + open_tag + "|" + close_tag + ")"
|
| 39 |
+
HTML_OPEN_CLOSE_TAG_RE = re.compile(HTML_OPEN_CLOSE_TAG_STR)
|
venv/lib/python3.12/site-packages/markdown_it/common/normalize_url.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from collections.abc import Callable
|
| 4 |
+
from contextlib import suppress
|
| 5 |
+
import re
|
| 6 |
+
from urllib.parse import quote, unquote, urlparse, urlunparse # noqa: F401
|
| 7 |
+
|
| 8 |
+
import mdurl
|
| 9 |
+
|
| 10 |
+
from .. import _punycode
|
| 11 |
+
|
| 12 |
+
RECODE_HOSTNAME_FOR = ("http:", "https:", "mailto:")
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def normalizeLink(url: str) -> str:
|
| 16 |
+
"""Normalize destination URLs in links
|
| 17 |
+
|
| 18 |
+
::
|
| 19 |
+
|
| 20 |
+
[label]: destination 'title'
|
| 21 |
+
^^^^^^^^^^^
|
| 22 |
+
"""
|
| 23 |
+
parsed = mdurl.parse(url, slashes_denote_host=True)
|
| 24 |
+
|
| 25 |
+
# Encode hostnames in urls like:
|
| 26 |
+
# `http://host/`, `https://host/`, `mailto:user@host`, `//host/`
|
| 27 |
+
#
|
| 28 |
+
# We don't encode unknown schemas, because it's likely that we encode
|
| 29 |
+
# something we shouldn't (e.g. `skype:name` treated as `skype:host`)
|
| 30 |
+
#
|
| 31 |
+
if parsed.hostname and (
|
| 32 |
+
not parsed.protocol or parsed.protocol in RECODE_HOSTNAME_FOR
|
| 33 |
+
):
|
| 34 |
+
with suppress(Exception):
|
| 35 |
+
parsed = parsed._replace(hostname=_punycode.to_ascii(parsed.hostname))
|
| 36 |
+
|
| 37 |
+
return mdurl.encode(mdurl.format(parsed))
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def normalizeLinkText(url: str) -> str:
|
| 41 |
+
"""Normalize autolink content
|
| 42 |
+
|
| 43 |
+
::
|
| 44 |
+
|
| 45 |
+
<destination>
|
| 46 |
+
~~~~~~~~~~~
|
| 47 |
+
"""
|
| 48 |
+
parsed = mdurl.parse(url, slashes_denote_host=True)
|
| 49 |
+
|
| 50 |
+
# Encode hostnames in urls like:
|
| 51 |
+
# `http://host/`, `https://host/`, `mailto:user@host`, `//host/`
|
| 52 |
+
#
|
| 53 |
+
# We don't encode unknown schemas, because it's likely that we encode
|
| 54 |
+
# something we shouldn't (e.g. `skype:name` treated as `skype:host`)
|
| 55 |
+
#
|
| 56 |
+
if parsed.hostname and (
|
| 57 |
+
not parsed.protocol or parsed.protocol in RECODE_HOSTNAME_FOR
|
| 58 |
+
):
|
| 59 |
+
with suppress(Exception):
|
| 60 |
+
parsed = parsed._replace(hostname=_punycode.to_unicode(parsed.hostname))
|
| 61 |
+
|
| 62 |
+
# add '%' to exclude list because of https://github.com/markdown-it/markdown-it/issues/720
|
| 63 |
+
return mdurl.decode(mdurl.format(parsed), mdurl.DECODE_DEFAULT_CHARS + "%")
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
BAD_PROTO_RE = re.compile(r"^(vbscript|javascript|file|data):")
|
| 67 |
+
GOOD_DATA_RE = re.compile(r"^data:image\/(gif|png|jpeg|webp);")
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def validateLink(url: str, validator: Callable[[str], bool] | None = None) -> bool:
|
| 71 |
+
"""Validate URL link is allowed in output.
|
| 72 |
+
|
| 73 |
+
This validator can prohibit more than really needed to prevent XSS.
|
| 74 |
+
It's a tradeoff to keep code simple and to be secure by default.
|
| 75 |
+
|
| 76 |
+
Note: url should be normalized at this point, and existing entities decoded.
|
| 77 |
+
"""
|
| 78 |
+
if validator is not None:
|
| 79 |
+
return validator(url)
|
| 80 |
+
url = url.strip().lower()
|
| 81 |
+
return bool(GOOD_DATA_RE.search(url)) if BAD_PROTO_RE.search(url) else True
|
venv/lib/python3.12/site-packages/markdown_it/common/utils.py
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Utilities for parsing source text"""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import re
|
| 6 |
+
from re import Match
|
| 7 |
+
from typing import TypeVar
|
| 8 |
+
import unicodedata
|
| 9 |
+
|
| 10 |
+
from .entities import entities
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def charCodeAt(src: str, pos: int) -> int | None:
|
| 14 |
+
"""
|
| 15 |
+
Returns the Unicode value of the character at the specified location.
|
| 16 |
+
|
| 17 |
+
@param - index The zero-based index of the desired character.
|
| 18 |
+
If there is no character at the specified index, NaN is returned.
|
| 19 |
+
|
| 20 |
+
This was added for compatibility with python
|
| 21 |
+
"""
|
| 22 |
+
try:
|
| 23 |
+
return ord(src[pos])
|
| 24 |
+
except IndexError:
|
| 25 |
+
return None
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def charStrAt(src: str, pos: int) -> str | None:
|
| 29 |
+
"""
|
| 30 |
+
Returns the Unicode value of the character at the specified location.
|
| 31 |
+
|
| 32 |
+
@param - index The zero-based index of the desired character.
|
| 33 |
+
If there is no character at the specified index, NaN is returned.
|
| 34 |
+
|
| 35 |
+
This was added for compatibility with python
|
| 36 |
+
"""
|
| 37 |
+
try:
|
| 38 |
+
return src[pos]
|
| 39 |
+
except IndexError:
|
| 40 |
+
return None
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
_ItemTV = TypeVar("_ItemTV")
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def arrayReplaceAt(
|
| 47 |
+
src: list[_ItemTV], pos: int, newElements: list[_ItemTV]
|
| 48 |
+
) -> list[_ItemTV]:
|
| 49 |
+
"""
|
| 50 |
+
Remove element from array and put another array at those position.
|
| 51 |
+
Useful for some operations with tokens
|
| 52 |
+
"""
|
| 53 |
+
return src[:pos] + newElements + src[pos + 1 :]
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def isValidEntityCode(c: int) -> bool:
|
| 57 |
+
# broken sequence
|
| 58 |
+
if c >= 0xD800 and c <= 0xDFFF:
|
| 59 |
+
return False
|
| 60 |
+
# never used
|
| 61 |
+
if c >= 0xFDD0 and c <= 0xFDEF:
|
| 62 |
+
return False
|
| 63 |
+
if ((c & 0xFFFF) == 0xFFFF) or ((c & 0xFFFF) == 0xFFFE):
|
| 64 |
+
return False
|
| 65 |
+
# control codes
|
| 66 |
+
if c >= 0x00 and c <= 0x08:
|
| 67 |
+
return False
|
| 68 |
+
if c == 0x0B:
|
| 69 |
+
return False
|
| 70 |
+
if c >= 0x0E and c <= 0x1F:
|
| 71 |
+
return False
|
| 72 |
+
if c >= 0x7F and c <= 0x9F:
|
| 73 |
+
return False
|
| 74 |
+
# out of range
|
| 75 |
+
return not (c > 0x10FFFF)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def fromCodePoint(c: int) -> str:
|
| 79 |
+
"""Convert ordinal to unicode.
|
| 80 |
+
|
| 81 |
+
Note, in the original Javascript two string characters were required,
|
| 82 |
+
for codepoints larger than `0xFFFF`.
|
| 83 |
+
But Python 3 can represent any unicode codepoint in one character.
|
| 84 |
+
"""
|
| 85 |
+
return chr(c)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
# UNESCAPE_MD_RE = re.compile(r'\\([!"#$%&\'()*+,\-.\/:;<=>?@[\\\]^_`{|}~])')
|
| 89 |
+
# ENTITY_RE_g = re.compile(r'&([a-z#][a-z0-9]{1,31})', re.IGNORECASE)
|
| 90 |
+
UNESCAPE_ALL_RE = re.compile(
|
| 91 |
+
r'\\([!"#$%&\'()*+,\-.\/:;<=>?@[\\\]^_`{|}~])' + "|" + r"&([a-z#][a-z0-9]{1,31});",
|
| 92 |
+
re.IGNORECASE,
|
| 93 |
+
)
|
| 94 |
+
DIGITAL_ENTITY_BASE10_RE = re.compile(r"#([0-9]{1,8})")
|
| 95 |
+
DIGITAL_ENTITY_BASE16_RE = re.compile(r"#x([a-f0-9]{1,8})", re.IGNORECASE)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def replaceEntityPattern(match: str, name: str) -> str:
|
| 99 |
+
"""Convert HTML entity patterns,
|
| 100 |
+
see https://spec.commonmark.org/0.30/#entity-references
|
| 101 |
+
"""
|
| 102 |
+
if name in entities:
|
| 103 |
+
return entities[name]
|
| 104 |
+
|
| 105 |
+
code: None | int = None
|
| 106 |
+
if pat := DIGITAL_ENTITY_BASE10_RE.fullmatch(name):
|
| 107 |
+
code = int(pat.group(1), 10)
|
| 108 |
+
elif pat := DIGITAL_ENTITY_BASE16_RE.fullmatch(name):
|
| 109 |
+
code = int(pat.group(1), 16)
|
| 110 |
+
|
| 111 |
+
if code is not None and isValidEntityCode(code):
|
| 112 |
+
return fromCodePoint(code)
|
| 113 |
+
|
| 114 |
+
return match
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def unescapeAll(string: str) -> str:
|
| 118 |
+
def replacer_func(match: Match[str]) -> str:
|
| 119 |
+
escaped = match.group(1)
|
| 120 |
+
if escaped:
|
| 121 |
+
return escaped
|
| 122 |
+
entity = match.group(2)
|
| 123 |
+
return replaceEntityPattern(match.group(), entity)
|
| 124 |
+
|
| 125 |
+
if "\\" not in string and "&" not in string:
|
| 126 |
+
return string
|
| 127 |
+
return UNESCAPE_ALL_RE.sub(replacer_func, string)
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
ESCAPABLE = r"""\\!"#$%&'()*+,./:;<=>?@\[\]^`{}|_~-"""
|
| 131 |
+
ESCAPE_CHAR = re.compile(r"\\([" + ESCAPABLE + r"])")
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def stripEscape(string: str) -> str:
|
| 135 |
+
"""Strip escape \\ characters"""
|
| 136 |
+
return ESCAPE_CHAR.sub(r"\1", string)
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def escapeHtml(raw: str) -> str:
|
| 140 |
+
"""Replace special characters "&", "<", ">" and '"' to HTML-safe sequences."""
|
| 141 |
+
# like html.escape, but without escaping single quotes
|
| 142 |
+
raw = raw.replace("&", "&") # Must be done first!
|
| 143 |
+
raw = raw.replace("<", "<")
|
| 144 |
+
raw = raw.replace(">", ">")
|
| 145 |
+
raw = raw.replace('"', """)
|
| 146 |
+
return raw
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
# //////////////////////////////////////////////////////////////////////////////
|
| 150 |
+
|
| 151 |
+
REGEXP_ESCAPE_RE = re.compile(r"[.?*+^$[\]\\(){}|-]")
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def escapeRE(string: str) -> str:
|
| 155 |
+
string = REGEXP_ESCAPE_RE.sub("\\$&", string)
|
| 156 |
+
return string
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
# //////////////////////////////////////////////////////////////////////////////
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def isSpace(code: int | None) -> bool:
|
| 163 |
+
"""Check if character code is a whitespace."""
|
| 164 |
+
return code in (0x09, 0x20)
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def isStrSpace(ch: str | None) -> bool:
|
| 168 |
+
"""Check if character is a whitespace."""
|
| 169 |
+
return ch in ("\t", " ")
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
MD_WHITESPACE = {
|
| 173 |
+
0x09, # \t
|
| 174 |
+
0x0A, # \n
|
| 175 |
+
0x0B, # \v
|
| 176 |
+
0x0C, # \f
|
| 177 |
+
0x0D, # \r
|
| 178 |
+
0x20, # space
|
| 179 |
+
0xA0,
|
| 180 |
+
0x1680,
|
| 181 |
+
0x202F,
|
| 182 |
+
0x205F,
|
| 183 |
+
0x3000,
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def isWhiteSpace(code: int) -> bool:
|
| 188 |
+
r"""Zs (unicode class) || [\t\f\v\r\n]"""
|
| 189 |
+
if code >= 0x2000 and code <= 0x200A:
|
| 190 |
+
return True
|
| 191 |
+
return code in MD_WHITESPACE
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
# //////////////////////////////////////////////////////////////////////////////
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def isPunctChar(ch: str) -> bool:
|
| 198 |
+
"""Check if character is a punctuation character."""
|
| 199 |
+
return unicodedata.category(ch).startswith(("P", "S"))
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
MD_ASCII_PUNCT = {
|
| 203 |
+
0x21, # /* ! */
|
| 204 |
+
0x22, # /* " */
|
| 205 |
+
0x23, # /* # */
|
| 206 |
+
0x24, # /* $ */
|
| 207 |
+
0x25, # /* % */
|
| 208 |
+
0x26, # /* & */
|
| 209 |
+
0x27, # /* ' */
|
| 210 |
+
0x28, # /* ( */
|
| 211 |
+
0x29, # /* ) */
|
| 212 |
+
0x2A, # /* * */
|
| 213 |
+
0x2B, # /* + */
|
| 214 |
+
0x2C, # /* , */
|
| 215 |
+
0x2D, # /* - */
|
| 216 |
+
0x2E, # /* . */
|
| 217 |
+
0x2F, # /* / */
|
| 218 |
+
0x3A, # /* : */
|
| 219 |
+
0x3B, # /* ; */
|
| 220 |
+
0x3C, # /* < */
|
| 221 |
+
0x3D, # /* = */
|
| 222 |
+
0x3E, # /* > */
|
| 223 |
+
0x3F, # /* ? */
|
| 224 |
+
0x40, # /* @ */
|
| 225 |
+
0x5B, # /* [ */
|
| 226 |
+
0x5C, # /* \ */
|
| 227 |
+
0x5D, # /* ] */
|
| 228 |
+
0x5E, # /* ^ */
|
| 229 |
+
0x5F, # /* _ */
|
| 230 |
+
0x60, # /* ` */
|
| 231 |
+
0x7B, # /* { */
|
| 232 |
+
0x7C, # /* | */
|
| 233 |
+
0x7D, # /* } */
|
| 234 |
+
0x7E, # /* ~ */
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def isMdAsciiPunct(ch: int) -> bool:
|
| 239 |
+
"""Markdown ASCII punctuation characters.
|
| 240 |
+
|
| 241 |
+
::
|
| 242 |
+
|
| 243 |
+
!, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \\, ], ^, _, `, {, |, }, or ~
|
| 244 |
+
|
| 245 |
+
See http://spec.commonmark.org/0.15/#ascii-punctuation-character
|
| 246 |
+
|
| 247 |
+
Don't confuse with unicode punctuation !!! It lacks some chars in ascii range.
|
| 248 |
+
|
| 249 |
+
"""
|
| 250 |
+
return ch in MD_ASCII_PUNCT
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
def normalizeReference(string: str) -> str:
|
| 254 |
+
"""Helper to unify [reference labels]."""
|
| 255 |
+
# Trim and collapse whitespace
|
| 256 |
+
#
|
| 257 |
+
string = re.sub(r"\s+", " ", string.strip())
|
| 258 |
+
|
| 259 |
+
# In node v10 'ẞ'.toLowerCase() === 'Ṿ', which is presumed to be a bug
|
| 260 |
+
# fixed in v12 (couldn't find any details).
|
| 261 |
+
#
|
| 262 |
+
# So treat this one as a special case
|
| 263 |
+
# (remove this when node v10 is no longer supported).
|
| 264 |
+
#
|
| 265 |
+
# if ('ẞ'.toLowerCase() === 'Ṿ') {
|
| 266 |
+
# str = str.replace(/ẞ/g, 'ß')
|
| 267 |
+
# }
|
| 268 |
+
|
| 269 |
+
# .toLowerCase().toUpperCase() should get rid of all differences
|
| 270 |
+
# between letter variants.
|
| 271 |
+
#
|
| 272 |
+
# Simple .toLowerCase() doesn't normalize 125 code points correctly,
|
| 273 |
+
# and .toUpperCase doesn't normalize 6 of them (list of exceptions:
|
| 274 |
+
# İ, ϴ, ẞ, Ω, K, Å - those are already uppercased, but have differently
|
| 275 |
+
# uppercased versions).
|
| 276 |
+
#
|
| 277 |
+
# Here's an example showing how it happens. Lets take greek letter omega:
|
| 278 |
+
# uppercase U+0398 (Θ), U+03f4 (ϴ) and lowercase U+03b8 (θ), U+03d1 (ϑ)
|
| 279 |
+
#
|
| 280 |
+
# Unicode entries:
|
| 281 |
+
# 0398;GREEK CAPITAL LETTER THETA;Lu;0;L;;;;;N;;;;03B8
|
| 282 |
+
# 03B8;GREEK SMALL LETTER THETA;Ll;0;L;;;;;N;;;0398;;0398
|
| 283 |
+
# 03D1;GREEK THETA SYMBOL;Ll;0;L;<compat> 03B8;;;;N;GREEK SMALL LETTER SCRIPT THETA;;0398;;0398
|
| 284 |
+
# 03F4;GREEK CAPITAL THETA SYMBOL;Lu;0;L;<compat> 0398;;;;N;;;;03B8
|
| 285 |
+
#
|
| 286 |
+
# Case-insensitive comparison should treat all of them as equivalent.
|
| 287 |
+
#
|
| 288 |
+
# But .toLowerCase() doesn't change ϑ (it's already lowercase),
|
| 289 |
+
# and .toUpperCase() doesn't change ϴ (already uppercase).
|
| 290 |
+
#
|
| 291 |
+
# Applying first lower then upper case normalizes any character:
|
| 292 |
+
# '\u0398\u03f4\u03b8\u03d1'.toLowerCase().toUpperCase() === '\u0398\u0398\u0398\u0398'
|
| 293 |
+
#
|
| 294 |
+
# Note: this is equivalent to unicode case folding; unicode normalization
|
| 295 |
+
# is a different step that is not required here.
|
| 296 |
+
#
|
| 297 |
+
# Final result should be uppercased, because it's later stored in an object
|
| 298 |
+
# (this avoid a conflict with Object.prototype members,
|
| 299 |
+
# most notably, `__proto__`)
|
| 300 |
+
#
|
| 301 |
+
return string.lower().upper()
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
LINK_OPEN_RE = re.compile(r"^<a[>\s]", flags=re.IGNORECASE)
|
| 305 |
+
LINK_CLOSE_RE = re.compile(r"^</a\s*>", flags=re.IGNORECASE)
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
def isLinkOpen(string: str) -> bool:
|
| 309 |
+
return bool(LINK_OPEN_RE.search(string))
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
def isLinkClose(string: str) -> bool:
|
| 313 |
+
return bool(LINK_CLOSE_RE.search(string))
|
venv/lib/python3.12/site-packages/markdown_it/helpers/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Functions for parsing Links"""
|
| 2 |
+
|
| 3 |
+
__all__ = ("parseLinkDestination", "parseLinkLabel", "parseLinkTitle")
|
| 4 |
+
from .parse_link_destination import parseLinkDestination
|
| 5 |
+
from .parse_link_label import parseLinkLabel
|
| 6 |
+
from .parse_link_title import parseLinkTitle
|
venv/lib/python3.12/site-packages/markdown_it/helpers/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (443 Bytes). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/helpers/__pycache__/parse_link_destination.cpython-312.pyc
ADDED
|
Binary file (2.1 kB). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/helpers/__pycache__/parse_link_label.cpython-312.pyc
ADDED
|
Binary file (1.41 kB). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/helpers/__pycache__/parse_link_title.cpython-312.pyc
ADDED
|
Binary file (2.39 kB). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/helpers/parse_link_destination.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Parse link destination
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from ..common.utils import charCodeAt, unescapeAll
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class _Result:
|
| 9 |
+
__slots__ = ("ok", "pos", "str")
|
| 10 |
+
|
| 11 |
+
def __init__(self) -> None:
|
| 12 |
+
self.ok = False
|
| 13 |
+
self.pos = 0
|
| 14 |
+
self.str = ""
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def parseLinkDestination(string: str, pos: int, maximum: int) -> _Result:
|
| 18 |
+
start = pos
|
| 19 |
+
result = _Result()
|
| 20 |
+
|
| 21 |
+
if charCodeAt(string, pos) == 0x3C: # /* < */
|
| 22 |
+
pos += 1
|
| 23 |
+
while pos < maximum:
|
| 24 |
+
code = charCodeAt(string, pos)
|
| 25 |
+
if code == 0x0A: # /* \n */)
|
| 26 |
+
return result
|
| 27 |
+
if code == 0x3C: # / * < * /
|
| 28 |
+
return result
|
| 29 |
+
if code == 0x3E: # /* > */) {
|
| 30 |
+
result.pos = pos + 1
|
| 31 |
+
result.str = unescapeAll(string[start + 1 : pos])
|
| 32 |
+
result.ok = True
|
| 33 |
+
return result
|
| 34 |
+
|
| 35 |
+
if code == 0x5C and pos + 1 < maximum: # \
|
| 36 |
+
pos += 2
|
| 37 |
+
continue
|
| 38 |
+
|
| 39 |
+
pos += 1
|
| 40 |
+
|
| 41 |
+
# no closing '>'
|
| 42 |
+
return result
|
| 43 |
+
|
| 44 |
+
# this should be ... } else { ... branch
|
| 45 |
+
|
| 46 |
+
level = 0
|
| 47 |
+
while pos < maximum:
|
| 48 |
+
code = charCodeAt(string, pos)
|
| 49 |
+
|
| 50 |
+
if code is None or code == 0x20:
|
| 51 |
+
break
|
| 52 |
+
|
| 53 |
+
# ascii control characters
|
| 54 |
+
if code < 0x20 or code == 0x7F:
|
| 55 |
+
break
|
| 56 |
+
|
| 57 |
+
if code == 0x5C and pos + 1 < maximum:
|
| 58 |
+
if charCodeAt(string, pos + 1) == 0x20:
|
| 59 |
+
break
|
| 60 |
+
pos += 2
|
| 61 |
+
continue
|
| 62 |
+
|
| 63 |
+
if code == 0x28: # /* ( */)
|
| 64 |
+
level += 1
|
| 65 |
+
if level > 32:
|
| 66 |
+
return result
|
| 67 |
+
|
| 68 |
+
if code == 0x29: # /* ) */)
|
| 69 |
+
if level == 0:
|
| 70 |
+
break
|
| 71 |
+
level -= 1
|
| 72 |
+
|
| 73 |
+
pos += 1
|
| 74 |
+
|
| 75 |
+
if start == pos:
|
| 76 |
+
return result
|
| 77 |
+
if level != 0:
|
| 78 |
+
return result
|
| 79 |
+
|
| 80 |
+
result.str = unescapeAll(string[start:pos])
|
| 81 |
+
result.pos = pos
|
| 82 |
+
result.ok = True
|
| 83 |
+
return result
|
venv/lib/python3.12/site-packages/markdown_it/helpers/parse_link_label.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Parse link label
|
| 3 |
+
|
| 4 |
+
this function assumes that first character ("[") already matches
|
| 5 |
+
returns the end of the label
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from markdown_it.rules_inline import StateInline
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def parseLinkLabel(state: StateInline, start: int, disableNested: bool = False) -> int:
|
| 13 |
+
labelEnd = -1
|
| 14 |
+
oldPos = state.pos
|
| 15 |
+
found = False
|
| 16 |
+
|
| 17 |
+
state.pos = start + 1
|
| 18 |
+
level = 1
|
| 19 |
+
|
| 20 |
+
while state.pos < state.posMax:
|
| 21 |
+
marker = state.src[state.pos]
|
| 22 |
+
if marker == "]":
|
| 23 |
+
level -= 1
|
| 24 |
+
if level == 0:
|
| 25 |
+
found = True
|
| 26 |
+
break
|
| 27 |
+
|
| 28 |
+
prevPos = state.pos
|
| 29 |
+
state.md.inline.skipToken(state)
|
| 30 |
+
if marker == "[":
|
| 31 |
+
if prevPos == state.pos - 1:
|
| 32 |
+
# increase level if we find text `[`,
|
| 33 |
+
# which is not a part of any token
|
| 34 |
+
level += 1
|
| 35 |
+
elif disableNested:
|
| 36 |
+
state.pos = oldPos
|
| 37 |
+
return -1
|
| 38 |
+
if found:
|
| 39 |
+
labelEnd = state.pos
|
| 40 |
+
|
| 41 |
+
# restore old state
|
| 42 |
+
state.pos = oldPos
|
| 43 |
+
|
| 44 |
+
return labelEnd
|
venv/lib/python3.12/site-packages/markdown_it/helpers/parse_link_title.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Parse link title"""
|
| 2 |
+
|
| 3 |
+
from ..common.utils import charCodeAt, unescapeAll
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class _State:
|
| 7 |
+
__slots__ = ("can_continue", "marker", "ok", "pos", "str")
|
| 8 |
+
|
| 9 |
+
def __init__(self) -> None:
|
| 10 |
+
self.ok = False
|
| 11 |
+
"""if `true`, this is a valid link title"""
|
| 12 |
+
self.can_continue = False
|
| 13 |
+
"""if `true`, this link can be continued on the next line"""
|
| 14 |
+
self.pos = 0
|
| 15 |
+
"""if `ok`, it's the position of the first character after the closing marker"""
|
| 16 |
+
self.str = ""
|
| 17 |
+
"""if `ok`, it's the unescaped title"""
|
| 18 |
+
self.marker = 0
|
| 19 |
+
"""expected closing marker character code"""
|
| 20 |
+
|
| 21 |
+
def __str__(self) -> str:
|
| 22 |
+
return self.str
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def parseLinkTitle(
|
| 26 |
+
string: str, start: int, maximum: int, prev_state: _State | None = None
|
| 27 |
+
) -> _State:
|
| 28 |
+
"""Parse link title within `str` in [start, max] range,
|
| 29 |
+
or continue previous parsing if `prev_state` is defined (equal to result of last execution).
|
| 30 |
+
"""
|
| 31 |
+
pos = start
|
| 32 |
+
state = _State()
|
| 33 |
+
|
| 34 |
+
if prev_state is not None:
|
| 35 |
+
# this is a continuation of a previous parseLinkTitle call on the next line,
|
| 36 |
+
# used in reference links only
|
| 37 |
+
state.str = prev_state.str
|
| 38 |
+
state.marker = prev_state.marker
|
| 39 |
+
else:
|
| 40 |
+
if pos >= maximum:
|
| 41 |
+
return state
|
| 42 |
+
|
| 43 |
+
marker = charCodeAt(string, pos)
|
| 44 |
+
|
| 45 |
+
# /* " */ /* ' */ /* ( */
|
| 46 |
+
if marker != 0x22 and marker != 0x27 and marker != 0x28:
|
| 47 |
+
return state
|
| 48 |
+
|
| 49 |
+
start += 1
|
| 50 |
+
pos += 1
|
| 51 |
+
|
| 52 |
+
# if opening marker is "(", switch it to closing marker ")"
|
| 53 |
+
if marker == 0x28:
|
| 54 |
+
marker = 0x29
|
| 55 |
+
|
| 56 |
+
state.marker = marker
|
| 57 |
+
|
| 58 |
+
while pos < maximum:
|
| 59 |
+
code = charCodeAt(string, pos)
|
| 60 |
+
if code == state.marker:
|
| 61 |
+
state.pos = pos + 1
|
| 62 |
+
state.str += unescapeAll(string[start:pos])
|
| 63 |
+
state.ok = True
|
| 64 |
+
return state
|
| 65 |
+
elif code == 0x28 and state.marker == 0x29: # /* ( */ /* ) */
|
| 66 |
+
return state
|
| 67 |
+
elif code == 0x5C and pos + 1 < maximum: # /* \ */
|
| 68 |
+
pos += 1
|
| 69 |
+
|
| 70 |
+
pos += 1
|
| 71 |
+
|
| 72 |
+
# no closing marker found, but this link title may continue on the next line (for references)
|
| 73 |
+
state.can_continue = True
|
| 74 |
+
state.str += unescapeAll(string[start:pos])
|
| 75 |
+
return state
|
venv/lib/python3.12/site-packages/markdown_it/main.py
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from collections.abc import Callable, Generator, Iterable, Mapping, MutableMapping
|
| 4 |
+
from contextlib import contextmanager
|
| 5 |
+
from typing import Any, Literal, overload
|
| 6 |
+
|
| 7 |
+
from . import helpers, presets
|
| 8 |
+
from .common import normalize_url, utils
|
| 9 |
+
from .parser_block import ParserBlock
|
| 10 |
+
from .parser_core import ParserCore
|
| 11 |
+
from .parser_inline import ParserInline
|
| 12 |
+
from .renderer import RendererHTML, RendererProtocol
|
| 13 |
+
from .rules_core.state_core import StateCore
|
| 14 |
+
from .token import Token
|
| 15 |
+
from .utils import EnvType, OptionsDict, OptionsType, PresetType
|
| 16 |
+
|
| 17 |
+
try:
|
| 18 |
+
import linkify_it
|
| 19 |
+
except ModuleNotFoundError:
|
| 20 |
+
linkify_it = None
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
_PRESETS: dict[str, PresetType] = {
|
| 24 |
+
"default": presets.default.make(),
|
| 25 |
+
"js-default": presets.js_default.make(),
|
| 26 |
+
"zero": presets.zero.make(),
|
| 27 |
+
"commonmark": presets.commonmark.make(),
|
| 28 |
+
"gfm-like": presets.gfm_like.make(),
|
| 29 |
+
"gfm-like2": presets.gfm_like2.make(),
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class MarkdownIt:
|
| 34 |
+
def __init__(
|
| 35 |
+
self,
|
| 36 |
+
config: str | PresetType = "commonmark",
|
| 37 |
+
options_update: Mapping[str, Any] | None = None,
|
| 38 |
+
*,
|
| 39 |
+
renderer_cls: Callable[[MarkdownIt], RendererProtocol] = RendererHTML,
|
| 40 |
+
):
|
| 41 |
+
"""Main parser class
|
| 42 |
+
|
| 43 |
+
:param config: name of configuration to load or a pre-defined dictionary
|
| 44 |
+
:param options_update: dictionary that will be merged into ``config["options"]``
|
| 45 |
+
:param renderer_cls: the class to load as the renderer:
|
| 46 |
+
``self.renderer = renderer_cls(self)
|
| 47 |
+
"""
|
| 48 |
+
# add modules
|
| 49 |
+
self.utils = utils
|
| 50 |
+
self.helpers = helpers
|
| 51 |
+
|
| 52 |
+
# initialise classes
|
| 53 |
+
self.inline = ParserInline()
|
| 54 |
+
self.block = ParserBlock()
|
| 55 |
+
self.core = ParserCore()
|
| 56 |
+
self.renderer = renderer_cls(self)
|
| 57 |
+
self.linkify = linkify_it.LinkifyIt() if linkify_it else None
|
| 58 |
+
|
| 59 |
+
# set the configuration
|
| 60 |
+
if options_update and not isinstance(options_update, Mapping):
|
| 61 |
+
# catch signature change where renderer_cls was not used as a key-word
|
| 62 |
+
raise TypeError(
|
| 63 |
+
f"options_update should be a mapping: {options_update}"
|
| 64 |
+
"\n(Perhaps you intended this to be the renderer_cls?)"
|
| 65 |
+
)
|
| 66 |
+
self.configure(config, options_update=options_update)
|
| 67 |
+
|
| 68 |
+
def __repr__(self) -> str:
|
| 69 |
+
return f"{self.__class__.__module__}.{self.__class__.__name__}()"
|
| 70 |
+
|
| 71 |
+
@overload
|
| 72 |
+
def __getitem__(self, name: Literal["inline"]) -> ParserInline: ...
|
| 73 |
+
|
| 74 |
+
@overload
|
| 75 |
+
def __getitem__(self, name: Literal["block"]) -> ParserBlock: ...
|
| 76 |
+
|
| 77 |
+
@overload
|
| 78 |
+
def __getitem__(self, name: Literal["core"]) -> ParserCore: ...
|
| 79 |
+
|
| 80 |
+
@overload
|
| 81 |
+
def __getitem__(self, name: Literal["renderer"]) -> RendererProtocol: ...
|
| 82 |
+
|
| 83 |
+
@overload
|
| 84 |
+
def __getitem__(self, name: str) -> Any: ...
|
| 85 |
+
|
| 86 |
+
def __getitem__(self, name: str) -> Any:
|
| 87 |
+
return {
|
| 88 |
+
"inline": self.inline,
|
| 89 |
+
"block": self.block,
|
| 90 |
+
"core": self.core,
|
| 91 |
+
"renderer": self.renderer,
|
| 92 |
+
}[name]
|
| 93 |
+
|
| 94 |
+
def set(self, options: OptionsType) -> None:
|
| 95 |
+
"""Set parser options (in the same format as in constructor).
|
| 96 |
+
Probably, you will never need it, but you can change options after constructor call.
|
| 97 |
+
|
| 98 |
+
__Note:__ To achieve the best possible performance, don't modify a
|
| 99 |
+
`markdown-it` instance options on the fly. If you need multiple configurations
|
| 100 |
+
it's best to create multiple instances and initialize each with separate config.
|
| 101 |
+
"""
|
| 102 |
+
self.options = OptionsDict(options)
|
| 103 |
+
|
| 104 |
+
def configure(
|
| 105 |
+
self, presets: str | PresetType, options_update: Mapping[str, Any] | None = None
|
| 106 |
+
) -> MarkdownIt:
|
| 107 |
+
"""Batch load of all options and component settings.
|
| 108 |
+
This is an internal method, and you probably will not need it.
|
| 109 |
+
But if you will - see available presets and data structure
|
| 110 |
+
[here](https://github.com/markdown-it/markdown-it/tree/master/lib/presets)
|
| 111 |
+
|
| 112 |
+
We strongly recommend to use presets instead of direct config loads.
|
| 113 |
+
That will give better compatibility with next versions.
|
| 114 |
+
"""
|
| 115 |
+
if isinstance(presets, str):
|
| 116 |
+
if presets not in _PRESETS:
|
| 117 |
+
raise KeyError(f"Wrong `markdown-it` preset '{presets}', check name")
|
| 118 |
+
config = _PRESETS[presets]
|
| 119 |
+
else:
|
| 120 |
+
config = presets
|
| 121 |
+
|
| 122 |
+
if not config:
|
| 123 |
+
raise ValueError("Wrong `markdown-it` config, can't be empty")
|
| 124 |
+
|
| 125 |
+
options = config.get("options", {}) or {}
|
| 126 |
+
if options_update:
|
| 127 |
+
options = {**options, **options_update} # type: ignore
|
| 128 |
+
|
| 129 |
+
self.set(options)
|
| 130 |
+
|
| 131 |
+
if "components" in config:
|
| 132 |
+
for name, component in config["components"].items():
|
| 133 |
+
rules = component.get("rules", None)
|
| 134 |
+
if rules:
|
| 135 |
+
self[name].ruler.enableOnly(rules)
|
| 136 |
+
rules2 = component.get("rules2", None)
|
| 137 |
+
if rules2:
|
| 138 |
+
self[name].ruler2.enableOnly(rules2)
|
| 139 |
+
|
| 140 |
+
return self
|
| 141 |
+
|
| 142 |
+
def get_all_rules(self) -> dict[str, list[str]]:
|
| 143 |
+
"""Return the names of all active rules."""
|
| 144 |
+
rules = {
|
| 145 |
+
chain: self[chain].ruler.get_all_rules()
|
| 146 |
+
for chain in ["core", "block", "inline"]
|
| 147 |
+
}
|
| 148 |
+
rules["inline2"] = self.inline.ruler2.get_all_rules()
|
| 149 |
+
return rules
|
| 150 |
+
|
| 151 |
+
def get_active_rules(self) -> dict[str, list[str]]:
|
| 152 |
+
"""Return the names of all active rules."""
|
| 153 |
+
rules = {
|
| 154 |
+
chain: self[chain].ruler.get_active_rules()
|
| 155 |
+
for chain in ["core", "block", "inline"]
|
| 156 |
+
}
|
| 157 |
+
rules["inline2"] = self.inline.ruler2.get_active_rules()
|
| 158 |
+
return rules
|
| 159 |
+
|
| 160 |
+
def enable(
|
| 161 |
+
self, names: str | Iterable[str], ignoreInvalid: bool = False
|
| 162 |
+
) -> MarkdownIt:
|
| 163 |
+
"""Enable list or rules. (chainable)
|
| 164 |
+
|
| 165 |
+
:param names: rule name or list of rule names to enable.
|
| 166 |
+
:param ignoreInvalid: set `true` to ignore errors when rule not found.
|
| 167 |
+
|
| 168 |
+
It will automatically find appropriate components,
|
| 169 |
+
containing rules with given names. If rule not found, and `ignoreInvalid`
|
| 170 |
+
not set - throws exception.
|
| 171 |
+
|
| 172 |
+
Example::
|
| 173 |
+
|
| 174 |
+
md = MarkdownIt().enable(['sub', 'sup']).disable('smartquotes')
|
| 175 |
+
|
| 176 |
+
"""
|
| 177 |
+
result = []
|
| 178 |
+
|
| 179 |
+
if isinstance(names, str):
|
| 180 |
+
names = [names]
|
| 181 |
+
|
| 182 |
+
for chain in ["core", "block", "inline"]:
|
| 183 |
+
result.extend(self[chain].ruler.enable(names, True))
|
| 184 |
+
result.extend(self.inline.ruler2.enable(names, True))
|
| 185 |
+
|
| 186 |
+
missed = [name for name in names if name not in result]
|
| 187 |
+
if missed and not ignoreInvalid:
|
| 188 |
+
raise ValueError(f"MarkdownIt. Failed to enable unknown rule(s): {missed}")
|
| 189 |
+
|
| 190 |
+
return self
|
| 191 |
+
|
| 192 |
+
def disable(
|
| 193 |
+
self, names: str | Iterable[str], ignoreInvalid: bool = False
|
| 194 |
+
) -> MarkdownIt:
|
| 195 |
+
"""The same as [[MarkdownIt.enable]], but turn specified rules off. (chainable)
|
| 196 |
+
|
| 197 |
+
:param names: rule name or list of rule names to disable.
|
| 198 |
+
:param ignoreInvalid: set `true` to ignore errors when rule not found.
|
| 199 |
+
|
| 200 |
+
"""
|
| 201 |
+
result = []
|
| 202 |
+
|
| 203 |
+
if isinstance(names, str):
|
| 204 |
+
names = [names]
|
| 205 |
+
|
| 206 |
+
for chain in ["core", "block", "inline"]:
|
| 207 |
+
result.extend(self[chain].ruler.disable(names, True))
|
| 208 |
+
result.extend(self.inline.ruler2.disable(names, True))
|
| 209 |
+
|
| 210 |
+
missed = [name for name in names if name not in result]
|
| 211 |
+
if missed and not ignoreInvalid:
|
| 212 |
+
raise ValueError(f"MarkdownIt. Failed to disable unknown rule(s): {missed}")
|
| 213 |
+
return self
|
| 214 |
+
|
| 215 |
+
@contextmanager
|
| 216 |
+
def reset_rules(self) -> Generator[None, None, None]:
|
| 217 |
+
"""A context manager, that will reset the current enabled rules on exit."""
|
| 218 |
+
chain_rules = self.get_active_rules()
|
| 219 |
+
yield
|
| 220 |
+
for chain, rules in chain_rules.items():
|
| 221 |
+
if chain != "inline2":
|
| 222 |
+
self[chain].ruler.enableOnly(rules)
|
| 223 |
+
self.inline.ruler2.enableOnly(chain_rules["inline2"])
|
| 224 |
+
|
| 225 |
+
def add_render_rule(
|
| 226 |
+
self, name: str, function: Callable[..., Any], fmt: str = "html"
|
| 227 |
+
) -> None:
|
| 228 |
+
"""Add a rule for rendering a particular Token type.
|
| 229 |
+
|
| 230 |
+
Only applied when ``renderer.__output__ == fmt``
|
| 231 |
+
"""
|
| 232 |
+
if self.renderer.__output__ == fmt:
|
| 233 |
+
self.renderer.rules[name] = function.__get__(self.renderer) # type: ignore
|
| 234 |
+
|
| 235 |
+
def use(
|
| 236 |
+
self, plugin: Callable[..., None], *params: Any, **options: Any
|
| 237 |
+
) -> MarkdownIt:
|
| 238 |
+
"""Load specified plugin with given params into current parser instance. (chainable)
|
| 239 |
+
|
| 240 |
+
It's just a sugar to call `plugin(md, params)` with curring.
|
| 241 |
+
|
| 242 |
+
Example::
|
| 243 |
+
|
| 244 |
+
def func(tokens, idx):
|
| 245 |
+
tokens[idx].content = tokens[idx].content.replace('foo', 'bar')
|
| 246 |
+
md = MarkdownIt().use(plugin, 'foo_replace', 'text', func)
|
| 247 |
+
|
| 248 |
+
"""
|
| 249 |
+
plugin(self, *params, **options)
|
| 250 |
+
return self
|
| 251 |
+
|
| 252 |
+
def parse(self, src: str, env: EnvType | None = None) -> list[Token]:
|
| 253 |
+
"""Parse the source string to a token stream
|
| 254 |
+
|
| 255 |
+
:param src: source string
|
| 256 |
+
:param env: environment sandbox
|
| 257 |
+
|
| 258 |
+
Parse input string and return list of block tokens (special token type
|
| 259 |
+
"inline" will contain list of inline tokens).
|
| 260 |
+
|
| 261 |
+
`env` is used to pass data between "distributed" rules and return additional
|
| 262 |
+
metadata like reference info, needed for the renderer. It also can be used to
|
| 263 |
+
inject data in specific cases. Usually, you will be ok to pass `{}`,
|
| 264 |
+
and then pass updated object to renderer.
|
| 265 |
+
"""
|
| 266 |
+
env = {} if env is None else env
|
| 267 |
+
if not isinstance(env, MutableMapping):
|
| 268 |
+
raise TypeError(f"Input data should be a MutableMapping, not {type(env)}")
|
| 269 |
+
if not isinstance(src, str):
|
| 270 |
+
raise TypeError(f"Input data should be a string, not {type(src)}")
|
| 271 |
+
state = StateCore(src, self, env)
|
| 272 |
+
self.core.process(state)
|
| 273 |
+
return state.tokens
|
| 274 |
+
|
| 275 |
+
def render(self, src: str, env: EnvType | None = None) -> Any:
|
| 276 |
+
"""Render markdown string into html. It does all magic for you :).
|
| 277 |
+
|
| 278 |
+
:param src: source string
|
| 279 |
+
:param env: environment sandbox
|
| 280 |
+
:returns: The output of the loaded renderer
|
| 281 |
+
|
| 282 |
+
`env` can be used to inject additional metadata (`{}` by default).
|
| 283 |
+
But you will not need it with high probability. See also comment
|
| 284 |
+
in [[MarkdownIt.parse]].
|
| 285 |
+
"""
|
| 286 |
+
env = {} if env is None else env
|
| 287 |
+
return self.renderer.render(self.parse(src, env), self.options, env)
|
| 288 |
+
|
| 289 |
+
def parseInline(self, src: str, env: EnvType | None = None) -> list[Token]:
|
| 290 |
+
"""The same as [[MarkdownIt.parse]] but skip all block rules.
|
| 291 |
+
|
| 292 |
+
:param src: source string
|
| 293 |
+
:param env: environment sandbox
|
| 294 |
+
|
| 295 |
+
It returns the
|
| 296 |
+
block tokens list with the single `inline` element, containing parsed inline
|
| 297 |
+
tokens in `children` property. Also updates `env` object.
|
| 298 |
+
"""
|
| 299 |
+
env = {} if env is None else env
|
| 300 |
+
if not isinstance(env, MutableMapping):
|
| 301 |
+
raise TypeError(f"Input data should be an MutableMapping, not {type(env)}")
|
| 302 |
+
if not isinstance(src, str):
|
| 303 |
+
raise TypeError(f"Input data should be a string, not {type(src)}")
|
| 304 |
+
state = StateCore(src, self, env)
|
| 305 |
+
state.inlineMode = True
|
| 306 |
+
self.core.process(state)
|
| 307 |
+
return state.tokens
|
| 308 |
+
|
| 309 |
+
def renderInline(self, src: str, env: EnvType | None = None) -> Any:
|
| 310 |
+
"""Similar to [[MarkdownIt.render]] but for single paragraph content.
|
| 311 |
+
|
| 312 |
+
:param src: source string
|
| 313 |
+
:param env: environment sandbox
|
| 314 |
+
|
| 315 |
+
Similar to [[MarkdownIt.render]] but for single paragraph content. Result
|
| 316 |
+
will NOT be wrapped into `<p>` tags.
|
| 317 |
+
"""
|
| 318 |
+
env = {} if env is None else env
|
| 319 |
+
return self.renderer.render(self.parseInline(src, env), self.options, env)
|
| 320 |
+
|
| 321 |
+
# link methods
|
| 322 |
+
|
| 323 |
+
def validateLink(self, url: str) -> bool:
|
| 324 |
+
"""Validate if the URL link is allowed in output.
|
| 325 |
+
|
| 326 |
+
This validator can prohibit more than really needed to prevent XSS.
|
| 327 |
+
It's a tradeoff to keep code simple and to be secure by default.
|
| 328 |
+
|
| 329 |
+
Note: the url should be normalized at this point, and existing entities decoded.
|
| 330 |
+
"""
|
| 331 |
+
return normalize_url.validateLink(url)
|
| 332 |
+
|
| 333 |
+
def normalizeLink(self, url: str) -> str:
|
| 334 |
+
"""Normalize destination URLs in links
|
| 335 |
+
|
| 336 |
+
::
|
| 337 |
+
|
| 338 |
+
[label]: destination 'title'
|
| 339 |
+
^^^^^^^^^^^
|
| 340 |
+
"""
|
| 341 |
+
return normalize_url.normalizeLink(url)
|
| 342 |
+
|
| 343 |
+
def normalizeLinkText(self, link: str) -> str:
|
| 344 |
+
"""Normalize autolink content
|
| 345 |
+
|
| 346 |
+
::
|
| 347 |
+
|
| 348 |
+
<destination>
|
| 349 |
+
~~~~~~~~~~~
|
| 350 |
+
"""
|
| 351 |
+
return normalize_url.normalizeLinkText(link)
|
venv/lib/python3.12/site-packages/markdown_it/parser_block.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Block-level tokenizer."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from collections.abc import Callable
|
| 6 |
+
import logging
|
| 7 |
+
from typing import TYPE_CHECKING
|
| 8 |
+
|
| 9 |
+
from . import rules_block
|
| 10 |
+
from .ruler import Ruler
|
| 11 |
+
from .rules_block.state_block import StateBlock
|
| 12 |
+
from .token import Token
|
| 13 |
+
from .utils import EnvType
|
| 14 |
+
|
| 15 |
+
if TYPE_CHECKING:
|
| 16 |
+
from markdown_it import MarkdownIt
|
| 17 |
+
|
| 18 |
+
LOGGER = logging.getLogger(__name__)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
RuleFuncBlockType = Callable[[StateBlock, int, int, bool], bool]
|
| 22 |
+
"""(state: StateBlock, startLine: int, endLine: int, silent: bool) -> matched: bool)
|
| 23 |
+
|
| 24 |
+
`silent` disables token generation, useful for lookahead.
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
_rules: list[tuple[str, RuleFuncBlockType, list[str]]] = [
|
| 28 |
+
# First 2 params - rule name & source. Secondary array - list of rules,
|
| 29 |
+
# which can be terminated by this one.
|
| 30 |
+
("table", rules_block.table, ["paragraph", "reference"]),
|
| 31 |
+
("code", rules_block.code, []),
|
| 32 |
+
("fence", rules_block.fence, ["paragraph", "reference", "blockquote", "list"]),
|
| 33 |
+
(
|
| 34 |
+
"blockquote",
|
| 35 |
+
rules_block.blockquote,
|
| 36 |
+
["paragraph", "reference", "blockquote", "list"],
|
| 37 |
+
),
|
| 38 |
+
("hr", rules_block.hr, ["paragraph", "reference", "blockquote", "list"]),
|
| 39 |
+
("list", rules_block.list_block, ["paragraph", "reference", "blockquote"]),
|
| 40 |
+
("reference", rules_block.reference, []),
|
| 41 |
+
("html_block", rules_block.html_block, ["paragraph", "reference", "blockquote"]),
|
| 42 |
+
("heading", rules_block.heading, ["paragraph", "reference", "blockquote"]),
|
| 43 |
+
("lheading", rules_block.lheading, []),
|
| 44 |
+
("paragraph", rules_block.paragraph, []),
|
| 45 |
+
]
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class ParserBlock:
|
| 49 |
+
"""
|
| 50 |
+
ParserBlock#ruler -> Ruler
|
| 51 |
+
|
| 52 |
+
[[Ruler]] instance. Keep configuration of block rules.
|
| 53 |
+
"""
|
| 54 |
+
|
| 55 |
+
def __init__(self) -> None:
|
| 56 |
+
self.ruler = Ruler[RuleFuncBlockType]()
|
| 57 |
+
for name, rule, alt in _rules:
|
| 58 |
+
self.ruler.push(name, rule, {"alt": alt})
|
| 59 |
+
|
| 60 |
+
def tokenize(self, state: StateBlock, startLine: int, endLine: int) -> None:
|
| 61 |
+
"""Generate tokens for input range."""
|
| 62 |
+
rules = self.ruler.getRules("")
|
| 63 |
+
line = startLine
|
| 64 |
+
maxNesting = state.md.options.maxNesting
|
| 65 |
+
hasEmptyLines = False
|
| 66 |
+
|
| 67 |
+
while line < endLine:
|
| 68 |
+
state.line = line = state.skipEmptyLines(line)
|
| 69 |
+
if line >= endLine:
|
| 70 |
+
break
|
| 71 |
+
if state.sCount[line] < state.blkIndent:
|
| 72 |
+
# Termination condition for nested calls.
|
| 73 |
+
# Nested calls currently used for blockquotes & lists
|
| 74 |
+
break
|
| 75 |
+
if state.level >= maxNesting:
|
| 76 |
+
# If nesting level exceeded - skip tail to the end.
|
| 77 |
+
# That's not ordinary situation and we should not care about content.
|
| 78 |
+
state.line = endLine
|
| 79 |
+
break
|
| 80 |
+
|
| 81 |
+
# Try all possible rules.
|
| 82 |
+
# On success, rule should:
|
| 83 |
+
# - update `state.line`
|
| 84 |
+
# - update `state.tokens`
|
| 85 |
+
# - return True
|
| 86 |
+
for rule in rules:
|
| 87 |
+
if rule(state, line, endLine, False):
|
| 88 |
+
break
|
| 89 |
+
|
| 90 |
+
# set state.tight if we had an empty line before current tag
|
| 91 |
+
# i.e. latest empty line should not count
|
| 92 |
+
state.tight = not hasEmptyLines
|
| 93 |
+
|
| 94 |
+
line = state.line
|
| 95 |
+
|
| 96 |
+
# paragraph might "eat" one newline after it in nested lists
|
| 97 |
+
if (line - 1) < endLine and state.isEmpty(line - 1):
|
| 98 |
+
hasEmptyLines = True
|
| 99 |
+
|
| 100 |
+
if line < endLine and state.isEmpty(line):
|
| 101 |
+
hasEmptyLines = True
|
| 102 |
+
line += 1
|
| 103 |
+
state.line = line
|
| 104 |
+
|
| 105 |
+
def parse(
|
| 106 |
+
self, src: str, md: MarkdownIt, env: EnvType, outTokens: list[Token]
|
| 107 |
+
) -> list[Token] | None:
|
| 108 |
+
"""Process input string and push block tokens into `outTokens`."""
|
| 109 |
+
if not src:
|
| 110 |
+
return None
|
| 111 |
+
state = StateBlock(src, md, env, outTokens)
|
| 112 |
+
self.tokenize(state, state.line, state.lineMax)
|
| 113 |
+
return state.tokens
|
venv/lib/python3.12/site-packages/markdown_it/parser_core.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
* class Core
|
| 3 |
+
*
|
| 4 |
+
* Top-level rules executor. Glues block/inline parsers and does intermediate
|
| 5 |
+
* transformations.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
from collections.abc import Callable
|
| 11 |
+
|
| 12 |
+
from .ruler import Ruler
|
| 13 |
+
from .rules_core import (
|
| 14 |
+
block,
|
| 15 |
+
inline,
|
| 16 |
+
linkify,
|
| 17 |
+
normalize,
|
| 18 |
+
replace,
|
| 19 |
+
smartquotes,
|
| 20 |
+
text_join,
|
| 21 |
+
)
|
| 22 |
+
from .rules_core.state_core import StateCore
|
| 23 |
+
|
| 24 |
+
RuleFuncCoreType = Callable[[StateCore], None]
|
| 25 |
+
|
| 26 |
+
_rules: list[tuple[str, RuleFuncCoreType]] = [
|
| 27 |
+
("normalize", normalize),
|
| 28 |
+
("block", block),
|
| 29 |
+
("inline", inline),
|
| 30 |
+
("linkify", linkify),
|
| 31 |
+
("replacements", replace),
|
| 32 |
+
("smartquotes", smartquotes),
|
| 33 |
+
("text_join", text_join),
|
| 34 |
+
]
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class ParserCore:
|
| 38 |
+
def __init__(self) -> None:
|
| 39 |
+
self.ruler = Ruler[RuleFuncCoreType]()
|
| 40 |
+
for name, rule in _rules:
|
| 41 |
+
self.ruler.push(name, rule)
|
| 42 |
+
|
| 43 |
+
def process(self, state: StateCore) -> None:
|
| 44 |
+
"""Executes core chain rules."""
|
| 45 |
+
for rule in self.ruler.getRules(""):
|
| 46 |
+
rule(state)
|
venv/lib/python3.12/site-packages/markdown_it/parser_inline.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tokenizes paragraph content."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from collections.abc import Callable
|
| 6 |
+
import functools
|
| 7 |
+
import re
|
| 8 |
+
from typing import TYPE_CHECKING
|
| 9 |
+
|
| 10 |
+
from . import rules_inline
|
| 11 |
+
from .ruler import Ruler
|
| 12 |
+
from .rules_inline.state_inline import StateInline
|
| 13 |
+
from .token import Token
|
| 14 |
+
from .utils import EnvType
|
| 15 |
+
|
| 16 |
+
if TYPE_CHECKING:
|
| 17 |
+
from markdown_it import MarkdownIt
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
# Default set of characters that terminate a text token and allow inline rules to fire.
|
| 21 |
+
# '{}$%@~+=:' reserved for extensions.
|
| 22 |
+
# Note: Don't confuse with "Markdown ASCII Punctuation" chars.
|
| 23 |
+
# http://spec.commonmark.org/0.15/#ascii-punctuation-character
|
| 24 |
+
_DEFAULT_TERMINATORS: frozenset[str] = frozenset(
|
| 25 |
+
{
|
| 26 |
+
"\n",
|
| 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 |
+
# Lazily compiled regex for the default terminator set. The @cache ensures it is
|
| 54 |
+
# compiled at most once (on first ParserInline instantiation) and shared across all
|
| 55 |
+
# instances that have not added extra chars, keeping __init__ cost near zero.
|
| 56 |
+
@functools.cache
|
| 57 |
+
def _default_terminator_re() -> re.Pattern[str]:
|
| 58 |
+
return re.compile("[" + re.escape("".join(_DEFAULT_TERMINATORS)) + "]")
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
# Parser rules
|
| 62 |
+
RuleFuncInlineType = Callable[[StateInline, bool], bool]
|
| 63 |
+
"""(state: StateInline, silent: bool) -> matched: bool)
|
| 64 |
+
|
| 65 |
+
`silent` disables token generation, useful for lookahead.
|
| 66 |
+
"""
|
| 67 |
+
_rules: list[tuple[str, RuleFuncInlineType]] = [
|
| 68 |
+
("text", rules_inline.text),
|
| 69 |
+
("linkify", rules_inline.linkify),
|
| 70 |
+
("newline", rules_inline.newline),
|
| 71 |
+
("escape", rules_inline.escape),
|
| 72 |
+
("backticks", rules_inline.backtick),
|
| 73 |
+
("strikethrough", rules_inline.strikethrough.tokenize),
|
| 74 |
+
("emphasis", rules_inline.emphasis.tokenize),
|
| 75 |
+
("link", rules_inline.link),
|
| 76 |
+
("image", rules_inline.image),
|
| 77 |
+
("autolink", rules_inline.autolink),
|
| 78 |
+
("html_inline", rules_inline.html_inline),
|
| 79 |
+
("entity", rules_inline.entity),
|
| 80 |
+
]
|
| 81 |
+
|
| 82 |
+
# Note `rule2` ruleset was created specifically for emphasis/strikethrough
|
| 83 |
+
# post-processing and may be changed in the future.
|
| 84 |
+
#
|
| 85 |
+
# Don't use this for anything except pairs (plugins working with `balance_pairs`).
|
| 86 |
+
#
|
| 87 |
+
RuleFuncInline2Type = Callable[[StateInline], None]
|
| 88 |
+
_rules2: list[tuple[str, RuleFuncInline2Type]] = [
|
| 89 |
+
("balance_pairs", rules_inline.link_pairs),
|
| 90 |
+
("strikethrough", rules_inline.strikethrough.postProcess),
|
| 91 |
+
("emphasis", rules_inline.emphasis.postProcess),
|
| 92 |
+
# rules for pairs separate '**' into its own text tokens, which may be left unused,
|
| 93 |
+
# rule below merges unused segments back with the rest of the text
|
| 94 |
+
("fragments_join", rules_inline.fragments_join),
|
| 95 |
+
]
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
class ParserInline:
|
| 99 |
+
def __init__(self) -> None:
|
| 100 |
+
self.ruler = Ruler[RuleFuncInlineType]()
|
| 101 |
+
for name, rule in _rules:
|
| 102 |
+
self.ruler.push(name, rule)
|
| 103 |
+
# Second ruler used for post-processing (e.g. in emphasis-like rules)
|
| 104 |
+
self.ruler2 = Ruler[RuleFuncInline2Type]()
|
| 105 |
+
for name, rule2 in _rules2:
|
| 106 |
+
self.ruler2.push(name, rule2)
|
| 107 |
+
# Characters that stop the text rule, allowing other inline rules to fire.
|
| 108 |
+
# _extra_terminator_chars is only allocated when add_terminator_char() is called
|
| 109 |
+
# with a char outside the defaults, keeping __init__ allocation-free.
|
| 110 |
+
self._extra_terminator_chars: set[str] = set()
|
| 111 |
+
# Pre-compiled regex shared with all default instances (no copy in the common path).
|
| 112 |
+
self.terminator_re: re.Pattern[str] = _default_terminator_re()
|
| 113 |
+
|
| 114 |
+
def add_terminator_char(self, ch: str) -> None:
|
| 115 |
+
"""Register a character that stops the ``text`` rule, allowing inline rules to fire.
|
| 116 |
+
|
| 117 |
+
This lets plugins declare which characters their inline rules react to,
|
| 118 |
+
mirroring the ``MARKER`` mechanism in the Rust markdown-it implementation.
|
| 119 |
+
|
| 120 |
+
:param ch: A single character to add to the terminator set.
|
| 121 |
+
"""
|
| 122 |
+
if ch not in _DEFAULT_TERMINATORS and ch not in self._extra_terminator_chars:
|
| 123 |
+
self._extra_terminator_chars.add(ch)
|
| 124 |
+
self.terminator_re = re.compile(
|
| 125 |
+
"["
|
| 126 |
+
+ re.escape(
|
| 127 |
+
"".join(_DEFAULT_TERMINATORS | self._extra_terminator_chars)
|
| 128 |
+
)
|
| 129 |
+
+ "]"
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
def skipToken(self, state: StateInline) -> None:
|
| 133 |
+
"""Skip single token by running all rules in validation mode;
|
| 134 |
+
returns `True` if any rule reported success
|
| 135 |
+
"""
|
| 136 |
+
ok = False
|
| 137 |
+
pos = state.pos
|
| 138 |
+
rules = self.ruler.getRules("")
|
| 139 |
+
maxNesting = state.md.options["maxNesting"]
|
| 140 |
+
cache = state.cache
|
| 141 |
+
|
| 142 |
+
if pos in cache:
|
| 143 |
+
state.pos = cache[pos]
|
| 144 |
+
return
|
| 145 |
+
|
| 146 |
+
if state.level < maxNesting:
|
| 147 |
+
for rule in rules:
|
| 148 |
+
# Increment state.level and decrement it later to limit recursion.
|
| 149 |
+
# It's harmless to do here, because no tokens are created.
|
| 150 |
+
# But ideally, we'd need a separate private state variable for this purpose.
|
| 151 |
+
state.level += 1
|
| 152 |
+
ok = rule(state, True)
|
| 153 |
+
state.level -= 1
|
| 154 |
+
if ok:
|
| 155 |
+
break
|
| 156 |
+
else:
|
| 157 |
+
# Too much nesting, just skip until the end of the paragraph.
|
| 158 |
+
#
|
| 159 |
+
# NOTE: this will cause links to behave incorrectly in the following case,
|
| 160 |
+
# when an amount of `[` is exactly equal to `maxNesting + 1`:
|
| 161 |
+
#
|
| 162 |
+
# [[[[[[[[[[[[[[[[[[[[[foo]()
|
| 163 |
+
#
|
| 164 |
+
# TODO: remove this workaround when CM standard will allow nested links
|
| 165 |
+
# (we can replace it by preventing links from being parsed in
|
| 166 |
+
# validation mode)
|
| 167 |
+
#
|
| 168 |
+
state.pos = state.posMax
|
| 169 |
+
|
| 170 |
+
if not ok:
|
| 171 |
+
state.pos += 1
|
| 172 |
+
cache[pos] = state.pos
|
| 173 |
+
|
| 174 |
+
def tokenize(self, state: StateInline) -> None:
|
| 175 |
+
"""Generate tokens for input range."""
|
| 176 |
+
ok = False
|
| 177 |
+
rules = self.ruler.getRules("")
|
| 178 |
+
end = state.posMax
|
| 179 |
+
maxNesting = state.md.options["maxNesting"]
|
| 180 |
+
|
| 181 |
+
while state.pos < end:
|
| 182 |
+
# Try all possible rules.
|
| 183 |
+
# On success, rule should:
|
| 184 |
+
#
|
| 185 |
+
# - update `state.pos`
|
| 186 |
+
# - update `state.tokens`
|
| 187 |
+
# - return true
|
| 188 |
+
|
| 189 |
+
if state.level < maxNesting:
|
| 190 |
+
for rule in rules:
|
| 191 |
+
ok = rule(state, False)
|
| 192 |
+
if ok:
|
| 193 |
+
break
|
| 194 |
+
|
| 195 |
+
if ok:
|
| 196 |
+
if state.pos >= end:
|
| 197 |
+
break
|
| 198 |
+
continue
|
| 199 |
+
|
| 200 |
+
state.pending += state.src[state.pos]
|
| 201 |
+
state.pos += 1
|
| 202 |
+
|
| 203 |
+
if state.pending:
|
| 204 |
+
state.pushPending()
|
| 205 |
+
|
| 206 |
+
def parse(
|
| 207 |
+
self, src: str, md: MarkdownIt, env: EnvType, tokens: list[Token]
|
| 208 |
+
) -> list[Token]:
|
| 209 |
+
"""Process input string and push inline tokens into `tokens`"""
|
| 210 |
+
state = StateInline(src, md, env, tokens)
|
| 211 |
+
self.tokenize(state)
|
| 212 |
+
rules2 = self.ruler2.getRules("")
|
| 213 |
+
for rule in rules2:
|
| 214 |
+
rule(state)
|
| 215 |
+
return state.tokens
|
venv/lib/python3.12/site-packages/markdown_it/port.yaml
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
- package: markdown-it/markdown-it
|
| 2 |
+
version: 14.1.0
|
| 3 |
+
commit: 0fe7ccb4b7f30236fb05f623be6924961d296d3d
|
| 4 |
+
date: Mar 19, 2024
|
| 5 |
+
notes:
|
| 6 |
+
- Rename variables that use python built-in names, e.g.
|
| 7 |
+
- `max` -> `maximum`
|
| 8 |
+
- `len` -> `length`
|
| 9 |
+
- `str` -> `string`
|
| 10 |
+
- |
|
| 11 |
+
Convert JS `for` loops to `while` loops
|
| 12 |
+
this is generally the main difference between the codes,
|
| 13 |
+
because in python you can't do e.g. `for {i=1;i<x;i++} {}`
|
| 14 |
+
- |
|
| 15 |
+
`env` is a common Python dictionary, and so does not have attribute access to keys,
|
| 16 |
+
as with JavaScript dictionaries.
|
| 17 |
+
`options` have attribute access only to core markdownit configuration options
|
| 18 |
+
- |
|
| 19 |
+
`Token.attrs` is a dictionary, instead of a list of lists.
|
| 20 |
+
Upstream the list format is only used to guarantee order: https://github.com/markdown-it/markdown-it/issues/142,
|
| 21 |
+
but in Python 3.7+ order of dictionaries is guaranteed.
|
| 22 |
+
One should anyhow use the `attrGet`, `attrSet`, `attrPush` and `attrJoin` methods
|
| 23 |
+
to manipulate `Token.attrs`, which have an identical signature to those upstream.
|
| 24 |
+
- Use python version of `charCodeAt`
|
| 25 |
+
- |
|
| 26 |
+
Use `str` units instead of `int`s to represent Unicode codepoints.
|
| 27 |
+
This provides a significant performance boost
|
| 28 |
+
- |
|
| 29 |
+
In markdown_it/rules_block/reference.py,
|
| 30 |
+
record line range in state.env["references"] and add state.env["duplicate_refs"]
|
| 31 |
+
This is to allow renderers to report on issues regarding references
|
| 32 |
+
- |
|
| 33 |
+
The `MarkdownIt.__init__` signature is slightly different for updating options,
|
| 34 |
+
since you must always specify the config first, e.g.
|
| 35 |
+
use `MarkdownIt("commonmark", {"html": False})` instead of `MarkdownIt({"html": False})`
|
| 36 |
+
- The default configuration preset for `MarkdownIt` is "commonmark" not "default"
|
| 37 |
+
- Allow custom renderer to be passed to `MarkdownIt`
|
| 38 |
+
- |
|
| 39 |
+
change render method signatures
|
| 40 |
+
`func(tokens, idx, options, env, slf)` to
|
| 41 |
+
`func(self, tokens, idx, options, env)`
|
| 42 |
+
- |
|
| 43 |
+
Extensions add render methods by format
|
| 44 |
+
`MarkdownIt.add_render_rule(name, function, fmt="html")`,
|
| 45 |
+
rather than `MarkdownIt.renderer.rules[name] = function`
|
| 46 |
+
and renderers should declare a class property `__output__ = "html"`.
|
| 47 |
+
This allows for extensibility to more than just HTML renderers
|
| 48 |
+
- inline tokens in tables are assigned a map (this is helpful for propagation to children)
|
venv/lib/python3.12/site-packages/markdown_it/presets/__init__.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__all__ = ("commonmark", "default", "gfm_like", "gfm_like2", "js_default", "zero")
|
| 2 |
+
|
| 3 |
+
from ..utils import PresetType
|
| 4 |
+
from . import commonmark, default, zero
|
| 5 |
+
|
| 6 |
+
js_default = default
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class gfm_like: # noqa: N801
|
| 10 |
+
"""GitHub Flavoured Markdown (GFM) like.
|
| 11 |
+
|
| 12 |
+
This adds the linkify, table and strikethrough components to CommmonMark.
|
| 13 |
+
|
| 14 |
+
Note, it lacks task-list items and raw HTML filtering,
|
| 15 |
+
to meet the the full GFM specification
|
| 16 |
+
(see https://github.github.com/gfm/#autolinks-extension-).
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
@staticmethod
|
| 20 |
+
def make() -> PresetType:
|
| 21 |
+
config = commonmark.make()
|
| 22 |
+
config["components"]["core"]["rules"].append("linkify")
|
| 23 |
+
config["components"]["block"]["rules"].append("table")
|
| 24 |
+
config["components"]["inline"]["rules"].extend(["strikethrough", "linkify"])
|
| 25 |
+
config["components"]["inline"]["rules2"].append("strikethrough")
|
| 26 |
+
config["options"]["linkify"] = True
|
| 27 |
+
config["options"]["html"] = True
|
| 28 |
+
return config
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class gfm_like2: # noqa: N801
|
| 32 |
+
"""GitHub Flavoured Markdown (GFM) like, extended.
|
| 33 |
+
|
| 34 |
+
Builds on ``gfm-like`` and additionally enables:
|
| 35 |
+
|
| 36 |
+
- Task lists (``- [x] done``)
|
| 37 |
+
- Alerts (``> [!NOTE]``)
|
| 38 |
+
- Single-tilde strikethrough (``~text~`` in addition to ``~~text~~``)
|
| 39 |
+
"""
|
| 40 |
+
|
| 41 |
+
@staticmethod
|
| 42 |
+
def make() -> PresetType:
|
| 43 |
+
config = gfm_like.make()
|
| 44 |
+
config["options"]["tasklists"] = True
|
| 45 |
+
config["options"]["tasklists_editable"] = False
|
| 46 |
+
config["options"]["alerts"] = True
|
| 47 |
+
config["options"]["strikethrough_single_tilde"] = True
|
| 48 |
+
return config
|
venv/lib/python3.12/site-packages/markdown_it/presets/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (2.4 kB). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/presets/__pycache__/commonmark.cpython-312.pyc
ADDED
|
Binary file (1.14 kB). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/presets/__pycache__/default.cpython-312.pyc
ADDED
|
Binary file (657 Bytes). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/presets/__pycache__/zero.cpython-312.pyc
ADDED
|
Binary file (918 Bytes). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/presets/commonmark.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Commonmark default options.
|
| 2 |
+
|
| 3 |
+
This differs to presets.default,
|
| 4 |
+
primarily in that it allows HTML and does not enable components:
|
| 5 |
+
|
| 6 |
+
- block: table
|
| 7 |
+
- inline: strikethrough
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from ..utils import PresetType
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def make() -> PresetType:
|
| 14 |
+
return {
|
| 15 |
+
"options": {
|
| 16 |
+
"maxNesting": 20, # Internal protection, recursion limit
|
| 17 |
+
"html": True, # Enable HTML tags in source,
|
| 18 |
+
# this is just a shorthand for .enable(["html_inline", "html_block"])
|
| 19 |
+
# used by the linkify rule:
|
| 20 |
+
"linkify": False, # autoconvert URL-like texts to links
|
| 21 |
+
# used by the replacements and smartquotes rules
|
| 22 |
+
# Enable some language-neutral replacements + quotes beautification
|
| 23 |
+
"typographer": False,
|
| 24 |
+
# used by the smartquotes rule:
|
| 25 |
+
# Double + single quotes replacement pairs, when typographer enabled,
|
| 26 |
+
# and smartquotes on. Could be either a String or an Array.
|
| 27 |
+
#
|
| 28 |
+
# For example, you can use '«»„“' for Russian, '„“‚‘' for German,
|
| 29 |
+
# and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp).
|
| 30 |
+
"quotes": "\u201c\u201d\u2018\u2019", # /* “”‘’ */
|
| 31 |
+
# Renderer specific; these options are used directly in the HTML renderer
|
| 32 |
+
"xhtmlOut": True, # Use '/' to close single tags (<br />)
|
| 33 |
+
"breaks": False, # Convert '\n' in paragraphs into <br>
|
| 34 |
+
"langPrefix": "language-", # CSS language prefix for fenced blocks
|
| 35 |
+
# Highlighter function. Should return escaped HTML,
|
| 36 |
+
# or '' if the source string is not changed and should be escaped externally.
|
| 37 |
+
# If result starts with <pre... internal wrapper is skipped.
|
| 38 |
+
#
|
| 39 |
+
# function (/*str, lang, attrs*/) { return ''; }
|
| 40 |
+
#
|
| 41 |
+
"highlight": None,
|
| 42 |
+
},
|
| 43 |
+
"components": {
|
| 44 |
+
"core": {"rules": ["normalize", "block", "inline", "text_join"]},
|
| 45 |
+
"block": {
|
| 46 |
+
"rules": [
|
| 47 |
+
"blockquote",
|
| 48 |
+
"code",
|
| 49 |
+
"fence",
|
| 50 |
+
"heading",
|
| 51 |
+
"hr",
|
| 52 |
+
"html_block",
|
| 53 |
+
"lheading",
|
| 54 |
+
"list",
|
| 55 |
+
"reference",
|
| 56 |
+
"paragraph",
|
| 57 |
+
]
|
| 58 |
+
},
|
| 59 |
+
"inline": {
|
| 60 |
+
"rules": [
|
| 61 |
+
"autolink",
|
| 62 |
+
"backticks",
|
| 63 |
+
"emphasis",
|
| 64 |
+
"entity",
|
| 65 |
+
"escape",
|
| 66 |
+
"html_inline",
|
| 67 |
+
"image",
|
| 68 |
+
"link",
|
| 69 |
+
"newline",
|
| 70 |
+
"text",
|
| 71 |
+
],
|
| 72 |
+
"rules2": ["balance_pairs", "emphasis", "fragments_join"],
|
| 73 |
+
},
|
| 74 |
+
},
|
| 75 |
+
}
|
venv/lib/python3.12/site-packages/markdown_it/presets/default.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""markdown-it default options."""
|
| 2 |
+
|
| 3 |
+
from ..utils import PresetType
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def make() -> PresetType:
|
| 7 |
+
return {
|
| 8 |
+
"options": {
|
| 9 |
+
"maxNesting": 100, # Internal protection, recursion limit
|
| 10 |
+
"html": False, # Enable HTML tags in source
|
| 11 |
+
# this is just a shorthand for .disable(["html_inline", "html_block"])
|
| 12 |
+
# used by the linkify rule:
|
| 13 |
+
"linkify": False, # autoconvert URL-like texts to links
|
| 14 |
+
# used by the replacements and smartquotes rules:
|
| 15 |
+
# Enable some language-neutral replacements + quotes beautification
|
| 16 |
+
"typographer": False,
|
| 17 |
+
# used by the smartquotes rule:
|
| 18 |
+
# Double + single quotes replacement pairs, when typographer enabled,
|
| 19 |
+
# and smartquotes on. Could be either a String or an Array.
|
| 20 |
+
# For example, you can use '«»„“' for Russian, '„“‚‘' for German,
|
| 21 |
+
# and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp).
|
| 22 |
+
"quotes": "\u201c\u201d\u2018\u2019", # /* “”‘’ */
|
| 23 |
+
# Renderer specific; these options are used directly in the HTML renderer
|
| 24 |
+
"xhtmlOut": False, # Use '/' to close single tags (<br />)
|
| 25 |
+
"breaks": False, # Convert '\n' in paragraphs into <br>
|
| 26 |
+
"langPrefix": "language-", # CSS language prefix for fenced blocks
|
| 27 |
+
# Highlighter function. Should return escaped HTML,
|
| 28 |
+
# or '' if the source string is not changed and should be escaped externally.
|
| 29 |
+
# If result starts with <pre... internal wrapper is skipped.
|
| 30 |
+
#
|
| 31 |
+
# function (/*str, lang, attrs*/) { return ''; }
|
| 32 |
+
#
|
| 33 |
+
"highlight": None,
|
| 34 |
+
},
|
| 35 |
+
"components": {"core": {}, "block": {}, "inline": {}},
|
| 36 |
+
}
|
venv/lib/python3.12/site-packages/markdown_it/presets/zero.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
"Zero" preset, with nothing enabled. Useful for manual configuring of simple
|
| 3 |
+
modes. For example, to parse bold/italic only.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from ..utils import PresetType
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def make() -> PresetType:
|
| 10 |
+
return {
|
| 11 |
+
"options": {
|
| 12 |
+
"maxNesting": 20, # Internal protection, recursion limit
|
| 13 |
+
"html": False, # Enable HTML tags in source
|
| 14 |
+
# this is just a shorthand for .disable(["html_inline", "html_block"])
|
| 15 |
+
# used by the linkify rule:
|
| 16 |
+
"linkify": False, # autoconvert URL-like texts to links
|
| 17 |
+
# used by the replacements and smartquotes rules:
|
| 18 |
+
# Enable some language-neutral replacements + quotes beautification
|
| 19 |
+
"typographer": False,
|
| 20 |
+
# used by the smartquotes rule:
|
| 21 |
+
# Double + single quotes replacement pairs, when typographer enabled,
|
| 22 |
+
# and smartquotes on. Could be either a String or an Array.
|
| 23 |
+
# For example, you can use '«»„“' for Russian, '„“‚‘' for German,
|
| 24 |
+
# and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp).
|
| 25 |
+
"quotes": "\u201c\u201d\u2018\u2019", # /* “”‘’ */
|
| 26 |
+
# Renderer specific; these options are used directly in the HTML renderer
|
| 27 |
+
"xhtmlOut": False, # Use '/' to close single tags (<br />)
|
| 28 |
+
"breaks": False, # Convert '\n' in paragraphs into <br>
|
| 29 |
+
"langPrefix": "language-", # CSS language prefix for fenced blocks
|
| 30 |
+
# Highlighter function. Should return escaped HTML,
|
| 31 |
+
# or '' if the source string is not changed and should be escaped externally.
|
| 32 |
+
# If result starts with <pre... internal wrapper is skipped.
|
| 33 |
+
# function (/*str, lang, attrs*/) { return ''; }
|
| 34 |
+
"highlight": None,
|
| 35 |
+
},
|
| 36 |
+
"components": {
|
| 37 |
+
"core": {"rules": ["normalize", "block", "inline", "text_join"]},
|
| 38 |
+
"block": {"rules": ["paragraph"]},
|
| 39 |
+
"inline": {
|
| 40 |
+
"rules": ["text"],
|
| 41 |
+
"rules2": ["balance_pairs", "fragments_join"],
|
| 42 |
+
},
|
| 43 |
+
},
|
| 44 |
+
}
|
venv/lib/python3.12/site-packages/markdown_it/py.typed
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# Marker file for PEP 561
|
venv/lib/python3.12/site-packages/markdown_it/renderer.py
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
class Renderer
|
| 3 |
+
|
| 4 |
+
Generates HTML from parsed token stream. Each instance has independent
|
| 5 |
+
copy of rules. Those can be rewritten with ease. Also, you can add new
|
| 6 |
+
rules if you create plugin and adds new token types.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
from collections.abc import Sequence
|
| 12 |
+
import inspect
|
| 13 |
+
from typing import Any, ClassVar, Protocol
|
| 14 |
+
|
| 15 |
+
from .common.utils import escapeHtml, unescapeAll
|
| 16 |
+
from .token import Token
|
| 17 |
+
from .utils import EnvType, OptionsDict
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class RendererProtocol(Protocol):
|
| 21 |
+
__output__: ClassVar[str]
|
| 22 |
+
|
| 23 |
+
def render(
|
| 24 |
+
self, tokens: Sequence[Token], options: OptionsDict, env: EnvType
|
| 25 |
+
) -> Any: ...
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class RendererHTML(RendererProtocol):
|
| 29 |
+
"""Contains render rules for tokens. Can be updated and extended.
|
| 30 |
+
|
| 31 |
+
Example:
|
| 32 |
+
|
| 33 |
+
Each rule is called as independent static function with fixed signature:
|
| 34 |
+
|
| 35 |
+
::
|
| 36 |
+
|
| 37 |
+
class Renderer:
|
| 38 |
+
def token_type_name(self, tokens, idx, options, env) {
|
| 39 |
+
# ...
|
| 40 |
+
return renderedHTML
|
| 41 |
+
|
| 42 |
+
::
|
| 43 |
+
|
| 44 |
+
class CustomRenderer(RendererHTML):
|
| 45 |
+
def strong_open(self, tokens, idx, options, env):
|
| 46 |
+
return '<b>'
|
| 47 |
+
def strong_close(self, tokens, idx, options, env):
|
| 48 |
+
return '</b>'
|
| 49 |
+
|
| 50 |
+
md = MarkdownIt(renderer_cls=CustomRenderer)
|
| 51 |
+
|
| 52 |
+
result = md.render(...)
|
| 53 |
+
|
| 54 |
+
See https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js
|
| 55 |
+
for more details and examples.
|
| 56 |
+
"""
|
| 57 |
+
|
| 58 |
+
__output__ = "html"
|
| 59 |
+
|
| 60 |
+
def __init__(self, parser: Any = None):
|
| 61 |
+
self.rules = {
|
| 62 |
+
k: v
|
| 63 |
+
for k, v in inspect.getmembers(self, predicate=inspect.ismethod)
|
| 64 |
+
if not (k.startswith("render") or k.startswith("_"))
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
def render(
|
| 68 |
+
self, tokens: Sequence[Token], options: OptionsDict, env: EnvType
|
| 69 |
+
) -> str:
|
| 70 |
+
"""Takes token stream and generates HTML.
|
| 71 |
+
|
| 72 |
+
:param tokens: list on block tokens to render
|
| 73 |
+
:param options: params of parser instance
|
| 74 |
+
:param env: additional data from parsed input
|
| 75 |
+
|
| 76 |
+
"""
|
| 77 |
+
result = ""
|
| 78 |
+
|
| 79 |
+
for i, token in enumerate(tokens):
|
| 80 |
+
if token.type == "inline":
|
| 81 |
+
if token.children:
|
| 82 |
+
result += self.renderInline(token.children, options, env)
|
| 83 |
+
elif token.type in self.rules:
|
| 84 |
+
result += self.rules[token.type](tokens, i, options, env)
|
| 85 |
+
else:
|
| 86 |
+
result += self.renderToken(tokens, i, options, env)
|
| 87 |
+
|
| 88 |
+
return result
|
| 89 |
+
|
| 90 |
+
def renderInline(
|
| 91 |
+
self, tokens: Sequence[Token], options: OptionsDict, env: EnvType
|
| 92 |
+
) -> str:
|
| 93 |
+
"""The same as ``render``, but for single token of `inline` type.
|
| 94 |
+
|
| 95 |
+
:param tokens: list on block tokens to render
|
| 96 |
+
:param options: params of parser instance
|
| 97 |
+
:param env: additional data from parsed input (references, for example)
|
| 98 |
+
"""
|
| 99 |
+
result = ""
|
| 100 |
+
|
| 101 |
+
for i, token in enumerate(tokens):
|
| 102 |
+
if token.type in self.rules:
|
| 103 |
+
result += self.rules[token.type](tokens, i, options, env)
|
| 104 |
+
else:
|
| 105 |
+
result += self.renderToken(tokens, i, options, env)
|
| 106 |
+
|
| 107 |
+
return result
|
| 108 |
+
|
| 109 |
+
def renderToken(
|
| 110 |
+
self,
|
| 111 |
+
tokens: Sequence[Token],
|
| 112 |
+
idx: int,
|
| 113 |
+
options: OptionsDict,
|
| 114 |
+
env: EnvType,
|
| 115 |
+
) -> str:
|
| 116 |
+
"""Default token renderer.
|
| 117 |
+
|
| 118 |
+
Can be overridden by custom function
|
| 119 |
+
|
| 120 |
+
:param idx: token index to render
|
| 121 |
+
:param options: params of parser instance
|
| 122 |
+
"""
|
| 123 |
+
result = ""
|
| 124 |
+
needLf = False
|
| 125 |
+
token = tokens[idx]
|
| 126 |
+
|
| 127 |
+
# Tight list paragraphs
|
| 128 |
+
if token.hidden:
|
| 129 |
+
return ""
|
| 130 |
+
|
| 131 |
+
# Insert a newline between hidden paragraph and subsequent opening
|
| 132 |
+
# block-level tag.
|
| 133 |
+
#
|
| 134 |
+
# For example, here we should insert a newline before blockquote:
|
| 135 |
+
# - a
|
| 136 |
+
# >
|
| 137 |
+
#
|
| 138 |
+
if token.block and token.nesting != -1 and idx and tokens[idx - 1].hidden:
|
| 139 |
+
result += "\n"
|
| 140 |
+
|
| 141 |
+
# Add token name, e.g. `<img`
|
| 142 |
+
result += ("</" if token.nesting == -1 else "<") + token.tag
|
| 143 |
+
|
| 144 |
+
# Encode attributes, e.g. `<img src="foo"`
|
| 145 |
+
result += self.renderAttrs(token)
|
| 146 |
+
|
| 147 |
+
# Add a slash for self-closing tags, e.g. `<img src="foo" /`
|
| 148 |
+
if token.nesting == 0 and options["xhtmlOut"]:
|
| 149 |
+
result += " /"
|
| 150 |
+
|
| 151 |
+
# Check if we need to add a newline after this tag
|
| 152 |
+
if token.block:
|
| 153 |
+
needLf = True
|
| 154 |
+
|
| 155 |
+
if token.nesting == 1 and (idx + 1 < len(tokens)):
|
| 156 |
+
nextToken = tokens[idx + 1]
|
| 157 |
+
|
| 158 |
+
if nextToken.type == "inline" or nextToken.hidden:
|
| 159 |
+
# Block-level tag containing an inline tag.
|
| 160 |
+
#
|
| 161 |
+
needLf = False
|
| 162 |
+
|
| 163 |
+
elif nextToken.nesting == -1 and nextToken.tag == token.tag:
|
| 164 |
+
# Opening tag + closing tag of the same type. E.g. `<li></li>`.
|
| 165 |
+
#
|
| 166 |
+
needLf = False
|
| 167 |
+
|
| 168 |
+
result += ">\n" if needLf else ">"
|
| 169 |
+
|
| 170 |
+
return result
|
| 171 |
+
|
| 172 |
+
@staticmethod
|
| 173 |
+
def renderAttrs(token: Token) -> str:
|
| 174 |
+
"""Render token attributes to string."""
|
| 175 |
+
result = ""
|
| 176 |
+
|
| 177 |
+
for key, value in token.attrItems():
|
| 178 |
+
result += " " + escapeHtml(key) + '="' + escapeHtml(str(value)) + '"'
|
| 179 |
+
|
| 180 |
+
return result
|
| 181 |
+
|
| 182 |
+
def renderInlineAsText(
|
| 183 |
+
self,
|
| 184 |
+
tokens: Sequence[Token] | None,
|
| 185 |
+
options: OptionsDict,
|
| 186 |
+
env: EnvType,
|
| 187 |
+
) -> str:
|
| 188 |
+
"""Special kludge for image `alt` attributes to conform CommonMark spec.
|
| 189 |
+
|
| 190 |
+
Don't try to use it! Spec requires to show `alt` content with stripped markup,
|
| 191 |
+
instead of simple escaping.
|
| 192 |
+
|
| 193 |
+
:param tokens: list on block tokens to render
|
| 194 |
+
:param options: params of parser instance
|
| 195 |
+
:param env: additional data from parsed input
|
| 196 |
+
"""
|
| 197 |
+
result = ""
|
| 198 |
+
|
| 199 |
+
for token in tokens or []:
|
| 200 |
+
if token.type == "text":
|
| 201 |
+
result += token.content
|
| 202 |
+
elif token.type == "image":
|
| 203 |
+
if token.children:
|
| 204 |
+
result += self.renderInlineAsText(token.children, options, env)
|
| 205 |
+
elif token.type == "softbreak":
|
| 206 |
+
result += "\n"
|
| 207 |
+
|
| 208 |
+
return result
|
| 209 |
+
|
| 210 |
+
###################################################
|
| 211 |
+
|
| 212 |
+
def list_item_open(
|
| 213 |
+
self,
|
| 214 |
+
tokens: Sequence[Token],
|
| 215 |
+
idx: int,
|
| 216 |
+
options: OptionsDict,
|
| 217 |
+
env: EnvType,
|
| 218 |
+
) -> str:
|
| 219 |
+
token = tokens[idx]
|
| 220 |
+
result = self.renderToken(tokens, idx, options, env)
|
| 221 |
+
if token.meta and "checked" in token.meta:
|
| 222 |
+
checked_attr = ' checked=""' if token.meta["checked"] else ""
|
| 223 |
+
disabled_attr = (
|
| 224 |
+
"" if options.get("tasklists_editable", False) else ' disabled=""'
|
| 225 |
+
)
|
| 226 |
+
result += (
|
| 227 |
+
'<input class="task-list-item-checkbox"'
|
| 228 |
+
f'{disabled_attr} type="checkbox"{checked_attr}> '
|
| 229 |
+
)
|
| 230 |
+
return result
|
| 231 |
+
|
| 232 |
+
def code_inline(
|
| 233 |
+
self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType
|
| 234 |
+
) -> str:
|
| 235 |
+
token = tokens[idx]
|
| 236 |
+
return (
|
| 237 |
+
"<code"
|
| 238 |
+
+ self.renderAttrs(token)
|
| 239 |
+
+ ">"
|
| 240 |
+
+ escapeHtml(tokens[idx].content)
|
| 241 |
+
+ "</code>"
|
| 242 |
+
)
|
| 243 |
+
|
| 244 |
+
def code_block(
|
| 245 |
+
self,
|
| 246 |
+
tokens: Sequence[Token],
|
| 247 |
+
idx: int,
|
| 248 |
+
options: OptionsDict,
|
| 249 |
+
env: EnvType,
|
| 250 |
+
) -> str:
|
| 251 |
+
token = tokens[idx]
|
| 252 |
+
|
| 253 |
+
return (
|
| 254 |
+
"<pre"
|
| 255 |
+
+ self.renderAttrs(token)
|
| 256 |
+
+ "><code>"
|
| 257 |
+
+ escapeHtml(tokens[idx].content)
|
| 258 |
+
+ "</code></pre>\n"
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
def fence(
|
| 262 |
+
self,
|
| 263 |
+
tokens: Sequence[Token],
|
| 264 |
+
idx: int,
|
| 265 |
+
options: OptionsDict,
|
| 266 |
+
env: EnvType,
|
| 267 |
+
) -> str:
|
| 268 |
+
token = tokens[idx]
|
| 269 |
+
info = unescapeAll(token.info).strip() if token.info else ""
|
| 270 |
+
langName = ""
|
| 271 |
+
langAttrs = ""
|
| 272 |
+
|
| 273 |
+
if info:
|
| 274 |
+
arr = info.split(maxsplit=1)
|
| 275 |
+
langName = arr[0]
|
| 276 |
+
if len(arr) == 2:
|
| 277 |
+
langAttrs = arr[1]
|
| 278 |
+
|
| 279 |
+
if options.highlight:
|
| 280 |
+
highlighted = options.highlight(
|
| 281 |
+
token.content, langName, langAttrs
|
| 282 |
+
) or escapeHtml(token.content)
|
| 283 |
+
else:
|
| 284 |
+
highlighted = escapeHtml(token.content)
|
| 285 |
+
|
| 286 |
+
if highlighted.startswith("<pre"):
|
| 287 |
+
return highlighted + "\n"
|
| 288 |
+
|
| 289 |
+
# If language exists, inject class gently, without modifying original token.
|
| 290 |
+
# May be, one day we will add .deepClone() for token and simplify this part, but
|
| 291 |
+
# now we prefer to keep things local.
|
| 292 |
+
if info:
|
| 293 |
+
# Fake token just to render attributes
|
| 294 |
+
tmpToken = Token(type="", tag="", nesting=0, attrs=token.attrs.copy())
|
| 295 |
+
tmpToken.attrJoin("class", options.langPrefix + langName)
|
| 296 |
+
|
| 297 |
+
return (
|
| 298 |
+
"<pre><code"
|
| 299 |
+
+ self.renderAttrs(tmpToken)
|
| 300 |
+
+ ">"
|
| 301 |
+
+ highlighted
|
| 302 |
+
+ "</code></pre>\n"
|
| 303 |
+
)
|
| 304 |
+
|
| 305 |
+
return (
|
| 306 |
+
"<pre><code"
|
| 307 |
+
+ self.renderAttrs(token)
|
| 308 |
+
+ ">"
|
| 309 |
+
+ highlighted
|
| 310 |
+
+ "</code></pre>\n"
|
| 311 |
+
)
|
| 312 |
+
|
| 313 |
+
def image(
|
| 314 |
+
self,
|
| 315 |
+
tokens: Sequence[Token],
|
| 316 |
+
idx: int,
|
| 317 |
+
options: OptionsDict,
|
| 318 |
+
env: EnvType,
|
| 319 |
+
) -> str:
|
| 320 |
+
token = tokens[idx]
|
| 321 |
+
|
| 322 |
+
# "alt" attr MUST be set, even if empty. Because it's mandatory and
|
| 323 |
+
# should be placed on proper position for tests.
|
| 324 |
+
if token.children:
|
| 325 |
+
token.attrSet("alt", self.renderInlineAsText(token.children, options, env))
|
| 326 |
+
else:
|
| 327 |
+
token.attrSet("alt", "")
|
| 328 |
+
|
| 329 |
+
return self.renderToken(tokens, idx, options, env)
|
| 330 |
+
|
| 331 |
+
def hardbreak(
|
| 332 |
+
self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType
|
| 333 |
+
) -> str:
|
| 334 |
+
return "<br />\n" if options.xhtmlOut else "<br>\n"
|
| 335 |
+
|
| 336 |
+
def softbreak(
|
| 337 |
+
self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType
|
| 338 |
+
) -> str:
|
| 339 |
+
return (
|
| 340 |
+
("<br />\n" if options.xhtmlOut else "<br>\n") if options.breaks else "\n"
|
| 341 |
+
)
|
| 342 |
+
|
| 343 |
+
def text(
|
| 344 |
+
self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType
|
| 345 |
+
) -> str:
|
| 346 |
+
return escapeHtml(tokens[idx].content)
|
| 347 |
+
|
| 348 |
+
def html_block(
|
| 349 |
+
self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType
|
| 350 |
+
) -> str:
|
| 351 |
+
return tokens[idx].content
|
| 352 |
+
|
| 353 |
+
def html_inline(
|
| 354 |
+
self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType
|
| 355 |
+
) -> str:
|
| 356 |
+
return tokens[idx].content
|
venv/lib/python3.12/site-packages/markdown_it/ruler.py
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
class Ruler
|
| 3 |
+
|
| 4 |
+
Helper class, used by [[MarkdownIt#core]], [[MarkdownIt#block]] and
|
| 5 |
+
[[MarkdownIt#inline]] to manage sequences of functions (rules):
|
| 6 |
+
|
| 7 |
+
- keep rules in defined order
|
| 8 |
+
- assign the name to each rule
|
| 9 |
+
- enable/disable rules
|
| 10 |
+
- add/replace rules
|
| 11 |
+
- allow assign rules to additional named chains (in the same)
|
| 12 |
+
- caching lists of active rules
|
| 13 |
+
|
| 14 |
+
You will not need use this class directly until write plugins. For simple
|
| 15 |
+
rules control use [[MarkdownIt.disable]], [[MarkdownIt.enable]] and
|
| 16 |
+
[[MarkdownIt.use]].
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
from collections.abc import Iterable
|
| 22 |
+
from dataclasses import dataclass, field
|
| 23 |
+
from typing import TYPE_CHECKING, Generic, TypedDict, TypeVar
|
| 24 |
+
import warnings
|
| 25 |
+
|
| 26 |
+
from .utils import EnvType
|
| 27 |
+
|
| 28 |
+
if TYPE_CHECKING:
|
| 29 |
+
from markdown_it import MarkdownIt
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class StateBase:
|
| 33 |
+
def __init__(self, src: str, md: MarkdownIt, env: EnvType):
|
| 34 |
+
self.src = src
|
| 35 |
+
self.env = env
|
| 36 |
+
self.md = md
|
| 37 |
+
|
| 38 |
+
@property
|
| 39 |
+
def src(self) -> str:
|
| 40 |
+
return self._src
|
| 41 |
+
|
| 42 |
+
@src.setter
|
| 43 |
+
def src(self, value: str) -> None:
|
| 44 |
+
self._src = value
|
| 45 |
+
self._srcCharCode: tuple[int, ...] | None = None
|
| 46 |
+
|
| 47 |
+
@property
|
| 48 |
+
def srcCharCode(self) -> tuple[int, ...]:
|
| 49 |
+
warnings.warn(
|
| 50 |
+
"StateBase.srcCharCode is deprecated. Use StateBase.src instead.",
|
| 51 |
+
DeprecationWarning,
|
| 52 |
+
stacklevel=2,
|
| 53 |
+
)
|
| 54 |
+
if self._srcCharCode is None:
|
| 55 |
+
self._srcCharCode = tuple(ord(c) for c in self._src)
|
| 56 |
+
return self._srcCharCode
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class RuleOptionsType(TypedDict, total=False):
|
| 60 |
+
alt: list[str]
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
RuleFuncTv = TypeVar("RuleFuncTv")
|
| 64 |
+
"""A rule function, whose signature is dependent on the state type."""
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
@dataclass(slots=True)
|
| 68 |
+
class Rule(Generic[RuleFuncTv]):
|
| 69 |
+
name: str
|
| 70 |
+
enabled: bool
|
| 71 |
+
fn: RuleFuncTv = field(repr=False)
|
| 72 |
+
alt: list[str]
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class Ruler(Generic[RuleFuncTv]):
|
| 76 |
+
def __init__(self) -> None:
|
| 77 |
+
# List of added rules.
|
| 78 |
+
self.__rules__: list[Rule[RuleFuncTv]] = []
|
| 79 |
+
# Cached rule chains.
|
| 80 |
+
# First level - chain name, '' for default.
|
| 81 |
+
# Second level - diginal anchor for fast filtering by charcodes.
|
| 82 |
+
self.__cache__: dict[str, list[RuleFuncTv]] | None = None
|
| 83 |
+
|
| 84 |
+
def __find__(self, name: str) -> int:
|
| 85 |
+
"""Find rule index by name"""
|
| 86 |
+
for i, rule in enumerate(self.__rules__):
|
| 87 |
+
if rule.name == name:
|
| 88 |
+
return i
|
| 89 |
+
return -1
|
| 90 |
+
|
| 91 |
+
def __compile__(self) -> None:
|
| 92 |
+
"""Build rules lookup cache"""
|
| 93 |
+
chains = {""}
|
| 94 |
+
# collect unique names
|
| 95 |
+
for rule in self.__rules__:
|
| 96 |
+
if not rule.enabled:
|
| 97 |
+
continue
|
| 98 |
+
for name in rule.alt:
|
| 99 |
+
chains.add(name)
|
| 100 |
+
self.__cache__ = {}
|
| 101 |
+
for chain in chains:
|
| 102 |
+
self.__cache__[chain] = []
|
| 103 |
+
for rule in self.__rules__:
|
| 104 |
+
if not rule.enabled:
|
| 105 |
+
continue
|
| 106 |
+
if chain and (chain not in rule.alt):
|
| 107 |
+
continue
|
| 108 |
+
self.__cache__[chain].append(rule.fn)
|
| 109 |
+
|
| 110 |
+
def at(
|
| 111 |
+
self, ruleName: str, fn: RuleFuncTv, options: RuleOptionsType | None = None
|
| 112 |
+
) -> None:
|
| 113 |
+
"""Replace rule by name with new function & options.
|
| 114 |
+
|
| 115 |
+
:param ruleName: rule name to replace.
|
| 116 |
+
:param fn: new rule function.
|
| 117 |
+
:param options: new rule options (not mandatory).
|
| 118 |
+
:raises: KeyError if name not found
|
| 119 |
+
"""
|
| 120 |
+
index = self.__find__(ruleName)
|
| 121 |
+
options = options or {}
|
| 122 |
+
if index == -1:
|
| 123 |
+
raise KeyError(f"Parser rule not found: {ruleName}")
|
| 124 |
+
self.__rules__[index].fn = fn
|
| 125 |
+
self.__rules__[index].alt = options.get("alt", [])
|
| 126 |
+
self.__cache__ = None
|
| 127 |
+
|
| 128 |
+
def before(
|
| 129 |
+
self,
|
| 130 |
+
beforeName: str,
|
| 131 |
+
ruleName: str,
|
| 132 |
+
fn: RuleFuncTv,
|
| 133 |
+
options: RuleOptionsType | None = None,
|
| 134 |
+
) -> None:
|
| 135 |
+
"""Add new rule to chain before one with given name.
|
| 136 |
+
|
| 137 |
+
:param beforeName: new rule will be added before this one.
|
| 138 |
+
:param ruleName: new rule will be added before this one.
|
| 139 |
+
:param fn: new rule function.
|
| 140 |
+
:param options: new rule options (not mandatory).
|
| 141 |
+
:raises: KeyError if name not found
|
| 142 |
+
"""
|
| 143 |
+
index = self.__find__(beforeName)
|
| 144 |
+
options = options or {}
|
| 145 |
+
if index == -1:
|
| 146 |
+
raise KeyError(f"Parser rule not found: {beforeName}")
|
| 147 |
+
self.__rules__.insert(
|
| 148 |
+
index, Rule[RuleFuncTv](ruleName, True, fn, options.get("alt", []))
|
| 149 |
+
)
|
| 150 |
+
self.__cache__ = None
|
| 151 |
+
|
| 152 |
+
def after(
|
| 153 |
+
self,
|
| 154 |
+
afterName: str,
|
| 155 |
+
ruleName: str,
|
| 156 |
+
fn: RuleFuncTv,
|
| 157 |
+
options: RuleOptionsType | None = None,
|
| 158 |
+
) -> None:
|
| 159 |
+
"""Add new rule to chain after one with given name.
|
| 160 |
+
|
| 161 |
+
:param afterName: new rule will be added after this one.
|
| 162 |
+
:param ruleName: new rule will be added after this one.
|
| 163 |
+
:param fn: new rule function.
|
| 164 |
+
:param options: new rule options (not mandatory).
|
| 165 |
+
:raises: KeyError if name not found
|
| 166 |
+
"""
|
| 167 |
+
index = self.__find__(afterName)
|
| 168 |
+
options = options or {}
|
| 169 |
+
if index == -1:
|
| 170 |
+
raise KeyError(f"Parser rule not found: {afterName}")
|
| 171 |
+
self.__rules__.insert(
|
| 172 |
+
index + 1, Rule[RuleFuncTv](ruleName, True, fn, options.get("alt", []))
|
| 173 |
+
)
|
| 174 |
+
self.__cache__ = None
|
| 175 |
+
|
| 176 |
+
def push(
|
| 177 |
+
self, ruleName: str, fn: RuleFuncTv, options: RuleOptionsType | None = None
|
| 178 |
+
) -> None:
|
| 179 |
+
"""Push new rule to the end of chain.
|
| 180 |
+
|
| 181 |
+
:param ruleName: new rule will be added to the end of chain.
|
| 182 |
+
:param fn: new rule function.
|
| 183 |
+
:param options: new rule options (not mandatory).
|
| 184 |
+
|
| 185 |
+
"""
|
| 186 |
+
self.__rules__.append(
|
| 187 |
+
Rule[RuleFuncTv](ruleName, True, fn, (options or {}).get("alt", []))
|
| 188 |
+
)
|
| 189 |
+
self.__cache__ = None
|
| 190 |
+
|
| 191 |
+
def enable(
|
| 192 |
+
self, names: str | Iterable[str], ignoreInvalid: bool = False
|
| 193 |
+
) -> list[str]:
|
| 194 |
+
"""Enable rules with given names.
|
| 195 |
+
|
| 196 |
+
:param names: name or list of rule names to enable.
|
| 197 |
+
:param ignoreInvalid: ignore errors when rule not found
|
| 198 |
+
:raises: KeyError if name not found and not ignoreInvalid
|
| 199 |
+
:return: list of found rule names
|
| 200 |
+
"""
|
| 201 |
+
if isinstance(names, str):
|
| 202 |
+
names = [names]
|
| 203 |
+
result: list[str] = []
|
| 204 |
+
for name in names:
|
| 205 |
+
idx = self.__find__(name)
|
| 206 |
+
if (idx < 0) and ignoreInvalid:
|
| 207 |
+
continue
|
| 208 |
+
if (idx < 0) and not ignoreInvalid:
|
| 209 |
+
raise KeyError(f"Rules manager: invalid rule name {name}")
|
| 210 |
+
self.__rules__[idx].enabled = True
|
| 211 |
+
result.append(name)
|
| 212 |
+
self.__cache__ = None
|
| 213 |
+
return result
|
| 214 |
+
|
| 215 |
+
def enableOnly(
|
| 216 |
+
self, names: str | Iterable[str], ignoreInvalid: bool = False
|
| 217 |
+
) -> list[str]:
|
| 218 |
+
"""Enable rules with given names, and disable everything else.
|
| 219 |
+
|
| 220 |
+
:param names: name or list of rule names to enable.
|
| 221 |
+
:param ignoreInvalid: ignore errors when rule not found
|
| 222 |
+
:raises: KeyError if name not found and not ignoreInvalid
|
| 223 |
+
:return: list of found rule names
|
| 224 |
+
"""
|
| 225 |
+
if isinstance(names, str):
|
| 226 |
+
names = [names]
|
| 227 |
+
for rule in self.__rules__:
|
| 228 |
+
rule.enabled = False
|
| 229 |
+
return self.enable(names, ignoreInvalid)
|
| 230 |
+
|
| 231 |
+
def disable(
|
| 232 |
+
self, names: str | Iterable[str], ignoreInvalid: bool = False
|
| 233 |
+
) -> list[str]:
|
| 234 |
+
"""Disable rules with given names.
|
| 235 |
+
|
| 236 |
+
:param names: name or list of rule names to enable.
|
| 237 |
+
:param ignoreInvalid: ignore errors when rule not found
|
| 238 |
+
:raises: KeyError if name not found and not ignoreInvalid
|
| 239 |
+
:return: list of found rule names
|
| 240 |
+
"""
|
| 241 |
+
if isinstance(names, str):
|
| 242 |
+
names = [names]
|
| 243 |
+
result = []
|
| 244 |
+
for name in names:
|
| 245 |
+
idx = self.__find__(name)
|
| 246 |
+
if (idx < 0) and ignoreInvalid:
|
| 247 |
+
continue
|
| 248 |
+
if (idx < 0) and not ignoreInvalid:
|
| 249 |
+
raise KeyError(f"Rules manager: invalid rule name {name}")
|
| 250 |
+
self.__rules__[idx].enabled = False
|
| 251 |
+
result.append(name)
|
| 252 |
+
self.__cache__ = None
|
| 253 |
+
return result
|
| 254 |
+
|
| 255 |
+
def getRules(self, chainName: str = "") -> list[RuleFuncTv]:
|
| 256 |
+
"""Return array of active functions (rules) for given chain name.
|
| 257 |
+
It analyzes rules configuration, compiles caches if not exists and returns result.
|
| 258 |
+
|
| 259 |
+
Default chain name is `''` (empty string). It can't be skipped.
|
| 260 |
+
That's done intentionally, to keep signature monomorphic for high speed.
|
| 261 |
+
|
| 262 |
+
"""
|
| 263 |
+
if self.__cache__ is None:
|
| 264 |
+
self.__compile__()
|
| 265 |
+
assert self.__cache__ is not None
|
| 266 |
+
# Chain can be empty, if rules disabled. But we still have to return Array.
|
| 267 |
+
return self.__cache__.get(chainName, []) or []
|
| 268 |
+
|
| 269 |
+
def get_all_rules(self) -> list[str]:
|
| 270 |
+
"""Return all available rule names."""
|
| 271 |
+
return [r.name for r in self.__rules__]
|
| 272 |
+
|
| 273 |
+
def get_active_rules(self) -> list[str]:
|
| 274 |
+
"""Return the active rule names."""
|
| 275 |
+
return [r.name for r in self.__rules__ if r.enabled]
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__all__ = (
|
| 2 |
+
"StateBlock",
|
| 3 |
+
"blockquote",
|
| 4 |
+
"code",
|
| 5 |
+
"fence",
|
| 6 |
+
"heading",
|
| 7 |
+
"hr",
|
| 8 |
+
"html_block",
|
| 9 |
+
"lheading",
|
| 10 |
+
"list_block",
|
| 11 |
+
"make_fence_rule",
|
| 12 |
+
"paragraph",
|
| 13 |
+
"reference",
|
| 14 |
+
"table",
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
from .blockquote import blockquote
|
| 18 |
+
from .code import code
|
| 19 |
+
from .fence import fence, make_fence_rule
|
| 20 |
+
from .heading import heading
|
| 21 |
+
from .hr import hr
|
| 22 |
+
from .html_block import html_block
|
| 23 |
+
from .lheading import lheading
|
| 24 |
+
from .list import list_block
|
| 25 |
+
from .paragraph import paragraph
|
| 26 |
+
from .reference import reference
|
| 27 |
+
from .state_block import StateBlock
|
| 28 |
+
from .table import table
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (698 Bytes). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/blockquote.cpython-312.pyc
ADDED
|
Binary file (9.22 kB). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/code.cpython-312.pyc
ADDED
|
Binary file (1.37 kB). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/fence.cpython-312.pyc
ADDED
|
Binary file (4.6 kB). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/heading.cpython-312.pyc
ADDED
|
Binary file (2.61 kB). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/hr.cpython-312.pyc
ADDED
|
Binary file (1.74 kB). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/html_block.cpython-312.pyc
ADDED
|
Binary file (3.6 kB). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/lheading.cpython-312.pyc
ADDED
|
Binary file (2.92 kB). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/list.cpython-312.pyc
ADDED
|
Binary file (9.99 kB). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/paragraph.cpython-312.pyc
ADDED
|
Binary file (2.16 kB). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/reference.cpython-312.pyc
ADDED
|
Binary file (6.19 kB). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/state_block.cpython-312.pyc
ADDED
|
Binary file (9.19 kB). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/__pycache__/table.cpython-312.pyc
ADDED
|
Binary file (7.37 kB). View file
|
|
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/blockquote.py
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Block quotes
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import logging
|
| 5 |
+
|
| 6 |
+
from ..common.utils import isStrSpace
|
| 7 |
+
from .state_block import StateBlock
|
| 8 |
+
|
| 9 |
+
LOGGER = logging.getLogger(__name__)
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def blockquote(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
|
| 13 |
+
LOGGER.debug(
|
| 14 |
+
"entering blockquote: %s, %s, %s, %s", state, startLine, endLine, silent
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
oldLineMax = state.lineMax
|
| 18 |
+
pos = state.bMarks[startLine] + state.tShift[startLine]
|
| 19 |
+
max = state.eMarks[startLine]
|
| 20 |
+
|
| 21 |
+
if state.is_code_block(startLine):
|
| 22 |
+
return False
|
| 23 |
+
|
| 24 |
+
# check the block quote marker
|
| 25 |
+
try:
|
| 26 |
+
if state.src[pos] != ">":
|
| 27 |
+
return False
|
| 28 |
+
except IndexError:
|
| 29 |
+
return False
|
| 30 |
+
pos += 1
|
| 31 |
+
|
| 32 |
+
# we know that it's going to be a valid blockquote,
|
| 33 |
+
# so no point trying to find the end of it in silent mode
|
| 34 |
+
if silent:
|
| 35 |
+
return True
|
| 36 |
+
|
| 37 |
+
# set offset past spaces and ">"
|
| 38 |
+
initial = offset = state.sCount[startLine] + 1
|
| 39 |
+
|
| 40 |
+
try:
|
| 41 |
+
second_char: str | None = state.src[pos]
|
| 42 |
+
except IndexError:
|
| 43 |
+
second_char = None
|
| 44 |
+
|
| 45 |
+
# skip one optional space after '>'
|
| 46 |
+
if second_char == " ":
|
| 47 |
+
# ' > test '
|
| 48 |
+
# ^ -- position start of line here:
|
| 49 |
+
pos += 1
|
| 50 |
+
initial += 1
|
| 51 |
+
offset += 1
|
| 52 |
+
adjustTab = False
|
| 53 |
+
spaceAfterMarker = True
|
| 54 |
+
elif second_char == "\t":
|
| 55 |
+
spaceAfterMarker = True
|
| 56 |
+
|
| 57 |
+
if (state.bsCount[startLine] + offset) % 4 == 3:
|
| 58 |
+
# ' >\t test '
|
| 59 |
+
# ^ -- position start of line here (tab has width==1)
|
| 60 |
+
pos += 1
|
| 61 |
+
initial += 1
|
| 62 |
+
offset += 1
|
| 63 |
+
adjustTab = False
|
| 64 |
+
else:
|
| 65 |
+
# ' >\t test '
|
| 66 |
+
# ^ -- position start of line here + shift bsCount slightly
|
| 67 |
+
# to make extra space appear
|
| 68 |
+
adjustTab = True
|
| 69 |
+
|
| 70 |
+
else:
|
| 71 |
+
spaceAfterMarker = False
|
| 72 |
+
|
| 73 |
+
oldBMarks = [state.bMarks[startLine]]
|
| 74 |
+
state.bMarks[startLine] = pos
|
| 75 |
+
|
| 76 |
+
while pos < max:
|
| 77 |
+
ch = state.src[pos]
|
| 78 |
+
|
| 79 |
+
if isStrSpace(ch):
|
| 80 |
+
if ch == "\t":
|
| 81 |
+
offset += (
|
| 82 |
+
4
|
| 83 |
+
- (offset + state.bsCount[startLine] + (1 if adjustTab else 0)) % 4
|
| 84 |
+
)
|
| 85 |
+
else:
|
| 86 |
+
offset += 1
|
| 87 |
+
|
| 88 |
+
else:
|
| 89 |
+
break
|
| 90 |
+
|
| 91 |
+
pos += 1
|
| 92 |
+
|
| 93 |
+
oldBSCount = [state.bsCount[startLine]]
|
| 94 |
+
state.bsCount[startLine] = (
|
| 95 |
+
state.sCount[startLine] + 1 + (1 if spaceAfterMarker else 0)
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
lastLineEmpty = pos >= max
|
| 99 |
+
|
| 100 |
+
oldSCount = [state.sCount[startLine]]
|
| 101 |
+
state.sCount[startLine] = offset - initial
|
| 102 |
+
|
| 103 |
+
oldTShift = [state.tShift[startLine]]
|
| 104 |
+
state.tShift[startLine] = pos - state.bMarks[startLine]
|
| 105 |
+
|
| 106 |
+
terminatorRules = state.md.block.ruler.getRules("blockquote")
|
| 107 |
+
|
| 108 |
+
oldParentType = state.parentType
|
| 109 |
+
state.parentType = "blockquote"
|
| 110 |
+
|
| 111 |
+
# Search the end of the block
|
| 112 |
+
#
|
| 113 |
+
# Block ends with either:
|
| 114 |
+
# 1. an empty line outside:
|
| 115 |
+
# ```
|
| 116 |
+
# > test
|
| 117 |
+
#
|
| 118 |
+
# ```
|
| 119 |
+
# 2. an empty line inside:
|
| 120 |
+
# ```
|
| 121 |
+
# >
|
| 122 |
+
# test
|
| 123 |
+
# ```
|
| 124 |
+
# 3. another tag:
|
| 125 |
+
# ```
|
| 126 |
+
# > test
|
| 127 |
+
# - - -
|
| 128 |
+
# ```
|
| 129 |
+
|
| 130 |
+
# for (nextLine = startLine + 1; nextLine < endLine; nextLine++) {
|
| 131 |
+
nextLine = startLine + 1
|
| 132 |
+
while nextLine < endLine:
|
| 133 |
+
# check if it's outdented, i.e. it's inside list item and indented
|
| 134 |
+
# less than said list item:
|
| 135 |
+
#
|
| 136 |
+
# ```
|
| 137 |
+
# 1. anything
|
| 138 |
+
# > current blockquote
|
| 139 |
+
# 2. checking this line
|
| 140 |
+
# ```
|
| 141 |
+
isOutdented = state.sCount[nextLine] < state.blkIndent
|
| 142 |
+
|
| 143 |
+
pos = state.bMarks[nextLine] + state.tShift[nextLine]
|
| 144 |
+
max = state.eMarks[nextLine]
|
| 145 |
+
|
| 146 |
+
if pos >= max:
|
| 147 |
+
# Case 1: line is not inside the blockquote, and this line is empty.
|
| 148 |
+
break
|
| 149 |
+
|
| 150 |
+
evaluatesTrue = state.src[pos] == ">" and not isOutdented
|
| 151 |
+
pos += 1
|
| 152 |
+
if evaluatesTrue:
|
| 153 |
+
# This line is inside the blockquote.
|
| 154 |
+
|
| 155 |
+
# set offset past spaces and ">"
|
| 156 |
+
initial = offset = state.sCount[nextLine] + 1
|
| 157 |
+
|
| 158 |
+
try:
|
| 159 |
+
next_char: str | None = state.src[pos]
|
| 160 |
+
except IndexError:
|
| 161 |
+
next_char = None
|
| 162 |
+
|
| 163 |
+
# skip one optional space after '>'
|
| 164 |
+
if next_char == " ":
|
| 165 |
+
# ' > test '
|
| 166 |
+
# ^ -- position start of line here:
|
| 167 |
+
pos += 1
|
| 168 |
+
initial += 1
|
| 169 |
+
offset += 1
|
| 170 |
+
adjustTab = False
|
| 171 |
+
spaceAfterMarker = True
|
| 172 |
+
elif next_char == "\t":
|
| 173 |
+
spaceAfterMarker = True
|
| 174 |
+
|
| 175 |
+
if (state.bsCount[nextLine] + offset) % 4 == 3:
|
| 176 |
+
# ' >\t test '
|
| 177 |
+
# ^ -- position start of line here (tab has width==1)
|
| 178 |
+
pos += 1
|
| 179 |
+
initial += 1
|
| 180 |
+
offset += 1
|
| 181 |
+
adjustTab = False
|
| 182 |
+
else:
|
| 183 |
+
# ' >\t test '
|
| 184 |
+
# ^ -- position start of line here + shift bsCount slightly
|
| 185 |
+
# to make extra space appear
|
| 186 |
+
adjustTab = True
|
| 187 |
+
|
| 188 |
+
else:
|
| 189 |
+
spaceAfterMarker = False
|
| 190 |
+
|
| 191 |
+
oldBMarks.append(state.bMarks[nextLine])
|
| 192 |
+
state.bMarks[nextLine] = pos
|
| 193 |
+
|
| 194 |
+
while pos < max:
|
| 195 |
+
ch = state.src[pos]
|
| 196 |
+
|
| 197 |
+
if isStrSpace(ch):
|
| 198 |
+
if ch == "\t":
|
| 199 |
+
offset += (
|
| 200 |
+
4
|
| 201 |
+
- (
|
| 202 |
+
offset
|
| 203 |
+
+ state.bsCount[nextLine]
|
| 204 |
+
+ (1 if adjustTab else 0)
|
| 205 |
+
)
|
| 206 |
+
% 4
|
| 207 |
+
)
|
| 208 |
+
else:
|
| 209 |
+
offset += 1
|
| 210 |
+
else:
|
| 211 |
+
break
|
| 212 |
+
|
| 213 |
+
pos += 1
|
| 214 |
+
|
| 215 |
+
lastLineEmpty = pos >= max
|
| 216 |
+
|
| 217 |
+
oldBSCount.append(state.bsCount[nextLine])
|
| 218 |
+
state.bsCount[nextLine] = (
|
| 219 |
+
state.sCount[nextLine] + 1 + (1 if spaceAfterMarker else 0)
|
| 220 |
+
)
|
| 221 |
+
|
| 222 |
+
oldSCount.append(state.sCount[nextLine])
|
| 223 |
+
state.sCount[nextLine] = offset - initial
|
| 224 |
+
|
| 225 |
+
oldTShift.append(state.tShift[nextLine])
|
| 226 |
+
state.tShift[nextLine] = pos - state.bMarks[nextLine]
|
| 227 |
+
|
| 228 |
+
nextLine += 1
|
| 229 |
+
continue
|
| 230 |
+
|
| 231 |
+
# Case 2: line is not inside the blockquote, and the last line was empty.
|
| 232 |
+
if lastLineEmpty:
|
| 233 |
+
break
|
| 234 |
+
|
| 235 |
+
# Case 3: another tag found.
|
| 236 |
+
terminate = False
|
| 237 |
+
|
| 238 |
+
for terminatorRule in terminatorRules:
|
| 239 |
+
if terminatorRule(state, nextLine, endLine, True):
|
| 240 |
+
terminate = True
|
| 241 |
+
break
|
| 242 |
+
|
| 243 |
+
if terminate:
|
| 244 |
+
# Quirk to enforce "hard termination mode" for paragraphs;
|
| 245 |
+
# normally if you call `tokenize(state, startLine, nextLine)`,
|
| 246 |
+
# paragraphs will look below nextLine for paragraph continuation,
|
| 247 |
+
# but if blockquote is terminated by another tag, they shouldn't
|
| 248 |
+
state.lineMax = nextLine
|
| 249 |
+
|
| 250 |
+
if state.blkIndent != 0:
|
| 251 |
+
# state.blkIndent was non-zero, we now set it to zero,
|
| 252 |
+
# so we need to re-calculate all offsets to appear as
|
| 253 |
+
# if indent wasn't changed
|
| 254 |
+
oldBMarks.append(state.bMarks[nextLine])
|
| 255 |
+
oldBSCount.append(state.bsCount[nextLine])
|
| 256 |
+
oldTShift.append(state.tShift[nextLine])
|
| 257 |
+
oldSCount.append(state.sCount[nextLine])
|
| 258 |
+
state.sCount[nextLine] -= state.blkIndent
|
| 259 |
+
|
| 260 |
+
break
|
| 261 |
+
|
| 262 |
+
oldBMarks.append(state.bMarks[nextLine])
|
| 263 |
+
oldBSCount.append(state.bsCount[nextLine])
|
| 264 |
+
oldTShift.append(state.tShift[nextLine])
|
| 265 |
+
oldSCount.append(state.sCount[nextLine])
|
| 266 |
+
|
| 267 |
+
# A negative indentation means that this is a paragraph continuation
|
| 268 |
+
#
|
| 269 |
+
state.sCount[nextLine] = -1
|
| 270 |
+
|
| 271 |
+
nextLine += 1
|
| 272 |
+
|
| 273 |
+
oldIndent = state.blkIndent
|
| 274 |
+
state.blkIndent = 0
|
| 275 |
+
|
| 276 |
+
# Detect GitHub-style alert marker on the first content line.
|
| 277 |
+
# Note: `startLine` here refers to the first content line of the
|
| 278 |
+
# blockquote, after the `>` prefix has already been stripped by the
|
| 279 |
+
# blockquote parser above (bMarks/tShift adjusted to skip `> `).
|
| 280 |
+
alert_kind = None
|
| 281 |
+
if state.md.options.get("alerts", False) and nextLine > startLine:
|
| 282 |
+
alert_kind = _detect_alert(state, startLine)
|
| 283 |
+
|
| 284 |
+
lines = [startLine, 0]
|
| 285 |
+
|
| 286 |
+
if alert_kind is not None:
|
| 287 |
+
# Emit alert tokens instead of blockquote tokens
|
| 288 |
+
alert_lower = alert_kind.lower()
|
| 289 |
+
token = state.push("alert_open", "div", 1)
|
| 290 |
+
token.markup = ">"
|
| 291 |
+
token.attrSet("class", f"markdown-alert markdown-alert-{alert_lower}")
|
| 292 |
+
token.map = lines
|
| 293 |
+
token.info = alert_kind
|
| 294 |
+
token.meta = {"kind": alert_kind}
|
| 295 |
+
|
| 296 |
+
# Emit a title paragraph: <p class="markdown-alert-title">Kind</p>
|
| 297 |
+
token = state.push("alert_title_open", "p", 1)
|
| 298 |
+
token.attrSet("class", "markdown-alert-title")
|
| 299 |
+
title_token = state.push("inline", "", 0)
|
| 300 |
+
title_token.content = alert_kind.capitalize()
|
| 301 |
+
title_token.children = []
|
| 302 |
+
token = state.push("alert_title_close", "p", -1)
|
| 303 |
+
|
| 304 |
+
# Skip the marker line (startLine) and tokenize from startLine + 1.
|
| 305 |
+
contentStart = startLine + 1
|
| 306 |
+
if contentStart < nextLine:
|
| 307 |
+
# tokenize() updates state.line to nextLine as part of its
|
| 308 |
+
# contract, consistent with the blockquote code path below.
|
| 309 |
+
state.md.block.tokenize(state, contentStart, nextLine)
|
| 310 |
+
else:
|
| 311 |
+
state.line = nextLine
|
| 312 |
+
|
| 313 |
+
token = state.push("alert_close", "div", -1)
|
| 314 |
+
token.markup = ">"
|
| 315 |
+
else:
|
| 316 |
+
token = state.push("blockquote_open", "blockquote", 1)
|
| 317 |
+
token.markup = ">"
|
| 318 |
+
token.map = lines
|
| 319 |
+
|
| 320 |
+
state.md.block.tokenize(state, startLine, nextLine)
|
| 321 |
+
|
| 322 |
+
token = state.push("blockquote_close", "blockquote", -1)
|
| 323 |
+
token.markup = ">"
|
| 324 |
+
|
| 325 |
+
state.lineMax = oldLineMax
|
| 326 |
+
state.parentType = oldParentType
|
| 327 |
+
# Update the opening token map for both alert and blockquote containers.
|
| 328 |
+
lines[1] = state.line
|
| 329 |
+
|
| 330 |
+
# Restore original tShift; this might not be necessary since the parser
|
| 331 |
+
# has already been here, but just to make sure we can do that.
|
| 332 |
+
for i, item in enumerate(oldTShift):
|
| 333 |
+
state.bMarks[i + startLine] = oldBMarks[i]
|
| 334 |
+
state.tShift[i + startLine] = item
|
| 335 |
+
state.sCount[i + startLine] = oldSCount[i]
|
| 336 |
+
state.bsCount[i + startLine] = oldBSCount[i]
|
| 337 |
+
|
| 338 |
+
state.blkIndent = oldIndent
|
| 339 |
+
|
| 340 |
+
return True
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
_ALERT_TYPES = {"NOTE", "TIP", "IMPORTANT", "WARNING", "CAUTION"}
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
def _detect_alert(state: StateBlock, startLine: int) -> str | None:
|
| 347 |
+
"""Detect ``[!TYPE]`` on *startLine* (after ``>`` prefix has been stripped).
|
| 348 |
+
|
| 349 |
+
Returns the alert type string (e.g. ``"NOTE"``) or ``None``.
|
| 350 |
+
"""
|
| 351 |
+
pos = state.bMarks[startLine] + state.tShift[startLine]
|
| 352 |
+
maximum = state.eMarks[startLine]
|
| 353 |
+
src = state.src
|
| 354 |
+
|
| 355 |
+
# Trim trailing whitespace
|
| 356 |
+
while maximum > pos and src[maximum - 1] in (" ", "\t"):
|
| 357 |
+
maximum -= 1
|
| 358 |
+
|
| 359 |
+
if maximum - pos < 4:
|
| 360 |
+
return None
|
| 361 |
+
if src[pos] != "[" or src[pos + 1] != "!":
|
| 362 |
+
return None
|
| 363 |
+
if src[maximum - 1] != "]":
|
| 364 |
+
return None
|
| 365 |
+
type_str = src[pos + 2 : maximum - 1].upper()
|
| 366 |
+
if type_str not in _ALERT_TYPES:
|
| 367 |
+
return None
|
| 368 |
+
return type_str
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/code.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Code block (4 spaces padded)."""
|
| 2 |
+
|
| 3 |
+
import logging
|
| 4 |
+
|
| 5 |
+
from .state_block import StateBlock
|
| 6 |
+
|
| 7 |
+
LOGGER = logging.getLogger(__name__)
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def code(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
|
| 11 |
+
LOGGER.debug("entering code: %s, %s, %s, %s", state, startLine, endLine, silent)
|
| 12 |
+
|
| 13 |
+
if not state.is_code_block(startLine):
|
| 14 |
+
return False
|
| 15 |
+
|
| 16 |
+
last = nextLine = startLine + 1
|
| 17 |
+
|
| 18 |
+
while nextLine < endLine:
|
| 19 |
+
if state.isEmpty(nextLine):
|
| 20 |
+
nextLine += 1
|
| 21 |
+
continue
|
| 22 |
+
|
| 23 |
+
if state.is_code_block(nextLine):
|
| 24 |
+
nextLine += 1
|
| 25 |
+
last = nextLine
|
| 26 |
+
continue
|
| 27 |
+
|
| 28 |
+
break
|
| 29 |
+
|
| 30 |
+
state.line = last
|
| 31 |
+
|
| 32 |
+
token = state.push("code_block", "code", 0)
|
| 33 |
+
token.content = state.getLines(startLine, last, 4 + state.blkIndent, False) + "\n"
|
| 34 |
+
token.map = [startLine, state.line]
|
| 35 |
+
|
| 36 |
+
return True
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/fence.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# fences (``` lang, ~~~ lang)
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
from collections.abc import Callable
|
| 5 |
+
import logging
|
| 6 |
+
|
| 7 |
+
from .state_block import StateBlock
|
| 8 |
+
|
| 9 |
+
LOGGER = logging.getLogger(__name__)
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def make_fence_rule(
|
| 13 |
+
*,
|
| 14 |
+
markers: tuple[str, ...] = ("~", "`"),
|
| 15 |
+
token_type: str = "fence",
|
| 16 |
+
exact_match: bool = False,
|
| 17 |
+
disallow_marker_in_info: tuple[str, ...] = ("`",),
|
| 18 |
+
min_markers: int = 3,
|
| 19 |
+
) -> Callable[[StateBlock, int, int, bool], bool]:
|
| 20 |
+
"""Create a fence parsing rule with configurable options.
|
| 21 |
+
|
| 22 |
+
:param markers: Tuple of single characters that can be used as fence markers.
|
| 23 |
+
:param token_type: The token type name to emit (e.g. "fence", "colon_fence").
|
| 24 |
+
:param exact_match: If True, the closing fence must have exactly the same
|
| 25 |
+
number of marker characters as the opening fence (not "at least as many").
|
| 26 |
+
This enables nesting of fences with different marker counts.
|
| 27 |
+
:param disallow_marker_in_info: Tuple of marker characters that are not allowed
|
| 28 |
+
to appear in the info string. The check only applies when the actual opening
|
| 29 |
+
marker is in this tuple (e.g. a tilde fence is unaffected by ``"`"`` being
|
| 30 |
+
listed). Per CommonMark, backtick fences cannot have backticks in the info
|
| 31 |
+
string. Use ``()`` to disable this restriction.
|
| 32 |
+
:param min_markers: Minimum number of marker characters to form a fence.
|
| 33 |
+
:return: A block rule function with signature
|
| 34 |
+
``(state, startLine, endLine, silent) -> bool``.
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
closing_matcher: Callable[[int, int], bool]
|
| 38 |
+
if exact_match:
|
| 39 |
+
# closing code fence must have exactly the same number of markers as the opening one
|
| 40 |
+
closing_matcher = lambda opening_len, closing_len: closing_len == opening_len # noqa: E731
|
| 41 |
+
else:
|
| 42 |
+
# closing code fence must be at least as long as the opening one
|
| 43 |
+
closing_matcher = lambda opening_len, closing_len: closing_len >= opening_len # noqa: E731
|
| 44 |
+
|
| 45 |
+
def _fence_rule(
|
| 46 |
+
state: StateBlock, startLine: int, endLine: int, silent: bool
|
| 47 |
+
) -> bool:
|
| 48 |
+
LOGGER.debug(
|
| 49 |
+
"entering fence: %s, %s, %s, %s", state, startLine, endLine, silent
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
haveEndMarker = False
|
| 53 |
+
pos = state.bMarks[startLine] + state.tShift[startLine]
|
| 54 |
+
maximum = state.eMarks[startLine]
|
| 55 |
+
|
| 56 |
+
if state.is_code_block(startLine):
|
| 57 |
+
return False
|
| 58 |
+
|
| 59 |
+
if pos + min_markers > maximum:
|
| 60 |
+
return False
|
| 61 |
+
|
| 62 |
+
marker = state.src[pos]
|
| 63 |
+
|
| 64 |
+
if marker not in markers:
|
| 65 |
+
return False
|
| 66 |
+
|
| 67 |
+
# scan marker length
|
| 68 |
+
mem = pos
|
| 69 |
+
pos = state.skipCharsStr(pos, marker)
|
| 70 |
+
|
| 71 |
+
length = pos - mem
|
| 72 |
+
|
| 73 |
+
if length < min_markers:
|
| 74 |
+
return False
|
| 75 |
+
|
| 76 |
+
markup = state.src[mem:pos]
|
| 77 |
+
params = state.src[pos:maximum]
|
| 78 |
+
|
| 79 |
+
if marker in disallow_marker_in_info and marker in params:
|
| 80 |
+
return False
|
| 81 |
+
|
| 82 |
+
# Since start is found, we can report success here in validation mode
|
| 83 |
+
if silent:
|
| 84 |
+
return True
|
| 85 |
+
|
| 86 |
+
# search end of block
|
| 87 |
+
nextLine = startLine
|
| 88 |
+
|
| 89 |
+
while True:
|
| 90 |
+
nextLine += 1
|
| 91 |
+
if nextLine >= endLine:
|
| 92 |
+
# unclosed block should be autoclosed by end of document.
|
| 93 |
+
# also block seems to be autoclosed by end of parent
|
| 94 |
+
break
|
| 95 |
+
|
| 96 |
+
pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]
|
| 97 |
+
maximum = state.eMarks[nextLine]
|
| 98 |
+
|
| 99 |
+
if pos < maximum and state.sCount[nextLine] < state.blkIndent:
|
| 100 |
+
# non-empty line with negative indent should stop the list:
|
| 101 |
+
# - ```
|
| 102 |
+
# test
|
| 103 |
+
break
|
| 104 |
+
|
| 105 |
+
try:
|
| 106 |
+
if state.src[pos] != marker:
|
| 107 |
+
continue
|
| 108 |
+
except IndexError:
|
| 109 |
+
break
|
| 110 |
+
|
| 111 |
+
if state.is_code_block(nextLine):
|
| 112 |
+
continue
|
| 113 |
+
|
| 114 |
+
pos = state.skipCharsStr(pos, marker)
|
| 115 |
+
|
| 116 |
+
if not closing_matcher(length, pos - mem):
|
| 117 |
+
continue
|
| 118 |
+
|
| 119 |
+
# make sure tail has spaces only
|
| 120 |
+
pos = state.skipSpaces(pos)
|
| 121 |
+
|
| 122 |
+
if pos < maximum:
|
| 123 |
+
continue
|
| 124 |
+
|
| 125 |
+
haveEndMarker = True
|
| 126 |
+
# found!
|
| 127 |
+
break
|
| 128 |
+
|
| 129 |
+
# If a fence has heading spaces, they should be removed from its inner block
|
| 130 |
+
length = state.sCount[startLine]
|
| 131 |
+
|
| 132 |
+
state.line = nextLine + (1 if haveEndMarker else 0)
|
| 133 |
+
|
| 134 |
+
token = state.push(token_type, "code", 0)
|
| 135 |
+
token.info = params
|
| 136 |
+
token.content = state.getLines(startLine + 1, nextLine, length, True)
|
| 137 |
+
token.markup = markup
|
| 138 |
+
token.map = [startLine, state.line]
|
| 139 |
+
|
| 140 |
+
return True
|
| 141 |
+
|
| 142 |
+
return _fence_rule
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
#: The default fence rule (backtick and tilde markers, CommonMark compliant).
|
| 146 |
+
fence = make_fence_rule()
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/heading.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Atex heading (#, ##, ...)"""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import logging
|
| 6 |
+
|
| 7 |
+
from ..common.utils import isStrSpace
|
| 8 |
+
from .state_block import StateBlock
|
| 9 |
+
|
| 10 |
+
LOGGER = logging.getLogger(__name__)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def heading(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
|
| 14 |
+
LOGGER.debug("entering heading: %s, %s, %s, %s", state, startLine, endLine, silent)
|
| 15 |
+
|
| 16 |
+
pos = state.bMarks[startLine] + state.tShift[startLine]
|
| 17 |
+
maximum = state.eMarks[startLine]
|
| 18 |
+
|
| 19 |
+
if state.is_code_block(startLine):
|
| 20 |
+
return False
|
| 21 |
+
|
| 22 |
+
ch: str | None = state.src[pos]
|
| 23 |
+
|
| 24 |
+
if ch != "#" or pos >= maximum:
|
| 25 |
+
return False
|
| 26 |
+
|
| 27 |
+
# count heading level
|
| 28 |
+
level = 1
|
| 29 |
+
pos += 1
|
| 30 |
+
try:
|
| 31 |
+
ch = state.src[pos]
|
| 32 |
+
except IndexError:
|
| 33 |
+
ch = None
|
| 34 |
+
while ch == "#" and pos < maximum and level <= 6:
|
| 35 |
+
level += 1
|
| 36 |
+
pos += 1
|
| 37 |
+
try:
|
| 38 |
+
ch = state.src[pos]
|
| 39 |
+
except IndexError:
|
| 40 |
+
ch = None
|
| 41 |
+
|
| 42 |
+
if level > 6 or (pos < maximum and not isStrSpace(ch)):
|
| 43 |
+
return False
|
| 44 |
+
|
| 45 |
+
if silent:
|
| 46 |
+
return True
|
| 47 |
+
|
| 48 |
+
# Let's cut tails like ' ### ' from the end of string
|
| 49 |
+
|
| 50 |
+
maximum = state.skipSpacesBack(maximum, pos)
|
| 51 |
+
tmp = state.skipCharsStrBack(maximum, "#", pos)
|
| 52 |
+
if tmp > pos and isStrSpace(state.src[tmp - 1]):
|
| 53 |
+
maximum = tmp
|
| 54 |
+
|
| 55 |
+
state.line = startLine + 1
|
| 56 |
+
|
| 57 |
+
token = state.push("heading_open", "h" + str(level), 1)
|
| 58 |
+
token.markup = "########"[:level]
|
| 59 |
+
token.map = [startLine, state.line]
|
| 60 |
+
|
| 61 |
+
token = state.push("inline", "", 0)
|
| 62 |
+
token.content = state.src[pos:maximum].strip()
|
| 63 |
+
token.map = [startLine, state.line]
|
| 64 |
+
token.children = []
|
| 65 |
+
|
| 66 |
+
token = state.push("heading_close", "h" + str(level), -1)
|
| 67 |
+
token.markup = "########"[:level]
|
| 68 |
+
|
| 69 |
+
return True
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/hr.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Horizontal rule
|
| 2 |
+
|
| 3 |
+
At least 3 of these characters on a line * - _
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import logging
|
| 7 |
+
|
| 8 |
+
from ..common.utils import isStrSpace
|
| 9 |
+
from .state_block import StateBlock
|
| 10 |
+
|
| 11 |
+
LOGGER = logging.getLogger(__name__)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def hr(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
|
| 15 |
+
LOGGER.debug("entering hr: %s, %s, %s, %s", state, startLine, endLine, silent)
|
| 16 |
+
|
| 17 |
+
pos = state.bMarks[startLine] + state.tShift[startLine]
|
| 18 |
+
maximum = state.eMarks[startLine]
|
| 19 |
+
|
| 20 |
+
if state.is_code_block(startLine):
|
| 21 |
+
return False
|
| 22 |
+
|
| 23 |
+
try:
|
| 24 |
+
marker = state.src[pos]
|
| 25 |
+
except IndexError:
|
| 26 |
+
return False
|
| 27 |
+
pos += 1
|
| 28 |
+
|
| 29 |
+
# Check hr marker
|
| 30 |
+
if marker not in ("*", "-", "_"):
|
| 31 |
+
return False
|
| 32 |
+
|
| 33 |
+
# markers can be mixed with spaces, but there should be at least 3 of them
|
| 34 |
+
|
| 35 |
+
cnt = 1
|
| 36 |
+
while pos < maximum:
|
| 37 |
+
ch = state.src[pos]
|
| 38 |
+
pos += 1
|
| 39 |
+
if ch != marker and not isStrSpace(ch):
|
| 40 |
+
return False
|
| 41 |
+
if ch == marker:
|
| 42 |
+
cnt += 1
|
| 43 |
+
|
| 44 |
+
if cnt < 3:
|
| 45 |
+
return False
|
| 46 |
+
|
| 47 |
+
if silent:
|
| 48 |
+
return True
|
| 49 |
+
|
| 50 |
+
state.line = startLine + 1
|
| 51 |
+
|
| 52 |
+
token = state.push("hr", "hr", 0)
|
| 53 |
+
token.map = [startLine, state.line]
|
| 54 |
+
token.markup = marker * (cnt + 1)
|
| 55 |
+
|
| 56 |
+
return True
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/html_block.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# HTML block
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import logging
|
| 5 |
+
import re
|
| 6 |
+
|
| 7 |
+
from ..common.html_blocks import block_names
|
| 8 |
+
from ..common.html_re import HTML_OPEN_CLOSE_TAG_STR
|
| 9 |
+
from .state_block import StateBlock
|
| 10 |
+
|
| 11 |
+
LOGGER = logging.getLogger(__name__)
|
| 12 |
+
|
| 13 |
+
# An array of opening and corresponding closing sequences for html tags,
|
| 14 |
+
# last argument defines whether it can terminate a paragraph or not
|
| 15 |
+
HTML_SEQUENCES: list[tuple[re.Pattern[str], re.Pattern[str], bool]] = [
|
| 16 |
+
(
|
| 17 |
+
re.compile(r"^<(script|pre|style|textarea)(?=(\s|>|$))", re.IGNORECASE),
|
| 18 |
+
re.compile(r"<\/(script|pre|style|textarea)>", re.IGNORECASE),
|
| 19 |
+
True,
|
| 20 |
+
),
|
| 21 |
+
(re.compile(r"^<!--"), re.compile(r"-->"), True),
|
| 22 |
+
(re.compile(r"^<\?"), re.compile(r"\?>"), True),
|
| 23 |
+
(re.compile(r"^<![A-Z]"), re.compile(r">"), True),
|
| 24 |
+
(re.compile(r"^<!\[CDATA\["), re.compile(r"\]\]>"), True),
|
| 25 |
+
(
|
| 26 |
+
re.compile("^</?(" + "|".join(block_names) + ")(?=(\\s|/?>|$))", re.IGNORECASE),
|
| 27 |
+
re.compile(r"^$"),
|
| 28 |
+
True,
|
| 29 |
+
),
|
| 30 |
+
(re.compile(HTML_OPEN_CLOSE_TAG_STR + "\\s*$"), re.compile(r"^$"), False),
|
| 31 |
+
]
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def html_block(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
|
| 35 |
+
LOGGER.debug(
|
| 36 |
+
"entering html_block: %s, %s, %s, %s", state, startLine, endLine, silent
|
| 37 |
+
)
|
| 38 |
+
pos = state.bMarks[startLine] + state.tShift[startLine]
|
| 39 |
+
maximum = state.eMarks[startLine]
|
| 40 |
+
|
| 41 |
+
if state.is_code_block(startLine):
|
| 42 |
+
return False
|
| 43 |
+
|
| 44 |
+
if not state.md.options.get("html", None):
|
| 45 |
+
return False
|
| 46 |
+
|
| 47 |
+
if state.src[pos] != "<":
|
| 48 |
+
return False
|
| 49 |
+
|
| 50 |
+
lineText = state.src[pos:maximum]
|
| 51 |
+
|
| 52 |
+
html_seq = None
|
| 53 |
+
for HTML_SEQUENCE in HTML_SEQUENCES:
|
| 54 |
+
if HTML_SEQUENCE[0].search(lineText):
|
| 55 |
+
html_seq = HTML_SEQUENCE
|
| 56 |
+
break
|
| 57 |
+
|
| 58 |
+
if not html_seq:
|
| 59 |
+
return False
|
| 60 |
+
|
| 61 |
+
if silent:
|
| 62 |
+
# true if this sequence can be a terminator, false otherwise
|
| 63 |
+
return html_seq[2]
|
| 64 |
+
|
| 65 |
+
nextLine = startLine + 1
|
| 66 |
+
|
| 67 |
+
# If we are here - we detected HTML block.
|
| 68 |
+
# Let's roll down till block end.
|
| 69 |
+
if not html_seq[1].search(lineText):
|
| 70 |
+
while nextLine < endLine:
|
| 71 |
+
if state.sCount[nextLine] < state.blkIndent:
|
| 72 |
+
break
|
| 73 |
+
|
| 74 |
+
pos = state.bMarks[nextLine] + state.tShift[nextLine]
|
| 75 |
+
maximum = state.eMarks[nextLine]
|
| 76 |
+
lineText = state.src[pos:maximum]
|
| 77 |
+
|
| 78 |
+
if html_seq[1].search(lineText):
|
| 79 |
+
if len(lineText) != 0:
|
| 80 |
+
nextLine += 1
|
| 81 |
+
break
|
| 82 |
+
nextLine += 1
|
| 83 |
+
|
| 84 |
+
state.line = nextLine
|
| 85 |
+
|
| 86 |
+
token = state.push("html_block", "", 0)
|
| 87 |
+
token.map = [startLine, nextLine]
|
| 88 |
+
token.content = state.getLines(startLine, nextLine, state.blkIndent, True)
|
| 89 |
+
|
| 90 |
+
return True
|
venv/lib/python3.12/site-packages/markdown_it/rules_block/lheading.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# lheading (---, ==)
|
| 2 |
+
import logging
|
| 3 |
+
|
| 4 |
+
from .state_block import StateBlock
|
| 5 |
+
|
| 6 |
+
LOGGER = logging.getLogger(__name__)
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def lheading(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
|
| 10 |
+
LOGGER.debug("entering lheading: %s, %s, %s, %s", state, startLine, endLine, silent)
|
| 11 |
+
|
| 12 |
+
level = None
|
| 13 |
+
nextLine = startLine + 1
|
| 14 |
+
ruler = state.md.block.ruler
|
| 15 |
+
terminatorRules = ruler.getRules("paragraph")
|
| 16 |
+
|
| 17 |
+
if state.is_code_block(startLine):
|
| 18 |
+
return False
|
| 19 |
+
|
| 20 |
+
oldParentType = state.parentType
|
| 21 |
+
state.parentType = "paragraph" # use paragraph to match terminatorRules
|
| 22 |
+
|
| 23 |
+
# jump line-by-line until empty one or EOF
|
| 24 |
+
while nextLine < endLine and not state.isEmpty(nextLine):
|
| 25 |
+
# this would be a code block normally, but after paragraph
|
| 26 |
+
# it's considered a lazy continuation regardless of what's there
|
| 27 |
+
if state.sCount[nextLine] - state.blkIndent > 3:
|
| 28 |
+
nextLine += 1
|
| 29 |
+
continue
|
| 30 |
+
|
| 31 |
+
# Check for underline in setext header
|
| 32 |
+
if state.sCount[nextLine] >= state.blkIndent:
|
| 33 |
+
pos = state.bMarks[nextLine] + state.tShift[nextLine]
|
| 34 |
+
maximum = state.eMarks[nextLine]
|
| 35 |
+
|
| 36 |
+
if pos < maximum:
|
| 37 |
+
marker = state.src[pos]
|
| 38 |
+
|
| 39 |
+
if marker in ("-", "="):
|
| 40 |
+
pos = state.skipCharsStr(pos, marker)
|
| 41 |
+
pos = state.skipSpaces(pos)
|
| 42 |
+
|
| 43 |
+
# /* = */
|
| 44 |
+
if pos >= maximum:
|
| 45 |
+
level = 1 if marker == "=" else 2
|
| 46 |
+
break
|
| 47 |
+
|
| 48 |
+
# quirk for blockquotes, this line should already be checked by that rule
|
| 49 |
+
if state.sCount[nextLine] < 0:
|
| 50 |
+
nextLine += 1
|
| 51 |
+
continue
|
| 52 |
+
|
| 53 |
+
# Some tags can terminate paragraph without empty line.
|
| 54 |
+
terminate = False
|
| 55 |
+
for terminatorRule in terminatorRules:
|
| 56 |
+
if terminatorRule(state, nextLine, endLine, True):
|
| 57 |
+
terminate = True
|
| 58 |
+
break
|
| 59 |
+
if terminate:
|
| 60 |
+
break
|
| 61 |
+
|
| 62 |
+
nextLine += 1
|
| 63 |
+
|
| 64 |
+
if not level:
|
| 65 |
+
# Didn't find valid underline
|
| 66 |
+
return False
|
| 67 |
+
|
| 68 |
+
content = state.getLines(startLine, nextLine, state.blkIndent, False).strip()
|
| 69 |
+
|
| 70 |
+
state.line = nextLine + 1
|
| 71 |
+
|
| 72 |
+
token = state.push("heading_open", "h" + str(level), 1)
|
| 73 |
+
token.markup = marker
|
| 74 |
+
token.map = [startLine, state.line]
|
| 75 |
+
|
| 76 |
+
token = state.push("inline", "", 0)
|
| 77 |
+
token.content = content
|
| 78 |
+
token.map = [startLine, state.line - 1]
|
| 79 |
+
token.children = []
|
| 80 |
+
|
| 81 |
+
token = state.push("heading_close", "h" + str(level), -1)
|
| 82 |
+
token.markup = marker
|
| 83 |
+
|
| 84 |
+
state.parentType = oldParentType
|
| 85 |
+
|
| 86 |
+
return True
|