markdown-exec 1.10.0__py3-none-any.whl → 1.10.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- markdown_exec/__init__.py +50 -138
- markdown_exec/_internal/__init__.py +0 -0
- markdown_exec/{debug.py → _internal/debug.py} +13 -15
- markdown_exec/_internal/formatters/__init__.py +1 -0
- markdown_exec/{formatters → _internal/formatters}/_exec_python.py +1 -1
- markdown_exec/_internal/formatters/base.py +191 -0
- markdown_exec/_internal/formatters/bash.py +32 -0
- markdown_exec/_internal/formatters/console.py +29 -0
- markdown_exec/_internal/formatters/markdown.py +11 -0
- markdown_exec/_internal/formatters/pycon.py +26 -0
- markdown_exec/_internal/formatters/pyodide.py +73 -0
- markdown_exec/_internal/formatters/python.py +85 -0
- markdown_exec/_internal/formatters/sh.py +32 -0
- markdown_exec/_internal/formatters/tree.py +60 -0
- markdown_exec/_internal/logger.py +89 -0
- markdown_exec/_internal/main.py +123 -0
- markdown_exec/_internal/mkdocs_plugin.py +143 -0
- markdown_exec/_internal/processors.py +136 -0
- markdown_exec/_internal/rendering.py +280 -0
- markdown_exec/{pyodide.css → assets/pyodide.css} +6 -1
- markdown_exec/{pyodide.js → assets/pyodide.js} +12 -6
- markdown_exec/formatters/__init__.py +17 -1
- markdown_exec/formatters/base.py +12 -183
- markdown_exec/formatters/bash.py +10 -25
- markdown_exec/formatters/console.py +12 -24
- markdown_exec/formatters/markdown.py +11 -5
- markdown_exec/formatters/pycon.py +12 -24
- markdown_exec/formatters/pyodide.py +12 -65
- markdown_exec/formatters/python.py +11 -79
- markdown_exec/formatters/sh.py +10 -25
- markdown_exec/formatters/tree.py +12 -55
- markdown_exec/logger.py +12 -87
- markdown_exec/mkdocs_plugin.py +11 -135
- markdown_exec/processors.py +12 -118
- markdown_exec/rendering.py +11 -270
- {markdown_exec-1.10.0.dist-info → markdown_exec-1.10.2.dist-info}/METADATA +6 -5
- markdown_exec-1.10.2.dist-info/RECORD +42 -0
- markdown_exec-1.10.2.dist-info/entry_points.txt +7 -0
- markdown_exec-1.10.0.dist-info/RECORD +0 -26
- markdown_exec-1.10.0.dist-info/entry_points.txt +0 -7
- /markdown_exec/{ansi.css → assets/ansi.css} +0 -0
- {markdown_exec-1.10.0.dist-info → markdown_exec-1.10.2.dist-info}/WHEEL +0 -0
- {markdown_exec-1.10.0.dist-info → markdown_exec-1.10.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,29 +1,17 @@
|
|
1
|
-
"""
|
1
|
+
"""Deprecated. Import from `markdown_exec` directly."""
|
2
2
|
|
3
|
-
|
3
|
+
# YORE: Bump 2: Remove file.
|
4
4
|
|
5
|
-
|
5
|
+
import warnings
|
6
|
+
from typing import Any
|
6
7
|
|
7
|
-
from markdown_exec.formatters
|
8
|
-
from markdown_exec.formatters.python import _run_python
|
9
|
-
from markdown_exec.logger import get_logger
|
8
|
+
from markdown_exec._internal.formatters import pycon
|
10
9
|
|
11
|
-
if TYPE_CHECKING:
|
12
|
-
from markupsafe import Markup
|
13
10
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
if line.startswith((">>> ", "... ")):
|
22
|
-
pycon_lines.append(line)
|
23
|
-
python_lines.append(line[4:])
|
24
|
-
python_code = "\n".join(python_lines)
|
25
|
-
return python_code, "\n".join(pycon_lines)
|
26
|
-
|
27
|
-
|
28
|
-
def _format_pycon(**kwargs: Any) -> Markup:
|
29
|
-
return base_format(language="pycon", run=_run_python, transform_source=_transform_source, **kwargs)
|
11
|
+
def __getattr__(name: str) -> Any:
|
12
|
+
warnings.warn(
|
13
|
+
"Importing from `markdown_exec.formatters.pycon` is deprecated. Import from `markdown_exec` directly.",
|
14
|
+
DeprecationWarning,
|
15
|
+
stacklevel=2,
|
16
|
+
)
|
17
|
+
return getattr(pycon, name)
|
@@ -1,70 +1,17 @@
|
|
1
|
-
"""
|
1
|
+
"""Deprecated. Import from `markdown_exec` directly."""
|
2
2
|
|
3
|
-
|
3
|
+
# YORE: Bump 2: Remove file.
|
4
4
|
|
5
|
-
|
5
|
+
import warnings
|
6
|
+
from typing import Any
|
6
7
|
|
7
|
-
|
8
|
-
from markdown import Markdown
|
8
|
+
from markdown_exec._internal.formatters import pyodide
|
9
9
|
|
10
|
-
# All Ace.js themes listed here:
|
11
|
-
# https://github.com/ajaxorg/ace/tree/master/src/theme
|
12
10
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
<link title="light" rel="alternate stylesheet" href="https://cdn.jsdelivr.net/npm/highlightjs-themes@1.0.0/tomorrow.min.css" disabled="disabled">
|
21
|
-
<link title="dark" rel="alternate stylesheet" href="https://cdn.jsdelivr.net/npm/highlightjs-themes@1.0.0/tomorrow-night-blue.min.css" disabled="disabled">
|
22
|
-
"""
|
23
|
-
|
24
|
-
template = """
|
25
|
-
<div class="pyodide">
|
26
|
-
<div class="pyodide-editor-bar">
|
27
|
-
<span class="pyodide-bar-item">Editor (session: %(session)s)</span><span id="%(id_prefix)srun" title="Run: press Ctrl-Enter" class="pyodide-bar-item pyodide-clickable"><span class="twemoji">%(play_emoji)s</span> Run</span>
|
28
|
-
</div>
|
29
|
-
<div><pre id="%(id_prefix)seditor" class="pyodide-editor">%(initial_code)s</pre></div>
|
30
|
-
<div class="pyodide-editor-bar">
|
31
|
-
<span class="pyodide-bar-item">Output</span><span id="%(id_prefix)sclear" class="pyodide-bar-item pyodide-clickable"><span class="twemoji">%(clear_emoji)s</span> Clear</span>
|
32
|
-
</div>
|
33
|
-
<pre><code id="%(id_prefix)soutput" class="pyodide-output"></code></pre>
|
34
|
-
</div>
|
35
|
-
|
36
|
-
<script>
|
37
|
-
document.addEventListener('DOMContentLoaded', (event) => {
|
38
|
-
setupPyodide('%(id_prefix)s', install=%(install)s, themeLight='%(theme_light)s', themeDark='%(theme_dark)s', session='%(session)s');
|
39
|
-
});
|
40
|
-
</script>
|
41
|
-
"""
|
42
|
-
|
43
|
-
_counter = 0
|
44
|
-
|
45
|
-
|
46
|
-
def _format_pyodide(code: str, md: Markdown, session: str, extra: dict, **options: Any) -> str: # noqa: ARG001
|
47
|
-
global _counter # noqa: PLW0603
|
48
|
-
_counter += 1
|
49
|
-
version = extra.pop("version", "0.26.4").lstrip("v")
|
50
|
-
install = extra.pop("install", "")
|
51
|
-
install = install.split(",") if install else []
|
52
|
-
exclude_assets = extra.pop("assets", "1").lower() in {"0", "false", "no", "off"}
|
53
|
-
theme = extra.pop("theme", "tomorrow,tomorrow_night")
|
54
|
-
if "," not in theme:
|
55
|
-
theme = f"{theme},{theme}"
|
56
|
-
theme_light, theme_dark = theme.split(",")
|
57
|
-
data = {
|
58
|
-
"id_prefix": f"exec-{_counter}--",
|
59
|
-
"initial_code": code,
|
60
|
-
"install": install,
|
61
|
-
"theme_light": theme_light.strip(),
|
62
|
-
"theme_dark": theme_dark.strip(),
|
63
|
-
"session": session or "default",
|
64
|
-
"play_emoji": play_emoji,
|
65
|
-
"clear_emoji": clear_emoji,
|
66
|
-
}
|
67
|
-
rendered = template % data
|
68
|
-
if exclude_assets:
|
69
|
-
return rendered
|
70
|
-
return assets.format(version=version) + rendered
|
11
|
+
def __getattr__(name: str) -> Any:
|
12
|
+
warnings.warn(
|
13
|
+
"Importing from `markdown_exec.formatters.pyodide` is deprecated. Import from `markdown_exec` directly.",
|
14
|
+
DeprecationWarning,
|
15
|
+
stacklevel=2,
|
16
|
+
)
|
17
|
+
return getattr(pyodide, name)
|
@@ -1,85 +1,17 @@
|
|
1
|
-
"""
|
1
|
+
"""Deprecated. Import from `markdown_exec` directly."""
|
2
2
|
|
3
|
-
|
3
|
+
# YORE: Bump 2: Remove file.
|
4
4
|
|
5
|
-
import
|
6
|
-
import sys
|
7
|
-
import traceback
|
8
|
-
from collections import defaultdict
|
9
|
-
from functools import partial
|
10
|
-
from io import StringIO
|
11
|
-
from types import ModuleType
|
5
|
+
import warnings
|
12
6
|
from typing import Any
|
13
7
|
|
14
|
-
from markdown_exec.formatters
|
15
|
-
from markdown_exec.formatters.base import ExecutionError, base_format
|
16
|
-
from markdown_exec.rendering import code_block
|
8
|
+
from markdown_exec._internal.formatters import python
|
17
9
|
|
18
|
-
_sessions_globals: dict[str, dict] = defaultdict(dict)
|
19
|
-
_sessions_counter: dict[str | None, int] = defaultdict(int)
|
20
|
-
_code_blocks: dict[str, list[str]] = {}
|
21
10
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
session: str | None = None,
|
30
|
-
title: str | None = None,
|
31
|
-
) -> str:
|
32
|
-
_sessions_counter[session] += 1
|
33
|
-
if id:
|
34
|
-
code_block_id = f"id {id}"
|
35
|
-
elif session:
|
36
|
-
code_block_id = f"session {session}; n{_sessions_counter[session]}"
|
37
|
-
if title:
|
38
|
-
code_block_id = f"{code_block_id}; title {title}"
|
39
|
-
else:
|
40
|
-
code_block_id = f"n{_sessions_counter[session]}"
|
41
|
-
if title:
|
42
|
-
code_block_id = f"{code_block_id}; title {title}"
|
43
|
-
return f"<code block: {code_block_id}>"
|
44
|
-
|
45
|
-
|
46
|
-
def _run_python(
|
47
|
-
code: str,
|
48
|
-
returncode: int | None = None, # noqa: ARG001
|
49
|
-
session: str | None = None,
|
50
|
-
id: str | None = None, # noqa: A002
|
51
|
-
**extra: str,
|
52
|
-
) -> str:
|
53
|
-
title = extra.get("title")
|
54
|
-
code_block_id = _code_block_id(id, session, title)
|
55
|
-
_code_blocks[code_block_id] = code.split("\n")
|
56
|
-
exec_globals = _sessions_globals[session] if session else {}
|
57
|
-
|
58
|
-
# Other libraries expect functions to have a valid `__module__` attribute.
|
59
|
-
# To achieve this, we need to add a `__name__` attribute to the globals.
|
60
|
-
# We compute the name from the code block ID, replacing invalid characters with `_`.
|
61
|
-
# We also create a module object with the same name and add it to `sys.modules`,
|
62
|
-
# because that's what yet other libraries expect (`dataclasses` for example).
|
63
|
-
module_name = re.sub(r"[^a-zA-Z\d]+", "_", code_block_id)
|
64
|
-
exec_globals["__name__"] = module_name
|
65
|
-
sys.modules[module_name] = ModuleType(module_name)
|
66
|
-
|
67
|
-
buffer = StringIO()
|
68
|
-
exec_globals["print"] = partial(_buffer_print, buffer)
|
69
|
-
|
70
|
-
try:
|
71
|
-
exec_python(code, code_block_id, exec_globals)
|
72
|
-
except Exception as error:
|
73
|
-
trace = traceback.TracebackException.from_exception(error)
|
74
|
-
for frame in trace.stack:
|
75
|
-
if frame.filename.startswith("<code block: "):
|
76
|
-
if sys.version_info >= (3, 13):
|
77
|
-
frame._lines = _code_blocks[frame.filename][frame.lineno - 1] # type: ignore[attr-defined,operator]
|
78
|
-
else:
|
79
|
-
frame._line = _code_blocks[frame.filename][frame.lineno - 1] # type: ignore[attr-defined,operator]
|
80
|
-
raise ExecutionError(code_block("python", "".join(trace.format()), **extra)) from error
|
81
|
-
return buffer.getvalue()
|
82
|
-
|
83
|
-
|
84
|
-
def _format_python(**kwargs: Any) -> str:
|
85
|
-
return base_format(language="python", run=_run_python, **kwargs)
|
11
|
+
def __getattr__(name: str) -> Any:
|
12
|
+
warnings.warn(
|
13
|
+
"Importing from `markdown_exec.formatters.python` is deprecated. Import from `markdown_exec` directly.",
|
14
|
+
DeprecationWarning,
|
15
|
+
stacklevel=2,
|
16
|
+
)
|
17
|
+
return getattr(python, name)
|
markdown_exec/formatters/sh.py
CHANGED
@@ -1,32 +1,17 @@
|
|
1
|
-
"""
|
1
|
+
"""Deprecated. Import from `markdown_exec` directly."""
|
2
2
|
|
3
|
-
|
3
|
+
# YORE: Bump 2: Remove file.
|
4
4
|
|
5
|
-
import
|
5
|
+
import warnings
|
6
6
|
from typing import Any
|
7
7
|
|
8
|
-
from markdown_exec.formatters
|
9
|
-
from markdown_exec.rendering import code_block
|
8
|
+
from markdown_exec._internal.formatters import sh
|
10
9
|
|
11
10
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
**extra: str,
|
18
|
-
) -> str:
|
19
|
-
process = subprocess.run( # noqa: S603
|
20
|
-
["sh", "-c", code], # noqa: S607
|
21
|
-
stdout=subprocess.PIPE,
|
22
|
-
stderr=subprocess.STDOUT,
|
23
|
-
text=True,
|
24
|
-
check=False,
|
11
|
+
def __getattr__(name: str) -> Any:
|
12
|
+
warnings.warn(
|
13
|
+
"Importing from `markdown_exec.formatters.sh` is deprecated. Import from `markdown_exec` directly.",
|
14
|
+
DeprecationWarning,
|
15
|
+
stacklevel=2,
|
25
16
|
)
|
26
|
-
|
27
|
-
raise ExecutionError(code_block("sh", process.stdout, **extra), process.returncode)
|
28
|
-
return process.stdout
|
29
|
-
|
30
|
-
|
31
|
-
def _format_sh(**kwargs: Any) -> str:
|
32
|
-
return base_format(language="sh", run=_run_sh, **kwargs)
|
17
|
+
return getattr(sh, name)
|
markdown_exec/formatters/tree.py
CHANGED
@@ -1,60 +1,17 @@
|
|
1
|
-
"""
|
1
|
+
"""Deprecated. Import from `markdown_exec` directly."""
|
2
2
|
|
3
|
-
|
3
|
+
# YORE: Bump 2: Remove file.
|
4
4
|
|
5
|
-
|
6
|
-
from typing import
|
5
|
+
import warnings
|
6
|
+
from typing import Any
|
7
7
|
|
8
|
-
from markdown_exec.
|
8
|
+
from markdown_exec._internal.formatters import tree
|
9
9
|
|
10
|
-
if TYPE_CHECKING:
|
11
|
-
from markdown import Markdown
|
12
10
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
parent.append((lstripped, []))
|
21
|
-
offset += 1
|
22
|
-
elif indent > base_indent:
|
23
|
-
offset = _rec_build_tree(lines, parent[-1][1], offset, indent)
|
24
|
-
else:
|
25
|
-
return offset
|
26
|
-
return offset
|
27
|
-
|
28
|
-
|
29
|
-
def _build_tree(code: str) -> list[tuple[str, list]]:
|
30
|
-
lines = dedent(code.strip()).split("\n")
|
31
|
-
root_layer: list[tuple[str, list]] = []
|
32
|
-
_rec_build_tree(lines, root_layer, 0, 0)
|
33
|
-
return root_layer
|
34
|
-
|
35
|
-
|
36
|
-
def _rec_format_tree(tree: list[tuple[str, list]], *, root: bool = True) -> list[str]:
|
37
|
-
lines = []
|
38
|
-
n_items = len(tree)
|
39
|
-
for index, node in enumerate(tree):
|
40
|
-
last = index == n_items - 1
|
41
|
-
prefix = "" if root else f"{'└' if last else '├'}── "
|
42
|
-
if node[1]:
|
43
|
-
lines.append(f"{prefix}📁 {node[0]}")
|
44
|
-
sublines = _rec_format_tree(node[1], root=False)
|
45
|
-
if root:
|
46
|
-
lines.extend(sublines)
|
47
|
-
else:
|
48
|
-
indent_char = " " if last else "│"
|
49
|
-
lines.extend([f"{indent_char} {line}" for line in sublines])
|
50
|
-
else:
|
51
|
-
name = node[0].split()[0]
|
52
|
-
icon = "📁" if name.endswith("/") else "📄"
|
53
|
-
lines.append(f"{prefix}{icon} {node[0]}")
|
54
|
-
return lines
|
55
|
-
|
56
|
-
|
57
|
-
def _format_tree(code: str, md: Markdown, result: str, **options: Any) -> str:
|
58
|
-
markdown = MarkdownConverter(md)
|
59
|
-
output = "\n".join(_rec_format_tree(_build_tree(code)))
|
60
|
-
return markdown.convert(code_block(result or "bash", output, **options.get("extra", {})))
|
11
|
+
def __getattr__(name: str) -> Any:
|
12
|
+
warnings.warn(
|
13
|
+
"Importing from `markdown_exec.formatters.tree` is deprecated. Import from `markdown_exec` directly.",
|
14
|
+
DeprecationWarning,
|
15
|
+
stacklevel=2,
|
16
|
+
)
|
17
|
+
return getattr(tree, name)
|
markdown_exec/logger.py
CHANGED
@@ -1,92 +1,17 @@
|
|
1
|
-
"""
|
1
|
+
"""Deprecated. Import from `markdown_exec` directly."""
|
2
2
|
|
3
|
-
|
4
|
-
function so dependant libraries can patch loggers as they see fit.
|
3
|
+
# YORE: Bump 2: Remove file.
|
5
4
|
|
6
|
-
|
7
|
-
|
5
|
+
import warnings
|
6
|
+
from typing import Any
|
8
7
|
|
9
|
-
|
10
|
-
import logging
|
11
|
-
from markdown_exec.logger import patch_loggers
|
8
|
+
from markdown_exec._internal import logger
|
12
9
|
|
13
10
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
def get_logger(name):
|
24
|
-
logger = logging.getLogger(f"mkdocs.plugins.{name}")
|
25
|
-
return LoggerAdapter(name.split(".", 1)[0], logger)
|
26
|
-
|
27
|
-
|
28
|
-
patch_loggers(get_logger)
|
29
|
-
```
|
30
|
-
"""
|
31
|
-
|
32
|
-
from __future__ import annotations
|
33
|
-
|
34
|
-
import logging
|
35
|
-
from typing import Any, Callable, ClassVar
|
36
|
-
|
37
|
-
|
38
|
-
class _Logger:
|
39
|
-
_default_logger: Any = logging.getLogger
|
40
|
-
_instances: ClassVar[dict[str, _Logger]] = {}
|
41
|
-
|
42
|
-
# See same code in Griffe project.
|
43
|
-
def __init__(self, name: str) -> None:
|
44
|
-
# Default logger that can be patched by third-party.
|
45
|
-
self._logger = self.__class__._default_logger(name)
|
46
|
-
|
47
|
-
def __getattr__(self, name: str) -> Any:
|
48
|
-
# Forward everything to the logger.
|
49
|
-
return getattr(self._logger, name)
|
50
|
-
|
51
|
-
@classmethod
|
52
|
-
def get(cls, name: str) -> _Logger:
|
53
|
-
"""Get a logger instance.
|
54
|
-
|
55
|
-
Parameters:
|
56
|
-
name: The logger name.
|
57
|
-
|
58
|
-
Returns:
|
59
|
-
The logger instance.
|
60
|
-
"""
|
61
|
-
if name not in cls._instances:
|
62
|
-
cls._instances[name] = cls(name)
|
63
|
-
return cls._instances[name]
|
64
|
-
|
65
|
-
@classmethod
|
66
|
-
def _patch_loggers(cls, get_logger_func: Callable) -> None:
|
67
|
-
# Patch current instances.
|
68
|
-
for name, instance in cls._instances.items():
|
69
|
-
instance._logger = get_logger_func(name)
|
70
|
-
# Future instances will be patched as well.
|
71
|
-
cls._default_logger = get_logger_func
|
72
|
-
|
73
|
-
|
74
|
-
def get_logger(name: str) -> _Logger:
|
75
|
-
"""Create and return a new logger instance.
|
76
|
-
|
77
|
-
Parameters:
|
78
|
-
name: The logger name.
|
79
|
-
|
80
|
-
Returns:
|
81
|
-
The logger.
|
82
|
-
"""
|
83
|
-
return _Logger.get(name)
|
84
|
-
|
85
|
-
|
86
|
-
def patch_loggers(get_logger_func: Callable[[str], Any]) -> None:
|
87
|
-
"""Patch loggers.
|
88
|
-
|
89
|
-
Parameters:
|
90
|
-
get_logger_func: A function accepting a name as parameter and returning a logger.
|
91
|
-
"""
|
92
|
-
_Logger._patch_loggers(get_logger_func)
|
11
|
+
def __getattr__(name: str) -> Any:
|
12
|
+
warnings.warn(
|
13
|
+
"Importing from `markdown_exec.logger` is deprecated. Import from `markdown_exec` directly.",
|
14
|
+
DeprecationWarning,
|
15
|
+
stacklevel=2,
|
16
|
+
)
|
17
|
+
return getattr(logger, name)
|
markdown_exec/mkdocs_plugin.py
CHANGED
@@ -1,141 +1,17 @@
|
|
1
|
-
"""
|
1
|
+
"""Deprecated. Import from `markdown_exec` directly."""
|
2
2
|
|
3
|
-
|
3
|
+
# YORE: Bump 2: Remove file.
|
4
4
|
|
5
|
-
import
|
6
|
-
import
|
7
|
-
from pathlib import Path
|
8
|
-
from typing import TYPE_CHECKING, Any
|
5
|
+
import warnings
|
6
|
+
from typing import Any
|
9
7
|
|
10
|
-
from
|
11
|
-
from mkdocs.config.base import Config
|
12
|
-
from mkdocs.exceptions import PluginError
|
13
|
-
from mkdocs.plugins import BasePlugin
|
14
|
-
from mkdocs.utils import write_file
|
8
|
+
from markdown_exec._internal import mkdocs_plugin
|
15
9
|
|
16
|
-
from markdown_exec import formatter, formatters, validator
|
17
|
-
from markdown_exec.logger import patch_loggers
|
18
|
-
from markdown_exec.rendering import MarkdownConverter, markdown_config
|
19
10
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
from mkdocs.structure.files import Files
|
26
|
-
|
27
|
-
try:
|
28
|
-
__import__("pygments_ansi_color")
|
29
|
-
except ImportError:
|
30
|
-
ansi_ok = False
|
31
|
-
else:
|
32
|
-
ansi_ok = True
|
33
|
-
|
34
|
-
|
35
|
-
class _LoggerAdapter(logging.LoggerAdapter):
|
36
|
-
def __init__(self, prefix: str, logger: logging.Logger) -> None:
|
37
|
-
super().__init__(logger, {})
|
38
|
-
self.prefix = prefix
|
39
|
-
|
40
|
-
def process(self, msg: str, kwargs: MutableMapping[str, Any]) -> tuple[str, MutableMapping[str, Any]]:
|
41
|
-
return f"{self.prefix}: {msg}", kwargs
|
42
|
-
|
43
|
-
|
44
|
-
def _get_logger(name: str) -> _LoggerAdapter:
|
45
|
-
logger = logging.getLogger(f"mkdocs.plugins.{name}")
|
46
|
-
return _LoggerAdapter(name.split(".", 1)[0], logger)
|
47
|
-
|
48
|
-
|
49
|
-
patch_loggers(_get_logger)
|
50
|
-
|
51
|
-
|
52
|
-
class MarkdownExecPluginConfig(Config):
|
53
|
-
"""Configuration of the plugin (for `mkdocs.yml`)."""
|
54
|
-
|
55
|
-
ansi = config_options.Choice(("auto", "off", "required", True, False), default="auto")
|
56
|
-
"""Whether the `ansi` extra is required when installing the package."""
|
57
|
-
languages = config_options.ListOfItems(
|
58
|
-
config_options.Choice(formatters.keys()),
|
59
|
-
default=list(formatters.keys()),
|
11
|
+
def __getattr__(name: str) -> Any:
|
12
|
+
warnings.warn(
|
13
|
+
"Importing from `markdown_exec.mkdocs_plugin` is deprecated. Import from `markdown_exec` directly.",
|
14
|
+
DeprecationWarning,
|
15
|
+
stacklevel=2,
|
60
16
|
)
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
class MarkdownExecPlugin(BasePlugin[MarkdownExecPluginConfig]):
|
65
|
-
"""MkDocs plugin to easily enable custom fences for code blocks execution."""
|
66
|
-
|
67
|
-
def on_config(self, config: MkDocsConfig) -> MkDocsConfig | None:
|
68
|
-
"""Configure the plugin.
|
69
|
-
|
70
|
-
Hook for the [`on_config` event](https://www.mkdocs.org/user-guide/plugins/#on_config).
|
71
|
-
In this hook, we add custom fences for all the supported languages.
|
72
|
-
|
73
|
-
We also save the Markdown extensions configuration
|
74
|
-
into [`markdown_config`][markdown_exec.rendering.markdown_config].
|
75
|
-
|
76
|
-
Arguments:
|
77
|
-
config: The MkDocs config object.
|
78
|
-
|
79
|
-
Returns:
|
80
|
-
The modified config.
|
81
|
-
"""
|
82
|
-
if "pymdownx.superfences" not in config["markdown_extensions"]:
|
83
|
-
message = "The 'markdown-exec' plugin requires the 'pymdownx.superfences' Markdown extension to work."
|
84
|
-
raise PluginError(message)
|
85
|
-
if self.config.ansi in ("required", True) and not ansi_ok:
|
86
|
-
raise PluginError(
|
87
|
-
"The configuration for the 'markdown-exec' plugin requires "
|
88
|
-
"that it is installed with the 'ansi' extra. "
|
89
|
-
"Install it with 'pip install markdown-exec[ansi]'.",
|
90
|
-
)
|
91
|
-
self.mkdocs_config_dir = os.getenv("MKDOCS_CONFIG_DIR")
|
92
|
-
os.environ["MKDOCS_CONFIG_DIR"] = os.path.dirname(config["config_file_path"])
|
93
|
-
self.languages = self.config.languages
|
94
|
-
mdx_configs = config.setdefault("mdx_configs", {})
|
95
|
-
superfences = mdx_configs.setdefault("pymdownx.superfences", {})
|
96
|
-
custom_fences = superfences.setdefault("custom_fences", [])
|
97
|
-
for language in self.languages:
|
98
|
-
custom_fences.append(
|
99
|
-
{
|
100
|
-
"name": language,
|
101
|
-
"class": language,
|
102
|
-
"validator": validator,
|
103
|
-
"format": formatter,
|
104
|
-
},
|
105
|
-
)
|
106
|
-
markdown_config.save(config.markdown_extensions, config.mdx_configs)
|
107
|
-
return config
|
108
|
-
|
109
|
-
def on_env( # noqa: D102
|
110
|
-
self,
|
111
|
-
env: Environment,
|
112
|
-
*,
|
113
|
-
config: MkDocsConfig,
|
114
|
-
files: Files, # noqa: ARG002
|
115
|
-
) -> Environment | None:
|
116
|
-
if self.config.ansi in ("required", True) or (self.config.ansi == "auto" and ansi_ok):
|
117
|
-
self._add_css(config, "ansi.css")
|
118
|
-
if "pyodide" in self.languages:
|
119
|
-
self._add_css(config, "pyodide.css")
|
120
|
-
self._add_js(config, "pyodide.js")
|
121
|
-
return env
|
122
|
-
|
123
|
-
def on_post_build(self, *, config: MkDocsConfig) -> None: # noqa: ARG002,D102
|
124
|
-
MarkdownConverter.counter = 0
|
125
|
-
markdown_config.reset()
|
126
|
-
if self.mkdocs_config_dir is None:
|
127
|
-
os.environ.pop("MKDOCS_CONFIG_DIR", None)
|
128
|
-
else:
|
129
|
-
os.environ["MKDOCS_CONFIG_DIR"] = self.mkdocs_config_dir
|
130
|
-
|
131
|
-
def _add_asset(self, config: MkDocsConfig, asset_file: str, asset_type: str) -> None:
|
132
|
-
asset_filename = f"assets/_markdown_exec_{asset_file}"
|
133
|
-
asset_content = Path(__file__).parent.joinpath(asset_file).read_text()
|
134
|
-
write_file(asset_content.encode("utf-8"), os.path.join(config.site_dir, asset_filename))
|
135
|
-
config[f"extra_{asset_type}"].insert(0, asset_filename)
|
136
|
-
|
137
|
-
def _add_css(self, config: MkDocsConfig, css_file: str) -> None:
|
138
|
-
self._add_asset(config, css_file, "css")
|
139
|
-
|
140
|
-
def _add_js(self, config: MkDocsConfig, js_file: str) -> None:
|
141
|
-
self._add_asset(config, js_file, "javascript")
|
17
|
+
return getattr(mkdocs_plugin, name)
|