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.
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.css → assets/pyodide.css} +6 -1
  21. markdown_exec/{pyodide.js → assets/pyodide.js} +12 -6
  22. markdown_exec/formatters/__init__.py +17 -1
  23. markdown_exec/formatters/base.py +12 -183
  24. markdown_exec/formatters/bash.py +10 -25
  25. markdown_exec/formatters/console.py +12 -24
  26. markdown_exec/formatters/markdown.py +11 -5
  27. markdown_exec/formatters/pycon.py +12 -24
  28. markdown_exec/formatters/pyodide.py +12 -65
  29. markdown_exec/formatters/python.py +11 -79
  30. markdown_exec/formatters/sh.py +10 -25
  31. markdown_exec/formatters/tree.py +12 -55
  32. markdown_exec/logger.py +12 -87
  33. markdown_exec/mkdocs_plugin.py +11 -135
  34. markdown_exec/processors.py +12 -118
  35. markdown_exec/rendering.py +11 -270
  36. {markdown_exec-1.10.0.dist-info → markdown_exec-1.10.2.dist-info}/METADATA +6 -5
  37. markdown_exec-1.10.2.dist-info/RECORD +42 -0
  38. markdown_exec-1.10.2.dist-info/entry_points.txt +7 -0
  39. markdown_exec-1.10.0.dist-info/RECORD +0 -26
  40. markdown_exec-1.10.0.dist-info/entry_points.txt +0 -7
  41. /markdown_exec/{ansi.css → assets/ansi.css} +0 -0
  42. {markdown_exec-1.10.0.dist-info → markdown_exec-1.10.2.dist-info}/WHEEL +0 -0
  43. {markdown_exec-1.10.0.dist-info → markdown_exec-1.10.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,280 @@
1
+ # Markdown extensions and helpers.
2
+
3
+ from __future__ import annotations
4
+
5
+ from contextlib import contextmanager
6
+ from functools import cache
7
+ from textwrap import indent
8
+ from typing import TYPE_CHECKING, Any
9
+
10
+ from markdown import Markdown
11
+ from markupsafe import Markup
12
+
13
+ from markdown_exec._internal.processors import (
14
+ HeadingReportingTreeprocessor,
15
+ IdPrependingTreeprocessor,
16
+ InsertHeadings,
17
+ RemoveHeadings,
18
+ )
19
+
20
+ if TYPE_CHECKING:
21
+ from collections.abc import Iterator
22
+ from xml.etree.ElementTree import Element
23
+
24
+ from markdown import Extension
25
+
26
+
27
+ def code_block(language: str, code: str, **options: str) -> str:
28
+ """Format code as a code block.
29
+
30
+ Parameters:
31
+ language: The code block language.
32
+ code: The source code to format.
33
+ **options: Additional options passed from the source, to add back to the generated code block.
34
+
35
+ Returns:
36
+ The formatted code block.
37
+ """
38
+ opts = " ".join(f'{opt_name}="{opt_value}"' for opt_name, opt_value in options.items())
39
+ return f"````````{language} {opts}\n{code}\n````````"
40
+
41
+
42
+ def tabbed(*tabs: tuple[str, str]) -> str:
43
+ """Format tabs using `pymdownx.tabbed` extension.
44
+
45
+ Parameters:
46
+ *tabs: Tuples of strings: title and text.
47
+
48
+ Returns:
49
+ The formatted tabs.
50
+ """
51
+ parts = []
52
+ for title, text in tabs:
53
+ title = title.replace(r"\|", "|").strip() # noqa: PLW2901
54
+ parts.append(f'=== "{title}"')
55
+ parts.append(indent(text, prefix=" " * 4))
56
+ parts.append("")
57
+ return "\n".join(parts)
58
+
59
+
60
+ def _hide_lines(source: str) -> str:
61
+ return "\n".join(line for line in source.split("\n") if "markdown-exec: hide" not in line).strip()
62
+
63
+
64
+ def add_source(
65
+ *,
66
+ source: str,
67
+ location: str,
68
+ output: str,
69
+ language: str,
70
+ tabs: tuple[str, str],
71
+ result: str = "",
72
+ **extra: str,
73
+ ) -> str:
74
+ """Add source code block to the output.
75
+
76
+ Parameters:
77
+ source: The source code block.
78
+ location: Where to add the source (above, below, tabbed-left, tabbed-right, console).
79
+ output: The current output.
80
+ language: The code language.
81
+ tabs: Tabs titles (if used).
82
+ result: Syntax to use when concatenating source and result with "console" location.
83
+ **extra: Extra options added back to source code block.
84
+
85
+ Raises:
86
+ ValueError: When the given location is not supported.
87
+
88
+ Returns:
89
+ The updated output.
90
+ """
91
+ source = _hide_lines(source)
92
+ if location == "console":
93
+ return code_block(result or language, source + "\n" + output, **extra)
94
+
95
+ source_block = code_block(language, source, **extra)
96
+ if location == "above":
97
+ return source_block + "\n\n" + output
98
+ if location == "below":
99
+ return output + "\n\n" + source_block
100
+ if location == "material-block":
101
+ return source_block + f'\n\n<div class="result" markdown="1" >\n\n{output}\n\n</div>'
102
+
103
+ source_tab_title, result_tab_title = tabs
104
+ if location == "tabbed-left":
105
+ return tabbed((source_tab_title, source_block), (result_tab_title, output))
106
+ if location == "tabbed-right":
107
+ return tabbed((result_tab_title, output), (source_tab_title, source_block))
108
+
109
+ raise ValueError(f"unsupported location for sources: {location}")
110
+
111
+
112
+ class MarkdownConfig:
113
+ """This class returns a singleton used to store Markdown extensions configuration.
114
+
115
+ You don't have to instantiate the singleton yourself:
116
+ we provide it as [`markdown_config`][markdown_exec.markdown_config].
117
+ """
118
+
119
+ _singleton: MarkdownConfig | None = None
120
+
121
+ def __new__(cls) -> MarkdownConfig: # noqa: PYI034
122
+ """Return the singleton instance."""
123
+ if cls._singleton is None:
124
+ cls._singleton = super().__new__(cls)
125
+ return cls._singleton
126
+
127
+ def __init__(self) -> None:
128
+ self.exts: list[str] | None = None
129
+ """The Markdown extensions."""
130
+ self.exts_config: dict[str, dict[str, Any]] | None = None
131
+ """The extensions configuration."""
132
+
133
+ def save(self, exts: list[str], exts_config: dict[str, dict[str, Any]]) -> None:
134
+ """Save Markdown extensions and their configuration.
135
+
136
+ Parameters:
137
+ exts: The Markdown extensions.
138
+ exts_config: The extensions configuration.
139
+ """
140
+ self.exts = exts
141
+ self.exts_config = exts_config
142
+
143
+ def reset(self) -> None:
144
+ """Reset Markdown extensions and their configuration."""
145
+ self.exts = None
146
+ self.exts_config = None
147
+
148
+
149
+ markdown_config = MarkdownConfig()
150
+ """This object can be used to save the configuration of your Markdown extensions.
151
+
152
+ For example, since we provide a MkDocs plugin, we use it to store the configuration
153
+ that was read from `mkdocs.yml`:
154
+
155
+ ```python
156
+ from markdown_exec.rendering import markdown_config
157
+
158
+ # ...in relevant events/hooks, access and modify extensions and their configs, then:
159
+ markdown_config.save(extensions, extensions_config)
160
+ ```
161
+
162
+ See the actual event hook: [`on_config`][markdown_exec.MarkdownExecPlugin.on_config].
163
+ See the [`save`][markdown_exec.MarkdownConfig.save]
164
+ and [`reset`][markdown_exec.MarkdownConfig.reset] methods.
165
+
166
+ Without it, Markdown Exec will rely on the `registeredExtensions` attribute
167
+ of the original Markdown instance, which does not forward everything
168
+ that was configured, notably extensions like `tables`. Other extensions
169
+ such as `attr_list` are visible, but fail to register properly when
170
+ reusing their instances. It means that the rendered HTML might differ
171
+ from what you expect (tables not rendered, attribute lists not injected,
172
+ emojis not working, etc.).
173
+ """
174
+
175
+ # FIXME: When a heading contains an XML entity such as &mdash;,
176
+ # the entity is stashed and replaced with a placeholder.
177
+ # The heading therefore contains this placeholder.
178
+ # When reporting the heading to the upper conversion layer (for the ToC),
179
+ # the placeholder gets unstashed using the upper Markdown instance
180
+ # instead of the neste one. If the upper instance doesn't know the placeholder,
181
+ # nothing happens. But if it knows it, we then get a heading with garbabe/previous
182
+ # contents within it, messing up the ToC.
183
+ # We should fix this somehow. In the meantime, the workaround is to avoid
184
+ # XML entities that get stashed in headings.
185
+
186
+
187
+ @cache
188
+ def _register_headings_processors(md: Markdown) -> None:
189
+ md.treeprocessors.register(
190
+ InsertHeadings(md),
191
+ InsertHeadings.name,
192
+ priority=75, # right before markdown.blockprocessors.HashHeaderProcessor
193
+ )
194
+ md.treeprocessors.register(
195
+ RemoveHeadings(md),
196
+ RemoveHeadings.name,
197
+ priority=4, # right after toc
198
+ )
199
+
200
+
201
+ def _mimic(md: Markdown, headings: list[Element], *, update_toc: bool = True) -> Markdown:
202
+ new_md = Markdown()
203
+ extensions: list[Extension | str] = markdown_config.exts or md.registeredExtensions # type: ignore[assignment]
204
+ extensions_config: dict[str, dict[str, Any]] = markdown_config.exts_config or {}
205
+ new_md.registerExtensions(extensions, extensions_config)
206
+ new_md.treeprocessors.register(
207
+ IdPrependingTreeprocessor(md, ""),
208
+ IdPrependingTreeprocessor.name,
209
+ priority=4, # right after 'toc' (needed because that extension adds ids to headings)
210
+ )
211
+ new_md._original_md = md # type: ignore[attr-defined]
212
+
213
+ if update_toc:
214
+ _register_headings_processors(md)
215
+ new_md.treeprocessors.register(
216
+ HeadingReportingTreeprocessor(new_md, headings),
217
+ HeadingReportingTreeprocessor.name,
218
+ priority=1, # Close to the end.
219
+ )
220
+
221
+ return new_md
222
+
223
+
224
+ @contextmanager
225
+ def _id_prefix(md: Markdown, prefix: str | None) -> Iterator[None]:
226
+ MarkdownConverter.counter += 1
227
+ id_prepending_processor = md.treeprocessors[IdPrependingTreeprocessor.name]
228
+ id_prepending_processor.id_prefix = prefix if prefix is not None else f"exec-{MarkdownConverter.counter}--" # type: ignore[attr-defined]
229
+ try:
230
+ yield
231
+ finally:
232
+ id_prepending_processor.id_prefix = "" # type: ignore[attr-defined]
233
+
234
+
235
+ class MarkdownConverter:
236
+ """Helper class to avoid breaking the original Markdown instance state."""
237
+
238
+ counter: int = 0
239
+ """A counter to generate unique IDs for code blocks."""
240
+
241
+ def __init__(self, md: Markdown, *, update_toc: bool = True) -> None:
242
+ self._md_ref: Markdown = md
243
+ self._headings: list[Element] = []
244
+ self._update_toc = update_toc
245
+
246
+ @property
247
+ def _original_md(self) -> Markdown:
248
+ return getattr(self._md_ref, "_original_md", self._md_ref)
249
+
250
+ def _report_headings(self, markup: Markup) -> None:
251
+ self._original_md.treeprocessors[InsertHeadings.name].headings[markup] = self._headings # type: ignore[attr-defined]
252
+ self._headings = []
253
+
254
+ def convert(self, text: str, stash: dict[str, str] | None = None, id_prefix: str | None = None) -> Markup:
255
+ """Convert Markdown text to safe HTML.
256
+
257
+ Parameters:
258
+ text: Markdown text.
259
+ stash: An HTML stash.
260
+
261
+ Returns:
262
+ Safe HTML.
263
+ """
264
+ md = _mimic(self._original_md, self._headings, update_toc=self._update_toc)
265
+
266
+ # convert markdown to html
267
+ with _id_prefix(md, id_prefix):
268
+ converted = md.convert(text)
269
+
270
+ # restore html from stash
271
+ for placeholder, stashed in (stash or {}).items():
272
+ converted = converted.replace(placeholder, stashed)
273
+
274
+ markup = Markup(converted) # noqa: S704
275
+
276
+ # pass headings to upstream conversion layer
277
+ if self._update_toc:
278
+ self._report_headings(markup)
279
+
280
+ return markup
@@ -47,4 +47,9 @@ html[data-theme="dark"] {
47
47
  .pyodide-clickable {
48
48
  cursor: pointer;
49
49
  text-align: right;
50
- }
50
+ }
51
+
52
+ /* For themes other than Material. */
53
+ .pyodide .twemoji svg {
54
+ width: 1rem;
55
+ }
@@ -22,9 +22,9 @@ async function evaluatePython(pyodide, editor, output, session) {
22
22
  try {
23
23
  result = await pyodide.runPythonAsync(code, { globals: getSession(session, pyodide) });
24
24
  } catch (error) {
25
- writeOutput(output, error);
25
+ writeOutput(output, new Option(error.toString()).innerHTML);
26
26
  }
27
- if (result) writeOutput(output, result);
27
+ if (result) writeOutput(output, new Option(result).innerHTML);
28
28
  hljs.highlightElement(output);
29
29
  }
30
30
 
@@ -91,11 +91,17 @@ async function setupPyodide(idPrefix, install = null, themeLight = 'tomorrow', t
91
91
  writeOutput(output, "Initializing...");
92
92
  let pyodide = await pyodidePromise;
93
93
  if (install && install.length) {
94
- micropip = pyodide.pyimport("micropip");
95
- for (const package of install)
96
- await micropip.install(package);
94
+ try {
95
+ micropip = pyodide.pyimport("micropip");
96
+ for (const package of install)
97
+ await micropip.install(package);
98
+ clearOutput(output);
99
+ } catch (error) {
100
+ clearOutput(output);
101
+ writeOutput(output, `Could not install one or more packages: ${install.join(", ")}\n`);
102
+ writeOutput(output, new Option(error.toString()).innerHTML);
103
+ }
97
104
  }
98
- clearOutput(output);
99
105
  run.onclick = () => evaluatePython(pyodide, editor, output, session);
100
106
  clear.onclick = () => clearOutput(output);
101
107
  output.parentElement.parentElement.addEventListener("keydown", (event) => {
@@ -1 +1,17 @@
1
- """This subpackage contains all the formatters."""
1
+ """Deprecated. Import from `markdown_exec` directly."""
2
+
3
+ # YORE: Bump 2: Remove file.
4
+
5
+ import warnings
6
+ from typing import Any
7
+
8
+ from markdown_exec._internal import formatters
9
+
10
+
11
+ def __getattr__(name: str) -> Any:
12
+ warnings.warn(
13
+ "Importing from `markdown_exec.formatters` is deprecated. Import from `markdown_exec` directly.",
14
+ DeprecationWarning,
15
+ stacklevel=2,
16
+ )
17
+ return getattr(formatters, name)
@@ -1,188 +1,17 @@
1
- """Generic formatter for executing 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 os
6
- from contextlib import contextmanager
7
- from textwrap import indent
8
- from typing import TYPE_CHECKING, Any, Callable
9
- from uuid import uuid4
5
+ import warnings
6
+ from typing import Any
10
7
 
11
- from markupsafe import Markup
8
+ from markdown_exec._internal.formatters import base
12
9
 
13
- from markdown_exec.logger import get_logger
14
- from markdown_exec.rendering import MarkdownConverter, add_source, code_block
15
10
 
16
- if TYPE_CHECKING:
17
- from collections.abc import Iterator
18
-
19
- from markdown.core import Markdown
20
-
21
- logger = get_logger(__name__)
22
- default_tabs = ("Source", "Result")
23
-
24
-
25
- @contextmanager
26
- def working_directory(path: str | None = None) -> Iterator[None]:
27
- """Change the working directory for the duration of the context.
28
-
29
- Parameters:
30
- path: The path to change the working directory to.
31
- """
32
- if path:
33
- old_cwd = os.getcwd()
34
- os.chdir(path)
35
- try:
36
- yield
37
- finally:
38
- os.chdir(old_cwd)
39
- else:
40
- yield
41
-
42
-
43
- @contextmanager
44
- def console_width(width: int | None = None) -> Iterator[None]:
45
- """Set the console width for the duration of the context.
46
-
47
- The console width is set using the `COLUMNS` environment variable.
48
-
49
- Parameters:
50
- width: The width to set the console to.
51
- """
52
- if width:
53
- old_width = os.environ.get("COLUMNS", None)
54
- os.environ["COLUMNS"] = str(width)
55
- try:
56
- yield
57
- finally:
58
- if old_width is None:
59
- del os.environ["COLUMNS"]
60
- else:
61
- os.environ["COLUMNS"] = old_width
62
- else:
63
- yield
64
-
65
-
66
- class ExecutionError(Exception):
67
- """Exception raised for errors during execution of a code block.
68
-
69
- Attributes:
70
- message: The exception message.
71
- returncode: The code returned by the execution of the code block.
72
- """
73
-
74
- def __init__(self, message: str, returncode: int | None = None) -> None: # noqa: D107
75
- super().__init__(message)
76
- self.returncode = returncode
77
-
78
-
79
- def _format_log_details(details: str, *, strip_fences: bool = False) -> str:
80
- if strip_fences:
81
- lines = details.split("\n")
82
- if lines[0].startswith("```") and lines[-1].startswith("```"):
83
- details = "\n".join(lines[1:-1])
84
- return indent(details, " " * 2)
85
-
86
-
87
- def base_format(
88
- *,
89
- language: str,
90
- run: Callable,
91
- code: str,
92
- md: Markdown,
93
- html: bool = False,
94
- source: str = "",
95
- result: str = "",
96
- tabs: tuple[str, str] = default_tabs,
97
- id: str = "", # noqa: A002
98
- id_prefix: str | None = None,
99
- returncode: int = 0,
100
- transform_source: Callable[[str], tuple[str, str]] | None = None,
101
- session: str | None = None,
102
- update_toc: bool = True,
103
- workdir: str | None = None,
104
- width: int | None = None,
105
- **options: Any,
106
- ) -> Markup:
107
- """Execute code and return HTML.
108
-
109
- Parameters:
110
- language: The code language.
111
- run: Function that runs code and returns output.
112
- code: The code to execute.
113
- md: The Markdown instance.
114
- html: Whether to inject output as HTML directly, without rendering.
115
- source: Whether to show source as well, and where.
116
- result: If provided, use as language to format result in a code block.
117
- tabs: Titles of tabs (if used).
118
- id: An optional ID for the code block (useful when warning about errors).
119
- id_prefix: A string used to prefix HTML ids in the generated HTML.
120
- returncode: The expected exit code.
121
- transform_source: An optional callable that returns transformed versions of the source.
122
- The input source is the one that is ran, the output source is the one that is
123
- rendered (when the source option is enabled).
124
- session: A session name, to persist state between executed code blocks.
125
- update_toc: Whether to include generated headings
126
- into the Markdown table of contents (toc extension).
127
- workdir: The working directory to use for the execution.
128
- **options: Additional options passed from the formatter.
129
-
130
- Returns:
131
- HTML contents.
132
- """
133
- markdown = MarkdownConverter(md, update_toc=update_toc)
134
- extra = options.get("extra", {})
135
-
136
- if transform_source:
137
- source_input, source_output = transform_source(code)
138
- else:
139
- source_input = code
140
- source_output = code
141
-
142
- try:
143
- with working_directory(workdir), console_width(width):
144
- output = run(source_input, returncode=returncode, session=session, id=id, **extra)
145
- except ExecutionError as error:
146
- identifier = id or extra.get("title", "")
147
- identifier = identifier and f"'{identifier}' "
148
- exit_message = "errors" if error.returncode is None else f"unexpected code {error.returncode}"
149
- log_message = (
150
- f"Execution of {language} code block {identifier}exited with {exit_message}\n\n"
151
- f"Code block is:\n\n{_format_log_details(source_input)}\n\n"
152
- f"Output is:\n\n{_format_log_details(str(error), strip_fences=True)}\n"
153
- )
154
- logger.warning(log_message)
155
- return markdown.convert(str(error))
156
-
157
- if not output and not source:
158
- return Markup()
159
-
160
- if html:
161
- if source:
162
- placeholder = f'<div class="{uuid4()}"></div>'
163
- wrapped_output = add_source(
164
- source=source_output,
165
- location=source,
166
- output=placeholder,
167
- language=language,
168
- tabs=tabs,
169
- **extra,
170
- )
171
- return markdown.convert(wrapped_output, stash={placeholder: output})
172
- return Markup(output)
173
-
174
- wrapped_output = output
175
- if result and source != "console":
176
- wrapped_output = code_block(result, output)
177
- if source:
178
- wrapped_output = add_source(
179
- source=source_output,
180
- location=source,
181
- output=wrapped_output,
182
- language=language,
183
- tabs=tabs,
184
- result=result,
185
- **extra,
186
- )
187
- prefix = id_prefix if id_prefix is not None else (f"{id}-" if id else None)
188
- return markdown.convert(wrapped_output, id_prefix=prefix)
11
+ def __getattr__(name: str) -> Any:
12
+ warnings.warn(
13
+ "Importing from `markdown_exec.formatters.base` is deprecated. Import from `markdown_exec` directly.",
14
+ DeprecationWarning,
15
+ stacklevel=2,
16
+ )
17
+ return getattr(base, 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 bash
10
9
 
11
10
 
12
- def _run_bash(
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
- ["bash", "-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.bash` 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_bash(**kwargs: Any) -> str:
32
- return base_format(language="bash", run=_run_bash, **kwargs)
17
+ return getattr(bash, name)
@@ -1,29 +1,17 @@
1
- """Formatter for executing shell console 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 textwrap
6
- from typing import TYPE_CHECKING, Any
5
+ import warnings
6
+ from typing import Any
7
7
 
8
- from markdown_exec.formatters.base import base_format
9
- from markdown_exec.formatters.sh import _run_sh
10
- from markdown_exec.logger import get_logger
8
+ from markdown_exec._internal.formatters import console
11
9
 
12
- if TYPE_CHECKING:
13
- from markupsafe import Markup
14
10
 
15
- logger = get_logger(__name__)
16
-
17
-
18
- def _transform_source(code: str) -> tuple[str, str]:
19
- sh_lines = []
20
- for line in code.split("\n"):
21
- prompt = line[:2]
22
- if prompt in {"$ ", "% "}:
23
- sh_lines.append(line[2:])
24
- sh_code = "\n".join(sh_lines)
25
- return sh_code, textwrap.indent(sh_code, prompt)
26
-
27
-
28
- def _format_console(**kwargs: Any) -> Markup:
29
- return base_format(language="console", run=_run_sh, transform_source=_transform_source, **kwargs)
11
+ def __getattr__(name: str) -> Any:
12
+ warnings.warn(
13
+ "Importing from `markdown_exec.formatters.console` is deprecated. Import from `markdown_exec` directly.",
14
+ DeprecationWarning,
15
+ stacklevel=2,
16
+ )
17
+ return getattr(console, name)
@@ -1,11 +1,17 @@
1
- """Formatter for literate Markdown."""
1
+ """Deprecated. Import from `markdown_exec` directly."""
2
2
 
3
- from __future__ import annotations
3
+ # YORE: Bump 2: Remove file.
4
4
 
5
+ import warnings
5
6
  from typing import Any
6
7
 
7
- from markdown_exec.formatters.base import base_format
8
+ from markdown_exec._internal.formatters import markdown
8
9
 
9
10
 
10
- def _format_markdown(**kwargs: Any) -> str:
11
- return base_format(language="md", run=lambda code, **_: code, **kwargs)
11
+ def __getattr__(name: str) -> Any:
12
+ warnings.warn(
13
+ "Importing from `markdown_exec.formatters.markdown` is deprecated. Import from `markdown_exec` directly.",
14
+ DeprecationWarning,
15
+ stacklevel=2,
16
+ )
17
+ return getattr(markdown, name)