diff --git a/docs/contributing.md b/docs/contributing.md index ead5145c..4103b976 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -315,10 +315,19 @@ words. #### Code Blocks -All code blocks should use the fenced code block style. If a code block is -demonstrating Markdown syntax, if can be assigned the `md-render` attribute, -and both the Markdown source and HTML output will be rendered in a nested set -of code blocks. +All code blocks should use the fenced code block style and indicate the +language of the code contained in the block to ensure proper syntax +highlighting. + +There are two special types of code blocks which will render output based on +the content of the code block. See [Rendered Markdown](#rendered-markdown) +and [Rendered Python](#rendered-python) below. + +##### Rendered Markdown + +If a code block is demonstrating Markdown syntax, it can be assigned the +`md-render` attribute in place of the language, and both the Markdown source +and HTML output will be rendered in a nested set of code blocks. ```` markdown ``` md-render @@ -388,6 +397,87 @@ The above code block would render as follows: Some *Markdown* text. ``` +##### Rendered Python + +If a code block is demonstrating Python code, it can be assigned the +`py-render` attribute in place of the language, and both the Python code and +output will be rendered in a nested set of code blocks. The language of the +output should be specified using the `output-lang` attribute. + +```` markdown +``` py-render { output-lang='html' } +import markdown + +src = 'Some **Markdown** text.' +fragment = markdown.markdown(src) +``` +```` + +The above code block will be rendered as follows: + +``` py-render { output-lang='html' } +import markdown + +src = 'Some **Markdown** text.' +fragment = markdown.markdown(src) +``` + +Note that the Python code is executed in an isolated environment. Therefore, +any imports need to be made to avoid errors. However, as all `py-render` +blocks on the same page are run within the same environment, an import only +needs to be made once (before the first use) for all code blocks on the same +page. Variables assigned in one block will be available in later blocks on the +same page. + +```` markdown +``` py-render { output-lang='html' } +from justhtml import JustHTML + +html = JustHTML(fragment).to_html() +``` +```` + +Notice that in the block above, the variable `fragment` from the previous code +block is available within this code block. However, `JustHTML` needs to be +imported as it had not been previously. + +``` py-render { output-lang='html' } +from justhtml import JustHTML + +html = JustHTML(fragment).to_html() +``` + +Each page contains it's own isolated environment. Objects defined on one page +will not be available on another page and would need to be redefined. + +Output will be generated if one of three conditions are met. Whichever +condition is encountered first (in decreasing order) is the controlling +condition. + +1. If an error is raised, a traceback will be rendered in the output and + highlighted using Pygment's `PythonTracebackLexer` (`py3tb`). +2. If the code writes to STDOUT (for example, it passes text to `print()`), + then the text sent to STDOUT will be rendered in the output and + highlighted using the language assigned to `output-lang`. +3. If the last line of the code assigns a value to a variable, the value of + that variable will be rendered in the output and highlighted using the + language assigned to `output-lang`. + +If none of the above conditions are met, then the code block will render as +normal without rendered output. However, the code block will have updated the +isolated Python environment and any objects created can be referenced in +later code blocks on the same page. + +If a Python code block should not have it's code executed and rendered, then +simply assign it the `python` attribute. It will then be rendered as a normal +Python code block. Any objects defined in standard Python code blocks +will **not** be available to `py-render` style blocks + +Code blocks which contain Python Console sessions are not supported by +`py-render`. They should be assigned `pycon` and contain the code and output +as copied out of a Python Console session. Any objects defined in Python +Session code blocks will **not** be available to `py-render` style blocks. + #### Changelog Any commit/pull request which changes the behavior of the Markdown library in diff --git a/docs/extensions/code_hilite.md b/docs/extensions/code_hilite.md index 6967ff08..67e8d5b7 100644 --- a/docs/extensions/code_hilite.md +++ b/docs/extensions/code_hilite.md @@ -262,7 +262,8 @@ markdown.markdown(some_text, extensions=['codehilite']) To keep the code block's language in the Pygments generated HTML output, one can provide a custom Pygments formatter that takes the `lang_str` option. For example, -```python +```py-render { output-lang='html' } +import markdown from pygments.formatters import HtmlFormatter from markdown.extensions.codehilite import CodeHiliteExtension @@ -285,24 +286,12 @@ some_text = '''\ print('hellow world') ''' -markdown.markdown( +output = markdown.markdown( some_text, extensions=[CodeHiliteExtension(pygments_formatter=CustomHtmlFormatter)], ) ``` -The formatter above will output the following HTML structure for a code block: - -```html -
-
- ...
-
-
-Some Markdown text.
-``` +Python-Markdown only ever outputs an HTML fragment. If you need a complete +HTML document, including ``, `` and `` tags, then you will +need to pass the output of Python-Markdown into some other tool. For a +minimal complete document, [JustHTML](https://emilstenstrom.github.io/justhtml/) +can do that with a single line of code: -If you need a complete HTML document, including ``, `` and -`` tags, then you will need to pass the output of Python-Markdown into -some other tool. For a minimal complete document, -[JustHTML](https://emilstenstrom.github.io/justhtml/) can do that with a -single line of code: - -``` python +``` py-render { output-lang='html' } from justhtml import JustHTML -doc = JustHTML(html) -``` - -Assuming the value of `html` from above, the value returned by -`doc.to_html()` would be the following string: - -``` html - - - -Some Markdown text.
- - +html = JustHTML(fragment).to_html() ``` For more sophisticated output, you may need to explore the use of a templating diff --git a/mkdocs.yml b/mkdocs.yml index 7c8fe760..389d9aa5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -157,13 +157,12 @@ markdown_extensions: pygments_lang_class: true - pymdownx.superfences: custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format - name: md-render class: md-render format: !!python/name:tools.superfences_formaters.md_render - + - name: py-render + class: py-render + format: !!python/name:tools.superfences_formaters.py_render plugins: - search diff --git a/pyproject.toml b/pyproject.toml index 1cc76c1b..c7dc8ea1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,6 +56,7 @@ docs = [ 'mkdocstrings-python==1.16.8', 'pygments==2.21.0', 'pymdown-extensions==11.0.2', + 'justhtml==3.11.2' ] [project.urls] diff --git a/tools/superfences_formaters.py b/tools/superfences_formaters.py index 24518c8b..4d99a8df 100644 --- a/tools/superfences_formaters.py +++ b/tools/superfences_formaters.py @@ -7,6 +7,9 @@ import markdown import yaml import re +import ast +import sys +from io import StringIO from collections import OrderedDict @@ -83,3 +86,92 @@ def md_render(src="", language="", class_name=None, options=None, md="", **kwarg source = md.preprocessors['fenced_code_block'].highlight(text, 'markdown', options, md, **kwargs) output = md.preprocessors['fenced_code_block'].highlight(html, 'html', result_options, md, **kwargs) return f'{source}\n