markdown-exec 1.10.1__py3-none-any.whl → 1.10.3__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.
Files changed (43) hide show
  1. markdown_exec/__init__.py +50 -138
  2. markdown_exec/_internal/__init__.py +0 -0
  3. markdown_exec/{debug.py → _internal/debug.py} +13 -15
  4. markdown_exec/_internal/formatters/__init__.py +1 -0
  5. markdown_exec/{formatters → _internal/formatters}/_exec_python.py +1 -1
  6. markdown_exec/_internal/formatters/base.py +191 -0
  7. markdown_exec/_internal/formatters/bash.py +32 -0
  8. markdown_exec/_internal/formatters/console.py +29 -0
  9. markdown_exec/_internal/formatters/markdown.py +11 -0
  10. markdown_exec/_internal/formatters/pycon.py +26 -0
  11. markdown_exec/_internal/formatters/pyodide.py +73 -0
  12. markdown_exec/_internal/formatters/python.py +85 -0
  13. markdown_exec/_internal/formatters/sh.py +32 -0
  14. markdown_exec/_internal/formatters/tree.py +60 -0
  15. markdown_exec/_internal/logger.py +89 -0
  16. markdown_exec/_internal/main.py +123 -0
  17. markdown_exec/_internal/mkdocs_plugin.py +143 -0
  18. markdown_exec/_internal/processors.py +136 -0
  19. markdown_exec/_internal/rendering.py +280 -0
  20. markdown_exec/{pyodide.js → assets/pyodide.js} +15 -7
  21. markdown_exec/formatters/__init__.py +17 -1
  22. markdown_exec/formatters/base.py +12 -183
  23. markdown_exec/formatters/bash.py +10 -25
  24. markdown_exec/formatters/console.py +12 -24
  25. markdown_exec/formatters/markdown.py +11 -5
  26. markdown_exec/formatters/pycon.py +12 -24
  27. markdown_exec/formatters/pyodide.py +12 -65
  28. markdown_exec/formatters/python.py +11 -79
  29. markdown_exec/formatters/sh.py +10 -25
  30. markdown_exec/formatters/tree.py +12 -55
  31. markdown_exec/logger.py +12 -87
  32. markdown_exec/mkdocs_plugin.py +11 -135
  33. markdown_exec/processors.py +12 -118
  34. markdown_exec/rendering.py +11 -270
  35. {markdown_exec-1.10.1.dist-info → markdown_exec-1.10.3.dist-info}/METADATA +4 -3
  36. markdown_exec-1.10.3.dist-info/RECORD +42 -0
  37. markdown_exec-1.10.3.dist-info/entry_points.txt +7 -0
  38. markdown_exec-1.10.1.dist-info/RECORD +0 -26
  39. markdown_exec-1.10.1.dist-info/entry_points.txt +0 -7
  40. /markdown_exec/{ansi.css → assets/ansi.css} +0 -0
  41. /markdown_exec/{pyodide.css → assets/pyodide.css} +0 -0
  42. {markdown_exec-1.10.1.dist-info → markdown_exec-1.10.3.dist-info}/WHEEL +0 -0
  43. {markdown_exec-1.10.1.dist-info → markdown_exec-1.10.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,29 +1,17 @@
1
- """Formatter for executing `pycon` code."""
1
+ """Deprecated. Import from `markdown_exec` directly."""
2
2
 
3
- from __future__ import annotations
3
+ # YORE: Bump 2: Remove file.
4
4
 
5
- from typing import TYPE_CHECKING, Any
5
+ import warnings
6
+ from typing import Any
6
7
 
7
- from markdown_exec.formatters.base import base_format
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
- logger = get_logger(__name__)
15
-
16
-
17
- def _transform_source(code: str) -> tuple[str, str]:
18
- python_lines = []
19
- pycon_lines = []
20
- for line in code.split("\n"):
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
- """Formatter for creating a Pyodide interactive editor."""
1
+ """Deprecated. Import from `markdown_exec` directly."""
2
2
 
3
- from __future__ import annotations
3
+ # YORE: Bump 2: Remove file.
4
4
 
5
- from typing import TYPE_CHECKING, Any
5
+ import warnings
6
+ from typing import Any
6
7
 
7
- if TYPE_CHECKING:
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
- play_emoji = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8 5.14v14l11-7-11-7Z"></path></svg>'
14
- clear_emoji = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M15.14 3c-.51 0-1.02.2-1.41.59L2.59 14.73c-.78.77-.78 2.04 0 2.83L5.03 20h7.66l8.72-8.73c.79-.77.79-2.04 0-2.83l-4.85-4.85c-.39-.39-.91-.59-1.42-.59M17 18l-2 2h7v-2"></path></svg>'
15
-
16
- assets = """
17
- <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.16.0/ace.js"></script>
18
- <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
19
- <script type="text/javascript" src="https://cdn.jsdelivr.net/pyodide/v{version}/full/pyodide.js"></script>
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
- """Formatter for executing Python code."""
1
+ """Deprecated. Import from `markdown_exec` directly."""
2
2
 
3
- from __future__ import annotations
3
+ # YORE: Bump 2: Remove file.
4
4
 
5
- import re
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._exec_python import exec_python
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
- def _buffer_print(buffer: StringIO, *texts: str, end: str = "\n", **kwargs: Any) -> None: # noqa: ARG001
24
- buffer.write(" ".join(str(text) for text in texts) + end)
25
-
26
-
27
- def _code_block_id(
28
- id: str | None = None, # noqa: A002
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)
@@ -1,32 +1,17 @@
1
- """Formatter for executing shell code."""
1
+ """Deprecated. Import from `markdown_exec` directly."""
2
2
 
3
- from __future__ import annotations
3
+ # YORE: Bump 2: Remove file.
4
4
 
5
- import subprocess
5
+ import warnings
6
6
  from typing import Any
7
7
 
8
- from markdown_exec.formatters.base import ExecutionError, base_format
9
- from markdown_exec.rendering import code_block
8
+ from markdown_exec._internal.formatters import sh
10
9
 
11
10
 
12
- def _run_sh(
13
- code: str,
14
- returncode: int | None = None,
15
- session: str | None = None, # noqa: ARG001
16
- id: str | None = None, # noqa: A002,ARG001
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
- if process.returncode != returncode:
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)
@@ -1,60 +1,17 @@
1
- """Formatter for file-system trees."""
1
+ """Deprecated. Import from `markdown_exec` directly."""
2
2
 
3
- from __future__ import annotations
3
+ # YORE: Bump 2: Remove file.
4
4
 
5
- from textwrap import dedent
6
- from typing import TYPE_CHECKING, Any
5
+ import warnings
6
+ from typing import Any
7
7
 
8
- from markdown_exec.rendering import MarkdownConverter, code_block
8
+ from markdown_exec._internal.formatters import tree
9
9
 
10
- if TYPE_CHECKING:
11
- from markdown import Markdown
12
10
 
13
-
14
- def _rec_build_tree(lines: list[str], parent: list, offset: int, base_indent: int) -> int:
15
- while offset < len(lines):
16
- line = lines[offset]
17
- lstripped = line.lstrip()
18
- indent = len(line) - len(lstripped)
19
- if indent == base_indent:
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
- """This module contains logging utilities.
1
+ """Deprecated. Import from `markdown_exec` directly."""
2
2
 
3
- We provide the [`patch_loggers`][markdown_exec.logger.patch_loggers]
4
- function so dependant libraries can patch loggers as they see fit.
3
+ # YORE: Bump 2: Remove file.
5
4
 
6
- For example, to fit in the MkDocs logging configuration
7
- and prefix each log message with the module name:
5
+ import warnings
6
+ from typing import Any
8
7
 
9
- ```python
10
- import logging
11
- from markdown_exec.logger import patch_loggers
8
+ from markdown_exec._internal import logger
12
9
 
13
10
 
14
- class LoggerAdapter(logging.LoggerAdapter):
15
- def __init__(self, prefix, logger):
16
- super().__init__(logger, {})
17
- self.prefix = prefix
18
-
19
- def process(self, msg, kwargs):
20
- return f"{self.prefix}: {msg}", kwargs
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)
@@ -1,141 +1,17 @@
1
- """This module contains an optional plugin for MkDocs."""
1
+ """Deprecated. Import from `markdown_exec` directly."""
2
2
 
3
- from __future__ import annotations
3
+ # YORE: Bump 2: Remove file.
4
4
 
5
- import logging
6
- import os
7
- from pathlib import Path
8
- from typing import TYPE_CHECKING, Any
5
+ import warnings
6
+ from typing import Any
9
7
 
10
- from mkdocs.config import config_options
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
- if TYPE_CHECKING:
21
- from collections.abc import MutableMapping
22
-
23
- from jinja2 import Environment
24
- from mkdocs.config.defaults import MkDocsConfig
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
- """Which languages to enabled the extension for."""
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)