add support for pygame scripts
This commit is contained in:
parent
fe4b003971
commit
3be22c5654
6 changed files with 180 additions and 89 deletions
|
|
@ -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())
|
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
204
lib/pyscript.typ
204
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-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,81 +45,131 @@
|
||||||
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 = (:)
|
||||||
// Package dependencies
|
// Package dependencies
|
||||||
if "dependencies" in metadata {
|
if "dependencies" in metadata {
|
||||||
pyscript-config.insert("packages", metadata.at("dependencies"))
|
pyscript-config.insert("packages", metadata.at("dependencies"))
|
||||||
}
|
}
|
||||||
// Files stetup
|
// Files stetup
|
||||||
if "files" in config {
|
if "files" in config {
|
||||||
pyscript-config.insert("files", config.at("files"))
|
pyscript-config.insert("files", config.at("files"))
|
||||||
}
|
}
|
||||||
// Interpreteur selection
|
// Interpreteur selection
|
||||||
if "interpreter" in config {
|
if "interpreter" in config {
|
||||||
pyscript-config.insert("interpreter", config.at("interpreter"))
|
pyscript-config.insert("interpreter", config.at("interpreter"))
|
||||||
} else if state-pyscript-default-interpreter.final() != none {
|
} else if state-pyscript-default-interpreter.final() != none {
|
||||||
assert(
|
assert(
|
||||||
state-pyscript-default-interpreter.final() in state-pyscript-interpreters.final(),
|
state-pyscript-default-interpreter.final() in state-pyscript-interpreters.final(),
|
||||||
message: state-pyscript-default-interpreter.final() + " is not in pyscript-interpreters",
|
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 default_val_terminal = true
|
pyscript-config.insert("interpreter", state-pyscript-interpreters.final().at(state-pyscript-default-interpreter.final()))
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
if pyscript-config != (:) {
|
let attrs = (
|
||||||
attrs.insert("config", json.encode(pyscript-config))
|
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)
|
||||||
|
|
||||||
let script = it.text;
|
assert(
|
||||||
if config.at("repl", default: false) {
|
canvas-attr not in state-pyscript-canvas-ids.at(here()),
|
||||||
script = "import code\n" + script + "\ncode.interact(banner='', local=globals())"
|
message: (
|
||||||
}
|
"Can not have multiple canvas with the same name. Note that SDL2 has issues working with canvas",
|
||||||
let displayed-code = it.text;
|
"with an id different than 'canvas', meaning that it is unadvised to have more than one script",
|
||||||
if config.at("hide-meta", default: false) {
|
"using SDL2 by page.",
|
||||||
displayed-code = displayed-code.replace(get-pep723(displayed-code), "").trim("\n")
|
"(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 not config.at("hide-code", default: false) {
|
if pyscript-config != (:) {
|
||||||
raw(
|
attrs.insert("config", json.encode(pyscript-config))
|
||||||
displayed-code,
|
}
|
||||||
block: true,
|
|
||||||
lang: "python",
|
|
||||||
align: it.align,
|
|
||||||
syntaxes: it.syntaxes,
|
|
||||||
theme: it.theme,
|
|
||||||
tab-size: it.tab-size,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
html.elem(
|
let script = it.text;
|
||||||
"script",
|
if config.at("repl", default: false) {
|
||||||
attrs: attrs,
|
script = "import code\n" + script + "\ncode.interact(banner='', local=globals())"
|
||||||
script
|
}
|
||||||
|
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 "<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)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
//raw(block: true, lang: "json", json.encode(metadata))
|
|
||||||
}
|
}
|
||||||
|
// 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))
|
||||||
|
|
|
||||||
BIN
test_template/isn_s_cube-0.1.0-py3-none-any.whl
Normal file
BIN
test_template/isn_s_cube-0.1.0-py3-none-any.whl
Normal file
Binary file not shown.
|
|
@ -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(
|
||||||
html.elem("script", attrs: (src: "./mini-coi.js"))
|
"https://pyscript.net/releases/2026.3.1/core.js",
|
||||||
html.elem("script", attrs: (type: "module", src: "https://pyscript.net/releases/2026.3.1/core.js"))
|
additionnal-head-tags: {
|
||||||
html.elem("link", attrs: (rel: "stylesheet", href: "https://pyscript.net/releases/2026.3.1/core.css"))
|
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",
|
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.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue