add support for pygame scripts

This commit is contained in:
Jean-Marie 'Histausse' Mineau 2026-06-07 15:57:09 +02:00
parent fe4b003971
commit 3be22c5654
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
6 changed files with 180 additions and 89 deletions

View file

@ -1,6 +1,6 @@
#import "./custom_html.typ" as chtml #import "./custom_html.typ" as chtml
#import "./html_utils.typ": get-css, get-js #import "./html_utils.typ": get-css, get-js, additionnal-head-tags
#import "./pyscript.typ": state-use-pyscript, state-pyscript-headers, state-pyscript-version #import "./pyscript.typ": state-use-pyscript, state-pyscript-data-list, state-pyscript-version
/// Generate the html <head> element for the page. /// Generate the html <head> element for the page.
#let html-head( #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() { context if state-use-pyscript.final() {
assert( assert(
state-pyscript-version.final() != none, state-pyscript-version.final() != none,
message: "Cannot run python script: pyscript-version is not set" message: "Cannot run python script: pyscript-version is not set"
) )
assert( 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" 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())
}) })
} }

View file

@ -3,6 +3,7 @@
#let css-list = state("css-list", ()) #let css-list = state("css-list", ())
#let js-list = state("js-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 /// Add string `css` to `css-list` if not already present
#let add-css(css) = context { #let add-css(css) = context {
@ -14,6 +15,11 @@
js-list.update(x => if js in x { x } else { x + (js,) }) js-list.update(x => if js in x { x } else { x + (js,) })
} }
/// Add additionnal html tag to insert in <head>
#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 /// Concatenate all css found in css-list at the end of the document
#let get-css() = context { #let get-css() = context {
css-list.final().join("\n\n") css-list.final().join("\n\n")

View file

@ -2,7 +2,7 @@
#import "./html_body.typ": html-body #import "./html_body.typ": html-body
#import "./html_utils.typ": html-show #import "./html_utils.typ": html-show
#import "./summary.typ": summary, card-list #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 "./rss.typ": rss
#import "./icons.typ" #import "./icons.typ"
@ -32,8 +32,9 @@
stylesheets: (), stylesheets: (),
/// List of related sites for metadata /// List of related sites for metadata
me-links: (), me-links: (),
/// Dictionnary of available tags to add in header for each versions of pyscript /// Dictionnary of available pyscript-data for each versions of pyscript
pyscript-headers: (:), /// expected in the form of ("<version>": pyscript-data-list("<url>", { html.link(...) }), ...)
pyscript-data-list: (:),
/// Dictionnary of available python version to add in header for each pyscript interpreter /// Dictionnary of available python version to add in header for each pyscript interpreter
pyscript-interpreters: (:), pyscript-interpreters: (:),
/// Pyscript version to use /// Pyscript version to use
@ -57,7 +58,7 @@
) = { ) = {
assert(type(url) == str, message: "A page must have an url") assert(type(url) == str, message: "A page must have an url")
context { 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-interpreters.update(x => pyscript-interpreters)
state-pyscript-version.update(x => pyscript-version) state-pyscript-version.update(x => pyscript-version)
state-pyscript-default-interpreter.update(x => pyscript-default-interpreter) state-pyscript-default-interpreter.update(x => pyscript-default-interpreter)

View file

@ -1,8 +1,22 @@
#import "./html_utils.typ": add-tag-in-head
#let state-use-pyscript = state("state-use-pyscript", false) #let state-use-pyscript = state("state-use-pyscript", false)
#let state-pyscript-version = state("state-pyscript-version", none) #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-interpreters = state("state-pyscript-interpreters", (:))
#let state-pyscript-default-interpreter = state("state-pyscript-default-interpreter", none) #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 <head>, 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) = { #let get-pep723(script) = {
script.find(regex( script.find(regex(
@ -31,7 +45,6 @@
metadata = (:) metadata = (:)
} }
state-use-pyscript.update(x => true) 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 = (:) let pyscript-config = (:)
@ -57,23 +70,40 @@
let attrs = ( let attrs = (
type: "py" type: "py"
) )
let default_val_terminal = true let default-val-terminal = true
let default_val_worker = true let default-val-worker = true
let default_val_canvas = none 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) { if config.at("pygame", default: false) {
default_val_terminal = false default-val-terminal = false
default_val_worker = false default-val-worker = false
default_val_canvas = "canvas" default-val-canvas = "canvas"
} }
if config.at("terminal", default: true) { if config.at("terminal", default: default-val-terminal) {
attrs.insert("terminal", "") attrs.insert("terminal", "")
} }
if config.at("worker", default: true) { if config.at("worker", default: default-val-worker) {
attrs.insert("worker", "") attrs.insert("worker", "")
} }
if config.at("canvas", default: default_val_canvas) not in (none, false) { let canvas-attr = config.at("canvas", default: default-val-canvas)
attrs.insert("canvas", 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)
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,))
} }
if pyscript-config != (:) { if pyscript-config != (:) {
@ -106,6 +136,40 @@
attrs: attrs, attrs: attrs,
script 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 "<CORE-JS-URL>";
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>", core-js-url)
)
)
}
}
// also need pyscript hook to link canvas to pyodide:
//
// <script type="module">
// // Pyscrypt has a py-game type that handle this, but it forces the interpreter to be
// // from the cdn, which is quite meh.
// // So I do the setup manually (https://docs.pyscript.net/2026.3.1/user-guide/pygame-ce/)
// import { hooks } from "./pyscript/core.js";
// 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);
// }
// });
// </script>
//raw(block: true, lang: "json", json.encode(metadata)) //raw(block: true, lang: "json", json.encode(metadata))
}

Binary file not shown.

View file

@ -61,12 +61,14 @@
icon: "https://jean-marie.mineau.eu/website_assets/platypus.png", icon: "https://jean-marie.mineau.eu/website_assets/platypus.png",
// Pyscript: // Pyscript:
pyscript-headers: ( pyscript-data-list: (
"remote-2026.3.1": { "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("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")) html.elem("link", attrs: (rel: "stylesheet", href: "https://pyscript.net/releases/2026.3.1/core.css"))
}, },
)
), ),
pyscript-version: "remote-2026.3.1", pyscript-version: "remote-2026.3.1",
) )
@ -125,7 +127,7 @@ pprint([(k, v["title"]) for k, v in data.items()][:10])
# [tool.pyscript.files] # [tool.pyscript.files]
# "https://peps.python.org/api/peps.json" = "./peps.json" # "https://peps.python.org/api/peps.json" = "./peps.json"
# /// # ///
# setting tool.pyscript.hide-meta to true will hide the `/// script` section
import json import json
from rich.pretty import pprint from rich.pretty import pprint
@ -136,6 +138,19 @@ pprint([(k, v["title"]) for k, v in data.items()][:10])
# Inline script metadata # 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 #summ.card
Test, `this is not a code block`, end test. Test, `this is not a code block`, end test.