sphinx-pyxel 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.
- sphinx_pyxel-0.1.0/LICENSE +21 -0
- sphinx_pyxel-0.1.0/PKG-INFO +111 -0
- sphinx_pyxel-0.1.0/README.md +94 -0
- sphinx_pyxel-0.1.0/pyproject.toml +25 -0
- sphinx_pyxel-0.1.0/setup.cfg +4 -0
- sphinx_pyxel-0.1.0/sphinx_pyxel/__init__.py +203 -0
- sphinx_pyxel-0.1.0/sphinx_pyxel.egg-info/PKG-INFO +111 -0
- sphinx_pyxel-0.1.0/sphinx_pyxel.egg-info/SOURCES.txt +9 -0
- sphinx_pyxel-0.1.0/sphinx_pyxel.egg-info/dependency_links.txt +1 -0
- sphinx_pyxel-0.1.0/sphinx_pyxel.egg-info/requires.txt +1 -0
- sphinx_pyxel-0.1.0/sphinx_pyxel.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 sphinx-pyxel contributors
|
|
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,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sphinx-pyxel
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Sphinx extension to embed Pyxel apps in HTML docs
|
|
5
|
+
Author: sphinx-pyxel
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/tetsuokoyama/sphinx-pyxel
|
|
8
|
+
Keywords: sphinx,pyxel,extension,sphinx-extension
|
|
9
|
+
Classifier: Framework :: Sphinx :: Extension
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: Sphinx>=5
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# sphinx-pyxel
|
|
19
|
+
|
|
20
|
+
A [Sphinx](https://www.sphinx-doc.org/) extension that embeds [Pyxel](https://github.com/kitao/pyxel) apps directly in your HTML documentation using the official Pyxel web runtime.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install sphinx-pyxel
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or from source:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
Add the extension to your `conf.py`:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
extensions = ["sphinx_pyxel"]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Then use the `pyxel` directive in any reStructuredText document:
|
|
43
|
+
|
|
44
|
+
```rst
|
|
45
|
+
.. pyxel:: examples/01_hello_pyxel.py
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
For a packaged app (`.pyxapp`) with gamepad support:
|
|
49
|
+
|
|
50
|
+
```rst
|
|
51
|
+
.. pyxel:: examples/30sec_of_daylight.pyxapp
|
|
52
|
+
:mode: play
|
|
53
|
+
:gamepad: enabled
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
If your app loads external resources, copy them next to it:
|
|
57
|
+
|
|
58
|
+
```rst
|
|
59
|
+
.. pyxel:: my_game.py
|
|
60
|
+
:assets: my_game.pyxres, my_game_bank.json
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Options
|
|
64
|
+
|
|
65
|
+
| Option | Default | Description |
|
|
66
|
+
|------------|--------------------------------------|-------------------------------------------------------------------|
|
|
67
|
+
| `mode` | `run` for `.py`, `play` for `.pyxapp`| `run` (just runs) or `play` (player controls, gamepad support). |
|
|
68
|
+
| `root` | `.` (or rel path to `pyxel_root`) | Root path served relative to the HTML page. |
|
|
69
|
+
| `name` | basename of the argument | File name served by the runtime. |
|
|
70
|
+
| `gamepad` | unset | `enabled` or `disabled` (only meaningful for `play`). |
|
|
71
|
+
| `assets` | unset | Comma-separated extra files to copy next to the app. |
|
|
72
|
+
| `script` | jsdelivr wasm build | URL of the Pyxel web runtime script. |
|
|
73
|
+
| `height` | `480px` | CSS height of the inline app window. |
|
|
74
|
+
|
|
75
|
+
## How it works
|
|
76
|
+
|
|
77
|
+
During the build, the directive copies the referenced app file (and any
|
|
78
|
+
`assets`) into the output directory next to the generated HTML page, then emits
|
|
79
|
+
a `<pyxel-run>` (or `<pyxel-play>`) custom element plus the Pyxel web runtime
|
|
80
|
+
script tag. The app runs entirely in the browser — no Python is executed by
|
|
81
|
+
Sphinx.
|
|
82
|
+
|
|
83
|
+
The app renders **inline** at the location of the directive (not fullscreen):
|
|
84
|
+
the extension places a ``#pyxel-screen`` container there and overrides the
|
|
85
|
+
runtime's fullscreen CSS so the canvas fills that container. Set ``:height:``
|
|
86
|
+
to control the window size.
|
|
87
|
+
|
|
88
|
+
## Config value: `pyxel_root`
|
|
89
|
+
|
|
90
|
+
Set ``pyxel_root`` in ``conf.py`` to collect every app into one shared
|
|
91
|
+
directory under the HTML output instead of copying it next to each page that
|
|
92
|
+
references it. Each emitted ``root`` then points from the page back at the
|
|
93
|
+
shared directory, so an app reused across many pages is stored once.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
pyxel_root = "_pyxel"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Limitations
|
|
100
|
+
|
|
101
|
+
- The embedded app only renders in the **HTML** builder. Other builders (LaTeX,
|
|
102
|
+
man, text, etc.) emit a short note instead. This is expected: the Pyxel web
|
|
103
|
+
runtime is JavaScript and only runs in a browser.
|
|
104
|
+
- One file is copied next to each page that references it **unless**
|
|
105
|
+
``pyxel_root`` is set, in which case apps are collected into one shared
|
|
106
|
+
directory. Two apps with the same basename under ``pyxel_root`` would
|
|
107
|
+
collide; give one a distinct ``:name:`` if that happens.
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# sphinx-pyxel
|
|
2
|
+
|
|
3
|
+
A [Sphinx](https://www.sphinx-doc.org/) extension that embeds [Pyxel](https://github.com/kitao/pyxel) apps directly in your HTML documentation using the official Pyxel web runtime.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install sphinx-pyxel
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or from source:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install .
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
Add the extension to your `conf.py`:
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
extensions = ["sphinx_pyxel"]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then use the `pyxel` directive in any reStructuredText document:
|
|
26
|
+
|
|
27
|
+
```rst
|
|
28
|
+
.. pyxel:: examples/01_hello_pyxel.py
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
For a packaged app (`.pyxapp`) with gamepad support:
|
|
32
|
+
|
|
33
|
+
```rst
|
|
34
|
+
.. pyxel:: examples/30sec_of_daylight.pyxapp
|
|
35
|
+
:mode: play
|
|
36
|
+
:gamepad: enabled
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
If your app loads external resources, copy them next to it:
|
|
40
|
+
|
|
41
|
+
```rst
|
|
42
|
+
.. pyxel:: my_game.py
|
|
43
|
+
:assets: my_game.pyxres, my_game_bank.json
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Options
|
|
47
|
+
|
|
48
|
+
| Option | Default | Description |
|
|
49
|
+
|------------|--------------------------------------|-------------------------------------------------------------------|
|
|
50
|
+
| `mode` | `run` for `.py`, `play` for `.pyxapp`| `run` (just runs) or `play` (player controls, gamepad support). |
|
|
51
|
+
| `root` | `.` (or rel path to `pyxel_root`) | Root path served relative to the HTML page. |
|
|
52
|
+
| `name` | basename of the argument | File name served by the runtime. |
|
|
53
|
+
| `gamepad` | unset | `enabled` or `disabled` (only meaningful for `play`). |
|
|
54
|
+
| `assets` | unset | Comma-separated extra files to copy next to the app. |
|
|
55
|
+
| `script` | jsdelivr wasm build | URL of the Pyxel web runtime script. |
|
|
56
|
+
| `height` | `480px` | CSS height of the inline app window. |
|
|
57
|
+
|
|
58
|
+
## How it works
|
|
59
|
+
|
|
60
|
+
During the build, the directive copies the referenced app file (and any
|
|
61
|
+
`assets`) into the output directory next to the generated HTML page, then emits
|
|
62
|
+
a `<pyxel-run>` (or `<pyxel-play>`) custom element plus the Pyxel web runtime
|
|
63
|
+
script tag. The app runs entirely in the browser — no Python is executed by
|
|
64
|
+
Sphinx.
|
|
65
|
+
|
|
66
|
+
The app renders **inline** at the location of the directive (not fullscreen):
|
|
67
|
+
the extension places a ``#pyxel-screen`` container there and overrides the
|
|
68
|
+
runtime's fullscreen CSS so the canvas fills that container. Set ``:height:``
|
|
69
|
+
to control the window size.
|
|
70
|
+
|
|
71
|
+
## Config value: `pyxel_root`
|
|
72
|
+
|
|
73
|
+
Set ``pyxel_root`` in ``conf.py`` to collect every app into one shared
|
|
74
|
+
directory under the HTML output instead of copying it next to each page that
|
|
75
|
+
references it. Each emitted ``root`` then points from the page back at the
|
|
76
|
+
shared directory, so an app reused across many pages is stored once.
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
pyxel_root = "_pyxel"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Limitations
|
|
83
|
+
|
|
84
|
+
- The embedded app only renders in the **HTML** builder. Other builders (LaTeX,
|
|
85
|
+
man, text, etc.) emit a short note instead. This is expected: the Pyxel web
|
|
86
|
+
runtime is JavaScript and only runs in a browser.
|
|
87
|
+
- One file is copied next to each page that references it **unless**
|
|
88
|
+
``pyxel_root`` is set, in which case apps are collected into one shared
|
|
89
|
+
directory. Two apps with the same basename under ``pyxel_root`` would
|
|
90
|
+
collide; give one a distinct ``:name:`` if that happens.
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
MIT
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sphinx-pyxel"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Sphinx extension to embed Pyxel apps in HTML docs"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
authors = [{name = "sphinx-pyxel"}]
|
|
12
|
+
requires-python = ">=3.8"
|
|
13
|
+
dependencies = ["Sphinx>=5"]
|
|
14
|
+
keywords = ["sphinx", "pyxel", "extension", "sphinx-extension"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Framework :: Sphinx :: Extension",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://github.com/tetsuokoyama/sphinx-pyxel"
|
|
23
|
+
|
|
24
|
+
[tool.setuptools.packages.find]
|
|
25
|
+
include = ["sphinx_pyxel*"]
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Sphinx extension to embed Pyxel apps in HTML documentation.
|
|
2
|
+
|
|
3
|
+
Provides the ``pyxel`` directive, which copies a Pyxel app (a ``.py`` file or a
|
|
4
|
+
packaged ``.pyxapp``) into the build output and renders it with the official
|
|
5
|
+
Pyxel web runtime::
|
|
6
|
+
|
|
7
|
+
.. pyxel:: examples/01_hello_pyxel.py
|
|
8
|
+
|
|
9
|
+
.. pyxel:: examples/30sec_of_daylight.pyxapp
|
|
10
|
+
:mode: play
|
|
11
|
+
:gamepad: enabled
|
|
12
|
+
|
|
13
|
+
Options:
|
|
14
|
+
:mode: run | play (auto-detected from extension; ``run`` for .py,
|
|
15
|
+
``play`` for .pyxapp)
|
|
16
|
+
:root: root path served relative to the HTML page (default: copied
|
|
17
|
+
next to the page, so root is "."; when ``pyxel_root`` is set,
|
|
18
|
+
default is the relative path from the page to the shared dir)
|
|
19
|
+
:name: file name served (default: basename of the argument)
|
|
20
|
+
:gamepad: enabled | disabled (only meaningful for ``play``)
|
|
21
|
+
:assets: comma-separated extra files to copy next to the app
|
|
22
|
+
(e.g. ``mygame.pyxres``)
|
|
23
|
+
:script: URL of the Pyxel web runtime (default: the jsdelivr wasm build)
|
|
24
|
+
|
|
25
|
+
Config value:
|
|
26
|
+
pyxel_root -- directory under the HTML output where apps are collected
|
|
27
|
+
(e.g. ``_pyxel``). When set, each app is copied there once
|
|
28
|
+
instead of next to every page that references it, and the
|
|
29
|
+
emitted ``root`` points back at it from each page. Default:
|
|
30
|
+
unset (copy next to the page).
|
|
31
|
+
|
|
32
|
+
Only the HTML builder embeds the runtime; other builders emit a note pointing
|
|
33
|
+
at the app file name.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import os
|
|
39
|
+
import shutil
|
|
40
|
+
|
|
41
|
+
from docutils import nodes
|
|
42
|
+
from sphinx.util.docutils import SphinxDirective
|
|
43
|
+
from sphinx.util import logging
|
|
44
|
+
|
|
45
|
+
__version__ = "0.1.0"
|
|
46
|
+
|
|
47
|
+
logger = logging.getLogger(__name__)
|
|
48
|
+
|
|
49
|
+
DEFAULT_SCRIPT = "https://cdn.jsdelivr.net/gh/kitao/pyxel/wasm/pyxel.js"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class PyxelDirective(SphinxDirective):
|
|
53
|
+
"""Embed a Pyxel app via the web runtime."""
|
|
54
|
+
|
|
55
|
+
has_content = False
|
|
56
|
+
required_arguments = 1
|
|
57
|
+
optional_arguments = 0
|
|
58
|
+
final_argument_whitespace = True
|
|
59
|
+
option_spec = {
|
|
60
|
+
"mode": str,
|
|
61
|
+
"root": str,
|
|
62
|
+
"name": str,
|
|
63
|
+
"gamepad": str,
|
|
64
|
+
"assets": str,
|
|
65
|
+
"script": str,
|
|
66
|
+
"height": str,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
def run(self):
|
|
70
|
+
rel_src, abs_src = self.env.relfn2path(self.arguments[0].strip())
|
|
71
|
+
self.env.note_dependency(rel_src)
|
|
72
|
+
|
|
73
|
+
if not os.path.isfile(abs_src):
|
|
74
|
+
logger.warning("pyxel: file not found: %s", abs_src, location=self.get_source_info())
|
|
75
|
+
return [self.state.document.reporter.warning(
|
|
76
|
+
"pyxel: file not found: %s" % self.arguments[0], line=self.lineno)]
|
|
77
|
+
|
|
78
|
+
basename = os.path.basename(abs_src)
|
|
79
|
+
ext = os.path.splitext(basename)[1].lower()
|
|
80
|
+
|
|
81
|
+
mode = self.options.get("mode")
|
|
82
|
+
if mode is None:
|
|
83
|
+
mode = "play" if ext == ".pyxapp" else "run"
|
|
84
|
+
if mode not in ("run", "play"):
|
|
85
|
+
logger.warning("pyxel: :mode: must be 'run' or 'play', got %r", mode,
|
|
86
|
+
location=self.get_source_info())
|
|
87
|
+
mode = "play" if ext == ".pyxapp" else "run"
|
|
88
|
+
|
|
89
|
+
page_dir = self._page_dir()
|
|
90
|
+
pyxel_root = self.env.config.pyxel_root
|
|
91
|
+
if pyxel_root:
|
|
92
|
+
# Collect apps into one shared dir under the output; emit a root
|
|
93
|
+
# relative to the page so the runtime can fetch them.
|
|
94
|
+
shared_dir = os.path.join(self.env.app.builder.outdir, pyxel_root)
|
|
95
|
+
os.makedirs(shared_dir, exist_ok=True)
|
|
96
|
+
dest = os.path.join(shared_dir, basename)
|
|
97
|
+
shutil.copyfile(abs_src, dest)
|
|
98
|
+
default_root = os.path.relpath(shared_dir, page_dir).replace(os.sep, "/")
|
|
99
|
+
asset_dir = shared_dir
|
|
100
|
+
else:
|
|
101
|
+
# Copy the app next to the generated HTML page so the runtime can
|
|
102
|
+
# fetch it with a relative path.
|
|
103
|
+
os.makedirs(page_dir, exist_ok=True)
|
|
104
|
+
dest = os.path.join(page_dir, basename)
|
|
105
|
+
shutil.copyfile(abs_src, dest)
|
|
106
|
+
default_root = "."
|
|
107
|
+
asset_dir = page_dir
|
|
108
|
+
|
|
109
|
+
assets = [a.strip() for a in (self.options.get("assets") or "").split(",") if a.strip()]
|
|
110
|
+
for asset in assets:
|
|
111
|
+
a_rel, a_abs = self.env.relfn2path(asset)
|
|
112
|
+
self.env.note_dependency(a_rel)
|
|
113
|
+
if os.path.isfile(a_abs):
|
|
114
|
+
shutil.copyfile(a_abs, os.path.join(asset_dir, os.path.basename(a_abs)))
|
|
115
|
+
else:
|
|
116
|
+
logger.warning("pyxel: asset not found: %s", a_abs, location=self.get_source_info())
|
|
117
|
+
|
|
118
|
+
root = self.options.get("root", default_root)
|
|
119
|
+
name = self.options.get("name", basename)
|
|
120
|
+
script = self.options.get("script", DEFAULT_SCRIPT)
|
|
121
|
+
|
|
122
|
+
attrs = f'\n root="{root}"\n name="{name}"'
|
|
123
|
+
if mode == "play" and self.options.get("gamepad"):
|
|
124
|
+
attrs += f'\n gamepad="{self.options["gamepad"]}"'
|
|
125
|
+
|
|
126
|
+
tag = "pyxel-" + mode
|
|
127
|
+
# Emit the runtime <script> once per page per URL: the Pyxel runtime
|
|
128
|
+
# declares module-level consts, so loading it twice redeclares them and
|
|
129
|
+
# throws "Identifier 'PYODIDE_URL' has already been declared".
|
|
130
|
+
if not hasattr(self.env, "_pyxel_scripts"):
|
|
131
|
+
self.env._pyxel_scripts = {}
|
|
132
|
+
emitted = self.env._pyxel_scripts.setdefault(self.env.docname, set())
|
|
133
|
+
script_tag = "" if script in emitted else f'<script src="{script}"></script>'
|
|
134
|
+
emitted.add(script)
|
|
135
|
+
|
|
136
|
+
# Emit <div id="pyxel-screen"> once per page. The runtime's
|
|
137
|
+
# _createScreenElements does querySelector("div#pyxel-screen") first and
|
|
138
|
+
# only creates+appends-to-body if none exists, so placing it here makes
|
|
139
|
+
# the app render inline at the directive's location instead of taking
|
|
140
|
+
# over the whole page. The runtime only supports one screen per page,
|
|
141
|
+
# so a second directive on the same page reuses this div.
|
|
142
|
+
if not hasattr(self.env, "_pyxel_screens"):
|
|
143
|
+
self.env._pyxel_screens = set()
|
|
144
|
+
screen_div = "" if self.env.docname in self.env._pyxel_screens else '<div id="pyxel-screen"></div>'
|
|
145
|
+
self.env._pyxel_screens.add(self.env.docname)
|
|
146
|
+
|
|
147
|
+
# The runtime's own pyxel.css forces #pyxel-screen fullscreen. Override
|
|
148
|
+
# with higher specificity (.pyxel-app div#pyxel-screen) so it sizes to
|
|
149
|
+
# the container instead. Cascade order doesn't matter: specificity wins.
|
|
150
|
+
height = self.options.get("height", "480px")
|
|
151
|
+
style = (
|
|
152
|
+
'<style>'
|
|
153
|
+
'.pyxel-app{position:relative;width:100%;}'
|
|
154
|
+
f'.pyxel-app div#pyxel-screen{{position:relative;left:auto;top:auto;'
|
|
155
|
+
f'width:100%;height:{height};background-color:#202224;}}'
|
|
156
|
+
'.pyxel-app div#pyxel-screen canvas#canvas{position:relative;'
|
|
157
|
+
'left:auto;top:auto;width:100%;height:100%;}'
|
|
158
|
+
'</style>'
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
html = (
|
|
162
|
+
f'<div class="pyxel-app">'
|
|
163
|
+
f'{style}'
|
|
164
|
+
f'{screen_div}'
|
|
165
|
+
f'{script_tag}'
|
|
166
|
+
f'<{tag}{attrs}\n ></{tag}>'
|
|
167
|
+
f'</div>'
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
container = nodes.container(classes=["pyxel"])
|
|
171
|
+
container += nodes.raw("", html, format="html")
|
|
172
|
+
# Fallback for non-HTML builders.
|
|
173
|
+
container += nodes.paragraph(
|
|
174
|
+
"", f"Pyxel app ({mode}): {name}. "
|
|
175
|
+
"View this page in the HTML build to play it."
|
|
176
|
+
)
|
|
177
|
+
return [container]
|
|
178
|
+
|
|
179
|
+
def _page_dir(self):
|
|
180
|
+
outdir = self.env.app.builder.outdir
|
|
181
|
+
outname = self.env.app.builder.get_outfilename(self.env.docname)
|
|
182
|
+
return os.path.dirname(outname) or outdir
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _purge_doc(app, env, docname):
|
|
186
|
+
scripts = getattr(env, "_pyxel_scripts", None)
|
|
187
|
+
if scripts:
|
|
188
|
+
scripts.pop(docname, None)
|
|
189
|
+
screens = getattr(env, "_pyxel_screens", None)
|
|
190
|
+
if screens:
|
|
191
|
+
screens.discard(docname)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def setup(app):
|
|
195
|
+
app.add_directive("pyxel", PyxelDirective)
|
|
196
|
+
app.add_node(nodes.container, override=True)
|
|
197
|
+
app.add_config_value("pyxel_root", None, "html")
|
|
198
|
+
app.connect("env-purge-doc", _purge_doc)
|
|
199
|
+
return {
|
|
200
|
+
"version": __version__,
|
|
201
|
+
"parallel_read_safe": False, # we copy files during read
|
|
202
|
+
"parallel_write_safe": True,
|
|
203
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sphinx-pyxel
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Sphinx extension to embed Pyxel apps in HTML docs
|
|
5
|
+
Author: sphinx-pyxel
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/tetsuokoyama/sphinx-pyxel
|
|
8
|
+
Keywords: sphinx,pyxel,extension,sphinx-extension
|
|
9
|
+
Classifier: Framework :: Sphinx :: Extension
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: Sphinx>=5
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# sphinx-pyxel
|
|
19
|
+
|
|
20
|
+
A [Sphinx](https://www.sphinx-doc.org/) extension that embeds [Pyxel](https://github.com/kitao/pyxel) apps directly in your HTML documentation using the official Pyxel web runtime.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install sphinx-pyxel
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or from source:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
Add the extension to your `conf.py`:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
extensions = ["sphinx_pyxel"]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Then use the `pyxel` directive in any reStructuredText document:
|
|
43
|
+
|
|
44
|
+
```rst
|
|
45
|
+
.. pyxel:: examples/01_hello_pyxel.py
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
For a packaged app (`.pyxapp`) with gamepad support:
|
|
49
|
+
|
|
50
|
+
```rst
|
|
51
|
+
.. pyxel:: examples/30sec_of_daylight.pyxapp
|
|
52
|
+
:mode: play
|
|
53
|
+
:gamepad: enabled
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
If your app loads external resources, copy them next to it:
|
|
57
|
+
|
|
58
|
+
```rst
|
|
59
|
+
.. pyxel:: my_game.py
|
|
60
|
+
:assets: my_game.pyxres, my_game_bank.json
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Options
|
|
64
|
+
|
|
65
|
+
| Option | Default | Description |
|
|
66
|
+
|------------|--------------------------------------|-------------------------------------------------------------------|
|
|
67
|
+
| `mode` | `run` for `.py`, `play` for `.pyxapp`| `run` (just runs) or `play` (player controls, gamepad support). |
|
|
68
|
+
| `root` | `.` (or rel path to `pyxel_root`) | Root path served relative to the HTML page. |
|
|
69
|
+
| `name` | basename of the argument | File name served by the runtime. |
|
|
70
|
+
| `gamepad` | unset | `enabled` or `disabled` (only meaningful for `play`). |
|
|
71
|
+
| `assets` | unset | Comma-separated extra files to copy next to the app. |
|
|
72
|
+
| `script` | jsdelivr wasm build | URL of the Pyxel web runtime script. |
|
|
73
|
+
| `height` | `480px` | CSS height of the inline app window. |
|
|
74
|
+
|
|
75
|
+
## How it works
|
|
76
|
+
|
|
77
|
+
During the build, the directive copies the referenced app file (and any
|
|
78
|
+
`assets`) into the output directory next to the generated HTML page, then emits
|
|
79
|
+
a `<pyxel-run>` (or `<pyxel-play>`) custom element plus the Pyxel web runtime
|
|
80
|
+
script tag. The app runs entirely in the browser — no Python is executed by
|
|
81
|
+
Sphinx.
|
|
82
|
+
|
|
83
|
+
The app renders **inline** at the location of the directive (not fullscreen):
|
|
84
|
+
the extension places a ``#pyxel-screen`` container there and overrides the
|
|
85
|
+
runtime's fullscreen CSS so the canvas fills that container. Set ``:height:``
|
|
86
|
+
to control the window size.
|
|
87
|
+
|
|
88
|
+
## Config value: `pyxel_root`
|
|
89
|
+
|
|
90
|
+
Set ``pyxel_root`` in ``conf.py`` to collect every app into one shared
|
|
91
|
+
directory under the HTML output instead of copying it next to each page that
|
|
92
|
+
references it. Each emitted ``root`` then points from the page back at the
|
|
93
|
+
shared directory, so an app reused across many pages is stored once.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
pyxel_root = "_pyxel"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Limitations
|
|
100
|
+
|
|
101
|
+
- The embedded app only renders in the **HTML** builder. Other builders (LaTeX,
|
|
102
|
+
man, text, etc.) emit a short note instead. This is expected: the Pyxel web
|
|
103
|
+
runtime is JavaScript and only runs in a browser.
|
|
104
|
+
- One file is copied next to each page that references it **unless**
|
|
105
|
+
``pyxel_root`` is set, in which case apps are collected into one shared
|
|
106
|
+
directory. Two apps with the same basename under ``pyxel_root`` would
|
|
107
|
+
collide; give one a distinct ``:name:`` if that happens.
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Sphinx>=5
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sphinx_pyxel
|