diff --git a/README.md b/README.md index 78ffaf7..a17a671 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,13 @@ and this HTML is injected in place of the code block. With `pip`: ```bash -pip install markdown-exec +pip install markdown-exec[ansi] ``` +The `ansi` extra provides the necessary bits (`pygments-ansi-color` and a CSS file) +to render ANSI colors in HTML code blocks. The CSS file is automatically added +to MkDocs' `extra_css` when Markdown Exec is activated via `plugins` (see below). + ## Configuration This extension relies on the diff --git a/docs/gallery.md b/docs/gallery.md index 94d87d2..b32c906 100644 --- a/docs/gallery.md +++ b/docs/gallery.md @@ -70,7 +70,17 @@ but also allows for less verbose source to generate the SVG snippets. ## Terminal output with colors -We use Rich again to render the output of a command in a terminal, with colors. +If you installed Markdown Exec with the `ansi` extra (`pip install markdown-exec[ansi]`), +the ANSI colors in the output of shell commands will be translated to HTML/CSS, +allowing to render them naturally in your documentation pages. +For this to happen, use the +[`result="ansi"` option](http://localhost:8000/markdown-exec/usage/#wrap-result-in-a-code-block). + +```bash exec="true" source="tabbed-right" title="ANSI terminal output" result="ansi" +--8<-- "gallery/ansi.sh" +``` + +As an alternative, we can use Rich again to render the output of a command in a terminal, with colors. This example is taken directly from the documentation of the [Griffe](https://github.com/mkdocstrings/griffe) project. ```python exec="true" html="true" source="tabbed-right" title="Rich terminal output" diff --git a/docs/snippets/gallery/ansi.sh b/docs/snippets/gallery/ansi.sh new file mode 100644 index 0000000..d59c090 --- /dev/null +++ b/docs/snippets/gallery/ansi.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# credits to https://github.com/42picky/42picky.github.io +text="xYz" # Some test text +echo -e "\n 40m 41m 42m 43m 44m 45m 46m 47m" +for FGs in ' m' ' 1m' ' 30m' '1;30m' ' 31m' '1;31m' ' 32m' \ + '1;32m' ' 33m' '1;33m' ' 34m' '1;34m' ' 35m' '1;35m' \ + ' 36m' '1;36m' ' 37m' '1;37m'; do + FG=${FGs// /} + echo -en " $FGs \033[$FG ${text} " + for BG in 40m 41m 42m 43m 44m 45m 46m 47m; do + echo -en "$EINS \033[$FG\033[${BG} ${text} \033[0m" + done + echo +done +echo \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 290077e..1b56c62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,9 @@ dependencies = [ "pymdown-extensions>=9", ] +[project.optional-dependencies] +ansi = ["pygments-ansi-color"] + [project.urls] Homepage = "https://pawamoy.github.io/markdown-exec" Documentation = "https://pawamoy.github.io/markdown-exec" diff --git a/scripts/setup.sh b/scripts/setup.sh index 08c3cbf..327ccf5 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -28,11 +28,11 @@ if [ -n "${PYTHON_VERSIONS}" ]; then for python_version in ${PYTHON_VERSIONS}; do if pdm use -f "python${python_version}" &>/dev/null; then echo "> Using Python ${python_version} interpreter" - pdm install + pdm install -G ansi else echo "> pdm use -f python${python_version}: Python interpreter not available?" >&2 fi done else - pdm install + pdm install -G ansi fi diff --git a/src/markdown_exec/ansi.css b/src/markdown_exec/ansi.css new file mode 100644 index 0000000..4c1b8a5 --- /dev/null +++ b/src/markdown_exec/ansi.css @@ -0,0 +1,266 @@ +/* + Inspired by https://spec.draculatheme.com/ specification, they should work + decently with both dark and light themes. + */ +:root { + --ansi-red: #ff5555; + --ansi-green: #50fa7b; + --ansi-blue: #265285; + --ansi-yellow: #ffb86c; + --ansi-magenta: #bd93f9; + --ansi-cyan: #8be9fd; + --ansi-black: #282a36; + --ansi-white: #f8f8f2; +} + +.-Color-Green, +.-Color-Faint-Green, +.-Color-Bold-Green { + color: var(--ansi-green); +} + +.-Color-Red, +.-Color-Faint-Red, +.-Color-Bold-Red { + color: var(--ansi-red); +} + +.-Color-Yellow, +.-Color-Faint-Yellow, +.-Color-Bold-Yellow { + color: var(--ansi-yellow); +} + +.-Color-Blue, +.-Color-Faint-Blue, +.-Color-Bold-Blue { + color: var(--ansi-blue); +} + +.-Color-Magenta, +.-Color-Faint-Magenta, +.-Color-Bold-Magenta { + color: var(--ansi-magenta); +} + +.-Color-Cyan, +.-Color-Faint-Cyan, +.-Color-Bold-Cyan { + color: var(--ansi-cyan); +} + +.-Color-White, +.-Color-Faint-White, +.-Color-Bold-White { + color: var(--ansi-white); +} + +.-Color-Black, +.-Color-Faint-Black, +.-Color-Bold-Black { + color: var(--ansi-black); +} + +.-Color-Faint { + opacity: 0.5; +} + +.-Color-Bold { + font-weight: bold; +} + +.-Color-BGBlack, +.-Color-Black-BGBlack, +.-Color-Blue-BGBlack, +.-Color-Bold-BGBlack, +.-Color-Bold-Black-BGBlack, +.-Color-Bold-Green-BGBlack, +.-Color-Bold-Cyan-BGBlack, +.-Color-Bold-Blue-BGBlack, +.-Color-Bold-Magenta-BGBlack, +.-Color-Bold-Red-BGBlack, +.-Color-Bold-White-BGBlack, +.-Color-Bold-Yellow-BGBlack, +.-Color-Cyan-BGBlack, +.-Color-Green-BGBlack, +.-Color-Magenta-BGBlack, +.-Color-Red-BGBlack, +.-Color-White-BGBlack, +.-Color-Yellow-BGBlack { + background-color: var(--ansi-black); +} + +.-Color-BGRed, +.-Color-Black-BGRed, +.-Color-Blue-BGRed, +.-Color-Bold-BGRed, +.-Color-Bold-Black-BGRed, +.-Color-Bold-Green-BGRed, +.-Color-Bold-Cyan-BGRed, +.-Color-Bold-Blue-BGRed, +.-Color-Bold-Magenta-BGRed, +.-Color-Bold-Red-BGRed, +.-Color-Bold-White-BGRed, +.-Color-Bold-Yellow-BGRed, +.-Color-Cyan-BGRed, +.-Color-Green-BGRed, +.-Color-Magenta-BGRed, +.-Color-Red-BGRed, +.-Color-White-BGRed, +.-Color-Yellow-BGRed { + background-color: var(--ansi-red); +} + +.-Color-BGGreen, +.-Color-Black-BGGreen, +.-Color-Blue-BGGreen, +.-Color-Bold-BGGreen, +.-Color-Bold-Black-BGGreen, +.-Color-Bold-Green-BGGreen, +.-Color-Bold-Cyan-BGGreen, +.-Color-Bold-Blue-BGGreen, +.-Color-Bold-Magenta-BGGreen, +.-Color-Bold-Red-BGGreen, +.-Color-Bold-White-BGGreen, +.-Color-Bold-Yellow-BGGreen, +.-Color-Cyan-BGGreen, +.-Color-Green-BGGreen, +.-Color-Magenta-BGGreen, +.-Color-Red-BGGreen, +.-Color-White-BGGreen, +.-Color-Yellow-BGGreen { + background-color: var(--ansi-green); +} + +.-Color-BGYellow, +.-Color-Black-BGYellow, +.-Color-Blue-BGYellow, +.-Color-Bold-BGYellow, +.-Color-Bold-Black-BGYellow, +.-Color-Bold-Green-BGYellow, +.-Color-Bold-Cyan-BGYellow, +.-Color-Bold-Blue-BGYellow, +.-Color-Bold-Magenta-BGYellow, +.-Color-Bold-Red-BGYellow, +.-Color-Bold-White-BGYellow, +.-Color-Bold-Yellow-BGYellow, +.-Color-Cyan-BGYellow, +.-Color-Green-BGYellow, +.-Color-Magenta-BGYellow, +.-Color-Red-BGYellow, +.-Color-White-BGYellow, +.-Color-Yellow-BGYellow { + background-color: var(--ansi-yellow); +} + +.-Color-BGBlue, +.-Color-Black-BGBlue, +.-Color-Blue-BGBlue, +.-Color-Bold-BGBlue, +.-Color-Bold-Black-BGBlue, +.-Color-Bold-Green-BGBlue, +.-Color-Bold-Cyan-BGBlue, +.-Color-Bold-Blue-BGBlue, +.-Color-Bold-Magenta-BGBlue, +.-Color-Bold-Red-BGBlue, +.-Color-Bold-White-BGBlue, +.-Color-Bold-Yellow-BGBlue, +.-Color-Cyan-BGBlue, +.-Color-Green-BGBlue, +.-Color-Magenta-BGBlue, +.-Color-Red-BGBlue, +.-Color-White-BGBlue, +.-Color-Yellow-BGBlue { + background-color: var(--ansi-blue); +} + +.-Color-BGMagenta, +.-Color-Black-BGMagenta, +.-Color-Blue-BGMagenta, +.-Color-Bold-BGMagenta, +.-Color-Bold-Black-BGMagenta, +.-Color-Bold-Green-BGMagenta, +.-Color-Bold-Cyan-BGMagenta, +.-Color-Bold-Blue-BGMagenta, +.-Color-Bold-Magenta-BGMagenta, +.-Color-Bold-Red-BGMagenta, +.-Color-Bold-White-BGMagenta, +.-Color-Bold-Yellow-BGMagenta, +.-Color-Cyan-BGMagenta, +.-Color-Green-BGMagenta, +.-Color-Magenta-BGMagenta, +.-Color-Red-BGMagenta, +.-Color-White-BGMagenta, +.-Color-Yellow-BGMagenta { + background-color: var(--ansi-magenta); +} + +.-Color-BGCyan, +.-Color-Black-BGCyan, +.-Color-Blue-BGCyan, +.-Color-Bold-BGCyan, +.-Color-Bold-Black-BGCyan, +.-Color-Bold-Green-BGCyan, +.-Color-Bold-Cyan-BGCyan, +.-Color-Bold-Blue-BGCyan, +.-Color-Bold-Magenta-BGCyan, +.-Color-Bold-Red-BGCyan, +.-Color-Bold-White-BGCyan, +.-Color-Bold-Yellow-BGCyan, +.-Color-Cyan-BGCyan, +.-Color-Green-BGCyan, +.-Color-Magenta-BGCyan, +.-Color-Red-BGCyan, +.-Color-White-BGCyan, +.-Color-Yellow-BGCyan { + background-color: var(--ansi-cyan); +} + +.-Color-BGWhite, +.-Color-Black-BGWhite, +.-Color-Blue-BGWhite, +.-Color-Bold-BGWhite, +.-Color-Bold-Black-BGWhite, +.-Color-Bold-Green-BGWhite, +.-Color-Bold-Cyan-BGWhite, +.-Color-Bold-Blue-BGWhite, +.-Color-Bold-Magenta-BGWhite, +.-Color-Bold-Red-BGWhite, +.-Color-Bold-White-BGWhite, +.-Color-Bold-Yellow-BGWhite, +.-Color-Cyan-BGWhite, +.-Color-Green-BGWhite, +.-Color-Magenta-BGWhite, +.-Color-Red-BGWhite, +.-Color-White-BGWhite, +.-Color-Yellow-BGWhite { + background-color: var(--ansi-white); +} + +.-Color-Black, +.-Color-Bold-Black, +.-Color-Black-BGBlack, +.-Color-Bold-Black-BGBlack, +.-Color-Black-BGGreen, +.-Color-Red-BGRed, +.-Color-Bold-Red-BGRed, +.-Color-Bold-Blue-BGBlue, +.-Color-Blue-BGBlue { + text-shadow: 0 0 1px var(--ansi-white); +} + +.-Color-Bold-Cyan-BGCyan, +.-Color-Bold-Magenta-BGMagenta, +.-Color-Bold-White, +.-Color-Bold-Yellow-BGYellow, +.-Color-Bold-Green-BGGreen, +.-Color-Cyan-BGCyan, +.-Color-Cyan-BGGreen, +.-Color-Green-BGCyan, +.-Color-Green-BGGreen, +.-Color-Magenta-BGMagenta, +.-Color-White, +.-Color-White-BGWhite, +.-Color-Yellow-BGYellow { + text-shadow: 0 0 1px var(--ansi-black); +} \ No newline at end of file diff --git a/src/markdown_exec/mkdocs_plugin.py b/src/markdown_exec/mkdocs_plugin.py index f21ea42..1227eb0 100644 --- a/src/markdown_exec/mkdocs_plugin.py +++ b/src/markdown_exec/mkdocs_plugin.py @@ -1,13 +1,30 @@ """This module contains an optional plugin for MkDocs.""" +from __future__ import annotations + import logging +import os +from pathlib import Path +from typing import TYPE_CHECKING from mkdocs.config import Config, config_options from mkdocs.plugins import BasePlugin +from mkdocs.utils import write_file from markdown_exec import formatter, formatters, validator from markdown_exec.logger import patch_loggers +if TYPE_CHECKING: + from jinja2 import Environment + from mkdocs.structure.files import Files + +try: + __import__("pygments_ansi_color") +except ImportError: + ansi_ok = False +else: + ansi_ok = True + class _LoggerAdapter(logging.LoggerAdapter): def __init__(self, prefix, logger): @@ -33,7 +50,6 @@ class MarkdownExecPlugin(BasePlugin): def on_config(self, config: Config, **kwargs) -> Config: # noqa: D102 self.languages = self.config["languages"] - mdx_configs = config.setdefault("mdx_configs", {}) superfences = mdx_configs.setdefault("pymdownx.superfences", {}) custom_fences = superfences.setdefault("custom_fences", []) @@ -47,3 +63,10 @@ def on_config(self, config: Config, **kwargs) -> Config: # noqa: D102 } ) return config + + def on_env(self, env: Environment, *, config: Config, files: Files) -> Environment | None: # noqa: D102 + css_filename = "assets/_markdown_exec_ansi.css" + css_content = Path(__file__).parent.joinpath("ansi.css").read_text() + write_file(css_content.encode("utf-8"), os.path.join(config["site_dir"], css_filename)) + config["extra_css"].insert(0, css_filename) + return env