diff --git a/docs/example_md.md b/docs/example_md.md index d03be05..b646589 100644 --- a/docs/example_md.md +++ b/docs/example_md.md @@ -8,30 +8,23 @@ py-config: # Example with MyST -## `py-repl` and `py-terminal` +## `py-editor` and `py-terminal` We can create a REPL which will output to a `div` and print `stdout` to a terminal with: ````md -```{py-repl} -:output: replOutput +```{py-editor} print("hallo world") import matplotlib.pyplot as plt plt.plot([1, 2, 3]) plt.gcf() ``` - -
- -```{py-terminal} -``` ```` Press `shift+enter` to run the code. -```{py-repl} -:output: replOutput +```{py-editor} print("hallo world") import matplotlib.pyplot as plt @@ -39,11 +32,6 @@ plt.plot([1, 2, 3]) plt.gcf() ``` -
- -```{py-terminal} -``` - ## `py-script` application Here is a simple application to replace "a" with "b", using the `py-script` directive: diff --git a/docs/example_rst.rst b/docs/example_rst.rst index 71e6f7c..92ddc57 100644 --- a/docs/example_rst.rst +++ b/docs/example_rst.rst @@ -8,42 +8,80 @@ Example with RST ================ -`py-repl` and `py-terminal` ----------------------------- +`py-editor` and `py-terminal` +----------------------------- -We can create a REPL which will output to a `div` and print `stdout` to a terminal with: +We can create an editor cell which will print its `stdout`: .. code-block:: restructuredtext - .. py-repl:: - :output: replOutput + .. py-editor:: print("hallo world") import matplotlib.pyplot as plt plt.plot([1, 2, 3]) plt.gcf() - .. raw:: html - -
- - .. py-terminal:: - Press `shift+enter` to run the code. -.. py-repl:: - :output: replOutput +.. py-editor:: print("hallo world") import matplotlib.pyplot as plt plt.plot([1, 2, 3]) plt.gcf() -.. raw:: html +By default, each editor uses a separate copy of the Python interpreter. Code blocks with the same `env` (environment) share a copy of the Python interpreter: + +.. code-block:: restructuredtext + + .. py-editor:: + :env: one + + x = 1 + + .. py-editor:: + :env: one + + print(x) + + .. py-editor:: + :env: two + + print(x) # Error: x is not defined + +Add the `setup` option to an editor tag in a given environment to include code that will run just before the first time the visible code in a block runs in that environment. Code in a `setup` block is invisible to the user. This is useful for setting up variables, imports, etc without cluttering up the editor cells. + +.. code-block:: restructuredtext + + .. py-editor:: + :env: one + :setup: + + # This code is not visible on the page + from datetime import datetime + + .. py-editor:: + :env: one + + print(datetime.now()) + +Use the `config` option to specify the url of a `PyScript Configuration File `_: + +.. code-block:: toml + + # config.toml + packages = ['numpy', 'pandas'] + +.. code-block:: restructuredtext + + .. py-editor:: + :config: config.toml -
+ import numpy as np + import pandas as pd -.. py-terminal:: + s = pd.Series([1, 3, 5, np.nan, 6, 8]) `py-script` application ----------------------- diff --git a/src/sphinx_pyscript.py b/sphinx_pyscript/__init__.py similarity index 60% rename from src/sphinx_pyscript.py rename to sphinx_pyscript/__init__.py index 0dfa4dc..f62ac3b 100644 --- a/src/sphinx_pyscript.py +++ b/sphinx_pyscript/__init__.py @@ -1,6 +1,6 @@ """A sphinx extension for adding pyscript to a page""" -__version__ = "0.1.0" +__version__ = "0.2.0" import json from pathlib import Path @@ -10,23 +10,29 @@ from docutils.parsers.rst import directives from sphinx.application import Sphinx from sphinx.util.docutils import SphinxDirective +from sphinx.util.fileutil import copy_asset_file from sphinx.util.logging import getLogger +DEFAULT_VERSION = "2024.5.2" + def setup(app: Sphinx): """Setup the extension""" app.add_config_value( - "pyscript_js", "https://pyscript.net/releases/2022.12.1/pyscript.js", "env" + "pyscript_js", f"https://pyscript.net/releases/{DEFAULT_VERSION}/core.js", "env" ) app.add_config_value( - "pyscript_css", "https://pyscript.net/releases/2022.12.1/pyscript.css", "env" + "pyscript_css", + f"https://pyscript.net/releases/{DEFAULT_VERSION}/core.css", + "env", ) app.add_directive("py-config", PyConfig) app.add_directive("py-script", PyScript) - app.add_directive("py-repl", PyRepl) + app.add_directive("py-editor", PyEditor) app.add_directive("py-terminal", PyTerminal) app.connect("doctree-read", doctree_read) app.connect("html-page-context", add_html_context) + app.connect("env-updated", copy_asset_files) return {"version": __version__, "parallel_read_safe": True} @@ -72,44 +78,66 @@ def run(self): code = "\n".join(self.content) else: raise self.error("Must provide either content or the 'file' option") - return [nodes.raw("", f"\n{code}\n\n", format="html")] + return [ + nodes.raw("", f"\n", format="html") + ] -class PyRepl(SphinxDirective): - """Add a py-repl tag""" +class PyEditor(SphinxDirective): + """Add a py-editor tag""" has_content = True + + """ + Notes on options. See https://docs.pyscript.net/2024.5.2/user-guide/editor/ for details + env: The name of a particular instance of the CPython interpreter. Cells with the same 'env' share an interpreter + setup: designates a 'setup' tag + config: The URL of a PyScript configuration file (TOML or JSON), or an inline configuration + """ option_spec = { - "auto-generate": directives.flag, - "output": directives.unchanged, + "env": directives.unchanged, + "setup": directives.flag, + "config": directives.unchanged, } def run(self): - """Add the py-repl tag""" + """Add the py-editor tag""" attrs = "" code = "" - if "auto-generate" in self.options: - attrs += ' auto-generate="true"' - if "output" in self.options: - attrs += f' output="{self.options["output"]}"' + if "env" in self.options: + attrs += f' env="{self.options["""env"""]}"' + if "config" in self.options: + attrs += f' config="{self.options["""config"""]}"' + if "setup" in self.options: + attrs += "setup" if self.content: code = "\n".join(self.content) - return [nodes.raw("", f"\n{code}\n\n", format="html")] + return [ + nodes.raw( + "", + f'\n', + format="html", + ) + ] class PyTerminal(SphinxDirective): """Add a py-terminal tag""" option_spec = { - "auto": directives.flag, + "worker": directives.flag, } def run(self): """Add the py-terminal tag""" attrs = "" - if "auto" in self.options: - attrs += " auto" - return [nodes.raw("", f"\n", format="html")] + if "worker" in self.options: + attrs += " worker" + return [ + nodes.raw( + "", f"\n", format="html" + ) + ] def add_html_context( @@ -117,7 +145,8 @@ def add_html_context( ): """Add extra variables to the HTML template context.""" if doctree and "pyscript" in doctree: - app.add_js_file(app.config.pyscript_js, loading_method="defer") + app.add_js_file(app.config.pyscript_js, type="module") + app.add_js_file("../mini-coi.js") app.add_css_file(app.config.pyscript_css) @@ -142,3 +171,10 @@ def doctree_read(app: Sphinx, doctree: nodes.document): format="html", ) ) + + +def copy_asset_files(app, _): + if app.builder.format == "html": + custom_file = (Path(__file__).parent / "mini-coi.js").absolute() + static_dir = (Path(app.builder.outdir)).absolute() + copy_asset_file(str(custom_file), str(static_dir)) diff --git a/sphinx_pyscript/mini-coi.js b/sphinx_pyscript/mini-coi.js new file mode 100644 index 0000000..128f073 --- /dev/null +++ b/sphinx_pyscript/mini-coi.js @@ -0,0 +1,31 @@ +/*! This script installs a service worker to set the COOP, COEP, and CORP headers required +to use SharedArrayBuffer in the browser. This is required to use py-editor cells. +See https://docs.pyscript.net/2024.5.2/user-guide/workers/ for details */ +/*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */ +/*! mini-coi - Andrea Giammarchi and contributors, licensed under MIT */ +(({ document: d, navigator: { serviceWorker: s } }) => { + if (d) { + const { currentScript: c } = d; + s.register(c.src, { scope: c.getAttribute('scope') || '.' }).then(r => { + r.addEventListener('updatefound', () => location.reload()); + if (r.active && !s.controller) location.reload(); + }); + } + else { + addEventListener('install', () => skipWaiting()); + addEventListener('activate', e => e.waitUntil(clients.claim())); + addEventListener('fetch', e => { + const { request: r } = e; + if (r.cache === 'only-if-cached' && r.mode !== 'same-origin') return; + e.respondWith(fetch(r).then(r => { + const { body, status, statusText } = r; + if (!status || status > 399) return r; + const h = new Headers(r.headers); + h.set('Cross-Origin-Opener-Policy', 'same-origin'); + h.set('Cross-Origin-Embedder-Policy', 'require-corp'); + h.set('Cross-Origin-Resource-Policy', 'cross-origin'); + return new Response(body, { status, statusText, headers: h }); + })); + }); + } + })(self); diff --git a/tests/test_basic.py b/tests/test_basic.py index 98128f7..7eb2072 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -14,8 +14,7 @@ def test_basic(sphinx_doctree: CreateDoctree): splashscreen: autoclose: true -.. py-repl:: - :output: replOutput +.. py-editor:: .. py-terminal:: @@ -33,15 +32,15 @@ def test_basic(sphinx_doctree: CreateDoctree): Test <raw format="html" xml:space="preserve"> - <py-repl output="replOutput"> + <script type="py-editor" > - </py-repl> + </script> <raw format="html" xml:space="preserve"> - <py-terminal></py-terminal> + <script type='py' terminal ></script> <raw format="html" xml:space="preserve"> - <py-script> + <script type='py'> print("Hello World") - </py-script> + </script> <raw format="html" xml:space="preserve"> <py-config type="json"> {