@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,158 @@
|
|
1
|
+
// PyScript py-terminal plugin
|
2
|
+
import { TYPES, hooks } from "../core.js";
|
3
|
+
|
4
|
+
const CDN = "https://cdn.jsdelivr.net/npm/xterm";
|
5
|
+
const XTERM = "5.3.0";
|
6
|
+
const XTERM_READLINE = "1.1.1";
|
7
|
+
const SELECTOR = [...TYPES.keys()]
|
8
|
+
.map((type) => `script[type="${type}"][terminal],${type}-script[terminal]`)
|
9
|
+
.join(",");
|
10
|
+
|
11
|
+
const pyTerminal = async () => {
|
12
|
+
const terminals = document.querySelectorAll(SELECTOR);
|
13
|
+
|
14
|
+
// no results will look further for runtime nodes
|
15
|
+
if (!terminals.length) return;
|
16
|
+
|
17
|
+
// we currently support only one terminal as in "classic"
|
18
|
+
if (terminals.length > 1)
|
19
|
+
console.warn("Unable to satisfy multiple terminals");
|
20
|
+
|
21
|
+
// if we arrived this far, let's drop the MutationObserver
|
22
|
+
mo.disconnect();
|
23
|
+
|
24
|
+
const [element] = terminals;
|
25
|
+
// hopefully to be removed in the near future!
|
26
|
+
if (element.matches('script[type="mpy"],mpy-script'))
|
27
|
+
throw new Error("Unsupported terminal");
|
28
|
+
|
29
|
+
// import styles once and lazily (only on valid terminal)
|
30
|
+
if (!document.querySelector(`link[href^="${CDN}"]`)) {
|
31
|
+
document.head.append(
|
32
|
+
Object.assign(document.createElement("link"), {
|
33
|
+
rel: "stylesheet",
|
34
|
+
href: `${CDN}@${XTERM}/css/xterm.min.css`,
|
35
|
+
}),
|
36
|
+
);
|
37
|
+
}
|
38
|
+
|
39
|
+
// lazy load these only when a valid terminal is found
|
40
|
+
const [{ Terminal }, { Readline }] = await Promise.all([
|
41
|
+
import(/* webpackIgnore: true */ `${CDN}@${XTERM}/+esm`),
|
42
|
+
import(
|
43
|
+
/* webpackIgnore: true */ `${CDN}-readline@${XTERM_READLINE}/+esm`
|
44
|
+
),
|
45
|
+
]);
|
46
|
+
|
47
|
+
const readline = new Readline();
|
48
|
+
|
49
|
+
// common main thread initialization for both worker
|
50
|
+
// or main case, bootstrapping the terminal on its target
|
51
|
+
const init = (options) => {
|
52
|
+
let target = element;
|
53
|
+
const selector = element.getAttribute("target");
|
54
|
+
if (selector) {
|
55
|
+
target =
|
56
|
+
document.getElementById(selector) ||
|
57
|
+
document.querySelector(selector);
|
58
|
+
if (!target) throw new Error(`Unknown target ${selector}`);
|
59
|
+
} else {
|
60
|
+
target = document.createElement(`${element.type}-terminal`);
|
61
|
+
target.style.display = "block";
|
62
|
+
element.after(target);
|
63
|
+
}
|
64
|
+
const terminal = new Terminal({
|
65
|
+
theme: {
|
66
|
+
background: "#191A19",
|
67
|
+
foreground: "#F5F2E7",
|
68
|
+
},
|
69
|
+
...options,
|
70
|
+
});
|
71
|
+
terminal.loadAddon(readline);
|
72
|
+
terminal.open(target);
|
73
|
+
terminal.focus();
|
74
|
+
};
|
75
|
+
|
76
|
+
// branch logic for the worker
|
77
|
+
if (element.hasAttribute("worker")) {
|
78
|
+
// when the remote thread onReady triggers:
|
79
|
+
// setup the interpreter stdout and stderr
|
80
|
+
const workerReady = ({ interpreter }, { sync }) => {
|
81
|
+
sync.pyterminal_drop_hooks();
|
82
|
+
const decoder = new TextDecoder();
|
83
|
+
const generic = {
|
84
|
+
isatty: true,
|
85
|
+
write(buffer) {
|
86
|
+
sync.pyterminal_write(decoder.decode(buffer));
|
87
|
+
return buffer.length;
|
88
|
+
},
|
89
|
+
};
|
90
|
+
interpreter.setStdout(generic);
|
91
|
+
interpreter.setStderr(generic);
|
92
|
+
};
|
93
|
+
|
94
|
+
// run in python code able to replace builtins.input
|
95
|
+
// using the xworker.sync non blocking prompt
|
96
|
+
const codeBefore = `
|
97
|
+
import builtins
|
98
|
+
from pyscript import sync as _sync
|
99
|
+
|
100
|
+
builtins.input = lambda prompt: _sync.pyterminal_read(prompt)
|
101
|
+
`;
|
102
|
+
|
103
|
+
// at the end of the code, make the terminal interactive
|
104
|
+
const codeAfter = `
|
105
|
+
import code as _code
|
106
|
+
_code.interact()
|
107
|
+
`;
|
108
|
+
|
109
|
+
// add a hook on the main thread to setup all sync helpers
|
110
|
+
// also bootstrapping the XTerm target on main
|
111
|
+
hooks.main.onWorker.add(function worker(_, xworker) {
|
112
|
+
hooks.main.onWorker.delete(worker);
|
113
|
+
init({
|
114
|
+
disableStdin: false,
|
115
|
+
cursorBlink: true,
|
116
|
+
cursorStyle: "block",
|
117
|
+
});
|
118
|
+
xworker.sync.pyterminal_read = readline.read.bind(readline);
|
119
|
+
xworker.sync.pyterminal_write = readline.write.bind(readline);
|
120
|
+
// allow a worker to drop main thread hooks ASAP
|
121
|
+
xworker.sync.pyterminal_drop_hooks = () => {
|
122
|
+
hooks.worker.onReady.delete(workerReady);
|
123
|
+
hooks.worker.codeBeforeRun.delete(codeBefore);
|
124
|
+
hooks.worker.codeAfterRun.delete(codeAfter);
|
125
|
+
};
|
126
|
+
});
|
127
|
+
|
128
|
+
// setup remote thread JS/Python code for whenever the
|
129
|
+
// worker is ready to become a terminal
|
130
|
+
hooks.worker.onReady.add(workerReady);
|
131
|
+
hooks.worker.codeBeforeRun.add(codeBefore);
|
132
|
+
hooks.worker.codeAfterRun.add(codeAfter);
|
133
|
+
} else {
|
134
|
+
// in the main case, just bootstrap XTerm without
|
135
|
+
// allowing any input as that's not possible / awkward
|
136
|
+
hooks.main.onReady.add(function main({ io }) {
|
137
|
+
console.warn("py-terminal is read only on main thread");
|
138
|
+
hooks.main.onReady.delete(main);
|
139
|
+
init({
|
140
|
+
disableStdin: true,
|
141
|
+
cursorBlink: false,
|
142
|
+
cursorStyle: "underline",
|
143
|
+
});
|
144
|
+
io.stdout = (value) => {
|
145
|
+
readline.write(`${value}\n`);
|
146
|
+
};
|
147
|
+
io.stderr = (error) => {
|
148
|
+
readline.write(`${error.message || error}\n`);
|
149
|
+
};
|
150
|
+
});
|
151
|
+
}
|
152
|
+
};
|
153
|
+
|
154
|
+
const mo = new MutationObserver(pyTerminal);
|
155
|
+
mo.observe(document, { childList: true, subtree: true });
|
156
|
+
|
157
|
+
// try to check the current document ASAP
|
158
|
+
export default pyTerminal();
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import { defineProperty } from "polyscript/exports";
|
2
|
+
|
3
|
+
// helper for all script[type="py"] out there
|
4
|
+
const before = (script) => {
|
5
|
+
defineProperty(document, "currentScript", {
|
6
|
+
configurable: true,
|
7
|
+
get: () => script,
|
8
|
+
});
|
9
|
+
};
|
10
|
+
|
11
|
+
const after = () => {
|
12
|
+
delete document.currentScript;
|
13
|
+
};
|
14
|
+
|
15
|
+
// common life-cycle handlers for any node
|
16
|
+
export default async (main, wrap, element, hook) => {
|
17
|
+
const isAsync = hook.endsWith("Async");
|
18
|
+
const isBefore = hook.startsWith("onBefore");
|
19
|
+
// make it possible to reach the current target node via Python
|
20
|
+
// or clean up for other scripts executing around this one
|
21
|
+
(isBefore ? before : after)(element);
|
22
|
+
for (const fn of main(hook)) {
|
23
|
+
if (isAsync) await fn(wrap, element);
|
24
|
+
else fn(wrap, element);
|
25
|
+
}
|
26
|
+
};
|
package/src/plugins.js
CHANGED
package/src/stdlib.js
CHANGED
@@ -10,16 +10,22 @@ import pyscript from "./stdlib/pyscript.js";
|
|
10
10
|
|
11
11
|
const { entries } = Object;
|
12
12
|
|
13
|
-
const python = [
|
13
|
+
const python = [
|
14
|
+
"import os as _os",
|
15
|
+
"from pathlib import Path as _Path",
|
16
|
+
"_path = None",
|
17
|
+
];
|
14
18
|
|
15
19
|
const write = (base, literal) => {
|
16
20
|
for (const [key, value] of entries(literal)) {
|
17
|
-
|
21
|
+
python.push(`_path = _Path("${base}/${key}")`);
|
18
22
|
if (typeof value === "string") {
|
19
23
|
const code = JSON.stringify(value);
|
20
|
-
python.push(
|
24
|
+
python.push(`_path.write_text(${code})`);
|
21
25
|
} else {
|
22
|
-
|
26
|
+
// @see https://github.com/pyscript/pyscript/pull/1813#issuecomment-1781502909
|
27
|
+
python.push(`if not _os.path.exists("${base}/${key}"):`);
|
28
|
+
python.push(" _path.mkdir(parents=True, exist_ok=True)");
|
23
29
|
write(`${base}/${key}`, value);
|
24
30
|
}
|
25
31
|
}
|
@@ -28,6 +34,8 @@ const write = (base, literal) => {
|
|
28
34
|
write(".", pyscript);
|
29
35
|
|
30
36
|
python.push("del _Path");
|
37
|
+
python.push("del _path");
|
38
|
+
python.push("del _os");
|
31
39
|
python.push("\n");
|
32
40
|
|
33
41
|
export default python.join("\n");
|
package/types/core.d.ts
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
+
import TYPES from "./types.js";
|
1
2
|
declare const exportedPyWorker: any;
|
2
3
|
declare const exportedHooks: any;
|
3
4
|
declare const exportedConfig: any;
|
4
|
-
|
5
|
+
declare const exportedWhenDefined: any;
|
6
|
+
export { TYPES, exportedPyWorker as PyWorker, exportedHooks as hooks, exportedConfig as config, exportedWhenDefined as whenDefined };
|
package/types/hooks.d.ts
CHANGED
@@ -1,13 +1,38 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
1
|
+
export function main(name: any): any;
|
2
|
+
export function worker(name: any): any;
|
3
|
+
export function codeFor(branch: any): {};
|
4
|
+
export function createFunction(self: any, name: any): any;
|
5
|
+
export namespace hooks {
|
6
|
+
namespace main {
|
7
|
+
let onWorker: Set<Function>;
|
8
|
+
let onReady: Set<Function>;
|
9
|
+
let onBeforeRun: Set<Function>;
|
10
|
+
let onBeforeRunAsync: Set<Function>;
|
11
|
+
let onAfterRun: Set<Function>;
|
12
|
+
let onAfterRunAsync: Set<Function>;
|
13
|
+
let codeBeforeRun: Set<string>;
|
14
|
+
let codeBeforeRunAsync: Set<string>;
|
15
|
+
let codeAfterRun: Set<string>;
|
16
|
+
let codeAfterRunAsync: Set<string>;
|
17
|
+
}
|
18
|
+
namespace worker {
|
19
|
+
let onReady_1: Set<Function>;
|
20
|
+
export { onReady_1 as onReady };
|
21
|
+
let onBeforeRun_1: Set<Function>;
|
22
|
+
export { onBeforeRun_1 as onBeforeRun };
|
23
|
+
let onBeforeRunAsync_1: Set<Function>;
|
24
|
+
export { onBeforeRunAsync_1 as onBeforeRunAsync };
|
25
|
+
let onAfterRun_1: Set<Function>;
|
26
|
+
export { onAfterRun_1 as onAfterRun };
|
27
|
+
let onAfterRunAsync_1: Set<Function>;
|
28
|
+
export { onAfterRunAsync_1 as onAfterRunAsync };
|
29
|
+
let codeBeforeRun_1: Set<string>;
|
30
|
+
export { codeBeforeRun_1 as codeBeforeRun };
|
31
|
+
let codeBeforeRunAsync_1: Set<string>;
|
32
|
+
export { codeBeforeRunAsync_1 as codeBeforeRunAsync };
|
33
|
+
let codeAfterRun_1: Set<string>;
|
34
|
+
export { codeAfterRun_1 as codeAfterRun };
|
35
|
+
let codeAfterRunAsync_1: Set<string>;
|
36
|
+
export { codeAfterRunAsync_1 as codeAfterRunAsync };
|
37
|
+
}
|
12
38
|
}
|
13
|
-
export default _default;
|
package/types/plugins.d.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
declare
|
2
|
-
|
3
|
-
|
1
|
+
declare const _default: {
|
2
|
+
error: () => Promise<typeof import("./plugins/error.js")>;
|
3
|
+
"py-terminal": () => Promise<typeof import("./plugins/py-terminal.js")>;
|
4
|
+
};
|
4
5
|
export default _default;
|
package/dist/error-0_IesYFM.js
DELETED
@@ -1,2 +0,0 @@
|
|
1
|
-
import{hooks as e}from"./core.js";function r(e){const r=document.createElement("div");r.className="py-error",r.textContent=e,r.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(r)}e.onInterpreterReady.add((function o(n){e.onInterpreterReady.delete(o);const{stderr:t}=n.io;n.io.stderr=(e,...o)=>(r(e.message||e),t(e,...o)),addEventListener("error",(({message:e})=>{e.startsWith("Uncaught PythonError")&&r(e)}))}));export{r as notify};
|
2
|
-
//# sourceMappingURL=error-0_IesYFM.js.map
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"file":"error-0_IesYFM.js","sources":["../src/plugins/error.js"],"sourcesContent":["// PyScript Error Plugin\nimport { hooks } from \"../core.js\";\n\nhooks.onInterpreterReady.add(function override(pyScript) {\n // be sure this override happens only once\n hooks.onInterpreterReady.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","onInterpreterReady","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,mBAAmBC,KAAI,SAASC,EAASC,GAE3CJ,EAAMC,mBAAmBI,OAAOF,GAGhC,MAAMG,OAAEA,GAAWF,EAASG,GAG5BH,EAASG,GAAGD,OAAS,CAACE,KAAUC,KAC5BpB,EAAOmB,EAAMlB,SAAWkB,GAEjBF,EAAOE,KAAUC,IAI5BC,iBAAiB,SAAS,EAAGpB,cACrBA,EAAQqB,WAAW,yBAAyBtB,EAAOC,EAAQ,GAEvE"}
|