mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-19 18:27:29 -05:00
[next] Improve config parsing on bootstrap (#1704)
This commit is contained in:
committed by
GitHub
parent
00fdc73015
commit
840bc803b7
@@ -16,6 +16,7 @@ repos:
|
|||||||
- id: check-json
|
- id: check-json
|
||||||
exclude: tsconfig\.json
|
exclude: tsconfig\.json
|
||||||
- id: check-toml
|
- id: check-toml
|
||||||
|
exclude: bad\.toml
|
||||||
- id: check-xml
|
- id: check-xml
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: detect-private-key
|
- id: detect-private-key
|
||||||
|
|||||||
10
pyscript.core/package-lock.json
generated
10
pyscript.core/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@pyscript/core",
|
"name": "@pyscript/core",
|
||||||
"version": "0.1.18",
|
"version": "0.1.19",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@pyscript/core",
|
"name": "@pyscript/core",
|
||||||
"version": "0.1.18",
|
"version": "0.1.19",
|
||||||
"license": "APACHE-2.0",
|
"license": "APACHE-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ungap/with-resolvers": "^0.1.0",
|
"@ungap/with-resolvers": "^0.1.0",
|
||||||
@@ -597,9 +597,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.4.519",
|
"version": "1.4.520",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.519.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.520.tgz",
|
||||||
"integrity": "sha512-kqs9oGYL4UFVkLKhqCTgBCYZv+wZ374yABDMqlDda9HvlkQxvSr7kgf4hfWVjMieDbX+1MwPHFBsOGCMIBaFKg==",
|
"integrity": "sha512-Frfus2VpYADsrh1lB3v/ft/WVFlVzOIm+Q0p7U7VqHI6qr7NWHYKe+Wif3W50n7JAFoBsWVsoU0+qDks6WQ60g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/entities": {
|
"node_modules/entities": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@pyscript/core",
|
"name": "@pyscript/core",
|
||||||
"version": "0.1.18",
|
"version": "0.1.19",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "PyScript",
|
"description": "PyScript",
|
||||||
"module": "./index.js",
|
"module": "./index.js",
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"server": "npx static-handler --cors --coep --coop --corp .",
|
"server": "npx static-handler --cors --coep --coop --corp .",
|
||||||
"build": "node rollup/stdlib.cjs && node rollup/plugins.cjs && rm -rf dist && rollup --config rollup/core.config.js && npm run ts",
|
"build": "node rollup/stdlib.cjs && node rollup/plugins.cjs && rm -rf dist && rollup --config rollup/core.config.js && npm run ts",
|
||||||
|
"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",
|
||||||
"ts": "tsc -p ."
|
"ts": "tsc -p ."
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -7,38 +7,96 @@ import { $ } from "basic-devtools";
|
|||||||
|
|
||||||
import allPlugins from "./plugins.js";
|
import allPlugins from "./plugins.js";
|
||||||
import { robustFetch as fetch, getText } from "./fetch.js";
|
import { robustFetch as fetch, getText } from "./fetch.js";
|
||||||
|
import { ErrorCode } from "./exceptions.js";
|
||||||
|
|
||||||
// TODO: this is not strictly polyscript related but handy ... not sure
|
const badURL = (url, expected = "") => {
|
||||||
// we should factor this utility out a part but this works anyway.
|
let message = `(${ErrorCode.BAD_CONFIG}): Invalid URL: ${url}`;
|
||||||
import { parse } from "../node_modules/polyscript/esm/toml.js";
|
if (expected) message += `\nexpected ${expected} content`;
|
||||||
|
throw new Error(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a string, returns its trimmed content as text,
|
||||||
|
* fetching it from a file if the content is a URL.
|
||||||
|
* @param {string} config either JSON, TOML, or a file to fetch
|
||||||
|
* @returns {{json: boolean, toml: boolean, text: string}}
|
||||||
|
*/
|
||||||
|
const configDetails = async (config) => {
|
||||||
|
let text = config?.trim();
|
||||||
|
// we only support an object as root config
|
||||||
|
let url = "",
|
||||||
|
toml = false,
|
||||||
|
json = /^{/.test(text) && /}$/.test(text);
|
||||||
|
// handle files by extension (relaxing urls parts after)
|
||||||
|
if (!json && /\.(\w+)(?:\?\S*)?$/.test(text)) {
|
||||||
|
const ext = RegExp.$1;
|
||||||
|
if (ext === "json" && type !== "toml") json = true;
|
||||||
|
else if (ext === "toml" && type !== "json") toml = true;
|
||||||
|
else badURL(text, type);
|
||||||
|
url = text;
|
||||||
|
text = (await fetch(url).then(getText)).trim();
|
||||||
|
}
|
||||||
|
return { json, toml: toml || (!json && !!text), text, url };
|
||||||
|
};
|
||||||
|
|
||||||
|
const syntaxError = (type, url, { message }) => {
|
||||||
|
let str = `(${ErrorCode.BAD_CONFIG}): Invalid ${type}`;
|
||||||
|
if (url) str += ` @ ${url}`;
|
||||||
|
return new SyntaxError(`${str}\n${message}`);
|
||||||
|
};
|
||||||
|
|
||||||
// find the shared config for all py-script elements
|
// find the shared config for all py-script elements
|
||||||
let config, plugins, parsed;
|
let config, plugins, parsed, error, type;
|
||||||
let pyConfig = $("py-config");
|
let pyConfig = $("py-config");
|
||||||
if (pyConfig) config = pyConfig.getAttribute("src") || pyConfig.textContent;
|
if (pyConfig) config = pyConfig.getAttribute("src") || pyConfig.textContent;
|
||||||
else {
|
else {
|
||||||
pyConfig = $('script[type="py"][config]');
|
pyConfig = $('script[type="py"][config]');
|
||||||
if (pyConfig) config = pyConfig.getAttribute("config");
|
if (pyConfig) config = pyConfig.getAttribute("config");
|
||||||
}
|
}
|
||||||
|
if (pyConfig) type = pyConfig.getAttribute("type");
|
||||||
|
|
||||||
// load its content if remote
|
// catch possible fetch errors
|
||||||
if (/^https?:\/\//.test(config)) config = await fetch(config).then(getText);
|
try {
|
||||||
|
const { json, toml, text, url } = await configDetails(config);
|
||||||
// parse config only if not empty
|
config = text;
|
||||||
if (config?.trim()) {
|
if (json || type === "json") {
|
||||||
try {
|
try {
|
||||||
parsed = JSON.parse(config);
|
parsed = JSON.parse(text);
|
||||||
} catch (_) {
|
} catch (e) {
|
||||||
parsed = await parse(config);
|
error = syntaxError("JSON", url, e);
|
||||||
|
}
|
||||||
|
} else if (toml || type === "toml") {
|
||||||
|
try {
|
||||||
|
const { parse } = await import(
|
||||||
|
/* webpackIgnore: true */
|
||||||
|
"https://cdn.jsdelivr.net/npm/@webreflection/toml-j0.4/toml.js"
|
||||||
|
);
|
||||||
|
parsed = parse(text);
|
||||||
|
} catch (e) {
|
||||||
|
error = syntaxError("TOML", url, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse all plugins and optionally ignore only
|
// parse all plugins and optionally ignore only
|
||||||
// those flagged as "undesired" via `!` prefix
|
// those flagged as "undesired" via `!` prefix
|
||||||
const toBeAwaited = [];
|
const toBeAwaited = [];
|
||||||
for (const [key, value] of Object.entries(allPlugins)) {
|
for (const [key, value] of Object.entries(allPlugins)) {
|
||||||
if (!parsed?.plugins?.includes(`!${key}`)) toBeAwaited.push(value());
|
if (error) {
|
||||||
|
if (key === "error") {
|
||||||
|
// show on page the config is broken, meaning that
|
||||||
|
// it was not possible to disable error plugin neither
|
||||||
|
// as that part wasn't correctly parsed anyway
|
||||||
|
value().then(({ notify }) => notify(error.message));
|
||||||
|
}
|
||||||
|
} else if (!parsed?.plugins?.includes(`!${key}`)) {
|
||||||
|
toBeAwaited.push(value());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// assign plugins as Promise.all only if needed
|
||||||
if (toBeAwaited.length) plugins = Promise.all(toBeAwaited);
|
if (toBeAwaited.length) plugins = Promise.all(toBeAwaited);
|
||||||
|
|
||||||
export { config, plugins };
|
export { config, plugins, error };
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { Hook } from "../node_modules/polyscript/esm/worker/hooks.js";
|
|||||||
|
|
||||||
import sync from "./sync.js";
|
import sync from "./sync.js";
|
||||||
import stdlib from "./stdlib.js";
|
import stdlib from "./stdlib.js";
|
||||||
import { config, plugins } from "./config.js";
|
import { config, plugins, error } from "./config.js";
|
||||||
import { robustFetch as fetch, getText } from "./fetch.js";
|
import { robustFetch as fetch, getText } from "./fetch.js";
|
||||||
|
|
||||||
const { assign, defineProperty, entries } = Object;
|
const { assign, defineProperty, entries } = Object;
|
||||||
@@ -128,74 +128,81 @@ const workerHooks = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// define the module as both `<script type="py">` and `<py-script>`
|
// define the module as both `<script type="py">` and `<py-script>`
|
||||||
define(TYPE, {
|
// but only if the config didn't throw an error
|
||||||
config,
|
error ||
|
||||||
env: `${TYPE}-script`,
|
define(TYPE, {
|
||||||
interpreter: "pyodide",
|
config,
|
||||||
...workerHooks,
|
env: `${TYPE}-script`,
|
||||||
onWorkerReady(_, xworker) {
|
interpreter: "pyodide",
|
||||||
assign(xworker.sync, sync);
|
...workerHooks,
|
||||||
},
|
onWorkerReady(_, xworker) {
|
||||||
onBeforeRun(pyodide, element) {
|
assign(xworker.sync, sync);
|
||||||
currentElement = element;
|
},
|
||||||
bootstrapNodeAndPlugins(pyodide, element, before, "onBeforeRun");
|
onBeforeRun(pyodide, element) {
|
||||||
},
|
currentElement = element;
|
||||||
onBeforeRunAsync(pyodide, element) {
|
bootstrapNodeAndPlugins(pyodide, element, before, "onBeforeRun");
|
||||||
currentElement = element;
|
},
|
||||||
bootstrapNodeAndPlugins(pyodide, element, before, "onBeforeRunAsync");
|
onBeforeRunAsync(pyodide, element) {
|
||||||
},
|
currentElement = element;
|
||||||
onAfterRun(pyodide, element) {
|
bootstrapNodeAndPlugins(
|
||||||
bootstrapNodeAndPlugins(pyodide, element, after, "onAfterRun");
|
pyodide,
|
||||||
},
|
element,
|
||||||
onAfterRunAsync(pyodide, element) {
|
before,
|
||||||
bootstrapNodeAndPlugins(pyodide, element, after, "onAfterRunAsync");
|
"onBeforeRunAsync",
|
||||||
},
|
|
||||||
async onInterpreterReady(pyodide, element) {
|
|
||||||
if (shouldRegister) {
|
|
||||||
shouldRegister = false;
|
|
||||||
registerModule(pyodide);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure plugins are bootstrapped already
|
|
||||||
if (plugins) await plugins;
|
|
||||||
|
|
||||||
// allows plugins to do whatever they want with the element
|
|
||||||
// before regular stuff happens in here
|
|
||||||
for (const callback of hooks.onInterpreterReady)
|
|
||||||
callback(pyodide, element);
|
|
||||||
|
|
||||||
if (isScript(element)) {
|
|
||||||
const {
|
|
||||||
attributes: { async: isAsync, target },
|
|
||||||
} = element;
|
|
||||||
const hasTarget = !!target?.value;
|
|
||||||
const show = hasTarget
|
|
||||||
? queryTarget(target.value)
|
|
||||||
: document.createElement("script-py");
|
|
||||||
|
|
||||||
if (!hasTarget) {
|
|
||||||
const { head, body } = document;
|
|
||||||
if (head.contains(element)) body.append(show);
|
|
||||||
else element.after(show);
|
|
||||||
}
|
|
||||||
if (!show.id) show.id = getID();
|
|
||||||
|
|
||||||
// allows the code to retrieve the target element via
|
|
||||||
// document.currentScript.target if needed
|
|
||||||
defineProperty(element, "target", { value: show });
|
|
||||||
|
|
||||||
// notify before the code runs
|
|
||||||
dispatch(element, TYPE);
|
|
||||||
pyodide[`run${isAsync ? "Async" : ""}`](
|
|
||||||
await fetchSource(element, pyodide.io, true),
|
|
||||||
);
|
);
|
||||||
} else {
|
},
|
||||||
// resolve PyScriptElement to allow connectedCallback
|
onAfterRun(pyodide, element) {
|
||||||
element._pyodide.resolve(pyodide);
|
bootstrapNodeAndPlugins(pyodide, element, after, "onAfterRun");
|
||||||
}
|
},
|
||||||
console.debug("[pyscript/main] PyScript Ready");
|
onAfterRunAsync(pyodide, element) {
|
||||||
},
|
bootstrapNodeAndPlugins(pyodide, element, after, "onAfterRunAsync");
|
||||||
});
|
},
|
||||||
|
async onInterpreterReady(pyodide, element) {
|
||||||
|
if (shouldRegister) {
|
||||||
|
shouldRegister = false;
|
||||||
|
registerModule(pyodide);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure plugins are bootstrapped already
|
||||||
|
if (plugins) await plugins;
|
||||||
|
|
||||||
|
// allows plugins to do whatever they want with the element
|
||||||
|
// before regular stuff happens in here
|
||||||
|
for (const callback of hooks.onInterpreterReady)
|
||||||
|
callback(pyodide, element);
|
||||||
|
|
||||||
|
if (isScript(element)) {
|
||||||
|
const {
|
||||||
|
attributes: { async: isAsync, target },
|
||||||
|
} = element;
|
||||||
|
const hasTarget = !!target?.value;
|
||||||
|
const show = hasTarget
|
||||||
|
? queryTarget(target.value)
|
||||||
|
: document.createElement("script-py");
|
||||||
|
|
||||||
|
if (!hasTarget) {
|
||||||
|
const { head, body } = document;
|
||||||
|
if (head.contains(element)) body.append(show);
|
||||||
|
else element.after(show);
|
||||||
|
}
|
||||||
|
if (!show.id) show.id = getID();
|
||||||
|
|
||||||
|
// allows the code to retrieve the target element via
|
||||||
|
// document.currentScript.target if needed
|
||||||
|
defineProperty(element, "target", { value: show });
|
||||||
|
|
||||||
|
// notify before the code runs
|
||||||
|
dispatch(element, TYPE);
|
||||||
|
pyodide[`run${isAsync ? "Async" : ""}`](
|
||||||
|
await fetchSource(element, pyodide.io, true),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// resolve PyScriptElement to allow connectedCallback
|
||||||
|
element._pyodide.resolve(pyodide);
|
||||||
|
}
|
||||||
|
console.debug("[pyscript/main] PyScript Ready");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
class PyScriptElement extends HTMLElement {
|
class PyScriptElement extends HTMLElement {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -226,7 +233,8 @@ class PyScriptElement extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("py-script", PyScriptElement);
|
// define py-script only if the config didn't throw an error
|
||||||
|
error || customElements.define("py-script", PyScriptElement);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A `Worker` facade able to bootstrap on the worker thread only a PyScript module.
|
* A `Worker` facade able to bootstrap on the worker thread only a PyScript module.
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ hooks.onBeforeRun.add(function override(pyScript) {
|
|||||||
// Error hook utilities
|
// Error hook utilities
|
||||||
|
|
||||||
// Custom function to show notifications
|
// Custom function to show notifications
|
||||||
function notify(message) {
|
export function notify(message) {
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.className = "py-error";
|
div.className = "py-error";
|
||||||
div.textContent = message;
|
div.textContent = message;
|
||||||
|
|||||||
1
pyscript.core/test/bad.toml
Normal file
1
pyscript.core/test/bad.toml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
files = [
|
||||||
11
pyscript.core/test/config-url.html
Normal file
11
pyscript.core/test/config-url.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>PyScript Next Plugin</title>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
<py-config src="bad.toml" type="toml"></py-config>
|
||||||
|
</head>
|
||||||
|
</html>
|
||||||
13
pyscript.core/test/config.html
Normal file
13
pyscript.core/test/config.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>PyScript Next Plugin</title>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
<py-config>
|
||||||
|
files = [
|
||||||
|
</py-config>
|
||||||
|
</head>
|
||||||
|
</html>
|
||||||
1
pyscript.core/types/config.d.ts
vendored
1
pyscript.core/types/config.d.ts
vendored
@@ -1,2 +1,3 @@
|
|||||||
export let config: any;
|
export let config: any;
|
||||||
export let plugins: any;
|
export let plugins: any;
|
||||||
|
export let error: any;
|
||||||
|
|||||||
2
pyscript.core/types/plugins/error.d.ts
vendored
2
pyscript.core/types/plugins/error.d.ts
vendored
@@ -1 +1 @@
|
|||||||
export {};
|
export function notify(message: any): void;
|
||||||
|
|||||||
Reference in New Issue
Block a user