@pyscript/core 0.2.10 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dev.cjs +31 -0
- package/dist/core.js +4 -3
- package/dist/core.js.map +1 -1
- package/dist/error-96hMSEw8.js +2 -0
- package/dist/error-96hMSEw8.js.map +1 -0
- package/dist/py-terminal-nF9DKGa8.js +2 -0
- package/dist/py-terminal-nF9DKGa8.js.map +1 -0
- package/dist.zip +0 -0
- package/docs/README.md +1 -1
- package/package.json +17 -6
- package/src/config.js +1 -1
- package/src/core.js +143 -129
- package/src/hooks.js +85 -22
- package/src/plugins/error.js +2 -2
- package/src/plugins/py-terminal.js +158 -0
- package/src/plugins-helper.js +26 -0
- package/src/plugins.js +1 -0
- package/src/stdlib.js +12 -4
- package/types/core.d.ts +3 -1
- package/types/hooks.d.ts +37 -12
- package/types/plugins/py-terminal.d.ts +2 -0
- package/types/plugins-helper.d.ts +2 -0
- package/types/plugins.d.ts +4 -3
- package/dist/error-0_IesYFM.js +0 -2
- package/dist/error-0_IesYFM.js.map +0 -1
@@ -0,0 +1,2 @@
|
|
1
|
+
import{hooks as e}from"./core.js";function o(e){const o=document.createElement("div");o.className="py-error",o.textContent=e,o.style.cssText="\n border: 1px solid red;\n background: #ffdddd;\n color: black;\n font-family: courier, monospace;\n white-space: pre;\n overflow-x: auto;\n padding: 8px;\n margin-top: 8px;\n ",document.body.append(o)}e.main.onReady.add((function n(r){e.main.onReady.delete(n);const{stderr:t}=r.io;r.io.stderr=(e,...n)=>(o(e.message||e),t(e,...n)),addEventListener("error",(({message:e})=>{e.startsWith("Uncaught PythonError")&&o(e)}))}));export{o as notify};
|
2
|
+
//# sourceMappingURL=error-96hMSEw8.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"error-96hMSEw8.js","sources":["../src/plugins/error.js"],"sourcesContent":["// PyScript Error Plugin\nimport { hooks } from \"../core.js\";\n\nhooks.main.onReady.add(function override(pyScript) {\n // be sure this override happens only once\n hooks.main.onReady.delete(override);\n\n // trap generic `stderr` to propagate to it regardless\n const { stderr } = pyScript.io;\n\n // override it with our own logic\n pyScript.io.stderr = (error, ...rest) => {\n notify(error.message || error);\n // let other plugins or stderr hook, if any, do the rest\n return stderr(error, ...rest);\n };\n\n // be sure uncaught Python errors are also visible\n addEventListener(\"error\", ({ message }) => {\n if (message.startsWith(\"Uncaught PythonError\")) notify(message);\n });\n});\n\n// Error hook utilities\n\n// Custom function to show notifications\n\n/**\n * Add a banner to the top of the page, notifying the user of an error\n * @param {string} message\n */\nexport function notify(message) {\n const div = document.createElement(\"div\");\n div.className = \"py-error\";\n div.textContent = message;\n div.style.cssText = `\n border: 1px solid red;\n background: #ffdddd;\n color: black;\n font-family: courier, monospace;\n white-space: pre;\n overflow-x: auto;\n padding: 8px;\n margin-top: 8px;\n `;\n document.body.append(div);\n}\n"],"names":["notify","message","div","document","createElement","className","textContent","style","cssText","body","append","hooks","main","onReady","add","override","pyScript","delete","stderr","io","error","rest","addEventListener","startsWith"],"mappings":"kCA+BO,SAASA,EAAOC,GACnB,MAAMC,EAAMC,SAASC,cAAc,OACnCF,EAAIG,UAAY,WAChBH,EAAII,YAAcL,EAClBC,EAAIK,MAAMC,QAAU,6MAUpBL,SAASM,KAAKC,OAAOR,EACzB,CA3CAS,EAAMC,KAAKC,QAAQC,KAAI,SAASC,EAASC,GAErCL,EAAMC,KAAKC,QAAQI,OAAOF,GAG1B,MAAMG,OAAEA,GAAWF,EAASG,GAG5BH,EAASG,GAAGD,OAAS,CAACE,KAAUC,KAC5BrB,EAAOoB,EAAMnB,SAAWmB,GAEjBF,EAAOE,KAAUC,IAI5BC,iBAAiB,SAAS,EAAGrB,cACrBA,EAAQsB,WAAW,yBAAyBvB,EAAOC,EAAQ,GAEvE"}
|
@@ -0,0 +1,2 @@
|
|
1
|
+
import{TYPES as e,hooks as t}from"./core.js";const r="https://cdn.jsdelivr.net/npm/xterm",n="5.3.0",o=[...e.keys()].map((e=>`script[type="${e}"][terminal],${e}-script[terminal]`)).join(","),i=async()=>{const e=document.querySelectorAll(o);if(!e.length)return;e.length>1&&console.warn("Unable to satisfy multiple terminals"),s.disconnect();const[i]=e;if(i.matches('script[type="mpy"],mpy-script'))throw new Error("Unsupported terminal");document.querySelector(`link[href^="${r}"]`)||document.head.append(Object.assign(document.createElement("link"),{rel:"stylesheet",href:`${r}@${n}/css/xterm.min.css`}));const[{Terminal:d},{Readline:a}]=await Promise.all([import(`${r}@${n}/+esm`),import(`${r}-readline@1.1.1/+esm`)]),c=new a,l=e=>{let t=i;const r=i.getAttribute("target");if(r){if(t=document.getElementById(r)||document.querySelector(r),!t)throw new Error(`Unknown target ${r}`)}else t=document.createElement(`${i.type}-terminal`),t.style.display="block",i.after(t);const n=new d({theme:{background:"#191A19",foreground:"#F5F2E7"},...e});n.loadAddon(c),n.open(t),n.focus()};if(i.hasAttribute("worker")){const e=({interpreter:e},{sync:t})=>{t.pyterminal_drop_hooks();const r=new TextDecoder,n={isatty:!0,write:e=>(t.pyterminal_write(r.decode(e)),e.length)};e.setStdout(n),e.setStderr(n)},r="\n import builtins\n from pyscript import sync as _sync\n\n builtins.input = lambda prompt: _sync.pyterminal_read(prompt)\n ",n="\n import code as _code\n _code.interact()\n ";t.main.onWorker.add((function o(i,s){t.main.onWorker.delete(o),l({disableStdin:!1,cursorBlink:!0,cursorStyle:"block"}),s.sync.pyterminal_read=c.read.bind(c),s.sync.pyterminal_write=c.write.bind(c),s.sync.pyterminal_drop_hooks=()=>{t.worker.onReady.delete(e),t.worker.codeBeforeRun.delete(r),t.worker.codeAfterRun.delete(n)}})),t.worker.onReady.add(e),t.worker.codeBeforeRun.add(r),t.worker.codeAfterRun.add(n)}else t.main.onReady.add((function e({io:r}){console.warn("py-terminal is read only on main thread"),t.main.onReady.delete(e),l({disableStdin:!0,cursorBlink:!1,cursorStyle:"underline"}),r.stdout=e=>{c.write(`${e}\n`)},r.stderr=e=>{c.write(`${e.message||e}\n`)}}))},s=new MutationObserver(i);s.observe(document,{childList:!0,subtree:!0});var d=i();export{d as default};
|
2
|
+
//# sourceMappingURL=py-terminal-nF9DKGa8.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"py-terminal-nF9DKGa8.js","sources":["../src/plugins/py-terminal.js"],"sourcesContent":["// PyScript py-terminal plugin\nimport { TYPES, hooks } from \"../core.js\";\n\nconst CDN = \"https://cdn.jsdelivr.net/npm/xterm\";\nconst XTERM = \"5.3.0\";\nconst XTERM_READLINE = \"1.1.1\";\nconst SELECTOR = [...TYPES.keys()]\n .map((type) => `script[type=\"${type}\"][terminal],${type}-script[terminal]`)\n .join(\",\");\n\nconst pyTerminal = async () => {\n const terminals = document.querySelectorAll(SELECTOR);\n\n // no results will look further for runtime nodes\n if (!terminals.length) return;\n\n // we currently support only one terminal as in \"classic\"\n if (terminals.length > 1)\n console.warn(\"Unable to satisfy multiple terminals\");\n\n // if we arrived this far, let's drop the MutationObserver\n mo.disconnect();\n\n const [element] = terminals;\n // hopefully to be removed in the near future!\n if (element.matches('script[type=\"mpy\"],mpy-script'))\n throw new Error(\"Unsupported terminal\");\n\n // import styles once and lazily (only on valid terminal)\n if (!document.querySelector(`link[href^=\"${CDN}\"]`)) {\n document.head.append(\n Object.assign(document.createElement(\"link\"), {\n rel: \"stylesheet\",\n href: `${CDN}@${XTERM}/css/xterm.min.css`,\n }),\n );\n }\n\n // lazy load these only when a valid terminal is found\n const [{ Terminal }, { Readline }] = await Promise.all([\n import(/* webpackIgnore: true */ `${CDN}@${XTERM}/+esm`),\n import(\n /* webpackIgnore: true */ `${CDN}-readline@${XTERM_READLINE}/+esm`\n ),\n ]);\n\n const readline = new Readline();\n\n // common main thread initialization for both worker\n // or main case, bootstrapping the terminal on its target\n const init = (options) => {\n let target = element;\n const selector = element.getAttribute(\"target\");\n if (selector) {\n target =\n document.getElementById(selector) ||\n document.querySelector(selector);\n if (!target) throw new Error(`Unknown target ${selector}`);\n } else {\n target = document.createElement(`${element.type}-terminal`);\n target.style.display = \"block\";\n element.after(target);\n }\n const terminal = new Terminal({\n theme: {\n background: \"#191A19\",\n foreground: \"#F5F2E7\",\n },\n ...options,\n });\n terminal.loadAddon(readline);\n terminal.open(target);\n terminal.focus();\n };\n\n // branch logic for the worker\n if (element.hasAttribute(\"worker\")) {\n // when the remote thread onReady triggers:\n // setup the interpreter stdout and stderr\n const workerReady = ({ interpreter }, { sync }) => {\n sync.pyterminal_drop_hooks();\n const decoder = new TextDecoder();\n const generic = {\n isatty: true,\n write(buffer) {\n sync.pyterminal_write(decoder.decode(buffer));\n return buffer.length;\n },\n };\n interpreter.setStdout(generic);\n interpreter.setStderr(generic);\n };\n\n // run in python code able to replace builtins.input\n // using the xworker.sync non blocking prompt\n const codeBefore = `\n import builtins\n from pyscript import sync as _sync\n\n builtins.input = lambda prompt: _sync.pyterminal_read(prompt)\n `;\n\n // at the end of the code, make the terminal interactive\n const codeAfter = `\n import code as _code\n _code.interact()\n `;\n\n // add a hook on the main thread to setup all sync helpers\n // also bootstrapping the XTerm target on main\n hooks.main.onWorker.add(function worker(_, xworker) {\n hooks.main.onWorker.delete(worker);\n init({\n disableStdin: false,\n cursorBlink: true,\n cursorStyle: \"block\",\n });\n xworker.sync.pyterminal_read = readline.read.bind(readline);\n xworker.sync.pyterminal_write = readline.write.bind(readline);\n // allow a worker to drop main thread hooks ASAP\n xworker.sync.pyterminal_drop_hooks = () => {\n hooks.worker.onReady.delete(workerReady);\n hooks.worker.codeBeforeRun.delete(codeBefore);\n hooks.worker.codeAfterRun.delete(codeAfter);\n };\n });\n\n // setup remote thread JS/Python code for whenever the\n // worker is ready to become a terminal\n hooks.worker.onReady.add(workerReady);\n hooks.worker.codeBeforeRun.add(codeBefore);\n hooks.worker.codeAfterRun.add(codeAfter);\n } else {\n // in the main case, just bootstrap XTerm without\n // allowing any input as that's not possible / awkward\n hooks.main.onReady.add(function main({ io }) {\n console.warn(\"py-terminal is read only on main thread\");\n hooks.main.onReady.delete(main);\n init({\n disableStdin: true,\n cursorBlink: false,\n cursorStyle: \"underline\",\n });\n io.stdout = (value) => {\n readline.write(`${value}\\n`);\n };\n io.stderr = (error) => {\n readline.write(`${error.message || error}\\n`);\n };\n });\n }\n};\n\nconst mo = new MutationObserver(pyTerminal);\nmo.observe(document, { childList: true, subtree: true });\n\n// try to check the current document ASAP\nexport default pyTerminal();\n"],"names":["CDN","XTERM","SELECTOR","TYPES","keys","map","type","join","pyTerminal","async","terminals","document","querySelectorAll","length","console","warn","mo","disconnect","element","matches","Error","querySelector","head","append","Object","assign","createElement","rel","href","Terminal","Readline","Promise","all","import","readline","init","options","target","selector","getAttribute","getElementById","style","display","after","terminal","theme","background","foreground","loadAddon","open","focus","hasAttribute","workerReady","interpreter","sync","pyterminal_drop_hooks","decoder","TextDecoder","generic","isatty","write","buffer","pyterminal_write","decode","setStdout","setStderr","codeBefore","codeAfter","hooks","main","onWorker","add","worker","_","xworker","delete","disableStdin","cursorBlink","cursorStyle","pyterminal_read","read","bind","onReady","codeBeforeRun","codeAfterRun","io","stdout","value","stderr","error","message","MutationObserver","observe","childList","subtree","pyTerminal$1"],"mappings":"6CAGA,MAAMA,EAAM,qCACNC,EAAQ,QAERC,EAAW,IAAIC,EAAMC,QACtBC,KAAKC,GAAS,gBAAgBA,iBAAoBA,uBAClDC,KAAK,KAEJC,EAAaC,UACf,MAAMC,EAAYC,SAASC,iBAAiBV,GAG5C,IAAKQ,EAAUG,OAAQ,OAGnBH,EAAUG,OAAS,GACnBC,QAAQC,KAAK,wCAGjBC,EAAGC,aAEH,MAAOC,GAAWR,EAElB,GAAIQ,EAAQC,QAAQ,iCAChB,MAAM,IAAIC,MAAM,wBAGfT,SAASU,cAAc,eAAerB,QACvCW,SAASW,KAAKC,OACVC,OAAOC,OAAOd,SAASe,cAAc,QAAS,CAC1CC,IAAK,aACLC,KAAM,GAAG5B,KAAOC,yBAM5B,OAAO4B,SAAEA,IAAYC,SAAEA,UAAoBC,QAAQC,IAAI,CACnDC,OAAiC,GAAGjC,KAAOC,UAC3CgC,OAC8B,GAAGjC,2BAI/BkC,EAAW,IAAIJ,EAIfK,EAAQC,IACV,IAAIC,EAASnB,EACb,MAAMoB,EAAWpB,EAAQqB,aAAa,UACtC,GAAID,GAIA,GAHAD,EACI1B,SAAS6B,eAAeF,IACxB3B,SAASU,cAAciB,IACtBD,EAAQ,MAAM,IAAIjB,MAAM,kBAAkBkB,UAE/CD,EAAS1B,SAASe,cAAc,GAAGR,EAAQZ,iBAC3C+B,EAAOI,MAAMC,QAAU,QACvBxB,EAAQyB,MAAMN,GAElB,MAAMO,EAAW,IAAIf,EAAS,CAC1BgB,MAAO,CACHC,WAAY,UACZC,WAAY,cAEbX,IAEPQ,EAASI,UAAUd,GACnBU,EAASK,KAAKZ,GACdO,EAASM,OAAO,EAIpB,GAAIhC,EAAQiC,aAAa,UAAW,CAGhC,MAAMC,EAAc,EAAGC,gBAAiBC,WACpCA,EAAKC,wBACL,MAAMC,EAAU,IAAIC,YACdC,EAAU,CACZC,QAAQ,EACRC,MAAMC,IACFP,EAAKQ,iBAAiBN,EAAQO,OAAOF,IAC9BA,EAAOhD,SAGtBwC,EAAYW,UAAUN,GACtBL,EAAYY,UAAUP,EAAQ,EAK5BQ,EAAa,uKAQbC,EAAY,6EAOlBC,EAAMC,KAAKC,SAASC,KAAI,SAASC,EAAOC,EAAGC,GACvCN,EAAMC,KAAKC,SAASK,OAAOH,GAC3BrC,EAAK,CACDyC,cAAc,EACdC,aAAa,EACbC,YAAa,UAEjBJ,EAAQpB,KAAKyB,gBAAkB7C,EAAS8C,KAAKC,KAAK/C,GAClDwC,EAAQpB,KAAKQ,iBAAmB5B,EAAS0B,MAAMqB,KAAK/C,GAEpDwC,EAAQpB,KAAKC,sBAAwB,KACjCa,EAAMI,OAAOU,QAAQP,OAAOvB,GAC5BgB,EAAMI,OAAOW,cAAcR,OAAOT,GAClCE,EAAMI,OAAOY,aAAaT,OAAOR,EAAU,CAE3D,IAIQC,EAAMI,OAAOU,QAAQX,IAAInB,GACzBgB,EAAMI,OAAOW,cAAcZ,IAAIL,GAC/BE,EAAMI,OAAOY,aAAab,IAAIJ,EACtC,MAGQC,EAAMC,KAAKa,QAAQX,KAAI,SAASF,GAAKgB,GAAEA,IACnCvE,QAAQC,KAAK,2CACbqD,EAAMC,KAAKa,QAAQP,OAAON,GAC1BlC,EAAK,CACDyC,cAAc,EACdC,aAAa,EACbC,YAAa,cAEjBO,EAAGC,OAAUC,IACTrD,EAAS0B,MAAM,GAAG2B,MAAU,EAEhCF,EAAGG,OAAUC,IACTvD,EAAS0B,MAAM,GAAG6B,EAAMC,SAAWD,MAAU,CAE7D,GACK,EAGCzE,EAAK,IAAI2E,iBAAiBnF,GAChCQ,EAAG4E,QAAQjF,SAAU,CAAEkF,WAAW,EAAMC,SAAS,IAGjD,IAAAC,EAAevF"}
|
package/dist.zip
ADDED
Binary file
|
package/docs/README.md
CHANGED
@@ -159,7 +159,7 @@ The commonly shared utilities are:
|
|
159
159
|
* **display** in both main and worker, refers to the good old `display` utility except:
|
160
160
|
* in the *main* it automatically uses the current script `target` to display content
|
161
161
|
* in the *worker* it still needs to know *where* to display content using the `target="dom-id"` named argument, as workers don't get a default target attached
|
162
|
-
* in both main and worker, the `append=
|
162
|
+
* in both main and worker, the `append=True` is the *default* behavior, which is inherited from the classic PyScript.
|
163
163
|
|
164
164
|
#### Extra main-only features
|
165
165
|
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@pyscript/core",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.3.1",
|
4
4
|
"type": "module",
|
5
5
|
"description": "PyScript",
|
6
6
|
"module": "./index.js",
|
@@ -20,9 +20,17 @@
|
|
20
20
|
},
|
21
21
|
"scripts": {
|
22
22
|
"server": "npx static-handler --coi .",
|
23
|
-
"build": "
|
23
|
+
"build": "npm run build:toml && npm run build:stdlib && npm run build:plugins && npm run build:core && eslint src/ && npm run ts && npm run test:mpy",
|
24
|
+
"build:core": "rm -rf dist && rollup --config rollup/core.config.js",
|
25
|
+
"build:plugins": "node rollup/plugins.cjs",
|
26
|
+
"build:stdlib": "node rollup/stdlib.cjs",
|
27
|
+
"build:toml": "node rollup/toml.cjs",
|
28
|
+
"test:mpy": "static-handler --coi . 2>/dev/null & SH_PID=$!; EXIT_CODE=0; playwright test --fully-parallel test/ || EXIT_CODE=$?; kill $SH_PID 2>/dev/null; exit $EXIT_CODE",
|
29
|
+
"dev": "node dev.cjs",
|
30
|
+
"release": "npm run build && npm run zip",
|
24
31
|
"size": "echo -e \"\\033[1mdist/*.js file size\\033[0m\"; for js in $(ls dist/*.js); do echo -e \"\\033[2m$js:\\033[0m $(cat $js | brotli | wc -c) bytes\"; done",
|
25
|
-
"ts": "tsc -p ."
|
32
|
+
"ts": "tsc -p .",
|
33
|
+
"zip": "zip -r dist.zip ./dist"
|
26
34
|
},
|
27
35
|
"keywords": [
|
28
36
|
"pyscript",
|
@@ -33,16 +41,19 @@
|
|
33
41
|
"dependencies": {
|
34
42
|
"@ungap/with-resolvers": "^0.1.0",
|
35
43
|
"basic-devtools": "^0.1.6",
|
36
|
-
"polyscript": "^0.
|
44
|
+
"polyscript": "^0.5.6",
|
37
45
|
"sticky-module": "^0.1.0",
|
46
|
+
"to-json-callback": "^0.1.1",
|
38
47
|
"type-checked-collections": "^0.1.7"
|
39
48
|
},
|
40
49
|
"devDependencies": {
|
50
|
+
"@playwright/test": "^1.39.0",
|
41
51
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
42
52
|
"@rollup/plugin-terser": "^0.4.4",
|
43
53
|
"@webreflection/toml-j0.4": "^1.1.3",
|
44
|
-
"
|
45
|
-
"
|
54
|
+
"chokidar": "^3.5.3",
|
55
|
+
"eslint": "^8.52.0",
|
56
|
+
"rollup": "^4.2.0",
|
46
57
|
"rollup-plugin-postcss": "^4.0.2",
|
47
58
|
"rollup-plugin-string": "^3.0.0",
|
48
59
|
"static-handler": "^0.4.3",
|
package/src/config.js
CHANGED
@@ -113,7 +113,7 @@ for (const [TYPE] of TYPES) {
|
|
113
113
|
value().then(({ notify }) => notify(error.message));
|
114
114
|
}
|
115
115
|
} else if (!parsed?.plugins?.includes(`!${key}`)) {
|
116
|
-
toBeAwaited.push(value());
|
116
|
+
toBeAwaited.push(value().then(({ default: p }) => p));
|
117
117
|
}
|
118
118
|
}
|
119
119
|
|
package/src/core.js
CHANGED
@@ -14,16 +14,17 @@ import {
|
|
14
14
|
dispatch,
|
15
15
|
queryTarget,
|
16
16
|
unescape,
|
17
|
+
whenDefined,
|
17
18
|
} from "polyscript/exports";
|
18
19
|
|
19
20
|
import "./all-done.js";
|
20
21
|
import TYPES from "./types.js";
|
21
22
|
import configs from "./config.js";
|
22
|
-
import hooks from "./hooks.js";
|
23
23
|
import sync from "./sync.js";
|
24
|
-
import
|
24
|
+
import bootstrapNodeAndPlugins from "./plugins-helper.js";
|
25
25
|
import { ErrorCode } from "./exceptions.js";
|
26
26
|
import { robustFetch as fetch, getText } from "./fetch.js";
|
27
|
+
import { hooks, main, worker, codeFor, createFunction } from "./hooks.js";
|
27
28
|
|
28
29
|
// allows lazy element features on code evaluation
|
29
30
|
let currentElement;
|
@@ -31,25 +32,6 @@ let currentElement;
|
|
31
32
|
// generic helper to disambiguate between custom element and script
|
32
33
|
const isScript = ({ tagName }) => tagName === "SCRIPT";
|
33
34
|
|
34
|
-
// helper for all script[type="py"] out there
|
35
|
-
const before = (script) => {
|
36
|
-
defineProperty(document, "currentScript", {
|
37
|
-
configurable: true,
|
38
|
-
get: () => script,
|
39
|
-
});
|
40
|
-
};
|
41
|
-
|
42
|
-
const after = () => {
|
43
|
-
delete document.currentScript;
|
44
|
-
};
|
45
|
-
|
46
|
-
// common life-cycle handlers for any node
|
47
|
-
const bootstrapNodeAndPlugins = (wrap, element, callback, hook) => {
|
48
|
-
// make it possible to reach the current target node via Python
|
49
|
-
callback(element);
|
50
|
-
for (const fn of hooks[hook]) fn(wrap, element);
|
51
|
-
};
|
52
|
-
|
53
35
|
let shouldRegister = true;
|
54
36
|
const registerModule = ({ XWorker: $XWorker, interpreter, io }) => {
|
55
37
|
// automatically use the pyscript stderr (when/if defined)
|
@@ -69,19 +51,6 @@ const registerModule = ({ XWorker: $XWorker, interpreter, io }) => {
|
|
69
51
|
: currentElement.id;
|
70
52
|
},
|
71
53
|
});
|
72
|
-
|
73
|
-
interpreter.runPython(stdlib, { globals: interpreter.runPython("{}") });
|
74
|
-
};
|
75
|
-
|
76
|
-
const workerHooks = {
|
77
|
-
codeBeforeRunWorker: () =>
|
78
|
-
[stdlib, ...hooks.codeBeforeRunWorker].map(dedent).join("\n"),
|
79
|
-
codeBeforeRunWorkerAsync: () =>
|
80
|
-
[stdlib, ...hooks.codeBeforeRunWorkerAsync].map(dedent).join("\n"),
|
81
|
-
codeAfterRunWorker: () =>
|
82
|
-
[...hooks.codeAfterRunWorker].map(dedent).join("\n"),
|
83
|
-
codeAfterRunWorkerAsync: () =>
|
84
|
-
[...hooks.codeAfterRunWorkerAsync].map(dedent).join("\n"),
|
85
54
|
};
|
86
55
|
|
87
56
|
// avoid multiple initialization of the same library
|
@@ -90,16 +59,26 @@ const [
|
|
90
59
|
PyWorker: exportedPyWorker,
|
91
60
|
hooks: exportedHooks,
|
92
61
|
config: exportedConfig,
|
62
|
+
whenDefined: exportedWhenDefined,
|
93
63
|
},
|
94
64
|
alreadyLive,
|
95
|
-
] = stickyModule("@pyscript/core", {
|
65
|
+
] = stickyModule("@pyscript/core", {
|
66
|
+
PyWorker,
|
67
|
+
hooks,
|
68
|
+
config: {},
|
69
|
+
whenDefined,
|
70
|
+
});
|
96
71
|
|
97
72
|
export {
|
73
|
+
TYPES,
|
98
74
|
exportedPyWorker as PyWorker,
|
99
75
|
exportedHooks as hooks,
|
100
76
|
exportedConfig as config,
|
77
|
+
exportedWhenDefined as whenDefined,
|
101
78
|
};
|
102
79
|
|
80
|
+
const hooked = new Map();
|
81
|
+
|
103
82
|
for (const [TYPE, interpreter] of TYPES) {
|
104
83
|
// avoid any dance if the module already landed
|
105
84
|
if (alreadyLive) break;
|
@@ -149,107 +128,141 @@ for (const [TYPE, interpreter] of TYPES) {
|
|
149
128
|
// possible early errors sent by polyscript
|
150
129
|
const errors = new Map();
|
151
130
|
|
131
|
+
// specific main and worker hooks
|
132
|
+
const hooks = {
|
133
|
+
main: {
|
134
|
+
...codeFor(main),
|
135
|
+
async onReady(wrap, element) {
|
136
|
+
if (shouldRegister) {
|
137
|
+
shouldRegister = false;
|
138
|
+
registerModule(wrap);
|
139
|
+
}
|
140
|
+
|
141
|
+
// allows plugins to do whatever they want with the element
|
142
|
+
// before regular stuff happens in here
|
143
|
+
for (const callback of main("onReady"))
|
144
|
+
await callback(wrap, element);
|
145
|
+
|
146
|
+
// now that all possible plugins are configured,
|
147
|
+
// bail out if polyscript encountered an error
|
148
|
+
if (errors.has(element)) {
|
149
|
+
let { message } = errors.get(element);
|
150
|
+
errors.delete(element);
|
151
|
+
const clone = message === INVALID_CONTENT;
|
152
|
+
message = `(${ErrorCode.CONFLICTING_CODE}) ${message} for `;
|
153
|
+
message += element.cloneNode(clone).outerHTML;
|
154
|
+
wrap.io.stderr(message);
|
155
|
+
return;
|
156
|
+
}
|
157
|
+
|
158
|
+
if (isScript(element)) {
|
159
|
+
const {
|
160
|
+
attributes: { async: isAsync, target },
|
161
|
+
} = element;
|
162
|
+
const hasTarget = !!target?.value;
|
163
|
+
const show = hasTarget
|
164
|
+
? queryTarget(element, target.value)
|
165
|
+
: document.createElement("script-py");
|
166
|
+
|
167
|
+
if (!hasTarget) {
|
168
|
+
const { head, body } = document;
|
169
|
+
if (head.contains(element)) body.append(show);
|
170
|
+
else element.after(show);
|
171
|
+
}
|
172
|
+
if (!show.id) show.id = getID();
|
173
|
+
|
174
|
+
// allows the code to retrieve the target element via
|
175
|
+
// document.currentScript.target if needed
|
176
|
+
defineProperty(element, "target", { value: show });
|
177
|
+
|
178
|
+
// notify before the code runs
|
179
|
+
dispatch(element, TYPE, "ready");
|
180
|
+
dispatchDone(
|
181
|
+
element,
|
182
|
+
isAsync,
|
183
|
+
wrap[`run${isAsync ? "Async" : ""}`](
|
184
|
+
await fetchSource(element, wrap.io, true),
|
185
|
+
),
|
186
|
+
);
|
187
|
+
} else {
|
188
|
+
// resolve PyScriptElement to allow connectedCallback
|
189
|
+
element._wrap.resolve(wrap);
|
190
|
+
}
|
191
|
+
console.debug("[pyscript/main] PyScript Ready");
|
192
|
+
},
|
193
|
+
onWorker(_, xworker) {
|
194
|
+
assign(xworker.sync, sync);
|
195
|
+
for (const callback of main("onWorker"))
|
196
|
+
callback(_, xworker);
|
197
|
+
},
|
198
|
+
onBeforeRun(wrap, element) {
|
199
|
+
currentElement = element;
|
200
|
+
bootstrapNodeAndPlugins(
|
201
|
+
main,
|
202
|
+
wrap,
|
203
|
+
element,
|
204
|
+
"onBeforeRun",
|
205
|
+
);
|
206
|
+
},
|
207
|
+
onBeforeRunAsync(wrap, element) {
|
208
|
+
currentElement = element;
|
209
|
+
return bootstrapNodeAndPlugins(
|
210
|
+
main,
|
211
|
+
wrap,
|
212
|
+
element,
|
213
|
+
"onBeforeRunAsync",
|
214
|
+
);
|
215
|
+
},
|
216
|
+
onAfterRun(wrap, element) {
|
217
|
+
bootstrapNodeAndPlugins(
|
218
|
+
main,
|
219
|
+
wrap,
|
220
|
+
element,
|
221
|
+
"onAfterRun",
|
222
|
+
);
|
223
|
+
},
|
224
|
+
onAfterRunAsync(wrap, element) {
|
225
|
+
return bootstrapNodeAndPlugins(
|
226
|
+
main,
|
227
|
+
wrap,
|
228
|
+
element,
|
229
|
+
"onAfterRunAsync",
|
230
|
+
);
|
231
|
+
},
|
232
|
+
},
|
233
|
+
worker: {
|
234
|
+
...codeFor(worker),
|
235
|
+
// these are lazy getters that returns a composition
|
236
|
+
// of the current hooks or undefined, if no hook is present
|
237
|
+
get onReady() {
|
238
|
+
return createFunction(this, "onReady", true);
|
239
|
+
},
|
240
|
+
get onBeforeRun() {
|
241
|
+
return createFunction(this, "onBeforeRun", false);
|
242
|
+
},
|
243
|
+
get onBeforeRunAsync() {
|
244
|
+
return createFunction(this, "onBeforeRunAsync", true);
|
245
|
+
},
|
246
|
+
get onAfterRun() {
|
247
|
+
return createFunction(this, "onAfterRun", false);
|
248
|
+
},
|
249
|
+
get onAfterRunAsync() {
|
250
|
+
return createFunction(this, "onAfterRunAsync", true);
|
251
|
+
},
|
252
|
+
},
|
253
|
+
};
|
254
|
+
|
255
|
+
hooked.set(TYPE, hooks);
|
256
|
+
|
152
257
|
define(TYPE, {
|
153
258
|
config,
|
154
259
|
interpreter,
|
260
|
+
hooks,
|
155
261
|
env: `${TYPE}-script`,
|
156
262
|
version: config?.interpreter,
|
157
263
|
onerror(error, element) {
|
158
264
|
errors.set(element, error);
|
159
265
|
},
|
160
|
-
...workerHooks,
|
161
|
-
onWorkerReady(_, xworker) {
|
162
|
-
assign(xworker.sync, sync);
|
163
|
-
for (const callback of hooks.onWorkerReady)
|
164
|
-
callback(_, xworker);
|
165
|
-
},
|
166
|
-
onBeforeRun(wrap, element) {
|
167
|
-
currentElement = element;
|
168
|
-
bootstrapNodeAndPlugins(
|
169
|
-
wrap,
|
170
|
-
element,
|
171
|
-
before,
|
172
|
-
"onBeforeRun",
|
173
|
-
);
|
174
|
-
},
|
175
|
-
onBeforeRunAsync(wrap, element) {
|
176
|
-
currentElement = element;
|
177
|
-
bootstrapNodeAndPlugins(
|
178
|
-
wrap,
|
179
|
-
element,
|
180
|
-
before,
|
181
|
-
"onBeforeRunAsync",
|
182
|
-
);
|
183
|
-
},
|
184
|
-
onAfterRun(wrap, element) {
|
185
|
-
bootstrapNodeAndPlugins(wrap, element, after, "onAfterRun");
|
186
|
-
},
|
187
|
-
onAfterRunAsync(wrap, element) {
|
188
|
-
bootstrapNodeAndPlugins(
|
189
|
-
wrap,
|
190
|
-
element,
|
191
|
-
after,
|
192
|
-
"onAfterRunAsync",
|
193
|
-
);
|
194
|
-
},
|
195
|
-
async onInterpreterReady(wrap, element) {
|
196
|
-
if (shouldRegister) {
|
197
|
-
shouldRegister = false;
|
198
|
-
registerModule(wrap);
|
199
|
-
}
|
200
|
-
|
201
|
-
// allows plugins to do whatever they want with the element
|
202
|
-
// before regular stuff happens in here
|
203
|
-
for (const callback of hooks.onInterpreterReady)
|
204
|
-
callback(wrap, element);
|
205
|
-
|
206
|
-
// now that all possible plugins are configured,
|
207
|
-
// bail out if polyscript encountered an error
|
208
|
-
if (errors.has(element)) {
|
209
|
-
let { message } = errors.get(element);
|
210
|
-
errors.delete(element);
|
211
|
-
const clone = message === INVALID_CONTENT;
|
212
|
-
message = `(${ErrorCode.CONFLICTING_CODE}) ${message} for `;
|
213
|
-
message += element.cloneNode(clone).outerHTML;
|
214
|
-
wrap.io.stderr(message);
|
215
|
-
return;
|
216
|
-
}
|
217
|
-
|
218
|
-
if (isScript(element)) {
|
219
|
-
const {
|
220
|
-
attributes: { async: isAsync, target },
|
221
|
-
} = element;
|
222
|
-
const hasTarget = !!target?.value;
|
223
|
-
const show = hasTarget
|
224
|
-
? queryTarget(element, target.value)
|
225
|
-
: document.createElement("script-py");
|
226
|
-
|
227
|
-
if (!hasTarget) {
|
228
|
-
const { head, body } = document;
|
229
|
-
if (head.contains(element)) body.append(show);
|
230
|
-
else element.after(show);
|
231
|
-
}
|
232
|
-
if (!show.id) show.id = getID();
|
233
|
-
|
234
|
-
// allows the code to retrieve the target element via
|
235
|
-
// document.currentScript.target if needed
|
236
|
-
defineProperty(element, "target", { value: show });
|
237
|
-
|
238
|
-
// notify before the code runs
|
239
|
-
dispatch(element, TYPE, "ready");
|
240
|
-
dispatchDone(
|
241
|
-
element,
|
242
|
-
isAsync,
|
243
|
-
wrap[`run${isAsync ? "Async" : ""}`](
|
244
|
-
await fetchSource(element, wrap.io, true),
|
245
|
-
),
|
246
|
-
);
|
247
|
-
} else {
|
248
|
-
// resolve PyScriptElement to allow connectedCallback
|
249
|
-
element._wrap.resolve(wrap);
|
250
|
-
}
|
251
|
-
console.debug("[pyscript/main] PyScript Ready");
|
252
|
-
},
|
253
266
|
});
|
254
267
|
|
255
268
|
customElements.define(
|
@@ -305,12 +318,13 @@ for (const [TYPE, interpreter] of TYPES) {
|
|
305
318
|
* @returns {Worker & {sync: ProxyHandler<object>}}
|
306
319
|
*/
|
307
320
|
function PyWorker(file, options) {
|
321
|
+
const hooks = hooked.get("py");
|
308
322
|
// this propagates pyscript worker hooks without needing a pyscript
|
309
323
|
// bootstrap + it passes arguments and enforces `pyodide`
|
310
324
|
// as the interpreter to use in the worker, as all hooks assume that
|
311
325
|
// and as `pyodide` is the only default interpreter that can deal with
|
312
326
|
// all the features we need to deliver pyscript out there.
|
313
|
-
const xworker = XWorker.call(new Hook(null,
|
327
|
+
const xworker = XWorker.call(new Hook(null, hooks), file, {
|
314
328
|
type: "pyodide",
|
315
329
|
...options,
|
316
330
|
});
|
package/src/hooks.js
CHANGED
@@ -1,28 +1,91 @@
|
|
1
1
|
import { typedSet } from "type-checked-collections";
|
2
|
+
import { dedent } from "polyscript/exports";
|
3
|
+
import toJSONCallback from "to-json-callback";
|
4
|
+
|
5
|
+
import stdlib from "./stdlib.js";
|
6
|
+
|
7
|
+
export const main = (name) => hooks.main[name];
|
8
|
+
export const worker = (name) => hooks.worker[name];
|
9
|
+
|
10
|
+
const code = (hooks, branch, key, lib) => {
|
11
|
+
hooks[key] = () => {
|
12
|
+
const arr = lib ? [lib] : [];
|
13
|
+
arr.push(...branch(key));
|
14
|
+
return arr.map(dedent).join("\n");
|
15
|
+
};
|
16
|
+
};
|
17
|
+
|
18
|
+
export const codeFor = (branch) => {
|
19
|
+
const hooks = {};
|
20
|
+
code(hooks, branch, `codeBeforeRun`, stdlib);
|
21
|
+
code(hooks, branch, `codeBeforeRunAsync`, stdlib);
|
22
|
+
code(hooks, branch, `codeAfterRun`);
|
23
|
+
code(hooks, branch, `codeAfterRunAsync`);
|
24
|
+
return hooks;
|
25
|
+
};
|
26
|
+
|
27
|
+
export const createFunction = (self, name) => {
|
28
|
+
const cbs = [...worker(name)];
|
29
|
+
if (cbs.length) {
|
30
|
+
const cb = toJSONCallback(
|
31
|
+
self[`_${name}`] ||
|
32
|
+
(name.endsWith("Async")
|
33
|
+
? async (wrap, xworker, ...cbs) => {
|
34
|
+
for (const cb of cbs) await cb(wrap, xworker);
|
35
|
+
}
|
36
|
+
: (wrap, xworker, ...cbs) => {
|
37
|
+
for (const cb of cbs) cb(wrap, xworker);
|
38
|
+
}),
|
39
|
+
);
|
40
|
+
const a = cbs.map(toJSONCallback).join(", ");
|
41
|
+
return Function(`return(w,x)=>(${cb})(w,x,...[${a}])`)();
|
42
|
+
}
|
43
|
+
};
|
2
44
|
|
3
45
|
const SetFunction = typedSet({ typeof: "function" });
|
4
46
|
const SetString = typedSet({ typeof: "string" });
|
5
47
|
|
6
|
-
export
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
48
|
+
export const hooks = {
|
49
|
+
main: {
|
50
|
+
/** @type {Set<function>} */
|
51
|
+
onWorker: new SetFunction(),
|
52
|
+
/** @type {Set<function>} */
|
53
|
+
onReady: new SetFunction(),
|
54
|
+
/** @type {Set<function>} */
|
55
|
+
onBeforeRun: new SetFunction(),
|
56
|
+
/** @type {Set<function>} */
|
57
|
+
onBeforeRunAsync: new SetFunction(),
|
58
|
+
/** @type {Set<function>} */
|
59
|
+
onAfterRun: new SetFunction(),
|
60
|
+
/** @type {Set<function>} */
|
61
|
+
onAfterRunAsync: new SetFunction(),
|
62
|
+
/** @type {Set<string>} */
|
63
|
+
codeBeforeRun: new SetString(),
|
64
|
+
/** @type {Set<string>} */
|
65
|
+
codeBeforeRunAsync: new SetString(),
|
66
|
+
/** @type {Set<string>} */
|
67
|
+
codeAfterRun: new SetString(),
|
68
|
+
/** @type {Set<string>} */
|
69
|
+
codeAfterRunAsync: new SetString(),
|
70
|
+
},
|
71
|
+
worker: {
|
72
|
+
/** @type {Set<function>} */
|
73
|
+
onReady: new SetFunction(),
|
74
|
+
/** @type {Set<function>} */
|
75
|
+
onBeforeRun: new SetFunction(),
|
76
|
+
/** @type {Set<function>} */
|
77
|
+
onBeforeRunAsync: new SetFunction(),
|
78
|
+
/** @type {Set<function>} */
|
79
|
+
onAfterRun: new SetFunction(),
|
80
|
+
/** @type {Set<function>} */
|
81
|
+
onAfterRunAsync: new SetFunction(),
|
82
|
+
/** @type {Set<string>} */
|
83
|
+
codeBeforeRun: new SetString(),
|
84
|
+
/** @type {Set<string>} */
|
85
|
+
codeBeforeRunAsync: new SetString(),
|
86
|
+
/** @type {Set<string>} */
|
87
|
+
codeAfterRun: new SetString(),
|
88
|
+
/** @type {Set<string>} */
|
89
|
+
codeAfterRunAsync: new SetString(),
|
90
|
+
},
|
28
91
|
};
|
package/src/plugins/error.js
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
// PyScript Error Plugin
|
2
2
|
import { hooks } from "../core.js";
|
3
3
|
|
4
|
-
hooks.
|
4
|
+
hooks.main.onReady.add(function override(pyScript) {
|
5
5
|
// be sure this override happens only once
|
6
|
-
hooks.
|
6
|
+
hooks.main.onReady.delete(override);
|
7
7
|
|
8
8
|
// trap generic `stderr` to propagate to it regardless
|
9
9
|
const { stderr } = pyScript.io;
|