diff --git a/lib/html_head.typ b/lib/html_head.typ
index 1c5fe31..0cbb3b3 100644
--- a/lib/html_head.typ
+++ b/lib/html_head.typ
@@ -1,6 +1,6 @@
#import "./custom_html.typ" as chtml
-#import "./html_utils.typ": get-css, get-js
-#import "./pyscript.typ": state-use-pyscript, state-pyscript-headers, state-pyscript-version
+#import "./html_utils.typ": get-css, get-js, additionnal-head-tags
+#import "./pyscript.typ": state-use-pyscript, state-pyscript-data-list, state-pyscript-version
/// Generate the html
element for the page.
#let html-head(
@@ -95,20 +95,25 @@
}
}}
+ html.style(get-css())
+ html.script(get-js())
+ context for tag in additionnal-head-tags.final() {
+ tag
+ }
+
context if state-use-pyscript.final() {
assert(
state-pyscript-version.final() != none,
message: "Cannot run python script: pyscript-version is not set"
)
assert(
- state-pyscript-version.final() in state-pyscript-headers.final(),
+ state-pyscript-version.final() in state-pyscript-data-list.final(),
message: "Cannot run python script: urls for " + state-pyscript-version.final() + " not set in pyscript-urls"
)
- state-pyscript-headers.final().at(state-pyscript-version.final())
+ let pyscript-data = state-pyscript-data-list.final().at(state-pyscript-version.final())
+ html.script(type: "module", src: pyscript-data.core-js-url)
+ pyscript-data.additionnal-head-tags
}
- html.style(get-css())
- html.script(get-js())
-
})
}
diff --git a/lib/html_utils.typ b/lib/html_utils.typ
index 6531b25..e90ead5 100644
--- a/lib/html_utils.typ
+++ b/lib/html_utils.typ
@@ -3,6 +3,7 @@
#let css-list = state("css-list", ())
#let js-list = state("js-list", ())
+#let additionnal-head-tags = state("additionnal-head-tags", ())
/// Add string `css` to `css-list` if not already present
#let add-css(css) = context {
@@ -14,6 +15,11 @@
js-list.update(x => if js in x { x } else { x + (js,) })
}
+/// Add additionnal html tag to insert in
+#let add-tag-in-head(tag) = context {
+ additionnal-head-tags.update(x => if tag in x { x } else { x + (tag, ) })
+}
+
/// Concatenate all css found in css-list at the end of the document
#let get-css() = context {
css-list.final().join("\n\n")
diff --git a/lib/main.typ b/lib/main.typ
index 8d79a72..6de6b54 100644
--- a/lib/main.typ
+++ b/lib/main.typ
@@ -2,7 +2,7 @@
#import "./html_body.typ": html-body
#import "./html_utils.typ": html-show
#import "./summary.typ": summary, card-list
-#import "./pyscript.typ": state-use-pyscript, state-pyscript-headers, state-pyscript-version, state-pyscript-interpreters, state-pyscript-default-interpreter, pyscript-show
+#import "./pyscript.typ": state-use-pyscript, state-pyscript-data-list, state-pyscript-version, state-pyscript-interpreters, state-pyscript-default-interpreter, pyscript-show, pyscript-data
#import "./rss.typ": rss
#import "./icons.typ"
@@ -32,8 +32,9 @@
stylesheets: (),
/// List of related sites for metadata
me-links: (),
- /// Dictionnary of available tags to add in header for each versions of pyscript
- pyscript-headers: (:),
+ /// Dictionnary of available pyscript-data for each versions of pyscript
+ /// expected in the form of ("": pyscript-data-list("", { html.link(...) }), ...)
+ pyscript-data-list: (:),
/// Dictionnary of available python version to add in header for each pyscript interpreter
pyscript-interpreters: (:),
/// Pyscript version to use
@@ -57,7 +58,7 @@
) = {
assert(type(url) == str, message: "A page must have an url")
context {
- state-pyscript-headers.update(x => pyscript-headers)
+ state-pyscript-data-list.update(x => pyscript-data-list)
state-pyscript-interpreters.update(x => pyscript-interpreters)
state-pyscript-version.update(x => pyscript-version)
state-pyscript-default-interpreter.update(x => pyscript-default-interpreter)
diff --git a/lib/pyscript.typ b/lib/pyscript.typ
index 0788d6e..9c6967b 100644
--- a/lib/pyscript.typ
+++ b/lib/pyscript.typ
@@ -1,8 +1,22 @@
+#import "./html_utils.typ": add-tag-in-head
+
#let state-use-pyscript = state("state-use-pyscript", false)
#let state-pyscript-version = state("state-pyscript-version", none)
-#let state-pyscript-headers = state("state-pyscript-headers", (:))
+#let state-pyscript-data-list = state("state-pyscript-data-list", (:))
#let state-pyscript-interpreters = state("state-pyscript-interpreters", (:))
#let state-pyscript-default-interpreter = state("state-pyscript-default-interpreter", none)
+#let state-pyscript-canvas-ids = state("state-pyscript-canvas-ids", ())
+
+/// Define data needed to load a version of pyscript
+#let pyscript-data(
+ /// Url to `core.js`
+ core-js-url,
+ /// Additionnal tags to add to , like mini-coi of core.css
+ additionnal-head-tags: {},
+) = (
+ core-js-url: core-js-url,
+ additionnal-head-tags: additionnal-head-tags
+)
#let get-pep723(script) = {
script.find(regex(
@@ -31,81 +45,131 @@
metadata = (:)
}
state-use-pyscript.update(x => true)
- context {
- let config = metadata.at("tool", default: (:)).at("pyscript", default: (:))
+ let config = metadata.at("tool", default: (:)).at("pyscript", default: (:))
- let pyscript-config = (:)
- // Package dependencies
- if "dependencies" in metadata {
- pyscript-config.insert("packages", metadata.at("dependencies"))
- }
- // Files stetup
- if "files" in config {
- pyscript-config.insert("files", config.at("files"))
- }
- // Interpreteur selection
- if "interpreter" in config {
- pyscript-config.insert("interpreter", config.at("interpreter"))
- } else if state-pyscript-default-interpreter.final() != none {
- assert(
- state-pyscript-default-interpreter.final() in state-pyscript-interpreters.final(),
- message: state-pyscript-default-interpreter.final() + " is not in pyscript-interpreters",
- )
- pyscript-config.insert("interpreter", state-pyscript-interpreters.final().at(state-pyscript-default-interpreter.final()))
- }
-
- let attrs = (
- type: "py"
+ let pyscript-config = (:)
+ // Package dependencies
+ if "dependencies" in metadata {
+ pyscript-config.insert("packages", metadata.at("dependencies"))
+ }
+ // Files stetup
+ if "files" in config {
+ pyscript-config.insert("files", config.at("files"))
+ }
+ // Interpreteur selection
+ if "interpreter" in config {
+ pyscript-config.insert("interpreter", config.at("interpreter"))
+ } else if state-pyscript-default-interpreter.final() != none {
+ assert(
+ state-pyscript-default-interpreter.final() in state-pyscript-interpreters.final(),
+ message: state-pyscript-default-interpreter.final() + " is not in pyscript-interpreters",
)
- let default_val_terminal = true
- let default_val_worker = true
- let default_val_canvas = none
- // TODO: canvas: can only be used once by page, need inserting canvas html tag and pyscript hook to link it to pyodide
- if config.at("pygame", default: false) {
- default_val_terminal = false
- default_val_worker = false
- default_val_canvas = "canvas"
- }
- if config.at("terminal", default: true) {
- attrs.insert("terminal", "")
- }
- if config.at("worker", default: true) {
- attrs.insert("worker", "")
- }
- if config.at("canvas", default: default_val_canvas) not in (none, false) {
- attrs.insert("canvas", config.at("canvas", default: default_val_canvas))
- }
+ pyscript-config.insert("interpreter", state-pyscript-interpreters.final().at(state-pyscript-default-interpreter.final()))
+ }
- if pyscript-config != (:) {
- attrs.insert("config", json.encode(pyscript-config))
- }
-
- let script = it.text;
- if config.at("repl", default: false) {
- script = "import code\n" + script + "\ncode.interact(banner='', local=globals())"
- }
- let displayed-code = it.text;
- if config.at("hide-meta", default: false) {
- displayed-code = displayed-code.replace(get-pep723(displayed-code), "").trim("\n")
- }
+ let attrs = (
+ type: "py"
+ )
+ let default-val-terminal = true
+ let default-val-worker = true
+ let default-val-canvas = none
+ if config.at("pygame", default: false) {
+ default-val-terminal = false
+ default-val-worker = false
+ default-val-canvas = "canvas"
+ }
+ if config.at("terminal", default: default-val-terminal) {
+ attrs.insert("terminal", "")
+ }
+ if config.at("worker", default: default-val-worker) {
+ attrs.insert("worker", "")
+ }
+ let canvas-attr = config.at("canvas", default: default-val-canvas)
+ if canvas-attr == false {
+ canvas-attr = none
+ }
+ if canvas-attr == true {
+ canvas-attr = "canvas"
+ }
+ if canvas-attr != none {
+ attrs.insert("canvas", canvas-attr)
- if not config.at("hide-code", default: false) {
- raw(
- displayed-code,
- block: true,
- lang: "python",
- align: it.align,
- syntaxes: it.syntaxes,
- theme: it.theme,
- tab-size: it.tab-size,
- )
- }
+ assert(
+ canvas-attr not in state-pyscript-canvas-ids.at(here()),
+ message: (
+ "Can not have multiple canvas with the same name. Note that SDL2 has issues working with canvas",
+ "with an id different than 'canvas', meaning that it is unadvised to have more than one script",
+ "using SDL2 by page.",
+ "(tool.pyscript.pygame = true set the default value for the SDL2 canvas to 'canvas' instead of none)"
+ ).join(" ")
+ )
+ state-pyscript-canvas-ids.update(canvas-ids => canvas-ids + (canvas-attr,))
+ }
- html.elem(
- "script",
- attrs: attrs,
- script
+ if pyscript-config != (:) {
+ attrs.insert("config", json.encode(pyscript-config))
+ }
+
+ let script = it.text;
+ if config.at("repl", default: false) {
+ script = "import code\n" + script + "\ncode.interact(banner='', local=globals())"
+ }
+ let displayed-code = it.text;
+ if config.at("hide-meta", default: false) {
+ displayed-code = displayed-code.replace(get-pep723(displayed-code), "").trim("\n")
+ }
+
+ if not config.at("hide-code", default: false) {
+ raw(
+ displayed-code,
+ block: true,
+ lang: "python",
+ align: it.align,
+ syntaxes: it.syntaxes,
+ theme: it.theme,
+ tab-size: it.tab-size,
+ )
+ }
+
+ html.elem(
+ "script",
+ attrs: attrs,
+ script
+ )
+ if canvas-attr != none {
+ html.elem("canvas", attrs: ("id": canvas-attr))
+
+ let core-js-url = state-pyscript-data-list.final().at(state-pyscript-version.final()).core-js-url
+ add-tag-in-head(
+ html.script(
+ type: "module",
+ ```
+ import { hooks } from "";
+ hooks.main.onReady.add((wrap, script) => {
+ if (script.hasAttribute("canvas")) {
+ const target = script.getAttribute("canvas");
+ const canvas = document.getElementById(target);
+ wrap.interpreter.canvas.setCanvas2D(canvas);
+ }
+ });
+ ```.text.replace("", core-js-url)
+ )
)
}
- //raw(block: true, lang: "json", json.encode(metadata))
}
+ // also need pyscript hook to link canvas to pyodide:
+ //
+//
+ //raw(block: true, lang: "json", json.encode(metadata))
diff --git a/test_template/isn_s_cube-0.1.0-py3-none-any.whl b/test_template/isn_s_cube-0.1.0-py3-none-any.whl
new file mode 100644
index 0000000..53c14cc
Binary files /dev/null and b/test_template/isn_s_cube-0.1.0-py3-none-any.whl differ
diff --git a/test_template/main.typ b/test_template/main.typ
index e42bc0a..6d14821 100644
--- a/test_template/main.typ
+++ b/test_template/main.typ
@@ -61,12 +61,14 @@
icon: "https://jean-marie.mineau.eu/website_assets/platypus.png",
// Pyscript:
- pyscript-headers: (
- "remote-2026.3.1": {
- html.elem("script", attrs: (src: "./mini-coi.js"))
- html.elem("script", attrs: (type: "module", src: "https://pyscript.net/releases/2026.3.1/core.js"))
- html.elem("link", attrs: (rel: "stylesheet", href: "https://pyscript.net/releases/2026.3.1/core.css"))
- },
+ pyscript-data-list: (
+ "remote-2026.3.1": pyscript-data(
+ "https://pyscript.net/releases/2026.3.1/core.js",
+ additionnal-head-tags: {
+ html.elem("script", attrs: (src: "./mini-coi.js"))
+ html.elem("link", attrs: (rel: "stylesheet", href: "https://pyscript.net/releases/2026.3.1/core.css"))
+ },
+ )
),
pyscript-version: "remote-2026.3.1",
)
@@ -125,7 +127,7 @@ pprint([(k, v["title"]) for k, v in data.items()][:10])
# [tool.pyscript.files]
# "https://peps.python.org/api/peps.json" = "./peps.json"
# ///
-
+# setting tool.pyscript.hide-meta to true will hide the `/// script` section
import json
from rich.pretty import pprint
@@ -136,6 +138,19 @@ pprint([(k, v["title"]) for k, v in data.items()][:10])
# Inline script metadata
```
+```python-run
+# /// script
+# dependencies = [
+# "pygame-ce",
+# "./isn_s_cube-0.1.0-py3-none-any.whl"
+# ]
+# [tool.pyscript]
+# pygame = true
+# ///
+from isn_s_cube import wasm
+await wasm()
+```
+
#summ.card
Test, `this is not a code block`, end test.