@pyscript/core 0.2.1 → 0.2.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"error-e4fe78fd.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\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":"kCA0BO,SAASA,EAAOC,GACnB,MAAMC,EAAMC,SAASC,cAAc,OACnCF,EAAIG,UAAY,WAChBH,EAAII,YAAcL,EAClBC,EAAIK,MAAMC,QAAU,6MAUpBL,SAASM,KAAKC,OAAOR,EACzB,CAtCAS,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"}
1
+ {"version":3,"file":"error-e4fe78fd.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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyscript/core",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "description": "PyScript",
6
6
  "module": "./index.js",
@@ -38,7 +38,7 @@
38
38
  "devDependencies": {
39
39
  "@rollup/plugin-node-resolve": "^15.2.1",
40
40
  "@rollup/plugin-terser": "^0.4.3",
41
- "rollup": "^3.29.2",
41
+ "rollup": "^3.29.3",
42
42
  "rollup-plugin-postcss": "^4.0.2",
43
43
  "rollup-plugin-string": "^3.0.0",
44
44
  "static-handler": "^0.4.2",
package/src/config.js CHANGED
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { $ } from "basic-devtools";
7
7
 
8
+ import TYPES from "./types.js";
8
9
  import allPlugins from "./plugins.js";
9
10
  import { robustFetch as fetch, getText } from "./fetch.js";
10
11
  import { ErrorCode } from "./exceptions.js";
@@ -19,9 +20,10 @@ const badURL = (url, expected = "") => {
19
20
  * Given a string, returns its trimmed content as text,
20
21
  * fetching it from a file if the content is a URL.
21
22
  * @param {string} config either JSON, TOML, or a file to fetch
23
+ * @param {string?} type the optional type to enforce
22
24
  * @returns {{json: boolean, toml: boolean, text: string}}
23
25
  */
24
- const configDetails = async (config) => {
26
+ const configDetails = async (config, type) => {
25
27
  let text = config?.trim();
26
28
  // we only support an object as root config
27
29
  let url = "",
@@ -45,66 +47,81 @@ const syntaxError = (type, url, { message }) => {
45
47
  return new SyntaxError(`${str}\n${message}`);
46
48
  };
47
49
 
48
- // find the shared config for all py-script elements
49
- let config, plugins, parsed, error, type;
50
- let pyConfig = $("py-config");
51
- if (pyConfig) {
52
- config = pyConfig.getAttribute("src") || pyConfig.textContent;
53
- type = pyConfig.getAttribute("type");
54
- } else {
55
- pyConfig = $(
56
- [
57
- 'script[type="py"][config]:not([worker])',
58
- "py-script[config]:not([worker])",
59
- ].join(","),
60
- );
61
- if (pyConfig) config = pyConfig.getAttribute("config");
62
- }
50
+ const configs = new Map();
63
51
 
64
- // catch possible fetch errors
65
- if (config) {
66
- try {
67
- const { json, toml, text, url } = await configDetails(config);
68
- config = text;
69
- if (json || type === "json") {
70
- try {
71
- parsed = JSON.parse(text);
72
- } catch (e) {
73
- error = syntaxError("JSON", url, e);
74
- }
75
- } else if (toml || type === "toml") {
76
- try {
77
- const { parse } = await import(
78
- /* webpackIgnore: true */
79
- "https://cdn.jsdelivr.net/npm/@webreflection/toml-j0.4/toml.js"
80
- );
81
- parsed = parse(text);
82
- } catch (e) {
83
- error = syntaxError("TOML", url, e);
52
+ for (const [TYPE] of TYPES) {
53
+ /** @type {Promise<any> | undefined} A Promise wrapping any plugins which should be loaded. */
54
+ let plugins;
55
+
56
+ /** @type {any} The PyScript configuration parsed from the JSON or TOML object*. May be any of the return types of JSON.parse() or toml-j0.4's parse() ( {number | string | boolean | null | object | Array} ) */
57
+ let parsed;
58
+
59
+ /** @type {SyntaxError | undefined} The error thrown when parsing the PyScript config, if any.*/
60
+ let error;
61
+
62
+ let config,
63
+ type,
64
+ pyConfig = $(`${TYPE}-config`);
65
+ if (pyConfig) {
66
+ config = pyConfig.getAttribute("src") || pyConfig.textContent;
67
+ type = pyConfig.getAttribute("type");
68
+ } else {
69
+ pyConfig = $(
70
+ [
71
+ `script[type="${TYPE}"][config]:not([worker])`,
72
+ `${TYPE}-script[config]:not([worker])`,
73
+ ].join(","),
74
+ );
75
+ if (pyConfig) config = pyConfig.getAttribute("config");
76
+ }
77
+
78
+ // catch possible fetch errors
79
+ if (config) {
80
+ try {
81
+ const { json, toml, text, url } = await configDetails(config, type);
82
+ config = text;
83
+ if (json || type === "json") {
84
+ try {
85
+ parsed = JSON.parse(text);
86
+ } catch (e) {
87
+ error = syntaxError("JSON", url, e);
88
+ }
89
+ } else if (toml || type === "toml") {
90
+ try {
91
+ const { parse } = await import(
92
+ /* webpackIgnore: true */
93
+ "https://cdn.jsdelivr.net/npm/@webreflection/toml-j0.4/toml.js"
94
+ );
95
+ parsed = parse(text);
96
+ } catch (e) {
97
+ error = syntaxError("TOML", url, e);
98
+ }
84
99
  }
100
+ } catch (e) {
101
+ error = e;
85
102
  }
86
- } catch (e) {
87
- error = e;
88
103
  }
89
- }
90
104
 
91
- // parse all plugins and optionally ignore only
92
- // those flagged as "undesired" via `!` prefix
93
- const toBeAwaited = [];
94
- for (const [key, value] of Object.entries(allPlugins)) {
95
- if (error) {
96
- if (key === "error") {
97
- // show on page the config is broken, meaning that
98
- // it was not possible to disable error plugin neither
99
- // as that part wasn't correctly parsed anyway
100
- value().then(({ notify }) => notify(error.message));
105
+ // parse all plugins and optionally ignore only
106
+ // those flagged as "undesired" via `!` prefix
107
+ const toBeAwaited = [];
108
+ for (const [key, value] of Object.entries(allPlugins)) {
109
+ if (error) {
110
+ if (key === "error") {
111
+ // show on page the config is broken, meaning that
112
+ // it was not possible to disable error plugin neither
113
+ // as that part wasn't correctly parsed anyway
114
+ value().then(({ notify }) => notify(error.message));
115
+ }
116
+ } else if (!parsed?.plugins?.includes(`!${key}`)) {
117
+ toBeAwaited.push(value());
101
118
  }
102
- } else if (!parsed?.plugins?.includes(`!${key}`)) {
103
- toBeAwaited.push(value());
104
119
  }
105
- }
106
120
 
107
- // assign plugins as Promise.all only if needed
108
- if (toBeAwaited.length) plugins = Promise.all(toBeAwaited);
121
+ // assign plugins as Promise.all only if needed
122
+ if (toBeAwaited.length) plugins = Promise.all(toBeAwaited);
123
+
124
+ configs.set(TYPE, { config: parsed, plugins, error });
125
+ }
109
126
 
110
- export { parsed as config, plugins, error };
127
+ export default configs;
package/src/core.js CHANGED
@@ -9,10 +9,11 @@ import { queryTarget } from "../node_modules/polyscript/esm/script-handler.js";
9
9
  import { dedent, dispatch } from "../node_modules/polyscript/esm/utils.js";
10
10
  import { Hook } from "../node_modules/polyscript/esm/worker/hooks.js";
11
11
 
12
- import { ErrorCode } from "./exceptions.js";
12
+ import TYPES from "./types.js";
13
+ import configs from "./config.js";
13
14
  import sync from "./sync.js";
14
15
  import stdlib from "./stdlib.js";
15
- import { config, plugins, error } from "./config.js";
16
+ import { ErrorCode } from "./exceptions.js";
16
17
  import { robustFetch as fetch, getText } from "./fetch.js";
17
18
 
18
19
  const { assign, defineProperty } = Object;
@@ -20,11 +21,6 @@ const { assign, defineProperty } = Object;
20
21
  // allows lazy element features on code evaluation
21
22
  let currentElement;
22
23
 
23
- const TYPES = new Map([
24
- ["py", "pyodide"],
25
- ["mpy", "micropython"],
26
- ]);
27
-
28
24
  // generic helper to disambiguate between custom element and script
29
25
  const isScript = ({ tagName }) => tagName === "SCRIPT";
30
26
 
@@ -103,7 +99,12 @@ const workerHooks = {
103
99
  [...hooks.codeAfterRunWorkerAsync].map(dedent).join("\n"),
104
100
  };
105
101
 
102
+ const exportedConfig = {};
103
+ export { exportedConfig as config };
104
+
106
105
  for (const [TYPE, interpreter] of TYPES) {
106
+ const { config, plugins, error } = configs.get(TYPE);
107
+
107
108
  // create a unique identifier when/if needed
108
109
  let id = 0;
109
110
  const getID = (prefix = TYPE) => `${prefix}-${id++}`;
@@ -273,6 +274,9 @@ for (const [TYPE, interpreter] of TYPES) {
273
274
 
274
275
  // define py-script only if the config didn't throw an error
275
276
  if (!error) customElements.define(`${TYPE}-script`, PyScriptElement);
277
+
278
+ // export the used config without allowing leaks through it
279
+ exportedConfig[TYPE] = structuredClone(config);
276
280
  }
277
281
 
278
282
  // TBD: I think manual worker cases are interesting in pyodide only
package/src/exceptions.js CHANGED
@@ -23,7 +23,17 @@ export const ErrorCode = {
23
23
  FETCH_UNAVAILABLE_ERROR: "PY0503",
24
24
  };
25
25
 
26
+ /**
27
+ * Keys of the ErrorCode object
28
+ * @typedef {keyof ErrorCode} ErrorCodes
29
+ * */
30
+
26
31
  export class UserError extends Error {
32
+ /**
33
+ * @param {ErrorCodes} errorCode
34
+ * @param {string} message
35
+ * @param {string} messageType
36
+ * */
27
37
  constructor(errorCode, message = "", messageType = "text") {
28
38
  super(`(${errorCode}): ${message}`);
29
39
  this.errorCode = errorCode;
@@ -33,6 +43,10 @@ export class UserError extends Error {
33
43
  }
34
44
 
35
45
  export class FetchError extends UserError {
46
+ /**
47
+ * @param {ErrorCodes} errorCode
48
+ * @param {string} message
49
+ * */
36
50
  constructor(errorCode, message) {
37
51
  super(errorCode, message);
38
52
  this.name = "FetchError";
@@ -40,12 +54,23 @@ export class FetchError extends UserError {
40
54
  }
41
55
 
42
56
  export class InstallError extends UserError {
57
+ /**
58
+ * @param {ErrorCodes} errorCode
59
+ * @param {string} message
60
+ * */
43
61
  constructor(errorCode, message) {
44
62
  super(errorCode, message);
45
63
  this.name = "InstallError";
46
64
  }
47
65
  }
48
66
 
67
+ /**
68
+ * Internal function for creating alert banners on the page
69
+ * @param {string} message The message to be displayed to the user
70
+ * @param {string} level The alert level of the message. Can be any string; 'error' or 'warning' cause matching messages to be emitted to the console
71
+ * @param {string} [messageType="text"] If set to "html", the message content will be assigned to the banner's innerHTML directly, instead of its textContent
72
+ * @param {any} [logMessage=true] An additional flag for whether the message should be sent to the console log.
73
+ */
49
74
  export function _createAlertBanner(
50
75
  message,
51
76
  level,
@@ -24,6 +24,11 @@ hooks.onInterpreterReady.add(function override(pyScript) {
24
24
  // Error hook utilities
25
25
 
26
26
  // Custom function to show notifications
27
+
28
+ /**
29
+ * Add a banner to the top of the page, notifying the user of an error
30
+ * @param {string} message
31
+ */
27
32
  export function notify(message) {
28
33
  const div = document.createElement("div");
29
34
  div.className = "py-error";
package/src/sync.js CHANGED
@@ -1,4 +1,8 @@
1
1
  export default {
2
+ /**
3
+ * 'Sleep' for the given number of seconds. Used to implement Python's time.sleep in Worker threads.
4
+ * @param {number} seconds The number of seconds to sleep.
5
+ */
2
6
  sleep(seconds) {
3
7
  return new Promise(($) => setTimeout($, seconds * 1000));
4
8
  },
package/src/types.js ADDED
@@ -0,0 +1,4 @@
1
+ export default new Map([
2
+ ["py", "pyodide"],
3
+ ["mpy", "micropython"],
4
+ ]);
package/types/config.d.ts CHANGED
@@ -1,4 +1,2 @@
1
- declare let parsed: any;
2
- export let plugins: any;
3
- export let error: any;
4
- export { parsed as config };
1
+ export default configs;
2
+ declare const configs: Map<any, any>;
package/types/core.d.ts CHANGED
@@ -21,5 +21,6 @@ export namespace hooks {
21
21
  let codeAfterRunWorker: Set<string>;
22
22
  let codeAfterRunWorkerAsync: Set<string>;
23
23
  }
24
- import { config } from "./config.js";
24
+ export { exportedConfig as config };
25
25
  import sync from "./sync.js";
26
+ declare const exportedConfig: {};
@@ -1,4 +1,11 @@
1
- export function _createAlertBanner(message: any, level: any, messageType?: string, logMessage?: boolean): void;
1
+ /**
2
+ * Internal function for creating alert banners on the page
3
+ * @param {string} message The message to be displayed to the user
4
+ * @param {string} level The alert level of the message. Can be any string; 'error' or 'warning' cause matching messages to be emitted to the console
5
+ * @param {string} [messageType="text"] If set to "html", the message content will be assigned to the banner's innerHTML directly, instead of its textContent
6
+ * @param {any} [logMessage=true] An additional flag for whether the message should be sent to the console log.
7
+ */
8
+ export function _createAlertBanner(message: string, level: string, messageType?: string, logMessage?: any): void;
2
9
  export namespace ErrorCode {
3
10
  let GENERIC: string;
4
11
  let CONFLICTING_CODE: string;
@@ -15,14 +22,50 @@ export namespace ErrorCode {
15
22
  let FETCH_SERVER_ERROR: string;
16
23
  let FETCH_UNAVAILABLE_ERROR: string;
17
24
  }
25
+ /**
26
+ * Keys of the ErrorCode object
27
+ * @typedef {keyof ErrorCode} ErrorCodes
28
+ * */
18
29
  export class UserError extends Error {
19
- constructor(errorCode: any, message?: string, messageType?: string);
20
- errorCode: any;
30
+ /**
31
+ * @param {ErrorCodes} errorCode
32
+ * @param {string} message
33
+ * @param {string} messageType
34
+ * */
35
+ constructor(errorCode: ErrorCodes, message?: string, messageType?: string);
36
+ errorCode: "GENERIC" | "CONFLICTING_CODE" | "BAD_CONFIG" | "MICROPIP_INSTALL_ERROR" | "BAD_PLUGIN_FILE_EXTENSION" | "NO_DEFAULT_EXPORT" | "TOP_LEVEL_AWAIT" | "FETCH_ERROR" | "FETCH_NAME_ERROR" | "FETCH_UNAUTHORIZED_ERROR" | "FETCH_FORBIDDEN_ERROR" | "FETCH_NOT_FOUND_ERROR" | "FETCH_SERVER_ERROR" | "FETCH_UNAVAILABLE_ERROR";
21
37
  messageType: string;
22
38
  }
23
39
  export class FetchError extends UserError {
24
- constructor(errorCode: any, message: any);
40
+ /**
41
+ * @param {ErrorCodes} errorCode
42
+ * @param {string} message
43
+ * */
44
+ constructor(errorCode: ErrorCodes, message: string);
25
45
  }
26
46
  export class InstallError extends UserError {
27
- constructor(errorCode: any, message: any);
47
+ /**
48
+ * @param {ErrorCodes} errorCode
49
+ * @param {string} message
50
+ * */
51
+ constructor(errorCode: ErrorCodes, message: string);
28
52
  }
53
+ /**
54
+ * Keys of the ErrorCode object
55
+ */
56
+ export type ErrorCodes = keyof {
57
+ GENERIC: string;
58
+ CONFLICTING_CODE: string;
59
+ BAD_CONFIG: string;
60
+ MICROPIP_INSTALL_ERROR: string;
61
+ BAD_PLUGIN_FILE_EXTENSION: string;
62
+ NO_DEFAULT_EXPORT: string;
63
+ TOP_LEVEL_AWAIT: string;
64
+ FETCH_ERROR: string;
65
+ FETCH_NAME_ERROR: string;
66
+ FETCH_UNAUTHORIZED_ERROR: string;
67
+ FETCH_FORBIDDEN_ERROR: string;
68
+ FETCH_NOT_FOUND_ERROR: string;
69
+ FETCH_SERVER_ERROR: string;
70
+ FETCH_UNAVAILABLE_ERROR: string;
71
+ };
@@ -1 +1,5 @@
1
- export function notify(message: any): void;
1
+ /**
2
+ * Add a banner to the top of the page, notifying the user of an error
3
+ * @param {string} message
4
+ */
5
+ export function notify(message: string): void;
package/types/sync.d.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  declare namespace _default {
2
- function sleep(seconds: any): Promise<any>;
2
+ /**
3
+ * 'Sleep' for the given number of seconds. Used to implement Python's time.sleep in Worker threads.
4
+ * @param {number} seconds The number of seconds to sleep.
5
+ */
6
+ function sleep(seconds: number): Promise<any>;
3
7
  }
4
8
  export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: Map<string, string>;
2
+ export default _default;