superfences-ps1 0.1.0__tar.gz
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.
- superfences_ps1-0.1.0/LICENSE.md +21 -0
- superfences_ps1-0.1.0/PKG-INFO +133 -0
- superfences_ps1-0.1.0/README.md +106 -0
- superfences_ps1-0.1.0/pyproject.toml +93 -0
- superfences_ps1-0.1.0/src/superfences_ps1/__init__.py +7 -0
- superfences_ps1-0.1.0/src/superfences_ps1/plugin.py +163 -0
- superfences_ps1-0.1.0/src/superfences_ps1/py.typed +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) `2026` `Tucker Beck`
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: superfences-ps1
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MkDocs plugin that adds a configurable shell-ps1 code fence with copy-safe prompt character
|
|
5
|
+
Keywords: mkdocs,mkdocs-plugin,plugin,pymdownx,superfences,shell,ps1,pygments,documentation,documentation-tools,mkdocs-material
|
|
6
|
+
Author: Tucker Beck
|
|
7
|
+
Author-email: Tucker Beck <tucker.beck@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE.md
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Framework :: MkDocs
|
|
17
|
+
Classifier: Topic :: Documentation
|
|
18
|
+
Requires-Dist: mkdocs>=1.5.0
|
|
19
|
+
Requires-Dist: pymdown-extensions>=10.0
|
|
20
|
+
Requires-Dist: pygments>=2.16
|
|
21
|
+
Requires-Python: >=3.12
|
|
22
|
+
Project-URL: homepage, https://github.com/dusktreader/superfences-ps1
|
|
23
|
+
Project-URL: source, https://github.com/dusktreader/superfences-ps1
|
|
24
|
+
Project-URL: changelog, https://github.com/dusktreader/superfences-ps1/blob/main/CHANGELOG.md
|
|
25
|
+
Project-URL: issues, https://github.com/dusktreader/superfences-ps1/issues
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# superfences-ps1
|
|
29
|
+
|
|
30
|
+
MkDocs plugin that adds a configurable shell-prompt SuperFences fence with copy-safe PS1 prompt characters.
|
|
31
|
+
|
|
32
|
+
The plugin registers a custom [SuperFences](https://facelessuser.github.io/pymdown-extensions/extensions/superfences/)
|
|
33
|
+
fence (default name `shell-ps1`) that prepends a configurable PS1 prompt character to every command line. The prompt is
|
|
34
|
+
rendered visibly in the documentation but is **never copied to the clipboard** and is **never selected by mouse** —
|
|
35
|
+
the plugin injects a `data-copy` attribute with the raw source text so Material for MkDocs' copy button bypasses the
|
|
36
|
+
prompt spans entirely, and `user-select: none` CSS is injected to prevent mouse selection of the prompt.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```shell
|
|
42
|
+
pip install superfences-ps1
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or with uv:
|
|
46
|
+
|
|
47
|
+
```shell
|
|
48
|
+
uv add superfences-ps1
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
## Requirements
|
|
53
|
+
|
|
54
|
+
- `pymdownx.superfences` must be listed under `markdown_extensions` in `mkdocs.yaml`
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
### `mkdocs.yaml` configuration
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
plugins:
|
|
64
|
+
- superfences-ps1:
|
|
65
|
+
fence_name: shell-ps1 # fence language identifier (default: shell-ps1)
|
|
66
|
+
prompt_char: "$" # PS1 prompt character prepended to each line (default: $)
|
|
67
|
+
prompt_color: "#5fb3b3" # optional CSS color for the prompt character
|
|
68
|
+
|
|
69
|
+
markdown_extensions:
|
|
70
|
+
- pymdownx.superfences
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
### Writing shell fences
|
|
75
|
+
|
|
76
|
+
Use the configured `fence_name` in your markdown:
|
|
77
|
+
|
|
78
|
+
````markdown
|
|
79
|
+
```shell-ps1
|
|
80
|
+
echo "Hello, world!"
|
|
81
|
+
ls -la
|
|
82
|
+
```
|
|
83
|
+
````
|
|
84
|
+
|
|
85
|
+
The plugin prepends the prompt character to each non-empty line before passing the content to Pygments. The rendered
|
|
86
|
+
output shows the prompt visually, but the copy button and mouse selection only grab the commands themselves.
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
## Configuration options
|
|
90
|
+
|
|
91
|
+
| Option | Type | Default | Description |
|
|
92
|
+
|----------------|-----------------|-------------|----------------------------------------------------------|
|
|
93
|
+
| `fence_name` | `str` | `shell-ps1` | The fence language identifier used in markdown |
|
|
94
|
+
| `prompt_char` | `str` | `$` | The PS1 prompt character prepended to each command line |
|
|
95
|
+
| `prompt_color` | `str` or `None` | `None` | Optional CSS color value for the rendered prompt |
|
|
96
|
+
|
|
97
|
+
> [!NOTE]
|
|
98
|
+
> `prompt_char` must be one of `$`, `%`, or `#` — the only characters Pygments' `BashSessionLexer` recognises as
|
|
99
|
+
> prompt markers, emitting them as `Generic.Prompt` (`.gp`) tokens. Any other value will cause a `PluginError` at
|
|
100
|
+
> build time. `>` in particular is the continuation prompt (`_ps2`) and causes the entire line to be tokenised as
|
|
101
|
+
> `Generic.Output` (`.go`), which would treat the command text as output.
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
## How it works
|
|
105
|
+
|
|
106
|
+
1. On `on_config`, the plugin validates that `pymdownx.superfences` is present and injects a custom fence formatter
|
|
107
|
+
into `mdx_configs["pymdownx.superfences"]["custom_fences"]`.
|
|
108
|
+
2. When MkDocs processes a page containing a fenced code block with the configured `fence_name`, SuperFences calls
|
|
109
|
+
the injected formatter.
|
|
110
|
+
3. The formatter prepends `<prompt_char> ` to every non-empty line, then passes the result to Pygments using the
|
|
111
|
+
`console` (`BashSessionLexer`) lexer. Pygments wraps the prompt in `<span class="gp">` and the command in
|
|
112
|
+
additional token spans.
|
|
113
|
+
4. The formatter injects a `data-copy` attribute on the wrapper `<div>` containing the raw (un-prompted) source.
|
|
114
|
+
Material for MkDocs' copy button reads `data-copy` in preference to `innerText`, so the prompt is never included
|
|
115
|
+
in clipboard content.
|
|
116
|
+
5. On `on_post_page`, the plugin injects a `<style>` block into each page's `<head>` with `user-select: none` on
|
|
117
|
+
`.gp` spans, preventing mouse selection of the prompt. If `prompt_color` is configured, the color rule is
|
|
118
|
+
combined into the same `<style>` block.
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
## Development
|
|
122
|
+
|
|
123
|
+
```shell
|
|
124
|
+
git clone https://github.com/dusktreader/superfences-ps1
|
|
125
|
+
cd superfences-ps1
|
|
126
|
+
uv sync
|
|
127
|
+
make qa/full
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
MIT — see [LICENSE.md](LICENSE.md).
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# superfences-ps1
|
|
2
|
+
|
|
3
|
+
MkDocs plugin that adds a configurable shell-prompt SuperFences fence with copy-safe PS1 prompt characters.
|
|
4
|
+
|
|
5
|
+
The plugin registers a custom [SuperFences](https://facelessuser.github.io/pymdown-extensions/extensions/superfences/)
|
|
6
|
+
fence (default name `shell-ps1`) that prepends a configurable PS1 prompt character to every command line. The prompt is
|
|
7
|
+
rendered visibly in the documentation but is **never copied to the clipboard** and is **never selected by mouse** —
|
|
8
|
+
the plugin injects a `data-copy` attribute with the raw source text so Material for MkDocs' copy button bypasses the
|
|
9
|
+
prompt spans entirely, and `user-select: none` CSS is injected to prevent mouse selection of the prompt.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```shell
|
|
15
|
+
pip install superfences-ps1
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or with uv:
|
|
19
|
+
|
|
20
|
+
```shell
|
|
21
|
+
uv add superfences-ps1
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## Requirements
|
|
26
|
+
|
|
27
|
+
- `pymdownx.superfences` must be listed under `markdown_extensions` in `mkdocs.yaml`
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### `mkdocs.yaml` configuration
|
|
34
|
+
|
|
35
|
+
```yaml
|
|
36
|
+
plugins:
|
|
37
|
+
- superfences-ps1:
|
|
38
|
+
fence_name: shell-ps1 # fence language identifier (default: shell-ps1)
|
|
39
|
+
prompt_char: "$" # PS1 prompt character prepended to each line (default: $)
|
|
40
|
+
prompt_color: "#5fb3b3" # optional CSS color for the prompt character
|
|
41
|
+
|
|
42
|
+
markdown_extensions:
|
|
43
|
+
- pymdownx.superfences
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
### Writing shell fences
|
|
48
|
+
|
|
49
|
+
Use the configured `fence_name` in your markdown:
|
|
50
|
+
|
|
51
|
+
````markdown
|
|
52
|
+
```shell-ps1
|
|
53
|
+
echo "Hello, world!"
|
|
54
|
+
ls -la
|
|
55
|
+
```
|
|
56
|
+
````
|
|
57
|
+
|
|
58
|
+
The plugin prepends the prompt character to each non-empty line before passing the content to Pygments. The rendered
|
|
59
|
+
output shows the prompt visually, but the copy button and mouse selection only grab the commands themselves.
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
## Configuration options
|
|
63
|
+
|
|
64
|
+
| Option | Type | Default | Description |
|
|
65
|
+
|----------------|-----------------|-------------|----------------------------------------------------------|
|
|
66
|
+
| `fence_name` | `str` | `shell-ps1` | The fence language identifier used in markdown |
|
|
67
|
+
| `prompt_char` | `str` | `$` | The PS1 prompt character prepended to each command line |
|
|
68
|
+
| `prompt_color` | `str` or `None` | `None` | Optional CSS color value for the rendered prompt |
|
|
69
|
+
|
|
70
|
+
> [!NOTE]
|
|
71
|
+
> `prompt_char` must be one of `$`, `%`, or `#` — the only characters Pygments' `BashSessionLexer` recognises as
|
|
72
|
+
> prompt markers, emitting them as `Generic.Prompt` (`.gp`) tokens. Any other value will cause a `PluginError` at
|
|
73
|
+
> build time. `>` in particular is the continuation prompt (`_ps2`) and causes the entire line to be tokenised as
|
|
74
|
+
> `Generic.Output` (`.go`), which would treat the command text as output.
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
## How it works
|
|
78
|
+
|
|
79
|
+
1. On `on_config`, the plugin validates that `pymdownx.superfences` is present and injects a custom fence formatter
|
|
80
|
+
into `mdx_configs["pymdownx.superfences"]["custom_fences"]`.
|
|
81
|
+
2. When MkDocs processes a page containing a fenced code block with the configured `fence_name`, SuperFences calls
|
|
82
|
+
the injected formatter.
|
|
83
|
+
3. The formatter prepends `<prompt_char> ` to every non-empty line, then passes the result to Pygments using the
|
|
84
|
+
`console` (`BashSessionLexer`) lexer. Pygments wraps the prompt in `<span class="gp">` and the command in
|
|
85
|
+
additional token spans.
|
|
86
|
+
4. The formatter injects a `data-copy` attribute on the wrapper `<div>` containing the raw (un-prompted) source.
|
|
87
|
+
Material for MkDocs' copy button reads `data-copy` in preference to `innerText`, so the prompt is never included
|
|
88
|
+
in clipboard content.
|
|
89
|
+
5. On `on_post_page`, the plugin injects a `<style>` block into each page's `<head>` with `user-select: none` on
|
|
90
|
+
`.gp` spans, preventing mouse selection of the prompt. If `prompt_color` is configured, the color rule is
|
|
91
|
+
combined into the same `<style>` block.
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
## Development
|
|
95
|
+
|
|
96
|
+
```shell
|
|
97
|
+
git clone https://github.com/dusktreader/superfences-ps1
|
|
98
|
+
cd superfences-ps1
|
|
99
|
+
uv sync
|
|
100
|
+
make qa/full
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT — see [LICENSE.md](LICENSE.md).
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "superfences-ps1"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MkDocs plugin that adds a configurable shell-ps1 code fence with copy-safe prompt character"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Tucker Beck", email = "tucker.beck@gmail.com"}
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
license = "MIT"
|
|
10
|
+
license-files = ["LICENSE.md"]
|
|
11
|
+
keywords = [
|
|
12
|
+
"mkdocs",
|
|
13
|
+
"mkdocs-plugin",
|
|
14
|
+
"plugin",
|
|
15
|
+
"pymdownx",
|
|
16
|
+
"superfences",
|
|
17
|
+
"shell",
|
|
18
|
+
"ps1",
|
|
19
|
+
"pygments",
|
|
20
|
+
"documentation",
|
|
21
|
+
"documentation-tools",
|
|
22
|
+
"mkdocs-material",
|
|
23
|
+
]
|
|
24
|
+
classifiers = [
|
|
25
|
+
"Development Status :: 3 - Alpha",
|
|
26
|
+
"Intended Audience :: Developers",
|
|
27
|
+
"License :: OSI Approved :: MIT License",
|
|
28
|
+
"Programming Language :: Python :: 3",
|
|
29
|
+
"Programming Language :: Python :: 3.12",
|
|
30
|
+
"Programming Language :: Python :: 3.13",
|
|
31
|
+
"Framework :: MkDocs",
|
|
32
|
+
"Topic :: Documentation",
|
|
33
|
+
]
|
|
34
|
+
requires-python = ">=3.12"
|
|
35
|
+
dependencies = [
|
|
36
|
+
"mkdocs>=1.5.0",
|
|
37
|
+
"pymdown-extensions>=10.0",
|
|
38
|
+
"pygments>=2.16",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.entry-points."mkdocs.plugins"]
|
|
42
|
+
superfences-ps1 = "superfences_ps1.plugin:ShellPromptPlugin"
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
homepage = "https://github.com/dusktreader/superfences-ps1"
|
|
46
|
+
source = "https://github.com/dusktreader/superfences-ps1"
|
|
47
|
+
changelog = "https://github.com/dusktreader/superfences-ps1/blob/main/CHANGELOG.md"
|
|
48
|
+
issues = "https://github.com/dusktreader/superfences-ps1/issues"
|
|
49
|
+
|
|
50
|
+
[tool.uv]
|
|
51
|
+
package = true
|
|
52
|
+
|
|
53
|
+
[dependency-groups]
|
|
54
|
+
dev = [
|
|
55
|
+
"mkdocs-material~=9.6",
|
|
56
|
+
"pre-commit>=4.5.1",
|
|
57
|
+
"pyclean~=3.1",
|
|
58
|
+
"pygments~=2.19",
|
|
59
|
+
"pytest~=8.3",
|
|
60
|
+
"pytest-cov~=6.0",
|
|
61
|
+
"pytest-pretty~=1.2",
|
|
62
|
+
"pytest-random-order~=1.1",
|
|
63
|
+
"ruff~=0.11",
|
|
64
|
+
"ty>=0.0.0a1",
|
|
65
|
+
"types-markdown>=3.10.2.20260211",
|
|
66
|
+
"typos~=1.31",
|
|
67
|
+
"yamllint>=1.38.0",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
[build-system]
|
|
71
|
+
requires = ["uv_build>=0.10.9,<0.11.0"]
|
|
72
|
+
build-backend = "uv_build"
|
|
73
|
+
|
|
74
|
+
[tool.ty.src]
|
|
75
|
+
include = ["src", "tests"]
|
|
76
|
+
|
|
77
|
+
[tool.pytest.ini_options]
|
|
78
|
+
addopts = [
|
|
79
|
+
"--random-order",
|
|
80
|
+
"--cov=src/superfences_ps1",
|
|
81
|
+
"--cov-report=term-missing",
|
|
82
|
+
"--cov-fail-under=85",
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
[tool.ruff]
|
|
86
|
+
line-length = 120
|
|
87
|
+
src = ["src/superfences_ps1", "tests"]
|
|
88
|
+
|
|
89
|
+
[tool.ruff.format]
|
|
90
|
+
docstring-code-format = true
|
|
91
|
+
|
|
92
|
+
[tool.typos.default]
|
|
93
|
+
extend-ignore-identifiers-re = []
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""
|
|
2
|
+
superfences-ps1 — MkDocs plugin that registers a SuperFences shell-prompt fence.
|
|
3
|
+
|
|
4
|
+
The fence prepends a configurable prompt character to each command line so that
|
|
5
|
+
Pygments emits a ``Generic.Prompt`` (``.gp``) token. Material for MkDocs strips
|
|
6
|
+
``.gp`` spans from the clipboard copy, so the prompt is visible but never copied.
|
|
7
|
+
"""
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import html
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from typing import override
|
|
4
|
+
|
|
5
|
+
import markdown
|
|
6
|
+
from mkdocs.config import config_options
|
|
7
|
+
from mkdocs.config.base import Config
|
|
8
|
+
from mkdocs.config.config_options import Optional, Type
|
|
9
|
+
from mkdocs.config.defaults import MkDocsConfig
|
|
10
|
+
from mkdocs.exceptions import PluginError
|
|
11
|
+
from mkdocs.plugins import BasePlugin, get_plugin_logger
|
|
12
|
+
from mkdocs.structure.pages import Page
|
|
13
|
+
from pygments import highlight
|
|
14
|
+
from pygments.formatters.html import HtmlFormatter
|
|
15
|
+
from pygments.lexers import get_lexer_by_name
|
|
16
|
+
|
|
17
|
+
log = get_plugin_logger(__name__)
|
|
18
|
+
|
|
19
|
+
DEFAULT_FENCE_NAME = "shell-ps1"
|
|
20
|
+
DEFAULT_PROMPT_CHAR = "$"
|
|
21
|
+
VALID_PROMPT_CHARS = ("$", "%", "#")
|
|
22
|
+
|
|
23
|
+
_SUPERFENCES_KEY = "pymdownx.superfences"
|
|
24
|
+
|
|
25
|
+
# Type alias for a superfences custom-fence formatter callable.
|
|
26
|
+
# The signature mirrors pymdownx.superfences.fence_code_format.
|
|
27
|
+
FormatterFn = Callable[..., str]
|
|
28
|
+
|
|
29
|
+
# Type alias for the loosely-typed superfences configuration dicts that
|
|
30
|
+
# come back from MkDocs' mdx_configs mapping (no stubs available).
|
|
31
|
+
_FenceDict = dict[str, object]
|
|
32
|
+
_SuperfencesCfg = dict[str, object]
|
|
33
|
+
_MdxConfigs = dict[str, object]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ShellPromptConfig(Config):
|
|
37
|
+
fence_name: Type[str] = config_options.Type(str, default=DEFAULT_FENCE_NAME)
|
|
38
|
+
prompt_char: Type[str] = config_options.Type(str, default=DEFAULT_PROMPT_CHAR)
|
|
39
|
+
prompt_color: Optional[str] = config_options.Optional(config_options.Type(str))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ShellPromptPlugin(BasePlugin[ShellPromptConfig]):
|
|
43
|
+
"""
|
|
44
|
+
MkDocs plugin that adds a configurable shell-prompt SuperFences fence.
|
|
45
|
+
|
|
46
|
+
The fence prepends `<prompt_char> ` to every non-empty line so that
|
|
47
|
+
Pygments' `BashSessionLexer` (`console`) emits a `Generic.Prompt`
|
|
48
|
+
token (`.gp` CSS class) for the prompt portion. A `data-copy` attribute
|
|
49
|
+
is injected on the wrapper `<div>` containing the raw (un-prompted) source
|
|
50
|
+
text so that Material for MkDocs' copy button uses that value instead of
|
|
51
|
+
reading `innerText`, keeping the prompt out of the clipboard.
|
|
52
|
+
|
|
53
|
+
Requires `pymdownx.superfences` in `markdown_extensions`.
|
|
54
|
+
|
|
55
|
+
Configuration (`mkdocs.yaml`):
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
plugins:
|
|
59
|
+
- superfences-ps1:
|
|
60
|
+
fence_name: shell # fence language identifier (default: shell)
|
|
61
|
+
prompt_char: "$" # prompt character prepended to each line (default: $)
|
|
62
|
+
prompt_color: "#89b0c2" # CSS color for the prompt character (default: unset)
|
|
63
|
+
```
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def _make_formatter(self, prompt_char: str) -> FormatterFn:
|
|
67
|
+
"""Return a SuperFences-compatible formatter function for the given prompt char."""
|
|
68
|
+
|
|
69
|
+
def formatter(
|
|
70
|
+
source: str,
|
|
71
|
+
_language: str,
|
|
72
|
+
css_class: str,
|
|
73
|
+
_options: dict[str, object],
|
|
74
|
+
_md: markdown.Markdown,
|
|
75
|
+
**_kwargs: object,
|
|
76
|
+
) -> str:
|
|
77
|
+
prompted = "\n".join(f"{prompt_char} {line}" if line.strip() else line for line in source.splitlines())
|
|
78
|
+
lexer = get_lexer_by_name("console", stripall=False)
|
|
79
|
+
fmt = HtmlFormatter(
|
|
80
|
+
cssclass=f"highlight {css_class}".strip(),
|
|
81
|
+
wrapcode=True,
|
|
82
|
+
)
|
|
83
|
+
rendered: str = highlight(prompted, lexer, fmt)
|
|
84
|
+
# Inject data-copy on the wrapper <div> with the raw (un-prompted) source
|
|
85
|
+
# text. Material for MkDocs' copy button reads this attribute in preference
|
|
86
|
+
# to innerText, so the prompt character is never included in the clipboard.
|
|
87
|
+
copy_text = html.escape(source.rstrip("\n"), quote=True)
|
|
88
|
+
return rendered.replace("<div ", f'<div data-copy="{copy_text}" ', 1)
|
|
89
|
+
|
|
90
|
+
return formatter
|
|
91
|
+
|
|
92
|
+
@override
|
|
93
|
+
def on_config(self, config: MkDocsConfig) -> MkDocsConfig | None:
|
|
94
|
+
"""Inject the shell-prompt fence into the superfences custom_fences list."""
|
|
95
|
+
fence_name = self.config.fence_name
|
|
96
|
+
prompt_char = self.config.prompt_char
|
|
97
|
+
|
|
98
|
+
ext_names: set[str] = set()
|
|
99
|
+
for ext in config.get("markdown_extensions", []):
|
|
100
|
+
if isinstance(ext, str):
|
|
101
|
+
ext_names.add(ext)
|
|
102
|
+
elif isinstance(ext, dict):
|
|
103
|
+
ext_names.update(ext.keys())
|
|
104
|
+
|
|
105
|
+
if prompt_char not in VALID_PROMPT_CHARS:
|
|
106
|
+
raise PluginError(
|
|
107
|
+
f"[superfences-ps1] Invalid prompt_char {prompt_char!r}. "
|
|
108
|
+
f"Must be one of: {', '.join(repr(c) for c in VALID_PROMPT_CHARS)}. "
|
|
109
|
+
"Other characters are not recognised as prompts by Pygments' BashSessionLexer."
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
if _SUPERFENCES_KEY not in ext_names:
|
|
113
|
+
raise PluginError(
|
|
114
|
+
f"[superfences-ps1] '{_SUPERFENCES_KEY}' must be listed under 'markdown_extensions' in mkdocs.yaml."
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
mdx_configs: _MdxConfigs = config.get("mdx_configs", {})
|
|
118
|
+
superfences_cfg: _SuperfencesCfg = mdx_configs.get(_SUPERFENCES_KEY, {})
|
|
119
|
+
custom_fences: list[_FenceDict] = superfences_cfg.get("custom_fences", [])
|
|
120
|
+
|
|
121
|
+
existing_names = {f["name"] for f in custom_fences}
|
|
122
|
+
if fence_name in existing_names:
|
|
123
|
+
log.warning(
|
|
124
|
+
"Fence name '%s' is already registered in superfences; skipping injection.",
|
|
125
|
+
fence_name,
|
|
126
|
+
)
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
custom_fences.append(
|
|
130
|
+
{
|
|
131
|
+
"name": fence_name,
|
|
132
|
+
"class": fence_name,
|
|
133
|
+
"format": self._make_formatter(prompt_char),
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
superfences_cfg["custom_fences"] = custom_fences
|
|
137
|
+
mdx_configs[_SUPERFENCES_KEY] = superfences_cfg
|
|
138
|
+
config["mdx_configs"] = mdx_configs
|
|
139
|
+
|
|
140
|
+
log.debug(
|
|
141
|
+
"Registered shell-prompt fence '%s' with prompt char '%s'.",
|
|
142
|
+
fence_name,
|
|
143
|
+
prompt_char,
|
|
144
|
+
)
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
@override
|
|
148
|
+
def on_post_page(self, output: str, /, *, page: Page, config: MkDocsConfig) -> str:
|
|
149
|
+
"""
|
|
150
|
+
Inject prompt CSS into each page's <head>.
|
|
151
|
+
|
|
152
|
+
Always injects `user-select: none` on `.gp` so the prompt character is excluded from mouse text selection.
|
|
153
|
+
Also injects a `color` rule when `prompt_color` is configured.
|
|
154
|
+
"""
|
|
155
|
+
if "</head>" not in output:
|
|
156
|
+
return output
|
|
157
|
+
|
|
158
|
+
prompt_color = self.config.prompt_color
|
|
159
|
+
rules = "user-select: none;"
|
|
160
|
+
if prompt_color:
|
|
161
|
+
rules += f" color: {prompt_color};"
|
|
162
|
+
style = f"<style>.highlight .gp {{ {rules} }}</style>"
|
|
163
|
+
return output.replace("</head>", f"{style}\n</head>", 1)
|
|
File without changes
|