From 0fa9211bababca91412b20426312447a744a7edd Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Sun, 5 May 2024 20:31:34 -0500 Subject: [PATCH 01/18] Change component names to new style --- src/sphinx_pyscript.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/sphinx_pyscript.py b/src/sphinx_pyscript.py index 0dfa4dc..5d7d3cd 100644 --- a/src/sphinx_pyscript.py +++ b/src/sphinx_pyscript.py @@ -11,19 +11,20 @@ from sphinx.application import Sphinx from sphinx.util.docutils import SphinxDirective from sphinx.util.logging import getLogger +DEFAULT_VERSION = "2024.4.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", PyRepl) app.add_directive("py-terminal", PyTerminal) app.connect("doctree-read", doctree_read) app.connect("html-page-context", add_html_context) @@ -72,7 +73,7 @@ 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): @@ -94,7 +95,7 @@ def run(self): attrs += f' output="{self.options["output"]}"' 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): @@ -107,9 +108,9 @@ class PyTerminal(SphinxDirective): 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 +118,7 @@ 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_css_file(app.config.pyscript_css) From e49a6450d9a83b4b43c6c659d5cc55a0ff2bd40b Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Sun, 12 May 2024 16:53:17 -0500 Subject: [PATCH 02/18] Update to 2024.4.2 Add mini-coi.js to automatically handle service worker headers Update default version to 2024.4.2 Rename PyRepl to PyEditor --- .../__init__.py | 13 ++++++++- sphinx_pyscript/mini-coi.js | 28 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) rename src/sphinx_pyscript.py => sphinx_pyscript/__init__.py (91%) create mode 100644 sphinx_pyscript/mini-coi.js diff --git a/src/sphinx_pyscript.py b/sphinx_pyscript/__init__.py similarity index 91% rename from src/sphinx_pyscript.py rename to sphinx_pyscript/__init__.py index 5d7d3cd..7708c0b 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,7 +10,10 @@ 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.4.2" @@ -28,6 +31,7 @@ def setup(app: Sphinx): app.add_directive("py-terminal", PyTerminal) app.connect("doctree-read", doctree_read) app.connect("html-page-context", add_html_context) + app.connect('build-finished', copy_asset_files) return {"version": __version__, "parallel_read_safe": True} @@ -119,6 +123,7 @@ 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, type="module") + app.add_js_file("../mini-coi.js") app.add_css_file(app.config.pyscript_css) @@ -143,3 +148,9 @@ def doctree_read(app: Sphinx, doctree: nodes.document): format="html", ) ) + +def copy_asset_files(app, exc): + if app.builder.format == 'html' and not exc: + custom_file = (Path(__file__).parent / 'mini-coi.js').absolute() + static_dir = (Path(app.builder.outdir)).absolute() + copy_asset_file(custom_file, static_dir) \ No newline at end of file diff --git a/sphinx_pyscript/mini-coi.js b/sphinx_pyscript/mini-coi.js new file mode 100644 index 0000000..9fbfc47 --- /dev/null +++ b/sphinx_pyscript/mini-coi.js @@ -0,0 +1,28 @@ +/*! 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); \ No newline at end of file From e4d94735e4e3ceecd9c4108020f72c79f1e464ba Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Sun, 12 May 2024 22:17:14 -0500 Subject: [PATCH 03/18] PyRepl -> PyEditor, fix attributes --- sphinx_pyscript/__init__.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/sphinx_pyscript/__init__.py b/sphinx_pyscript/__init__.py index 7708c0b..a92c7d2 100644 --- a/sphinx_pyscript/__init__.py +++ b/sphinx_pyscript/__init__.py @@ -27,7 +27,7 @@ def setup(app: Sphinx): ) app.add_directive("py-config", PyConfig) app.add_directive("py-script", PyScript) - app.add_directive("py-editor", 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) @@ -80,23 +80,26 @@ def run(self): return [nodes.raw("", f"\n", format="html")] -class PyRepl(SphinxDirective): +class PyEditor(SphinxDirective): """Add a py-repl tag""" has_content = True option_spec = { - "auto-generate": directives.flag, - "output": directives.unchanged, + "setup": directives.flag, + "env": directives.unchanged, + "config": directives.unchanged, } def run(self): """Add the py-repl 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 += f'setup' if self.content: code = "\n".join(self.content) return [nodes.raw("", f"\n", format="html")] @@ -106,7 +109,6 @@ class PyTerminal(SphinxDirective): """Add a py-terminal tag""" option_spec = { - "auto": directives.flag, } def run(self): From f5cc0f41d1d5cc85a28bdd04f75ab01ec651fab5 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Sun, 12 May 2024 22:22:53 -0500 Subject: [PATCH 04/18] Adjust docs and tests --- docs/example_rst.rst | 22 +--- sphinx_pyscript/sphinx_pyscript.py | 159 +++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+), 18 deletions(-) create mode 100644 sphinx_pyscript/sphinx_pyscript.py diff --git a/docs/example_rst.rst b/docs/example_rst.rst index 71e6f7c..a7358d5 100644 --- a/docs/example_rst.rst +++ b/docs/example_rst.rst @@ -8,43 +8,29 @@ 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 `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 - -
- -.. py-terminal:: - `py-script` application ----------------------- diff --git a/sphinx_pyscript/sphinx_pyscript.py b/sphinx_pyscript/sphinx_pyscript.py new file mode 100644 index 0000000..4d3801c --- /dev/null +++ b/sphinx_pyscript/sphinx_pyscript.py @@ -0,0 +1,159 @@ +"""A sphinx extension for adding pyscript to a page""" + +__version__ = "0.2.0" + +import json +from os import path +from pathlib import Path + +import yaml +from docutils import nodes +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.4.2" + + +def setup(app: Sphinx): + """Setup the extension""" + app.add_config_value( + "pyscript_js", f"https://pyscript.net/releases/{DEFAULT_VERSION}/core.js", "env" + ) + app.add_config_value( + "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-editor", PyRepl) + app.add_directive("py-terminal", PyTerminal) + app.connect("doctree-read", doctree_read) + app.connect("html-page-context", add_html_context) + app.connect('build-finished', copy_asset_files) + + return {"version": __version__, "parallel_read_safe": True} + + +LOGGER = getLogger(__name__) + + +class PyConfig(SphinxDirective): + """Parse the py-config as a directive""" + + has_content = True + + def run(self): + """Parse the config""" + if self.content: + try: + config = yaml.safe_load("\n".join(self.content)) + except Exception: + raise self.error("Could not read config as YAML") + else: + config = {} + self.env.metadata[self.env.docname]["py-config"] = json.dumps(config) + return [] + + +class PyScript(SphinxDirective): + """Add a py-script tag""" + + has_content = True + option_spec = { + "file": directives.path, + } + + def run(self): + """Add the py-script tag""" + if "file" in self.options: + path = Path(self.env.relfn2path(self.options["file"])[1]) + try: + code = path.read_text(encoding="utf8") + except Exception as exc: + raise self.error(f"Could not read file: {exc}") + self.env.note_dependency(path) + elif self.content: + code = "\n".join(self.content) + else: + raise self.error("Must provide either content or the 'file' option") + return [nodes.raw("", f"\n", format="html")] + + +class PyRepl(SphinxDirective): + """Add a py-editor tag""" + + has_content = True + option_spec = { + "auto-generate": directives.flag, + "output": directives.unchanged, + } + + def run(self): + """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 self.content: + code = "\n".join(self.content) + return [nodes.raw("", f"\n", format="html")] + + +class PyTerminal(SphinxDirective): + """Add a py-terminal tag""" + + option_spec = { + "auto": directives.flag, + } + + def run(self): + """Add the py-terminal tag""" + attrs = "" + if "worker" in self.options: + attrs += " worker" + return [nodes.raw("", f"\n", format="html")] + + +def add_html_context( + app: Sphinx, pagename: str, templatename: str, context, doctree: nodes.document +): + """Add extra variables to the HTML template context.""" + if doctree and "pyscript" in doctree: + 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) + + +def doctree_read(app: Sphinx, doctree: nodes.document): + """Setup the doctree.""" + metadata = app.env.metadata[app.env.docname] + if "py-config" in metadata: + try: + data = json.loads(metadata["py-config"]) + assert isinstance(data, dict), "must be a dictionary" + except Exception as exc: + LOGGER.warning( + f"Could not parse pyscript config: {exc}", location=(app.env.docname, 0) + ) + else: + doctree["pyscript"] = True + data_str = json.dumps(data, indent=2) + doctree.append( + nodes.raw( + "", + f'\n{data_str}\n\n', + format="html", + ) + ) + +def copy_asset_files(app, exc): + if app.builder.format == 'html' and not exc: + custom_file = (Path(__file__).parent / 'mini-coi.js').absolute() + static_dir = (Path(app.builder.outdir)).absolute() + copy_asset_file(custom_file, static_dir) From 98543ec04893a2cbee5fa4442bf9d92fa1c60e40 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 03:23:33 +0000 Subject: [PATCH 05/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- sphinx_pyscript/__init__.py | 37 +++-- sphinx_pyscript/mini-coi.js | 2 +- sphinx_pyscript/sphinx_pyscript.py | 235 +++++++++++++++-------------- 3 files changed, 150 insertions(+), 124 deletions(-) diff --git a/sphinx_pyscript/__init__.py b/sphinx_pyscript/__init__.py index a92c7d2..022c27f 100644 --- a/sphinx_pyscript/__init__.py +++ b/sphinx_pyscript/__init__.py @@ -13,7 +13,6 @@ from sphinx.util.fileutil import copy_asset_file from sphinx.util.logging import getLogger - DEFAULT_VERSION = "2024.4.2" @@ -23,7 +22,9 @@ def setup(app: Sphinx): "pyscript_js", f"https://pyscript.net/releases/{DEFAULT_VERSION}/core.js", "env" ) app.add_config_value( - "pyscript_css", f"https://pyscript.net/releases/{DEFAULT_VERSION}/core.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) @@ -31,7 +32,7 @@ def setup(app: Sphinx): app.add_directive("py-terminal", PyTerminal) app.connect("doctree-read", doctree_read) app.connect("html-page-context", add_html_context) - app.connect('build-finished', copy_asset_files) + app.connect("build-finished", copy_asset_files) return {"version": __version__, "parallel_read_safe": True} @@ -77,7 +78,9 @@ 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", format="html")] + return [ + nodes.raw("", f"\n", format="html") + ] class PyEditor(SphinxDirective): @@ -99,24 +102,33 @@ def run(self): if "config" in self.options: attrs += f' config="{self.options["""config"""]}"' if "setup" in self.options: - attrs += f'setup' + attrs += f"setup" if self.content: code = "\n".join(self.content) - return [nodes.raw("", f"\n", format="html")] + return [ + nodes.raw( + "", + f"\n", + format="html", + ) + ] class PyTerminal(SphinxDirective): """Add a py-terminal tag""" - option_spec = { - } + option_spec = {} def run(self): """Add the py-terminal tag""" attrs = "" if "worker" in self.options: attrs += " worker" - return [nodes.raw("", f"\n", format="html")] + return [ + nodes.raw( + "", f"\n", format="html" + ) + ] def add_html_context( @@ -151,8 +163,9 @@ def doctree_read(app: Sphinx, doctree: nodes.document): ) ) + def copy_asset_files(app, exc): - if app.builder.format == 'html' and not exc: - custom_file = (Path(__file__).parent / 'mini-coi.js').absolute() + if app.builder.format == "html" and not exc: + custom_file = (Path(__file__).parent / "mini-coi.js").absolute() static_dir = (Path(app.builder.outdir)).absolute() - copy_asset_file(custom_file, static_dir) \ No newline at end of file + copy_asset_file(custom_file, static_dir) diff --git a/sphinx_pyscript/mini-coi.js b/sphinx_pyscript/mini-coi.js index 9fbfc47..0ee39c1 100644 --- a/sphinx_pyscript/mini-coi.js +++ b/sphinx_pyscript/mini-coi.js @@ -25,4 +25,4 @@ })); }); } - })(self); \ No newline at end of file + })(self); diff --git a/sphinx_pyscript/sphinx_pyscript.py b/sphinx_pyscript/sphinx_pyscript.py index 4d3801c..d4dc1ab 100644 --- a/sphinx_pyscript/sphinx_pyscript.py +++ b/sphinx_pyscript/sphinx_pyscript.py @@ -14,146 +14,159 @@ from sphinx.util.fileutil import copy_asset_file from sphinx.util.logging import getLogger - - DEFAULT_VERSION = "2024.4.2" def setup(app: Sphinx): - """Setup the extension""" - app.add_config_value( - "pyscript_js", f"https://pyscript.net/releases/{DEFAULT_VERSION}/core.js", "env" - ) - app.add_config_value( - "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-editor", PyRepl) - app.add_directive("py-terminal", PyTerminal) - app.connect("doctree-read", doctree_read) - app.connect("html-page-context", add_html_context) - app.connect('build-finished', copy_asset_files) - - return {"version": __version__, "parallel_read_safe": True} + """Setup the extension""" + app.add_config_value( + "pyscript_js", f"https://pyscript.net/releases/{DEFAULT_VERSION}/core.js", "env" + ) + app.add_config_value( + "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-editor", PyRepl) + app.add_directive("py-terminal", PyTerminal) + app.connect("doctree-read", doctree_read) + app.connect("html-page-context", add_html_context) + app.connect("build-finished", copy_asset_files) + + return {"version": __version__, "parallel_read_safe": True} LOGGER = getLogger(__name__) class PyConfig(SphinxDirective): - """Parse the py-config as a directive""" + """Parse the py-config as a directive""" - has_content = True + has_content = True - def run(self): - """Parse the config""" - if self.content: - try: - config = yaml.safe_load("\n".join(self.content)) - except Exception: - raise self.error("Could not read config as YAML") - else: - config = {} - self.env.metadata[self.env.docname]["py-config"] = json.dumps(config) - return [] + def run(self): + """Parse the config""" + if self.content: + try: + config = yaml.safe_load("\n".join(self.content)) + except Exception: + raise self.error("Could not read config as YAML") + else: + config = {} + self.env.metadata[self.env.docname]["py-config"] = json.dumps(config) + return [] class PyScript(SphinxDirective): - """Add a py-script tag""" - - has_content = True - option_spec = { - "file": directives.path, - } - - def run(self): - """Add the py-script tag""" - if "file" in self.options: - path = Path(self.env.relfn2path(self.options["file"])[1]) - try: - code = path.read_text(encoding="utf8") - except Exception as exc: - raise self.error(f"Could not read file: {exc}") - self.env.note_dependency(path) - elif self.content: - code = "\n".join(self.content) - else: - raise self.error("Must provide either content or the 'file' option") - return [nodes.raw("", f"\n", format="html")] + """Add a py-script tag""" + + has_content = True + option_spec = { + "file": directives.path, + } + + def run(self): + """Add the py-script tag""" + if "file" in self.options: + path = Path(self.env.relfn2path(self.options["file"])[1]) + try: + code = path.read_text(encoding="utf8") + except Exception as exc: + raise self.error(f"Could not read file: {exc}") + self.env.note_dependency(path) + elif self.content: + code = "\n".join(self.content) + else: + raise self.error("Must provide either content or the 'file' option") + return [ + nodes.raw("", f"\n", format="html") + ] class PyRepl(SphinxDirective): - """Add a py-editor tag""" - - has_content = True - option_spec = { - "auto-generate": directives.flag, - "output": directives.unchanged, - } - - def run(self): - """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 self.content: - code = "\n".join(self.content) - return [nodes.raw("", f"\n", format="html")] + """Add a py-editor tag""" + + has_content = True + option_spec = { + "auto-generate": directives.flag, + "output": directives.unchanged, + } + + def run(self): + """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 self.content: + code = "\n".join(self.content) + return [ + nodes.raw( + "", + f"\n", + format="html", + ) + ] class PyTerminal(SphinxDirective): - """Add a py-terminal tag""" + """Add a py-terminal tag""" - option_spec = { - "auto": directives.flag, - } + option_spec = { + "auto": directives.flag, + } - def run(self): - """Add the py-terminal tag""" - attrs = "" - if "worker" in self.options: - attrs += " worker" - return [nodes.raw("", f"\n", format="html")] + def run(self): + """Add the py-terminal tag""" + attrs = "" + if "worker" in self.options: + attrs += " worker" + return [ + nodes.raw( + "", f"\n", format="html" + ) + ] def add_html_context( - app: Sphinx, pagename: str, templatename: str, context, doctree: nodes.document + app: Sphinx, pagename: str, templatename: str, context, doctree: nodes.document ): - """Add extra variables to the HTML template context.""" - if doctree and "pyscript" in doctree: - 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) + """Add extra variables to the HTML template context.""" + if doctree and "pyscript" in doctree: + 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) def doctree_read(app: Sphinx, doctree: nodes.document): - """Setup the doctree.""" - metadata = app.env.metadata[app.env.docname] - if "py-config" in metadata: - try: - data = json.loads(metadata["py-config"]) - assert isinstance(data, dict), "must be a dictionary" - except Exception as exc: - LOGGER.warning( - f"Could not parse pyscript config: {exc}", location=(app.env.docname, 0) - ) - else: - doctree["pyscript"] = True - data_str = json.dumps(data, indent=2) - doctree.append( - nodes.raw( - "", - f'\n{data_str}\n\n', - format="html", - ) - ) + """Setup the doctree.""" + metadata = app.env.metadata[app.env.docname] + if "py-config" in metadata: + try: + data = json.loads(metadata["py-config"]) + assert isinstance(data, dict), "must be a dictionary" + except Exception as exc: + LOGGER.warning( + f"Could not parse pyscript config: {exc}", location=(app.env.docname, 0) + ) + else: + doctree["pyscript"] = True + data_str = json.dumps(data, indent=2) + doctree.append( + nodes.raw( + "", + f'\n{data_str}\n\n', + format="html", + ) + ) + def copy_asset_files(app, exc): - if app.builder.format == 'html' and not exc: - custom_file = (Path(__file__).parent / 'mini-coi.js').absolute() - static_dir = (Path(app.builder.outdir)).absolute() - copy_asset_file(custom_file, static_dir) + if app.builder.format == "html" and not exc: + custom_file = (Path(__file__).parent / "mini-coi.js").absolute() + static_dir = (Path(app.builder.outdir)).absolute() + copy_asset_file(custom_file, static_dir) From 112fc143611d45dc255b0380d04f606f970bd6f4 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Sun, 12 May 2024 22:29:32 -0500 Subject: [PATCH 06/18] Fix two more old 'py-repl' names --- sphinx_pyscript/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx_pyscript/__init__.py b/sphinx_pyscript/__init__.py index a92c7d2..6508e7d 100644 --- a/sphinx_pyscript/__init__.py +++ b/sphinx_pyscript/__init__.py @@ -81,7 +81,7 @@ def run(self): class PyEditor(SphinxDirective): - """Add a py-repl tag""" + """Add a py-editor tag""" has_content = True option_spec = { @@ -91,7 +91,7 @@ class PyEditor(SphinxDirective): } def run(self): - """Add the py-repl tag""" + """Add the py-editor tag""" attrs = "" code = "" if "env" in self.options: From 93912a2f14758c1cc5a474ed6400857955489b42 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Sun, 12 May 2024 22:31:26 -0500 Subject: [PATCH 07/18] Fix pre-commit isssues, remove unused file --- sphinx_pyscript/__init__.py | 37 ++++--- sphinx_pyscript/mini-coi.js | 2 +- sphinx_pyscript/sphinx_pyscript.py | 159 ----------------------------- 3 files changed, 26 insertions(+), 172 deletions(-) delete mode 100644 sphinx_pyscript/sphinx_pyscript.py diff --git a/sphinx_pyscript/__init__.py b/sphinx_pyscript/__init__.py index 6508e7d..ce01157 100644 --- a/sphinx_pyscript/__init__.py +++ b/sphinx_pyscript/__init__.py @@ -13,7 +13,6 @@ from sphinx.util.fileutil import copy_asset_file from sphinx.util.logging import getLogger - DEFAULT_VERSION = "2024.4.2" @@ -23,7 +22,9 @@ def setup(app: Sphinx): "pyscript_js", f"https://pyscript.net/releases/{DEFAULT_VERSION}/core.js", "env" ) app.add_config_value( - "pyscript_css", f"https://pyscript.net/releases/{DEFAULT_VERSION}/core.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) @@ -31,7 +32,7 @@ def setup(app: Sphinx): app.add_directive("py-terminal", PyTerminal) app.connect("doctree-read", doctree_read) app.connect("html-page-context", add_html_context) - app.connect('build-finished', copy_asset_files) + app.connect("build-finished", copy_asset_files) return {"version": __version__, "parallel_read_safe": True} @@ -77,7 +78,9 @@ 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", format="html")] + return [ + nodes.raw("", f"\n", format="html") + ] class PyEditor(SphinxDirective): @@ -99,24 +102,33 @@ def run(self): if "config" in self.options: attrs += f' config="{self.options["""config"""]}"' if "setup" in self.options: - attrs += f'setup' + attrs += "setup" if self.content: code = "\n".join(self.content) - return [nodes.raw("", f"\n", format="html")] + return [ + nodes.raw( + "", + f"\n", + format="html", + ) + ] class PyTerminal(SphinxDirective): """Add a py-terminal tag""" - option_spec = { - } + option_spec = {} def run(self): """Add the py-terminal tag""" attrs = "" if "worker" in self.options: attrs += " worker" - return [nodes.raw("", f"\n", format="html")] + return [ + nodes.raw( + "", f"\n", format="html" + ) + ] def add_html_context( @@ -151,8 +163,9 @@ def doctree_read(app: Sphinx, doctree: nodes.document): ) ) + def copy_asset_files(app, exc): - if app.builder.format == 'html' and not exc: - custom_file = (Path(__file__).parent / 'mini-coi.js').absolute() + if app.builder.format == "html" and not exc: + custom_file = (Path(__file__).parent / "mini-coi.js").absolute() static_dir = (Path(app.builder.outdir)).absolute() - copy_asset_file(custom_file, static_dir) \ No newline at end of file + copy_asset_file(custom_file, static_dir) diff --git a/sphinx_pyscript/mini-coi.js b/sphinx_pyscript/mini-coi.js index 9fbfc47..0ee39c1 100644 --- a/sphinx_pyscript/mini-coi.js +++ b/sphinx_pyscript/mini-coi.js @@ -25,4 +25,4 @@ })); }); } - })(self); \ No newline at end of file + })(self); diff --git a/sphinx_pyscript/sphinx_pyscript.py b/sphinx_pyscript/sphinx_pyscript.py deleted file mode 100644 index 4d3801c..0000000 --- a/sphinx_pyscript/sphinx_pyscript.py +++ /dev/null @@ -1,159 +0,0 @@ -"""A sphinx extension for adding pyscript to a page""" - -__version__ = "0.2.0" - -import json -from os import path -from pathlib import Path - -import yaml -from docutils import nodes -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.4.2" - - -def setup(app: Sphinx): - """Setup the extension""" - app.add_config_value( - "pyscript_js", f"https://pyscript.net/releases/{DEFAULT_VERSION}/core.js", "env" - ) - app.add_config_value( - "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-editor", PyRepl) - app.add_directive("py-terminal", PyTerminal) - app.connect("doctree-read", doctree_read) - app.connect("html-page-context", add_html_context) - app.connect('build-finished', copy_asset_files) - - return {"version": __version__, "parallel_read_safe": True} - - -LOGGER = getLogger(__name__) - - -class PyConfig(SphinxDirective): - """Parse the py-config as a directive""" - - has_content = True - - def run(self): - """Parse the config""" - if self.content: - try: - config = yaml.safe_load("\n".join(self.content)) - except Exception: - raise self.error("Could not read config as YAML") - else: - config = {} - self.env.metadata[self.env.docname]["py-config"] = json.dumps(config) - return [] - - -class PyScript(SphinxDirective): - """Add a py-script tag""" - - has_content = True - option_spec = { - "file": directives.path, - } - - def run(self): - """Add the py-script tag""" - if "file" in self.options: - path = Path(self.env.relfn2path(self.options["file"])[1]) - try: - code = path.read_text(encoding="utf8") - except Exception as exc: - raise self.error(f"Could not read file: {exc}") - self.env.note_dependency(path) - elif self.content: - code = "\n".join(self.content) - else: - raise self.error("Must provide either content or the 'file' option") - return [nodes.raw("", f"\n", format="html")] - - -class PyRepl(SphinxDirective): - """Add a py-editor tag""" - - has_content = True - option_spec = { - "auto-generate": directives.flag, - "output": directives.unchanged, - } - - def run(self): - """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 self.content: - code = "\n".join(self.content) - return [nodes.raw("", f"\n", format="html")] - - -class PyTerminal(SphinxDirective): - """Add a py-terminal tag""" - - option_spec = { - "auto": directives.flag, - } - - def run(self): - """Add the py-terminal tag""" - attrs = "" - if "worker" in self.options: - attrs += " worker" - return [nodes.raw("", f"\n", format="html")] - - -def add_html_context( - app: Sphinx, pagename: str, templatename: str, context, doctree: nodes.document -): - """Add extra variables to the HTML template context.""" - if doctree and "pyscript" in doctree: - 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) - - -def doctree_read(app: Sphinx, doctree: nodes.document): - """Setup the doctree.""" - metadata = app.env.metadata[app.env.docname] - if "py-config" in metadata: - try: - data = json.loads(metadata["py-config"]) - assert isinstance(data, dict), "must be a dictionary" - except Exception as exc: - LOGGER.warning( - f"Could not parse pyscript config: {exc}", location=(app.env.docname, 0) - ) - else: - doctree["pyscript"] = True - data_str = json.dumps(data, indent=2) - doctree.append( - nodes.raw( - "", - f'\n{data_str}\n\n', - format="html", - ) - ) - -def copy_asset_files(app, exc): - if app.builder.format == 'html' and not exc: - custom_file = (Path(__file__).parent / 'mini-coi.js').absolute() - static_dir = (Path(app.builder.outdir)).absolute() - copy_asset_file(custom_file, static_dir) From f5fe453d291b7ce4d607cc5a3e006689880d2b93 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Sun, 12 May 2024 22:34:54 -0500 Subject: [PATCH 08/18] Turn paths into strings to make readthedocs happy --- sphinx_pyscript/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx_pyscript/__init__.py b/sphinx_pyscript/__init__.py index ce01157..243e594 100644 --- a/sphinx_pyscript/__init__.py +++ b/sphinx_pyscript/__init__.py @@ -168,4 +168,4 @@ def copy_asset_files(app, exc): if app.builder.format == "html" and not exc: custom_file = (Path(__file__).parent / "mini-coi.js").absolute() static_dir = (Path(app.builder.outdir)).absolute() - copy_asset_file(custom_file, static_dir) + copy_asset_file(str(custom_file), str(static_dir)) From 7506ec9bd9d2842ba950c7978c8dc184056d1e2d Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Sun, 12 May 2024 22:38:37 -0500 Subject: [PATCH 09/18] Don't fail on warning, to avoid issues with manipulating static file --- .readthedocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 13aa7e0..0653d0d 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -14,4 +14,4 @@ python: sphinx: builder: html - fail_on_warning: true + fail_on_warning: false From 65b7162bcabd6a6546022229d2a0fb16b3b7d8a7 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Mon, 13 May 2024 10:59:39 -0500 Subject: [PATCH 10/18] Add documentation on new py-editor options --- docs/example_rst.rst | 54 ++++++++++++++++++++++++++++++++++++- sphinx_pyscript/__init__.py | 9 ++++++- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/docs/example_rst.rst b/docs/example_rst.rst index a7358d5..d57f28f 100644 --- a/docs/example_rst.rst +++ b/docs/example_rst.rst @@ -11,7 +11,7 @@ Example with RST `py-editor` and `py-terminal` ---------------------------- -We can create an editor cell which will print `stdout`: +We can create an editor cell which will print its `stdout`: .. code-block:: restructuredtext @@ -31,6 +31,58 @@ Press `shift+enter` to run the code. plt.plot([1, 2, 3]) plt.gcf() +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 + + s = pd.Series([1, 3, 5, np.nan, 6, 8]) + `py-script` application ----------------------- diff --git a/sphinx_pyscript/__init__.py b/sphinx_pyscript/__init__.py index 243e594..cee2b40 100644 --- a/sphinx_pyscript/__init__.py +++ b/sphinx_pyscript/__init__.py @@ -87,9 +87,16 @@ 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 = { - "setup": directives.flag, "env": directives.unchanged, + "setup": directives.flag, "config": directives.unchanged, } From 00f423b0d05e0334975f346b729ce786cb5847cc Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Mon, 13 May 2024 10:59:53 -0500 Subject: [PATCH 11/18] Add comment explaining why mini-coi is necessary --- sphinx_pyscript/mini-coi.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sphinx_pyscript/mini-coi.js b/sphinx_pyscript/mini-coi.js index 0ee39c1..0a9ac2d 100644 --- a/sphinx_pyscript/mini-coi.js +++ b/sphinx_pyscript/mini-coi.js @@ -1,3 +1,6 @@ +/*! 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 } }) => { From 9e2cb8a7b14c26a4eb081944766ba5775ec31da2 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Mon, 13 May 2024 11:00:11 -0500 Subject: [PATCH 12/18] Add 'worker' in terminal option_spec --- sphinx_pyscript/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sphinx_pyscript/__init__.py b/sphinx_pyscript/__init__.py index cee2b40..f202263 100644 --- a/sphinx_pyscript/__init__.py +++ b/sphinx_pyscript/__init__.py @@ -124,7 +124,9 @@ def run(self): class PyTerminal(SphinxDirective): """Add a py-terminal tag""" - option_spec = {} + option_spec = { + "worker": directives.flag, + } def run(self): """Add the py-terminal tag""" From 85e06c6fe58fad230e69976effc866192f850d0c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 16:03:14 +0000 Subject: [PATCH 13/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/example_rst.rst | 2 +- sphinx_pyscript/__init__.py | 2 +- sphinx_pyscript/mini-coi.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/example_rst.rst b/docs/example_rst.rst index d57f28f..02c7e72 100644 --- a/docs/example_rst.rst +++ b/docs/example_rst.rst @@ -69,7 +69,7 @@ Add the `setup` option to an editor tag in a given environment to include code t Use the `config` option to specify the url of a `PyScript Configuration File `_: .. code-block:: toml - + # config.toml packages = ['numpy', 'pandas'] diff --git a/sphinx_pyscript/__init__.py b/sphinx_pyscript/__init__.py index f202263..92edc6b 100644 --- a/sphinx_pyscript/__init__.py +++ b/sphinx_pyscript/__init__.py @@ -91,7 +91,7 @@ class PyEditor(SphinxDirective): """ 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 + setup: designates a 'setup' tag config: The URL of a PyScript configuration file (TOML or JSON), or an inline configuration """ option_spec = { diff --git a/sphinx_pyscript/mini-coi.js b/sphinx_pyscript/mini-coi.js index 0a9ac2d..128f073 100644 --- a/sphinx_pyscript/mini-coi.js +++ b/sphinx_pyscript/mini-coi.js @@ -1,4 +1,4 @@ -/*! This script installs a service worker to set the COOP, COEP, and CORP headers required +/*! 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 */ From b9447d53cb4d29a2bc137e10adb0c636bf1766c7 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Mon, 13 May 2024 11:07:49 -0500 Subject: [PATCH 14/18] fail_on_warning -> true --- .readthedocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 0653d0d..13aa7e0 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -14,4 +14,4 @@ python: sphinx: builder: html - fail_on_warning: false + fail_on_warning: true From d6b88c379ed202ea08b722470f020bf4e970f447 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Mon, 13 May 2024 11:13:16 -0500 Subject: [PATCH 15/18] Make title underline two chars longer --- docs/example_rst.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/example_rst.rst b/docs/example_rst.rst index 02c7e72..92ddc57 100644 --- a/docs/example_rst.rst +++ b/docs/example_rst.rst @@ -9,7 +9,7 @@ Example with RST ================ `py-editor` and `py-terminal` ----------------------------- +----------------------------- We can create an editor cell which will print its `stdout`: From 701c2d7eacd8bb88ee07d992af8bb7849edead08 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Mon, 13 May 2024 12:07:15 -0500 Subject: [PATCH 16/18] Fix/update tests --- tests/test_basic.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_basic.py b/tests/test_basic.py index 7203d29..7eb2072 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -16,6 +16,8 @@ def test_basic(sphinx_doctree: CreateDoctree): .. py-editor:: +.. py-terminal:: + .. py-script:: print("Hello World") @@ -30,15 +32,15 @@ def test_basic(sphinx_doctree: CreateDoctree): Test <raw format="html" xml:space="preserve"> - <script type="py-editor"> + <script type="py-editor" > </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"> { From ff3550e4cabedafc1a3dfddcf277f2a701e89058 Mon Sep 17 00:00:00 2001 From: Jeff Glass <glass.jeffrey@gmail.com> Date: Mon, 13 May 2024 12:49:47 -0500 Subject: [PATCH 17/18] Move file copy to event --- sphinx_pyscript/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sphinx_pyscript/__init__.py b/sphinx_pyscript/__init__.py index 92edc6b..b419baa 100644 --- a/sphinx_pyscript/__init__.py +++ b/sphinx_pyscript/__init__.py @@ -32,7 +32,7 @@ def setup(app: Sphinx): app.add_directive("py-terminal", PyTerminal) app.connect("doctree-read", doctree_read) app.connect("html-page-context", add_html_context) - app.connect("build-finished", copy_asset_files) + app.connect("env-updated", copy_asset_files) return {"version": __version__, "parallel_read_safe": True} @@ -115,7 +115,7 @@ def run(self): return [ nodes.raw( "", - f"<script type='py-editor' {attrs}>\n{code}\n</script>\n", + f'<script type="py-editor" {attrs}>\n{code}\n</script>\n', format="html", ) ] @@ -173,8 +173,8 @@ def doctree_read(app: Sphinx, doctree: nodes.document): ) -def copy_asset_files(app, exc): - if app.builder.format == "html" and not exc: +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)) From 14dc7a215101587308ea7bbf747bfe08384182be Mon Sep 17 00:00:00 2001 From: Jeff Glass <glass.jeffrey@gmail.com> Date: Mon, 13 May 2024 13:54:23 -0500 Subject: [PATCH 18/18] Update to PyScript 2024.5.2 --- sphinx_pyscript/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx_pyscript/__init__.py b/sphinx_pyscript/__init__.py index b419baa..f62ac3b 100644 --- a/sphinx_pyscript/__init__.py +++ b/sphinx_pyscript/__init__.py @@ -13,7 +13,7 @@ from sphinx.util.fileutil import copy_asset_file from sphinx.util.logging import getLogger -DEFAULT_VERSION = "2024.4.2" +DEFAULT_VERSION = "2024.5.2" def setup(app: Sphinx):