@pyscript/core 0.0.0
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/README.md +33 -0
- package/cjs/custom-types.js +212 -0
- package/cjs/fetch-utils.js +12 -0
- package/cjs/index.js +73 -0
- package/cjs/interpreter/_python.js +16 -0
- package/cjs/interpreter/_utils.js +141 -0
- package/cjs/interpreter/micropython.js +34 -0
- package/cjs/interpreter/pyodide.js +38 -0
- package/cjs/interpreter/ruby-wasm-wasi.js +43 -0
- package/cjs/interpreter/wasmoon.js +40 -0
- package/cjs/interpreters.js +64 -0
- package/cjs/listeners.js +71 -0
- package/cjs/loader.js +43 -0
- package/cjs/package.json +1 -0
- package/cjs/plugins/pyscript.js +105 -0
- package/cjs/script-handler.js +141 -0
- package/cjs/toml.js +10 -0
- package/cjs/utils.js +19 -0
- package/cjs/worker/class.js +46 -0
- package/cjs/worker/hooks.js +2 -0
- package/cjs/worker/xworker.js +3 -0
- package/esm/custom-types.js +207 -0
- package/esm/fetch-utils.js +8 -0
- package/esm/index.js +68 -0
- package/esm/interpreter/_python.js +12 -0
- package/esm/interpreter/_utils.js +133 -0
- package/esm/interpreter/micropython.js +33 -0
- package/esm/interpreter/pyodide.js +37 -0
- package/esm/interpreter/ruby-wasm-wasi.js +42 -0
- package/esm/interpreter/wasmoon.js +39 -0
- package/esm/interpreters.js +58 -0
- package/esm/listeners.js +68 -0
- package/esm/loader.js +40 -0
- package/esm/script-handler.js +136 -0
- package/esm/toml.js +8 -0
- package/esm/utils.js +20 -0
- package/esm/worker/class.js +45 -0
- package/esm/worker/hooks.js +1 -0
- package/esm/worker/xworker.js +2 -0
- package/min.js +2 -0
- package/package.json +55 -0
- package/types/coincident/structured.d.ts +5 -0
- package/types/fetch-utils.d.ts +3 -0
- package/types/index.d.ts +2 -0
- package/types/loader.d.ts +2 -0
- package/types/plugins.d.ts +61 -0
- package/types/pyscript/pyscript.core/esm/custom-types.d.ts +54 -0
- package/types/pyscript/pyscript.core/esm/fetch-utils.d.ts +3 -0
- package/types/pyscript/pyscript.core/esm/index.d.ts +2 -0
- package/types/pyscript/pyscript.core/esm/interpreter/_python.d.ts +5 -0
- package/types/pyscript/pyscript.core/esm/interpreter/_utils.d.ts +11 -0
- package/types/pyscript/pyscript.core/esm/interpreter/micropython.d.ts +17 -0
- package/types/pyscript/pyscript.core/esm/interpreter/pyodide.d.ts +17 -0
- package/types/pyscript/pyscript.core/esm/interpreter/ruby-wasm-wasi.d.ts +15 -0
- package/types/pyscript/pyscript.core/esm/interpreter/ruby.d.ts +16 -0
- package/types/pyscript/pyscript.core/esm/interpreter/wasmoon.d.ts +21 -0
- package/types/pyscript/pyscript.core/esm/interpreters.d.ts +9 -0
- package/types/pyscript/pyscript.core/esm/listeners.d.ts +2 -0
- package/types/pyscript/pyscript.core/esm/loader.d.ts +2 -0
- package/types/pyscript/pyscript.core/esm/plugins.d.ts +54 -0
- package/types/pyscript/pyscript.core/esm/runtime/_python.d.ts +8 -0
- package/types/pyscript/pyscript.core/esm/runtime/_utils.d.ts +11 -0
- package/types/pyscript/pyscript.core/esm/runtime/micropython.d.ts +20 -0
- package/types/pyscript/pyscript.core/esm/runtime/pyodide.d.ts +20 -0
- package/types/pyscript/pyscript.core/esm/runtime/ruby.d.ts +15 -0
- package/types/pyscript/pyscript.core/esm/runtime/wasmoon.d.ts +21 -0
- package/types/pyscript/pyscript.core/esm/runtimes.d.ts +9 -0
- package/types/pyscript/pyscript.core/esm/script-handler.d.ts +4 -0
- package/types/pyscript/pyscript.core/esm/toml.d.ts +1 -0
- package/types/pyscript/pyscript.core/esm/utils.d.ts +23 -0
- package/types/pyscript/pyscript.core/esm/worker/class.d.ts +19 -0
- package/types/pyscript/pyscript.core/esm/worker/hooks.d.ts +2 -0
- package/types/pyscript/pyscript.core/esm/worker/xworker.d.ts +2 -0
- package/types/runtime/_python.d.ts +8 -0
- package/types/runtime/_utils.d.ts +11 -0
- package/types/runtime/micropython.d.ts +20 -0
- package/types/runtime/pyodide.d.ts +20 -0
- package/types/runtime/ruby.d.ts +15 -0
- package/types/runtime/wasmoon.d.ts +21 -0
- package/types/runtimes.d.ts +9 -0
- package/types/script-handler.d.ts +3 -0
- package/types/toml.d.ts +1 -0
- package/types/utils.d.ts +23 -0
- package/types/worker/class.d.ts +19 -0
- package/types/worker/hooks.d.ts +2 -0
- package/types/worker/xworker.d.ts +2 -0
package/README.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# @pyscript/core
|
2
|
+
|
3
|
+
[](https://github.com/WebReflection/python/actions/workflows/node.js.yml) [](https://coveralls.io/github/WebReflection/python?branch=api)
|
4
|
+
|
5
|
+
---
|
6
|
+
|
7
|
+
## Development
|
8
|
+
|
9
|
+
The working folder (source code of truth) is the `./esm` one, while the `./cjs` is populated as dual module and to test (but it's 1:1 code, no trnaspilation except for imports/exports).
|
10
|
+
|
11
|
+
```sh
|
12
|
+
# install all dependencies needed by core
|
13
|
+
npm i
|
14
|
+
```
|
15
|
+
|
16
|
+
### Build / Artifacts
|
17
|
+
|
18
|
+
This project requires some automatic artifact creation to:
|
19
|
+
|
20
|
+
* create a _Worker_ as a _Blob_ based on the same code used by this repo
|
21
|
+
* create automatically the list of runtimes available via the module
|
22
|
+
* create the `min.js` file used by most integration tests
|
23
|
+
* create a sha256 version of the Blob content for CSP cases
|
24
|
+
|
25
|
+
Accordingly, to build latest project:
|
26
|
+
|
27
|
+
```sh
|
28
|
+
# create all artifacts needed to test core
|
29
|
+
npm run build
|
30
|
+
|
31
|
+
# optionally spin a server with CORS, COOP, and COEP enabled
|
32
|
+
npm run server
|
33
|
+
```
|
@@ -0,0 +1,212 @@
|
|
1
|
+
'use strict';
|
2
|
+
require("@ungap/with-resolvers");
|
3
|
+
const { $$ } = require("basic-devtools");
|
4
|
+
|
5
|
+
const { assign, create } = require("./utils.js");
|
6
|
+
const { getDetails } = require("./script-handler.js");
|
7
|
+
const {
|
8
|
+
registry: defaultRegistry,
|
9
|
+
prefixes,
|
10
|
+
configs
|
11
|
+
} = require("./interpreters.js");
|
12
|
+
const { getRuntimeID } = require("./loader.js");
|
13
|
+
const { io } = require("./interpreter/_utils.js");
|
14
|
+
const { addAllListeners } = require("./listeners.js");
|
15
|
+
|
16
|
+
const workerHooks = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require("./worker/hooks.js"));
|
17
|
+
|
18
|
+
const CUSTOM_SELECTORS = [];
|
19
|
+
exports.CUSTOM_SELECTORS = CUSTOM_SELECTORS;
|
20
|
+
|
21
|
+
/**
|
22
|
+
* @typedef {Object} Runtime custom configuration
|
23
|
+
* @prop {object} interpreter the bootstrapped interpreter
|
24
|
+
* @prop {(url:string, options?: object) => Worker} XWorker an XWorker constructor that defaults to same interpreter on the Worker.
|
25
|
+
* @prop {object} config a cloned config used to bootstrap the interpreter
|
26
|
+
* @prop {(code:string) => any} run an utility to run code within the interpreter
|
27
|
+
* @prop {(code:string) => Promise<any>} runAsync an utility to run code asynchronously within the interpreter
|
28
|
+
* @prop {(path:string, data:ArrayBuffer) => void} writeFile an utility to write a file in the virtual FS, if available
|
29
|
+
*/
|
30
|
+
|
31
|
+
const patched = new Map();
|
32
|
+
const types = new Map();
|
33
|
+
const waitList = new Map();
|
34
|
+
|
35
|
+
// REQUIRES INTEGRATION TEST
|
36
|
+
/* c8 ignore start */
|
37
|
+
/**
|
38
|
+
* @param {Element} node any DOM element registered via define.
|
39
|
+
*/
|
40
|
+
const handleCustomType = (node) => {
|
41
|
+
for (const selector of CUSTOM_SELECTORS) {
|
42
|
+
if (node.matches(selector)) {
|
43
|
+
const type = types.get(selector);
|
44
|
+
const { resolve } = waitList.get(type);
|
45
|
+
const { options, known } = registry.get(type);
|
46
|
+
if (!known.has(node)) {
|
47
|
+
known.add(node);
|
48
|
+
const {
|
49
|
+
interpreter: runtime,
|
50
|
+
version,
|
51
|
+
config,
|
52
|
+
env,
|
53
|
+
onRuntimeReady,
|
54
|
+
} = options;
|
55
|
+
const name = getRuntimeID(runtime, version);
|
56
|
+
const id = env || `${name}${config ? `|${config}` : ""}`;
|
57
|
+
const { interpreter: engine, XWorker } = getDetails(
|
58
|
+
runtime,
|
59
|
+
id,
|
60
|
+
name,
|
61
|
+
version,
|
62
|
+
config,
|
63
|
+
);
|
64
|
+
engine.then((interpreter) => {
|
65
|
+
if (!patched.has(id)) {
|
66
|
+
const module = create(defaultRegistry.get(runtime));
|
67
|
+
const {
|
68
|
+
onBeforeRun,
|
69
|
+
onBeforeRunAsync,
|
70
|
+
onAfterRun,
|
71
|
+
onAfterRunAsync,
|
72
|
+
codeBeforeRunWorker,
|
73
|
+
codeBeforeRunWorkerAsync,
|
74
|
+
codeAfterRunWorker,
|
75
|
+
codeAfterRunWorkerAsync,
|
76
|
+
} = options;
|
77
|
+
|
78
|
+
// These two loops mimic a `new Map(arrayContent)` without needing
|
79
|
+
// the new Map overhead so that [name, [before, after]] can be easily destructured
|
80
|
+
// and new sync or async patches become easy to add (when the logic is the same).
|
81
|
+
|
82
|
+
// patch sync
|
83
|
+
for (const [name, [before, after]] of [
|
84
|
+
["run", [onBeforeRun, onAfterRun]],
|
85
|
+
]) {
|
86
|
+
const method = module[name];
|
87
|
+
module[name] = function (interpreter, code) {
|
88
|
+
if (before) before.call(this, resolved, node);
|
89
|
+
const result = method.call(
|
90
|
+
this,
|
91
|
+
interpreter,
|
92
|
+
code,
|
93
|
+
);
|
94
|
+
if (after) after.call(this, resolved, node);
|
95
|
+
return result;
|
96
|
+
};
|
97
|
+
}
|
98
|
+
|
99
|
+
// patch async
|
100
|
+
for (const [name, [before, after]] of [
|
101
|
+
["runAsync", [onBeforeRunAsync, onAfterRunAsync]],
|
102
|
+
]) {
|
103
|
+
const method = module[name];
|
104
|
+
module[name] = async function (interpreter, code) {
|
105
|
+
if (before)
|
106
|
+
await before.call(this, resolved, node);
|
107
|
+
const result = await method.call(
|
108
|
+
this,
|
109
|
+
interpreter,
|
110
|
+
code,
|
111
|
+
);
|
112
|
+
if (after)
|
113
|
+
await after.call(this, resolved, node);
|
114
|
+
return result;
|
115
|
+
};
|
116
|
+
}
|
117
|
+
|
118
|
+
// setup XWorker hooks, allowing strings to be forwarded to the worker
|
119
|
+
// whenever it's created, as functions can't possibly be serialized
|
120
|
+
// unless these are pure with no outer scope access (or globals vars)
|
121
|
+
// so that making it strings disambiguate about their running context.
|
122
|
+
workerHooks.set(XWorker, {
|
123
|
+
beforeRun: codeBeforeRunWorker,
|
124
|
+
beforeRunAsync: codeBeforeRunWorkerAsync,
|
125
|
+
afterRun: codeAfterRunWorker,
|
126
|
+
afterRunAsync: codeAfterRunWorkerAsync,
|
127
|
+
});
|
128
|
+
|
129
|
+
module.setGlobal(interpreter, "XWorker", XWorker);
|
130
|
+
|
131
|
+
const resolved = {
|
132
|
+
type,
|
133
|
+
interpreter,
|
134
|
+
XWorker,
|
135
|
+
io: io.get(interpreter),
|
136
|
+
config: structuredClone(configs.get(name)),
|
137
|
+
run: module.run.bind(module, interpreter),
|
138
|
+
runAsync: module.runAsync.bind(module, interpreter),
|
139
|
+
};
|
140
|
+
|
141
|
+
patched.set(id, resolved);
|
142
|
+
resolve(resolved);
|
143
|
+
}
|
144
|
+
|
145
|
+
onRuntimeReady?.(patched.get(id), node);
|
146
|
+
});
|
147
|
+
}
|
148
|
+
}
|
149
|
+
}
|
150
|
+
};
|
151
|
+
exports.handleCustomType = handleCustomType;
|
152
|
+
|
153
|
+
/**
|
154
|
+
* @type {Map<string, {options:object, known:WeakSet<Element>}>}
|
155
|
+
*/
|
156
|
+
const registry = new Map();
|
157
|
+
|
158
|
+
/**
|
159
|
+
* @typedef {Object} PluginOptions custom configuration
|
160
|
+
* @prop {'pyodide' | 'micropython' | 'wasmoon' | 'ruby-wasm-wasi'} interpreter the interpreter to use
|
161
|
+
* @prop {string} [version] the optional interpreter version to use
|
162
|
+
* @prop {string} [config] the optional config to use within such interpreter
|
163
|
+
* @prop {(environment: object, node: Element) => void} [onRuntimeReady] the callback that will be invoked once
|
164
|
+
*/
|
165
|
+
|
166
|
+
/**
|
167
|
+
* Allows custom types and components on the page to receive interpreters to execute any code
|
168
|
+
* @param {string} type the unique `<script type="...">` identifier
|
169
|
+
* @param {PluginOptions} options the custom type configuration
|
170
|
+
*/
|
171
|
+
const define = (type, options) => {
|
172
|
+
if (defaultRegistry.has(type) || registry.has(type))
|
173
|
+
throw new Error(`<script type="${type}"> already registered`);
|
174
|
+
|
175
|
+
if (!defaultRegistry.has(options?.interpreter))
|
176
|
+
throw new Error(`Unspecified interpreter`);
|
177
|
+
|
178
|
+
// allows reaching out the interpreter helpers on events
|
179
|
+
defaultRegistry.set(type, defaultRegistry.get(options?.interpreter));
|
180
|
+
|
181
|
+
// ensure a Promise can resolve once a custom type has been bootstrapped
|
182
|
+
whenDefined(type);
|
183
|
+
|
184
|
+
// allows selector -> registry by type
|
185
|
+
const selectors = [`script[type="${type}"]`, `${type}-script`];
|
186
|
+
for (const selector of selectors) types.set(selector, type);
|
187
|
+
|
188
|
+
CUSTOM_SELECTORS.push(...selectors);
|
189
|
+
prefixes.push(`${type}-`);
|
190
|
+
|
191
|
+
// ensure always same env for this custom type
|
192
|
+
registry.set(type, {
|
193
|
+
options: assign({ env: type }, options),
|
194
|
+
known: new WeakSet(),
|
195
|
+
});
|
196
|
+
|
197
|
+
addAllListeners(document);
|
198
|
+
$$(selectors.join(",")).forEach(handleCustomType);
|
199
|
+
};
|
200
|
+
exports.define = define;
|
201
|
+
|
202
|
+
/**
|
203
|
+
* Resolves whenever a defined custom type is bootstrapped on the page
|
204
|
+
* @param {string} type the unique `<script type="...">` identifier
|
205
|
+
* @returns {Promise<object>}
|
206
|
+
*/
|
207
|
+
const whenDefined = (type) => {
|
208
|
+
if (!waitList.has(type)) waitList.set(type, Promise.withResolvers());
|
209
|
+
return waitList.get(type).promise;
|
210
|
+
};
|
211
|
+
exports.whenDefined = whenDefined;
|
212
|
+
/* c8 ignore stop */
|
@@ -0,0 +1,12 @@
|
|
1
|
+
'use strict';
|
2
|
+
/** @param {Response} response */
|
3
|
+
const getBuffer = (response) => response.arrayBuffer();
|
4
|
+
exports.getBuffer = getBuffer;
|
5
|
+
|
6
|
+
/** @param {Response} response */
|
7
|
+
const getJSON = (response) => response.json();
|
8
|
+
exports.getJSON = getJSON;
|
9
|
+
|
10
|
+
/** @param {Response} response */
|
11
|
+
const getText = (response) => response.text();
|
12
|
+
exports.getText = getText;
|
package/cjs/index.js
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
'use strict';
|
2
|
+
const { $$ } = require("basic-devtools");
|
3
|
+
|
4
|
+
const xworker = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require("./worker/class.js"));
|
5
|
+
const { handle } = require("./script-handler.js");
|
6
|
+
const { assign } = require("./utils.js");
|
7
|
+
const { selectors, prefixes } = require("./interpreters.js");
|
8
|
+
const { CUSTOM_SELECTORS, handleCustomType } = require("./custom-types.js");
|
9
|
+
const { listener, addAllListeners } = require("./listeners.js");
|
10
|
+
|
11
|
+
(m => {
|
12
|
+
exports.define = m.define;
|
13
|
+
exports.whenDefined = m.whenDefined;
|
14
|
+
})(require("./custom-types.js"));
|
15
|
+
const XWorker = xworker();
|
16
|
+
exports.XWorker = XWorker;
|
17
|
+
|
18
|
+
const INTERPRETER_SELECTORS = selectors.join(",");
|
19
|
+
|
20
|
+
const mo = new MutationObserver((records) => {
|
21
|
+
for (const { type, target, attributeName, addedNodes } of records) {
|
22
|
+
// attributes are tested via integration / e2e
|
23
|
+
/* c8 ignore next 17 */
|
24
|
+
if (type === "attributes") {
|
25
|
+
const i = attributeName.lastIndexOf("-") + 1;
|
26
|
+
if (i) {
|
27
|
+
const prefix = attributeName.slice(0, i);
|
28
|
+
for (const p of prefixes) {
|
29
|
+
if (prefix === p) {
|
30
|
+
const type = attributeName.slice(i);
|
31
|
+
if (type !== "env") {
|
32
|
+
const method = target.hasAttribute(attributeName)
|
33
|
+
? "add"
|
34
|
+
: "remove";
|
35
|
+
target[`${method}EventListener`](type, listener);
|
36
|
+
}
|
37
|
+
break;
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
continue;
|
42
|
+
}
|
43
|
+
for (const node of addedNodes) {
|
44
|
+
if (node.nodeType === 1) {
|
45
|
+
addAllListeners(node);
|
46
|
+
if (node.matches(INTERPRETER_SELECTORS)) handle(node);
|
47
|
+
else {
|
48
|
+
$$(INTERPRETER_SELECTORS, node).forEach(handle);
|
49
|
+
if (!CUSTOM_SELECTORS.length) continue;
|
50
|
+
handleCustomType(node);
|
51
|
+
$$(CUSTOM_SELECTORS.join(","), node).forEach(
|
52
|
+
handleCustomType,
|
53
|
+
);
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
});
|
59
|
+
|
60
|
+
const observe = (root) => {
|
61
|
+
mo.observe(root, { childList: true, subtree: true, attributes: true });
|
62
|
+
return root;
|
63
|
+
};
|
64
|
+
|
65
|
+
const { attachShadow } = Element.prototype;
|
66
|
+
assign(Element.prototype, {
|
67
|
+
attachShadow(init) {
|
68
|
+
return observe(attachShadow.call(this, init));
|
69
|
+
},
|
70
|
+
});
|
71
|
+
|
72
|
+
addAllListeners(observe(document));
|
73
|
+
$$(INTERPRETER_SELECTORS, document).forEach(handle);
|
@@ -0,0 +1,16 @@
|
|
1
|
+
'use strict';
|
2
|
+
const { clean, writeFile: writeFileUtil } = require("./_utils.js");
|
3
|
+
|
4
|
+
// REQUIRES INTEGRATION TEST
|
5
|
+
/* c8 ignore start */
|
6
|
+
const run = (interpreter, code) => interpreter.runPython(clean(code));
|
7
|
+
exports.run = run;
|
8
|
+
|
9
|
+
const runAsync = (interpreter, code) =>
|
10
|
+
interpreter.runPythonAsync(clean(code));
|
11
|
+
exports.runAsync = runAsync;
|
12
|
+
|
13
|
+
const writeFile = ({ FS }, path, buffer) =>
|
14
|
+
writeFileUtil(FS, path, buffer);
|
15
|
+
exports.writeFile = writeFile;
|
16
|
+
/* c8 ignore stop */
|
@@ -0,0 +1,141 @@
|
|
1
|
+
'use strict';
|
2
|
+
require("@ungap/with-resolvers");
|
3
|
+
|
4
|
+
const { getBuffer } = require("../fetch-utils.js");
|
5
|
+
const { absoluteURL } = require("../utils.js");
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Trim code only if it's a single line that prettier or other tools might have modified.
|
9
|
+
* @param {string} code code that might be a single line
|
10
|
+
* @returns {string}
|
11
|
+
*/
|
12
|
+
const clean = (code) =>
|
13
|
+
code.replace(/^[^\r\n]+$/, (line) => line.trim());
|
14
|
+
exports.clean = clean;
|
15
|
+
|
16
|
+
// REQUIRES INTEGRATION TEST
|
17
|
+
/* c8 ignore start */
|
18
|
+
const io = new WeakMap();
|
19
|
+
exports.io = io;
|
20
|
+
const stdio = (init) => {
|
21
|
+
const context = init || console;
|
22
|
+
const localIO = {
|
23
|
+
stderr: (context.stderr || console.error).bind(context),
|
24
|
+
stdout: (context.stdout || console.log).bind(context),
|
25
|
+
};
|
26
|
+
return {
|
27
|
+
stderr: (...args) => localIO.stderr(...args),
|
28
|
+
stdout: (...args) => localIO.stdout(...args),
|
29
|
+
async get(engine) {
|
30
|
+
const interpreter = await engine;
|
31
|
+
io.set(interpreter, localIO);
|
32
|
+
return interpreter;
|
33
|
+
},
|
34
|
+
};
|
35
|
+
};
|
36
|
+
exports.stdio = stdio;
|
37
|
+
/* c8 ignore stop */
|
38
|
+
|
39
|
+
// This should be the only helper needed for all Emscripten based FS exports
|
40
|
+
const writeFile = (FS, path, buffer) => {
|
41
|
+
const { parentPath, name } = FS.analyzePath(path, true);
|
42
|
+
FS.mkdirTree(parentPath);
|
43
|
+
return FS.writeFile([parentPath, name].join("/"), new Uint8Array(buffer), {
|
44
|
+
canOwn: true,
|
45
|
+
});
|
46
|
+
};
|
47
|
+
exports.writeFile = writeFile;
|
48
|
+
|
49
|
+
// This is instead a fallback for Lua or others
|
50
|
+
const writeFileShim = (FS, path, buffer) => {
|
51
|
+
path = resolve(FS, path);
|
52
|
+
mkdirTree(FS, dirname(path));
|
53
|
+
return FS.writeFile(path, new Uint8Array(buffer), { canOwn: true });
|
54
|
+
};
|
55
|
+
exports.writeFileShim = writeFileShim;
|
56
|
+
|
57
|
+
const dirname = (path) => {
|
58
|
+
const tree = path.split("/");
|
59
|
+
tree.pop();
|
60
|
+
return tree.join("/");
|
61
|
+
};
|
62
|
+
|
63
|
+
const mkdirTree = (FS, path) => {
|
64
|
+
const current = [];
|
65
|
+
for (const branch of path.split("/")) {
|
66
|
+
current.push(branch);
|
67
|
+
if (branch) FS.mkdir(current.join("/"));
|
68
|
+
}
|
69
|
+
};
|
70
|
+
|
71
|
+
const resolve = (FS, path) => {
|
72
|
+
const tree = [];
|
73
|
+
for (const branch of path.split("/")) {
|
74
|
+
switch (branch) {
|
75
|
+
case "":
|
76
|
+
break;
|
77
|
+
case ".":
|
78
|
+
break;
|
79
|
+
case "..":
|
80
|
+
tree.pop();
|
81
|
+
break;
|
82
|
+
default:
|
83
|
+
tree.push(branch);
|
84
|
+
}
|
85
|
+
}
|
86
|
+
return [FS.cwd()].concat(tree).join("/").replace(/^\/+/, "/");
|
87
|
+
};
|
88
|
+
|
89
|
+
const { all, isArray } = require("../utils.js");
|
90
|
+
|
91
|
+
const calculateFetchPaths = (config_fetch) => {
|
92
|
+
// REQUIRES INTEGRATION TEST
|
93
|
+
/* c8 ignore start */
|
94
|
+
for (const { files, to_file, from = "" } of config_fetch) {
|
95
|
+
if (files !== undefined && to_file !== undefined)
|
96
|
+
throw new Error(
|
97
|
+
`Cannot use 'to_file' and 'files' parameters together!`,
|
98
|
+
);
|
99
|
+
if (files === undefined && to_file === undefined && from.endsWith("/"))
|
100
|
+
throw new Error(
|
101
|
+
`Couldn't determine the filename from the path ${from}, please supply 'to_file' parameter.`,
|
102
|
+
);
|
103
|
+
}
|
104
|
+
/* c8 ignore stop */
|
105
|
+
return config_fetch.flatMap(
|
106
|
+
({ from = "", to_folder = ".", to_file, files }) => {
|
107
|
+
if (isArray(files))
|
108
|
+
return files.map((file) => ({
|
109
|
+
url: joinPaths([from, file]),
|
110
|
+
path: joinPaths([to_folder, file]),
|
111
|
+
}));
|
112
|
+
const filename = to_file || from.slice(1 + from.lastIndexOf("/"));
|
113
|
+
return [{ url: from, path: joinPaths([to_folder, filename]) }];
|
114
|
+
},
|
115
|
+
);
|
116
|
+
};
|
117
|
+
|
118
|
+
const joinPaths = (parts) => {
|
119
|
+
const res = parts
|
120
|
+
.map((part) => part.trim().replace(/(^[/]*|[/]*$)/g, ""))
|
121
|
+
.filter((p) => p !== "" && p !== ".")
|
122
|
+
.join("/");
|
123
|
+
|
124
|
+
return parts[0].startsWith("/") ? `/${res}` : res;
|
125
|
+
};
|
126
|
+
|
127
|
+
const fetchResolved = (config_fetch, url) =>
|
128
|
+
fetch(absoluteURL(url, base.get(config_fetch)));
|
129
|
+
|
130
|
+
const base = new WeakMap();
|
131
|
+
exports.base = base;
|
132
|
+
|
133
|
+
const fetchPaths = (module, interpreter, config_fetch) =>
|
134
|
+
all(
|
135
|
+
calculateFetchPaths(config_fetch).map(({ url, path }) =>
|
136
|
+
fetchResolved(config_fetch, url)
|
137
|
+
.then(getBuffer)
|
138
|
+
.then((buffer) => module.writeFile(interpreter, path, buffer)),
|
139
|
+
),
|
140
|
+
);
|
141
|
+
exports.fetchPaths = fetchPaths;
|
@@ -0,0 +1,34 @@
|
|
1
|
+
'use strict';
|
2
|
+
const { fetchPaths, stdio } = require("./_utils.js");
|
3
|
+
const { run, runAsync, writeFile } = require("./_python.js");
|
4
|
+
|
5
|
+
const type = "micropython";
|
6
|
+
|
7
|
+
// REQUIRES INTEGRATION TEST
|
8
|
+
/* c8 ignore start */
|
9
|
+
module.exports = {
|
10
|
+
type,
|
11
|
+
module: (version = "1.20.0-239") =>
|
12
|
+
`https://cdn.jsdelivr.net/npm/@micropython/micropython-webassembly-pyscript@${version}/micropython.mjs`,
|
13
|
+
async engine({ loadMicroPython }, config, url) {
|
14
|
+
const { stderr, stdout, get } = stdio();
|
15
|
+
url = url.replace(/\.m?js$/, ".wasm");
|
16
|
+
const runtime = await get(loadMicroPython({ stderr, stdout, url }));
|
17
|
+
if (config.fetch) await fetchPaths(this, runtime, config.fetch);
|
18
|
+
return runtime;
|
19
|
+
},
|
20
|
+
setGlobal(interpreter, name, value) {
|
21
|
+
const id = `__pyscript_${this.type}_${name}`;
|
22
|
+
globalThis[id] = value;
|
23
|
+
this.run(interpreter, `from js import ${id};${name}=${id};`);
|
24
|
+
},
|
25
|
+
deleteGlobal(interpreter, name) {
|
26
|
+
const id = `__pyscript_${this.type}_${name}`;
|
27
|
+
this.run(interpreter, `del ${id};del ${name}`);
|
28
|
+
delete globalThis[id];
|
29
|
+
},
|
30
|
+
run,
|
31
|
+
runAsync,
|
32
|
+
writeFile,
|
33
|
+
};
|
34
|
+
/* c8 ignore stop */
|
@@ -0,0 +1,38 @@
|
|
1
|
+
'use strict';
|
2
|
+
const { fetchPaths, stdio } = require("./_utils.js");
|
3
|
+
const { run, runAsync, writeFile } = require("./_python.js");
|
4
|
+
|
5
|
+
const type = "pyodide";
|
6
|
+
|
7
|
+
// REQUIRES INTEGRATION TEST
|
8
|
+
/* c8 ignore start */
|
9
|
+
module.exports = {
|
10
|
+
type,
|
11
|
+
module: (version = "0.23.2") =>
|
12
|
+
`https://cdn.jsdelivr.net/pyodide/v${version}/full/pyodide.mjs`,
|
13
|
+
async engine({ loadPyodide }, config, url) {
|
14
|
+
const { stderr, stdout, get } = stdio();
|
15
|
+
const indexURL = url.slice(0, url.lastIndexOf("/"));
|
16
|
+
const interpreter = await get(
|
17
|
+
loadPyodide({ stderr, stdout, indexURL }),
|
18
|
+
);
|
19
|
+
if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
|
20
|
+
if (config.packages) {
|
21
|
+
await interpreter.loadPackage("micropip");
|
22
|
+
const micropip = await interpreter.pyimport("micropip");
|
23
|
+
await micropip.install(config.packages);
|
24
|
+
micropip.destroy();
|
25
|
+
}
|
26
|
+
return interpreter;
|
27
|
+
},
|
28
|
+
setGlobal(interpreter, name, value) {
|
29
|
+
interpreter.globals.set(name, value);
|
30
|
+
},
|
31
|
+
deleteGlobal(interpreter, name) {
|
32
|
+
interpreter.globals.delete(name);
|
33
|
+
},
|
34
|
+
run,
|
35
|
+
runAsync,
|
36
|
+
writeFile,
|
37
|
+
};
|
38
|
+
/* c8 ignore stop */
|
@@ -0,0 +1,43 @@
|
|
1
|
+
'use strict';
|
2
|
+
const { clean, fetchPaths } = require("./_utils.js");
|
3
|
+
|
4
|
+
const type = "ruby-wasm-wasi";
|
5
|
+
|
6
|
+
// MISSING:
|
7
|
+
// * there is no VFS apparently or I couldn't reach any
|
8
|
+
// * I've no idea how to override the stderr and stdout
|
9
|
+
// * I've no idea how to import packages
|
10
|
+
|
11
|
+
// REQUIRES INTEGRATION TEST
|
12
|
+
/* c8 ignore start */
|
13
|
+
module.exports = {
|
14
|
+
type,
|
15
|
+
experimental: true,
|
16
|
+
module: (version = "2.0.0") =>
|
17
|
+
`https://cdn.jsdelivr.net/npm/ruby-3_2-wasm-wasi@${version}/dist/browser.esm.js`,
|
18
|
+
async engine({ DefaultRubyVM }, config, url) {
|
19
|
+
const response = await fetch(
|
20
|
+
`${url.slice(0, url.lastIndexOf("/"))}/ruby.wasm`,
|
21
|
+
);
|
22
|
+
const module = await WebAssembly.compile(await response.arrayBuffer());
|
23
|
+
const { vm: interpreter } = await DefaultRubyVM(module);
|
24
|
+
if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
|
25
|
+
return interpreter;
|
26
|
+
},
|
27
|
+
setGlobal(interpreter, name, value) {
|
28
|
+
const id = `__pyscript_ruby_wasm_wasi_${name}`;
|
29
|
+
globalThis[id] = value;
|
30
|
+
this.run(interpreter, `require "js";$${name}=JS::eval("return ${id}")`);
|
31
|
+
},
|
32
|
+
deleteGlobal(interpreter, name) {
|
33
|
+
const id = `__pyscript_ruby_wasm_wasi_${name}`;
|
34
|
+
this.run(interpreter, `$${name}=nil`);
|
35
|
+
delete globalThis[id];
|
36
|
+
},
|
37
|
+
run: (interpreter, code) => interpreter.eval(clean(code)),
|
38
|
+
runAsync: (interpreter, code) => interpreter.evalAsync(clean(code)),
|
39
|
+
writeFile: () => {
|
40
|
+
throw new Error(`writeFile is not supported in ${type}`);
|
41
|
+
},
|
42
|
+
};
|
43
|
+
/* c8 ignore stop */
|
@@ -0,0 +1,40 @@
|
|
1
|
+
'use strict';
|
2
|
+
const { clean, fetchPaths, stdio, writeFileShim } = require("./_utils.js");
|
3
|
+
|
4
|
+
const type = "wasmoon";
|
5
|
+
|
6
|
+
// REQUIRES INTEGRATION TEST
|
7
|
+
/* c8 ignore start */
|
8
|
+
module.exports = {
|
9
|
+
type,
|
10
|
+
module: (version = "1.15.0") =>
|
11
|
+
`https://cdn.jsdelivr.net/npm/wasmoon@${version}/+esm`,
|
12
|
+
async engine({ LuaFactory, LuaLibraries }, config) {
|
13
|
+
const { stderr, stdout, get } = stdio();
|
14
|
+
const interpreter = await get(new LuaFactory().createEngine());
|
15
|
+
interpreter.global.getTable(LuaLibraries.Base, (index) => {
|
16
|
+
interpreter.global.setField(index, "print", stdout);
|
17
|
+
interpreter.global.setField(index, "printErr", stderr);
|
18
|
+
});
|
19
|
+
if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
|
20
|
+
return interpreter;
|
21
|
+
},
|
22
|
+
setGlobal(interpreter, name, value) {
|
23
|
+
interpreter.global.set(name, value);
|
24
|
+
},
|
25
|
+
deleteGlobal(interpreter, name) {
|
26
|
+
interpreter.global.set(name, void 0);
|
27
|
+
},
|
28
|
+
run: (interpreter, code) => interpreter.doStringSync(clean(code)),
|
29
|
+
runAsync: (interpreter, code) => interpreter.doString(clean(code)),
|
30
|
+
writeFile: (
|
31
|
+
{
|
32
|
+
cmodule: {
|
33
|
+
module: { FS },
|
34
|
+
},
|
35
|
+
},
|
36
|
+
path,
|
37
|
+
buffer,
|
38
|
+
) => writeFileShim(FS, path, buffer),
|
39
|
+
};
|
40
|
+
/* c8 ignore stop */
|