Format the TypeScript files (#877)

This commit is contained in:
woxtu
2022-10-28 17:48:27 +09:00
committed by GitHub
parent 1c53d91c6b
commit 9543019336
13 changed files with 235 additions and 286 deletions

View File

@@ -47,13 +47,13 @@ export class PyBox extends HTMLElement {
// now we need to set widths // now we need to set widths
this.widths = []; this.widths = [];
const widthsAttr = getAttribute( this, "widths" ); const widthsAttr = getAttribute(this, 'widths');
if (widthsAttr) { if (widthsAttr) {
for (const w of widthsAttr.split(';')) { for (const w of widthsAttr.split(';')) {
if (w.includes('/')){ if (w.includes('/')) {
this.widths.push(w.split('/')[0]) this.widths.push(w.split('/')[0]);
}else{ } else {
this.widths.push(w) this.widths.push(w);
} }
} }
} else { } else {
@@ -63,7 +63,7 @@ export class PyBox extends HTMLElement {
this.widths.forEach((width, index) => { this.widths.forEach((width, index) => {
const node: ChildNode = mainDiv.childNodes[index]; const node: ChildNode = mainDiv.childNodes[index];
(<HTMLElement>node).style.flex = width; (<HTMLElement>node).style.flex = width;
addClasses((<HTMLElement>node), ['py-box-child']); addClasses(<HTMLElement>node, ['py-box-child']);
}); });
this.appendChild(mainDiv); this.appendChild(mainDiv);

View File

@@ -1,5 +1,5 @@
import { getAttribute, addClasses, htmlDecode, ensureUniqueId } from '../utils'; import { getAttribute, addClasses, htmlDecode, ensureUniqueId } from '../utils';
import { getLogger } from '../logger' import { getLogger } from '../logger';
import type { Runtime } from '../runtime'; import type { Runtime } from '../runtime';
const logger = getLogger('py-button'); const logger = getLogger('py-button');
@@ -18,14 +18,14 @@ export function make_PyButton(runtime: Runtime) {
this.defaultClass = ['py-button']; this.defaultClass = ['py-button'];
const label = getAttribute(this, "label"); const label = getAttribute(this, 'label');
if (label) { if (label) {
this.label = label; this.label = label;
} }
// Styling does the same thing as class in normal HTML. Using the name "class" makes the style to malfunction // Styling does the same thing as class in normal HTML. Using the name "class" makes the style to malfunction
const styling = getAttribute(this, "styling"); const styling = getAttribute(this, 'styling');
if ( styling ) { if (styling) {
const klass = styling.trim(); const klass = styling.trim();
if (klass === '') { if (klass === '') {
this.class = this.defaultClass; this.class = this.defaultClass;
@@ -43,7 +43,7 @@ export function make_PyButton(runtime: Runtime) {
async connectedCallback() { async connectedCallback() {
ensureUniqueId(this); ensureUniqueId(this);
this.code = htmlDecode(this.innerHTML) || ""; this.code = htmlDecode(this.innerHTML) || '';
this.mount_name = this.id.split('-').join('_'); this.mount_name = this.id.split('-').join('_');
this.innerHTML = ''; this.innerHTML = '';

View File

@@ -1,5 +1,5 @@
import { getAttribute, addClasses, htmlDecode, ensureUniqueId } from '../utils'; import { getAttribute, addClasses, htmlDecode, ensureUniqueId } from '../utils';
import { getLogger } from '../logger' import { getLogger } from '../logger';
import type { Runtime } from '../runtime'; import type { Runtime } from '../runtime';
const logger = getLogger('py-inputbox'); const logger = getLogger('py-inputbox');
@@ -14,7 +14,7 @@ export function make_PyInputBox(runtime: Runtime) {
constructor() { constructor() {
super(); super();
const label = getAttribute( this, "label"); const label = getAttribute(this, 'label');
if (label) { if (label) {
this.label = label; this.label = label;
} }

View File

@@ -1,6 +1,6 @@
import { basicSetup, EditorView } from 'codemirror'; import { basicSetup, EditorView } from 'codemirror';
import { python } from '@codemirror/lang-python'; import { python } from '@codemirror/lang-python';
import { indentUnit } from '@codemirror/language' import { indentUnit } from '@codemirror/language';
import { Compartment } from '@codemirror/state'; import { Compartment } from '@codemirror/state';
import { keymap } from '@codemirror/view'; import { keymap } from '@codemirror/view';
import { defaultKeymap } from '@codemirror/commands'; import { defaultKeymap } from '@codemirror/commands';
@@ -14,7 +14,6 @@ import { getLogger } from '../logger';
const logger = getLogger('py-repl'); const logger = getLogger('py-repl');
export function make_PyRepl(runtime: Runtime) { export function make_PyRepl(runtime: Runtime) {
/* High level structure of py-repl DOM, and the corresponding JS names. /* High level structure of py-repl DOM, and the corresponding JS names.
this <py-repl> this <py-repl>
@@ -63,7 +62,7 @@ export function make_PyRepl(runtime: Runtime) {
makeEditor(pySrc: string): EditorView { makeEditor(pySrc: string): EditorView {
const languageConf = new Compartment(); const languageConf = new Compartment();
const extensions = [ const extensions = [
indentUnit.of(" "), indentUnit.of(' '),
basicSetup, basicSetup,
languageConf.of(python()), languageConf.of(python()),
keymap.of([ keymap.of([
@@ -181,22 +180,20 @@ export function make_PyRepl(runtime: Runtime) {
} }
getOutputElement(): HTMLElement { getOutputElement(): HTMLElement {
const outputID = getAttribute(this, "output"); const outputID = getAttribute(this, 'output');
if (outputID !== null) { if (outputID !== null) {
const el = document.getElementById(outputID); const el = document.getElementById(outputID);
if (el === null) { if (el === null) {
const err = `py-repl ERROR: cannot find the output element #${outputID} in the DOM` const err = `py-repl ERROR: cannot find the output element #${outputID} in the DOM`;
this.outDiv.innerText = err; this.outDiv.innerText = err;
return undefined; return undefined;
} }
return el; return el;
} } else {
else {
return this.outDiv; return this.outDiv;
} }
} }
// XXX the autogenerate logic is very messy. We should redo it, and it // XXX the autogenerate logic is very messy. We should redo it, and it
// should be the default. // should be the default.
autogenerateMaybe(): void { autogenerateMaybe(): void {
@@ -210,19 +207,19 @@ export function make_PyRepl(runtime: Runtime) {
newPyRepl.setAttribute('root', this.getAttribute('root')); newPyRepl.setAttribute('root', this.getAttribute('root'));
newPyRepl.id = this.getAttribute('root') + '-' + nextExecId.toString(); newPyRepl.id = this.getAttribute('root') + '-' + nextExecId.toString();
if(this.hasAttribute('auto-generate')) { if (this.hasAttribute('auto-generate')) {
newPyRepl.setAttribute('auto-generate', ''); newPyRepl.setAttribute('auto-generate', '');
this.removeAttribute('auto-generate'); this.removeAttribute('auto-generate');
} }
const outputMode = getAttribute( this, 'output-mode') const outputMode = getAttribute(this, 'output-mode');
if(outputMode) { if (outputMode) {
newPyRepl.setAttribute('output-mode', outputMode); newPyRepl.setAttribute('output-mode', outputMode);
} }
const addReplAttribute = (attribute: string) => { const addReplAttribute = (attribute: string) => {
const attr = getAttribute( this, attribute) const attr = getAttribute(this, attribute);
if(attr) { if (attr) {
newPyRepl.setAttribute(attribute, attr); newPyRepl.setAttribute(attribute, attr);
} }
}; };
@@ -230,13 +227,12 @@ export function make_PyRepl(runtime: Runtime) {
addReplAttribute('output'); addReplAttribute('output');
newPyRepl.setAttribute('exec-id', nextExecId.toString()); newPyRepl.setAttribute('exec-id', nextExecId.toString());
if( this.parentElement ){ if (this.parentElement) {
this.parentElement.appendChild(newPyRepl); this.parentElement.appendChild(newPyRepl);
} }
} }
} }
} }
return PyRepl return PyRepl;
} }

View File

@@ -6,9 +6,7 @@ import { pyExec } from '../pyexec';
const logger = getLogger('py-script'); const logger = getLogger('py-script');
export function make_PyScript(runtime: Runtime) { export function make_PyScript(runtime: Runtime) {
class PyScript extends HTMLElement { class PyScript extends HTMLElement {
async connectedCallback() { async connectedCallback() {
ensureUniqueId(this); ensureUniqueId(this);
const pySrc = await this.getPySrc(); const pySrc = await this.getPySrc();
@@ -24,8 +22,7 @@ export function make_PyScript(runtime: Runtime) {
const url = this.getAttribute('src'); const url = this.getAttribute('src');
const response = await fetch(url); const response = await fetch(url);
return await response.text(); return await response.text();
} } else {
else {
return htmlDecode(this.innerHTML); return htmlDecode(this.innerHTML);
} }
} }
@@ -37,97 +34,97 @@ export function make_PyScript(runtime: Runtime) {
/** Defines all possible py-on* and their corresponding event types */ /** Defines all possible py-on* and their corresponding event types */
const pyAttributeToEvent: Map<string, string> = new Map<string, string>([ const pyAttributeToEvent: Map<string, string> = new Map<string, string>([
// Leaving pys-onClick and pys-onKeyDown for backward compatibility // Leaving pys-onClick and pys-onKeyDown for backward compatibility
["pys-onClick", "click"], ['pys-onClick', 'click'],
["pys-onKeyDown", "keydown"], ['pys-onKeyDown', 'keydown'],
["py-onClick", "click"], ['py-onClick', 'click'],
["py-onKeyDown", "keydown"], ['py-onKeyDown', 'keydown'],
// Window Events // Window Events
["py-afterprint", "afterprint"], ['py-afterprint', 'afterprint'],
["py-beforeprint", "beforeprint"], ['py-beforeprint', 'beforeprint'],
["py-beforeunload", "beforeunload"], ['py-beforeunload', 'beforeunload'],
["py-error", "error"], ['py-error', 'error'],
["py-hashchange", "hashchange"], ['py-hashchange', 'hashchange'],
["py-load", "load"], ['py-load', 'load'],
["py-message", "message"], ['py-message', 'message'],
["py-offline", "offline"], ['py-offline', 'offline'],
["py-online", "online"], ['py-online', 'online'],
["py-pagehide", "pagehide"], ['py-pagehide', 'pagehide'],
["py-pageshow", "pageshow"], ['py-pageshow', 'pageshow'],
["py-popstate", "popstate"], ['py-popstate', 'popstate'],
["py-resize", "resize"], ['py-resize', 'resize'],
["py-storage", "storage"], ['py-storage', 'storage'],
["py-unload", "unload"], ['py-unload', 'unload'],
// Form Events // Form Events
["py-blur", "blur"], ['py-blur', 'blur'],
["py-change", "change"], ['py-change', 'change'],
["py-contextmenu", "contextmenu"], ['py-contextmenu', 'contextmenu'],
["py-focus", "focus"], ['py-focus', 'focus'],
["py-input", "input"], ['py-input', 'input'],
["py-invalid", "invalid"], ['py-invalid', 'invalid'],
["py-reset", "reset"], ['py-reset', 'reset'],
["py-search", "search"], ['py-search', 'search'],
["py-select", "select"], ['py-select', 'select'],
["py-submit", "submit"], ['py-submit', 'submit'],
// Keyboard Events // Keyboard Events
["py-keydown", "keydown"], ['py-keydown', 'keydown'],
["py-keypress", "keypress"], ['py-keypress', 'keypress'],
["py-keyup", "keyup"], ['py-keyup', 'keyup'],
// Mouse Events // Mouse Events
["py-click", "click"], ['py-click', 'click'],
["py-dblclick", "dblclick"], ['py-dblclick', 'dblclick'],
["py-mousedown", "mousedown"], ['py-mousedown', 'mousedown'],
["py-mousemove", "mousemove"], ['py-mousemove', 'mousemove'],
["py-mouseout", "mouseout"], ['py-mouseout', 'mouseout'],
["py-mouseover", "mouseover"], ['py-mouseover', 'mouseover'],
["py-mouseup", "mouseup"], ['py-mouseup', 'mouseup'],
["py-mousewheel", "mousewheel"], ['py-mousewheel', 'mousewheel'],
["py-wheel", "wheel"], ['py-wheel', 'wheel'],
// Drag Events // Drag Events
["py-drag", "drag"], ['py-drag', 'drag'],
["py-dragend", "dragend"], ['py-dragend', 'dragend'],
["py-dragenter", "dragenter"], ['py-dragenter', 'dragenter'],
["py-dragleave", "dragleave"], ['py-dragleave', 'dragleave'],
["py-dragover", "dragover"], ['py-dragover', 'dragover'],
["py-dragstart", "dragstart"], ['py-dragstart', 'dragstart'],
["py-drop", "drop"], ['py-drop', 'drop'],
["py-scroll", "scroll"], ['py-scroll', 'scroll'],
// Clipboard Events // Clipboard Events
["py-copy", "copy"], ['py-copy', 'copy'],
["py-cut", "cut"], ['py-cut', 'cut'],
["py-paste", "paste"], ['py-paste', 'paste'],
// Media Events // Media Events
["py-abort", "abort"], ['py-abort', 'abort'],
["py-canplay", "canplay"], ['py-canplay', 'canplay'],
["py-canplaythrough", "canplaythrough"], ['py-canplaythrough', 'canplaythrough'],
["py-cuechange", "cuechange"], ['py-cuechange', 'cuechange'],
["py-durationchange", "durationchange"], ['py-durationchange', 'durationchange'],
["py-emptied", "emptied"], ['py-emptied', 'emptied'],
["py-ended", "ended"], ['py-ended', 'ended'],
["py-loadeddata", "loadeddata"], ['py-loadeddata', 'loadeddata'],
["py-loadedmetadata", "loadedmetadata"], ['py-loadedmetadata', 'loadedmetadata'],
["py-loadstart", "loadstart"], ['py-loadstart', 'loadstart'],
["py-pause", "pause"], ['py-pause', 'pause'],
["py-play", "play"], ['py-play', 'play'],
["py-playing", "playing"], ['py-playing', 'playing'],
["py-progress", "progress"], ['py-progress', 'progress'],
["py-ratechange", "ratechange"], ['py-ratechange', 'ratechange'],
["py-seeked", "seeked"], ['py-seeked', 'seeked'],
["py-seeking", "seeking"], ['py-seeking', 'seeking'],
["py-stalled", "stalled"], ['py-stalled', 'stalled'],
["py-suspend", "suspend"], ['py-suspend', 'suspend'],
["py-timeupdate", "timeupdate"], ['py-timeupdate', 'timeupdate'],
["py-volumechange", "volumechange"], ['py-volumechange', 'volumechange'],
["py-waiting", "waiting"], ['py-waiting', 'waiting'],
// Misc Events // Misc Events
["py-toggle", "toggle"], ['py-toggle', 'toggle'],
]); ]);
/** Initialize all elements with py-* handlers attributes */ /** Initialize all elements with py-* handlers attributes */
export async function initHandlers(runtime: Runtime) { export async function initHandlers(runtime: Runtime) {
@@ -142,22 +139,27 @@ async function createElementsWithEventListeners(runtime: Runtime, pyAttribute: s
const matches: NodeListOf<HTMLElement> = document.querySelectorAll(`[${pyAttribute}]`); const matches: NodeListOf<HTMLElement> = document.querySelectorAll(`[${pyAttribute}]`);
for (const el of matches) { for (const el of matches) {
if (el.id.length === 0) { if (el.id.length === 0) {
throw new TypeError(`<${el.tagName.toLowerCase()}> must have an id attribute, when using the ${pyAttribute} attribute`) throw new TypeError(
`<${el.tagName.toLowerCase()}> must have an id attribute, when using the ${pyAttribute} attribute`,
);
} }
const handlerCode = el.getAttribute(pyAttribute); const handlerCode = el.getAttribute(pyAttribute);
const event = pyAttributeToEvent.get(pyAttribute); const event = pyAttributeToEvent.get(pyAttribute);
if (pyAttribute === 'pys-onClick' || pyAttribute === 'pys-onKeyDown'){ if (pyAttribute === 'pys-onClick' || pyAttribute === 'pys-onKeyDown') {
console.warn("Use of pys-onClick and pys-onKeyDown attributes is deprecated in favor of py-onClick() and py-onKeyDown(). pys-on* attributes will be deprecated in a future version of PyScript.") console.warn(
'Use of pys-onClick and pys-onKeyDown attributes is deprecated in favor of py-onClick() and py-onKeyDown(). pys-on* attributes will be deprecated in a future version of PyScript.',
);
const source = ` const source = `
from pyodide.ffi import create_proxy from pyodide.ffi import create_proxy
Element("${el.id}").element.addEventListener("${event}", create_proxy(${handlerCode})) Element("${el.id}").element.addEventListener("${event}", create_proxy(${handlerCode}))
`; `;
await runtime.run(source); await runtime.run(source);
} } else {
else{
el.addEventListener(event, () => { el.addEventListener(event, () => {
(async() => {await runtime.run(handlerCode)})(); (async () => {
await runtime.run(handlerCode);
})();
}); });
} }
// TODO: Should we actually map handlers in JS instead of Python? // TODO: Should we actually map handlers in JS instead of Python?
@@ -174,7 +176,6 @@ async function createElementsWithEventListeners(runtime: Runtime, pyAttribute: s
// // pyodide.runPython(handlerCode); // // pyodide.runPython(handlerCode);
// } // }
} }
} }
/** Mount all elements with attribute py-mount into the Python namespace */ /** Mount all elements with attribute py-mount into the Python namespace */

View File

@@ -1,10 +1,9 @@
import type { Runtime } from '../runtime'; import type { Runtime } from '../runtime';
import type {PyProxy} from "pyodide" import type { PyProxy } from 'pyodide';
import { getLogger } from '../logger'; import { getLogger } from '../logger';
const logger = getLogger('py-register-widget'); const logger = getLogger('py-register-widget');
function createWidget(runtime: Runtime, name: string, code: string, klass: string) { function createWidget(runtime: Runtime, name: string, code: string, klass: string) {
class CustomWidget extends HTMLElement { class CustomWidget extends HTMLElement {
shadow: ShadowRoot; shadow: ShadowRoot;
@@ -65,14 +64,14 @@ export function make_PyWidget(runtime: Runtime) {
this.wrapper = document.createElement('slot'); this.wrapper = document.createElement('slot');
this.shadow.appendChild(this.wrapper); this.shadow.appendChild(this.wrapper);
this.addAttributes('src','name','klass'); this.addAttributes('src', 'name', 'klass');
} }
addAttributes(...attrs:string[]){ addAttributes(...attrs: string[]) {
for (const each of attrs){ for (const each of attrs) {
const property = each === "src" ? "source" : each; const property = each === 'src' ? 'source' : each;
if (this.hasAttribute(each)) { if (this.hasAttribute(each)) {
this[property]=this.getAttribute(each); this[property] = this.getAttribute(each);
} }
} }
} }

View File

@@ -42,14 +42,14 @@ function getLogger(prefix: string): Logger {
} }
function _makeLogger(prefix: string): Logger { function _makeLogger(prefix: string): Logger {
prefix = "[" + prefix + "] "; prefix = '[' + prefix + '] ';
function make(level: string) { function make(level: string) {
const out_fn = console[level].bind(console); const out_fn = console[level].bind(console);
function fn(fmt: string, ...args: unknown[]) { function fn(fmt: string, ...args: unknown[]) {
out_fn(prefix + fmt, ...args); out_fn(prefix + fmt, ...args);
} }
return fn return fn;
} }
// 'log' is intentionally omitted // 'log' is intentionally omitted
@@ -58,7 +58,7 @@ function _makeLogger(prefix: string): Logger {
const warn = make('warn'); const warn = make('warn');
const error = make('error'); const error = make('error');
return {debug, info, warn, error}; return { debug, info, warn, error };
} }
export { getLogger }; export { getLogger };

View File

@@ -7,18 +7,16 @@ import { make_PyScript, initHandlers, mountElements } from './components/pyscrip
import { PyLoader } from './components/pyloader'; import { PyLoader } from './components/pyloader';
import { PyodideRuntime } from './pyodide'; import { PyodideRuntime } from './pyodide';
import { getLogger } from './logger'; import { getLogger } from './logger';
import { handleFetchError, showError, globalExport } from './utils' import { handleFetchError, showError, globalExport } from './utils';
import { createCustomElements } from './components/elements'; import { createCustomElements } from './components/elements';
type ImportType = { [key: string]: unknown } type ImportType = { [key: string]: unknown };
type ImportMapType = { type ImportMapType = {
imports: ImportType | null imports: ImportType | null;
} };
const logger = getLogger('pyscript/main'); const logger = getLogger('pyscript/main');
/* High-level overview of the lifecycle of a PyScript App: /* High-level overview of the lifecycle of a PyScript App:
1. pyscript.js is loaded by the browser. PyScriptApp().main() is called 1. pyscript.js is loaded by the browser. PyScriptApp().main() is called
@@ -51,9 +49,7 @@ More concretely:
- PyScriptApp.afterRuntimeLoad() implements all the points >= 5. - PyScriptApp.afterRuntimeLoad() implements all the points >= 5.
*/ */
class PyScriptApp { class PyScriptApp {
config: AppConfig; config: AppConfig;
loader: PyLoader; loader: PyLoader;
runtime: Runtime; runtime: Runtime;
@@ -75,15 +71,16 @@ class PyScriptApp {
logger.info('searching for <py-config>'); logger.info('searching for <py-config>');
const elements = document.getElementsByTagName('py-config'); const elements = document.getElementsByTagName('py-config');
let el: Element | null = null; let el: Element | null = null;
if (elements.length > 0) if (elements.length > 0) el = elements[0];
el = elements[0];
if (elements.length >= 2) { if (elements.length >= 2) {
// XXX: ideally, I would like to have a way to raise "fatal // XXX: ideally, I would like to have a way to raise "fatal
// errors" and stop the computation, but currently our life cycle // errors" and stop the computation, but currently our life cycle
// is too messy to implement it reliably. We might want to revisit // is too messy to implement it reliably. We might want to revisit
// this once it's in a better shape. // this once it's in a better shape.
showError("Multiple &lt;py-config&gt; tags detected. Only the first is " + showError(
"going to be parsed, all the others will be ignored"); 'Multiple &lt;py-config&gt; tags detected. Only the first is ' +
'going to be parsed, all the others will be ignored',
);
} }
this.config = loadConfigFromElement(el); this.config = loadConfigFromElement(el);
logger.info('config loaded:\n' + JSON.stringify(this.config, null, 2)); logger.info('config loaded:\n' + JSON.stringify(this.config, null, 2));
@@ -102,17 +99,15 @@ class PyScriptApp {
loadRuntime() { loadRuntime() {
logger.info('Initializing runtime'); logger.info('Initializing runtime');
if (this.config.runtimes.length == 0) { if (this.config.runtimes.length == 0) {
showError("Fatal error: config.runtimes is empty"); showError('Fatal error: config.runtimes is empty');
return; return;
} }
if (this.config.runtimes.length > 1) { if (this.config.runtimes.length > 1) {
showError("Multiple runtimes are not supported yet. " + showError('Multiple runtimes are not supported yet. ' + 'Only the first will be used');
"Only the first will be used");
} }
const runtime_cfg = this.config.runtimes[0]; const runtime_cfg = this.config.runtimes[0];
this.runtime = new PyodideRuntime(this.config, runtime_cfg.src, this.runtime = new PyodideRuntime(this.config, runtime_cfg.src, runtime_cfg.name, runtime_cfg.lang);
runtime_cfg.name, runtime_cfg.lang);
this.loader.log(`Downloading ${runtime_cfg.name}...`); this.loader.log(`Downloading ${runtime_cfg.name}...`);
const script = document.createElement('script'); // create a script DOM node const script = document.createElement('script'); // create a script DOM node
script.src = this.runtime.src; script.src = this.runtime.src;
@@ -160,13 +155,12 @@ class PyScriptApp {
logger.info('PyScript page fully initialized'); logger.info('PyScript page fully initialized');
} }
// lifecycle (6) // lifecycle (6)
async setupVirtualEnv(runtime: Runtime): Promise<void> { async setupVirtualEnv(runtime: Runtime): Promise<void> {
// XXX: maybe the following calls could be parallelized, instead of // XXX: maybe the following calls could be parallelized, instead of
// await()ing immediately. For now I'm using await to be 100% // await()ing immediately. For now I'm using await to be 100%
// compatible with the old behavior. // compatible with the old behavior.
logger.info("Packages to install: ", this.config.packages); logger.info('Packages to install: ', this.config.packages);
await runtime.installPackage(this.config.packages); await runtime.installPackage(this.config.packages);
await this.fetchPaths(runtime); await this.fetchPaths(runtime);
} }
@@ -178,7 +172,7 @@ class PyScriptApp {
// initialized. But we could easily do it in JS in parallel with the // initialized. But we could easily do it in JS in parallel with the
// download/startup of pyodide. // download/startup of pyodide.
const paths = this.config.paths; const paths = this.config.paths;
logger.info("Paths to fetch: ", paths) logger.info('Paths to fetch: ', paths);
for (const singleFile of paths) { for (const singleFile of paths) {
logger.info(` fetching path: ${singleFile}`); logger.info(` fetching path: ${singleFile}`);
try { try {
@@ -188,7 +182,7 @@ class PyScriptApp {
handleFetchError(<Error>e, singleFile); handleFetchError(<Error>e, singleFile);
} }
} }
logger.info("All paths fetched"); logger.info('All paths fetched');
} }
// lifecycle (7) // lifecycle (7)
@@ -239,7 +233,6 @@ class PyScriptApp {
} }
} }
} }
} }
function pyscript_get_config() { function pyscript_get_config() {
@@ -251,4 +244,4 @@ globalExport('pyscript_get_config', pyscript_get_config);
const globalApp = new PyScriptApp(); const globalApp = new PyScriptApp();
globalApp.main(); globalApp.main();
export const runtime = globalApp.runtime export const runtime = globalApp.runtime;

View File

@@ -1,7 +1,7 @@
import toml from '../src/toml' import toml from '../src/toml';
import { getLogger } from './logger'; import { getLogger } from './logger';
import { version } from './runtime'; import { version } from './runtime';
import { getAttribute, readTextFromPath, showError } from './utils' import { getAttribute, readTextFromPath, showError } from './utils';
const logger = getLogger('py-config'); const logger = getLogger('py-config');
@@ -31,29 +31,30 @@ export type RuntimeConfig = {
export type PyScriptMetadata = { export type PyScriptMetadata = {
version?: string; version?: string;
time?: string; time?: string;
} };
const allKeys = { const allKeys = {
"string": ["name", "description", "version", "type", "author_name", "author_email", "license"], string: ['name', 'description', 'version', 'type', 'author_name', 'author_email', 'license'],
"number": ["schema_version"], number: ['schema_version'],
"boolean": ["autoclose_loader"], boolean: ['autoclose_loader'],
"array": ["runtimes", "packages", "paths", "plugins"] array: ['runtimes', 'packages', 'paths', 'plugins'],
}; };
export const defaultConfig: AppConfig = { export const defaultConfig: AppConfig = {
"schema_version": 1, schema_version: 1,
"type": "app", type: 'app',
"autoclose_loader": true, autoclose_loader: true,
"runtimes": [{ runtimes: [
"src": "https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js", {
"name": "pyodide-0.21.3", src: 'https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js',
"lang": "python" name: 'pyodide-0.21.3',
}], lang: 'python',
"packages":[], },
"paths":[], ],
"plugins": [] packages: [],
} paths: [],
plugins: [],
};
export function loadConfigFromElement(el: Element): AppConfig { export function loadConfigFromElement(el: Element): AppConfig {
let srcConfig: AppConfig; let srcConfig: AppConfig;
@@ -61,47 +62,41 @@ export function loadConfigFromElement(el: Element): AppConfig {
if (el === null) { if (el === null) {
srcConfig = {}; srcConfig = {};
inlineConfig = {}; inlineConfig = {};
} } else {
else { const configType = getAttribute(el, 'type') || 'toml';
const configType = getAttribute(el, "type") || "toml";
srcConfig = extractFromSrc(el, configType); srcConfig = extractFromSrc(el, configType);
inlineConfig = extractFromInline(el, configType); inlineConfig = extractFromInline(el, configType);
} }
srcConfig = mergeConfig(srcConfig, defaultConfig); srcConfig = mergeConfig(srcConfig, defaultConfig);
const result = mergeConfig(inlineConfig, srcConfig); const result = mergeConfig(inlineConfig, srcConfig);
result.pyscript = { result.pyscript = {
"version": version, version: version,
"time": new Date().toISOString() time: new Date().toISOString(),
}; };
return result; return result;
} }
function extractFromSrc(el: Element, configType: string) { function extractFromSrc(el: Element, configType: string) {
const src = getAttribute(el, "src") const src = getAttribute(el, 'src');
if (src) { if (src) {
logger.info('loading ', src) logger.info('loading ', src);
return validateConfig(readTextFromPath(src), configType); return validateConfig(readTextFromPath(src), configType);
} }
return {}; return {};
} }
function extractFromInline(el: Element, configType: string) { function extractFromInline(el: Element, configType: string) {
if (el.innerHTML !== '') if (el.innerHTML !== '') {
{
logger.info('loading <py-config> content'); logger.info('loading <py-config> content');
return validateConfig(el.innerHTML, configType); return validateConfig(el.innerHTML, configType);
} }
return {}; return {};
} }
function fillUserData(inputConfig: AppConfig, resultConfig: AppConfig): AppConfig function fillUserData(inputConfig: AppConfig, resultConfig: AppConfig): AppConfig {
{ for (const key in inputConfig) {
for (const key in inputConfig)
{
// fill in all extra keys ignored by the validator // fill in all extra keys ignored by the validator
if (!(key in defaultConfig)) if (!(key in defaultConfig)) {
{
resultConfig[key] = inputConfig[key]; resultConfig[key] = inputConfig[key];
} }
} }
@@ -109,32 +104,22 @@ function fillUserData(inputConfig: AppConfig, resultConfig: AppConfig): AppConfi
} }
function mergeConfig(inlineConfig: AppConfig, externalConfig: AppConfig): AppConfig { function mergeConfig(inlineConfig: AppConfig, externalConfig: AppConfig): AppConfig {
if (Object.keys(inlineConfig).length === 0 && Object.keys(externalConfig).length === 0) if (Object.keys(inlineConfig).length === 0 && Object.keys(externalConfig).length === 0) {
{
return defaultConfig; return defaultConfig;
} } else if (Object.keys(inlineConfig).length === 0) {
else if (Object.keys(inlineConfig).length === 0)
{
return externalConfig; return externalConfig;
} } else if (Object.keys(externalConfig).length === 0) {
else if(Object.keys(externalConfig).length === 0)
{
return inlineConfig; return inlineConfig;
} } else {
else
{
let merged: AppConfig = {}; let merged: AppConfig = {};
for (const keyType in allKeys) for (const keyType in allKeys) {
{
const keys: string[] = allKeys[keyType]; const keys: string[] = allKeys[keyType];
keys.forEach(function(item: string){ keys.forEach(function (item: string) {
if (keyType === "boolean") if (keyType === 'boolean') {
{ merged[item] =
merged[item] = (typeof inlineConfig[item] !== "undefined") ? inlineConfig[item] : externalConfig[item]; typeof inlineConfig[item] !== 'undefined' ? inlineConfig[item] : externalConfig[item];
} } else {
else
{
merged[item] = inlineConfig[item] || externalConfig[item]; merged[item] = inlineConfig[item] || externalConfig[item];
} }
}); });
@@ -149,20 +134,18 @@ function mergeConfig(inlineConfig: AppConfig, externalConfig: AppConfig): AppCon
} }
} }
function parseConfig(configText: string, configType = "toml") { function parseConfig(configText: string, configType = 'toml') {
let config: object; let config: object;
if (configType === "toml") { if (configType === 'toml') {
try { try {
// TOML parser is soft and can parse even JSON strings, this additional check prevents it. // TOML parser is soft and can parse even JSON strings, this additional check prevents it.
if (configText.trim()[0] === "{") if (configText.trim()[0] === '{') {
{
const errMessage = `config supplied: ${configText} is an invalid TOML and cannot be parsed`; const errMessage = `config supplied: ${configText} is an invalid TOML and cannot be parsed`;
showError(`<p>${errMessage}</p>`); showError(`<p>${errMessage}</p>`);
throw Error(errMessage); throw Error(errMessage);
} }
config = toml.parse(configText); config = toml.parse(configText);
} } catch (err) {
catch (err) {
const errMessage: string = err.toString(); const errMessage: string = err.toString();
showError(`<p>config supplied: ${configText} is an invalid TOML and cannot be parsed: ${errMessage}</p>`); showError(`<p>config supplied: ${configText} is an invalid TOML and cannot be parsed: ${errMessage}</p>`);
// we cannot easily just "throw err" here, because for some reason // we cannot easily just "throw err" here, because for some reason
@@ -175,52 +158,42 @@ function parseConfig(configText: string, configType = "toml") {
// it's correctly handled by playwright. // it's correctly handled by playwright.
throw SyntaxError(errMessage); throw SyntaxError(errMessage);
} }
} } else if (configType === 'json') {
else if (configType === "json") {
try { try {
config = JSON.parse(configText); config = JSON.parse(configText);
} } catch (err) {
catch (err) {
const errMessage: string = err.toString(); const errMessage: string = err.toString();
showError(`<p>config supplied: ${configText} is an invalid JSON and cannot be parsed: ${errMessage}</p>`); showError(`<p>config supplied: ${configText} is an invalid JSON and cannot be parsed: ${errMessage}</p>`);
throw err; throw err;
} }
} } else {
else {
showError(`<p>type of config supplied is: ${configType}, supported values are ["toml", "json"].</p>`); showError(`<p>type of config supplied is: ${configType}, supported values are ["toml", "json"].</p>`);
} }
return config; return config;
} }
function validateConfig(configText: string, configType = "toml") { function validateConfig(configText: string, configType = 'toml') {
const config = parseConfig(configText, configType); const config = parseConfig(configText, configType);
const finalConfig: AppConfig = {} const finalConfig: AppConfig = {};
for (const keyType in allKeys) for (const keyType in allKeys) {
{
const keys: string[] = allKeys[keyType]; const keys: string[] = allKeys[keyType];
keys.forEach(function(item: string){ keys.forEach(function (item: string) {
if (validateParamInConfig(item, keyType, config)) if (validateParamInConfig(item, keyType, config)) {
{ if (item === 'runtimes') {
if (item === "runtimes")
{
finalConfig[item] = []; finalConfig[item] = [];
const runtimes = config[item] as object[]; const runtimes = config[item] as object[];
runtimes.forEach(function(eachRuntime: object){ runtimes.forEach(function (eachRuntime: object) {
const runtimeConfig: object = {}; const runtimeConfig: object = {};
for (const eachRuntimeParam in eachRuntime) for (const eachRuntimeParam in eachRuntime) {
{ if (validateParamInConfig(eachRuntimeParam, 'string', eachRuntime)) {
if (validateParamInConfig(eachRuntimeParam, "string", eachRuntime))
{
runtimeConfig[eachRuntimeParam] = eachRuntime[eachRuntimeParam]; runtimeConfig[eachRuntimeParam] = eachRuntime[eachRuntimeParam];
} }
} }
finalConfig[item].push(runtimeConfig); finalConfig[item].push(runtimeConfig);
}); });
} } else {
else
{
finalConfig[item] = config[item]; finalConfig[item] = config[item];
} }
} }
@@ -231,9 +204,8 @@ function validateConfig(configText: string, configType = "toml") {
} }
function validateParamInConfig(paramName: string, paramType: string, config: object): boolean { function validateParamInConfig(paramName: string, paramType: string, config: object): boolean {
if (paramName in config) if (paramName in config) {
{ return paramType === 'array' ? Array.isArray(config[paramName]) : typeof config[paramName] === paramType;
return paramType === "array" ? Array.isArray(config[paramName]) : typeof config[paramName] === paramType;
} }
return false; return false;
} }

View File

@@ -4,8 +4,7 @@ import type { Runtime } from './runtime';
const logger = getLogger('pyexec'); const logger = getLogger('pyexec');
export async function pyExec(runtime: Runtime, pysrc: string, outElem: HTMLElement) export async function pyExec(runtime: Runtime, pysrc: string, outElem: HTMLElement) {
{
// this is the python function defined in pyscript.py // this is the python function defined in pyscript.py
const set_current_display_target = runtime.globals.get('set_current_display_target'); const set_current_display_target = runtime.globals.get('set_current_display_target');
ensureUniqueId(outElem); ensureUniqueId(outElem);
@@ -13,16 +12,14 @@ export async function pyExec(runtime: Runtime, pysrc: string, outElem: HTMLEleme
try { try {
try { try {
return await runtime.run(pysrc); return await runtime.run(pysrc);
} } catch (err) {
catch (err) {
// XXX: currently we display exceptions in the same position as // XXX: currently we display exceptions in the same position as
// the output. But we probably need a better way to do that, // the output. But we probably need a better way to do that,
// e.g. allowing plugins to intercept exceptions and display them // e.g. allowing plugins to intercept exceptions and display them
// in a configurable way. // in a configurable way.
displayPyException(err, outElem); displayPyException(err, outElem);
} }
} } finally {
finally {
set_current_display_target(undefined); set_current_display_target(undefined);
} }
} }
@@ -36,8 +33,7 @@ export async function pyExec(runtime: Runtime, pysrc: string, outElem: HTMLEleme
*/ */
export function pyDisplay(runtime: Runtime, obj: any, kwargs: object) { export function pyDisplay(runtime: Runtime, obj: any, kwargs: object) {
const display = runtime.globals.get('display'); const display = runtime.globals.get('display');
if (kwargs === undefined) if (kwargs === undefined) display(obj);
display(obj);
else { else {
display.callKwargs(obj, kwargs); display.callKwargs(obj, kwargs);
} }
@@ -46,18 +42,17 @@ export function pyDisplay(runtime: Runtime, obj: any, kwargs: object) {
function displayPyException(err: any, errElem: HTMLElement) { function displayPyException(err: any, errElem: HTMLElement) {
//addClasses(errElem, ['py-error']) //addClasses(errElem, ['py-error'])
const pre = document.createElement('pre'); const pre = document.createElement('pre');
pre.className = "py-error"; pre.className = 'py-error';
if (err.name === "PythonError") { if (err.name === 'PythonError') {
// err.message contains the python-level traceback (i.e. a string // err.message contains the python-level traceback (i.e. a string
// starting with: "Traceback (most recent call last) ..." // starting with: "Traceback (most recent call last) ..."
logger.error("Python exception:\n" + err.message); logger.error('Python exception:\n' + err.message);
pre.innerText = err.message; pre.innerText = err.message;
} } else {
else {
// this is very likely a normal JS exception. The best we can do is to // this is very likely a normal JS exception. The best we can do is to
// display it as is. // display it as is.
logger.error("Non-python exception:\n" + err); logger.error('Non-python exception:\n' + err);
pre.innerText = err; pre.innerText = err;
} }
errElem.appendChild(pre); errElem.appendChild(pre);

View File

@@ -80,9 +80,7 @@ export class PyodideRuntime extends Runtime {
async loadPackage(names: string | string[]): Promise<void> { async loadPackage(names: string | string[]): Promise<void> {
logger.info(`pyodide.loadPackage: ${names.toString()}`); logger.info(`pyodide.loadPackage: ${names.toString()}`);
await this.interpreter.loadPackage(names, await this.interpreter.loadPackage(names, logger.info.bind(logger), logger.info.bind(logger));
logger.info.bind(logger),
logger.info.bind(logger));
} }
async installPackage(package_name: string | string[]): Promise<void> { async installPackage(package_name: string | string[]): Promise<void> {
@@ -97,15 +95,13 @@ export class PyodideRuntime extends Runtime {
async loadFromFile(path: string): Promise<void> { async loadFromFile(path: string): Promise<void> {
const pathArr = path.split('/'); const pathArr = path.split('/');
const filename = pathArr.pop(); const filename = pathArr.pop();
for(let i=0; i<pathArr.length; i++) for (let i = 0; i < pathArr.length; i++) {
{ const eachPath = pathArr.slice(0, i + 1).join('/');
const eachPath = pathArr.slice(0, i+1).join('/'); const { exists, parentExists } = this.interpreter.FS.analyzePath(eachPath);
const {exists, parentExists} = this.interpreter.FS.analyzePath(eachPath);
if (!parentExists) { if (!parentExists) {
throw new Error(`'INTERNAL ERROR! cannot create ${path}, this should never happen'`) throw new Error(`'INTERNAL ERROR! cannot create ${path}, this should never happen'`);
} }
if (!exists) if (!exists) {
{
this.interpreter.FS.mkdir(eachPath); this.interpreter.FS.mkdir(eachPath);
} }
} }

View File

@@ -4,11 +4,9 @@ import { getLogger } from './logger';
const logger = getLogger('pyscript/runtime'); const logger = getLogger('pyscript/runtime');
export const version = "<<VERSION>>"; export const version = '<<VERSION>>';
export type RuntimeInterpreter = PyodideInterface | null; export type RuntimeInterpreter = PyodideInterface | null;
/* /*
Runtime class is a super class that all different runtimes must respect Runtime class is a super class that all different runtimes must respect
and adhere to. and adhere to.
@@ -65,8 +63,8 @@ export abstract class Runtime extends Object {
* */ * */
async runButDontRaise(code: string): Promise<unknown> { async runButDontRaise(code: string): Promise<unknown> {
return this.run(code).catch(err => { return this.run(code).catch(err => {
const error = err as Error const error = err as Error;
logger.error("Error:", error); logger.error('Error:', error);
}); });
} }

View File

@@ -11,7 +11,7 @@ export function removeClasses(element: HTMLElement, classes: string[]) {
} }
export function escape(str: string): string { export function escape(str: string): string {
return str.replace(/</g, "&lt;").replace(/>/g, "&gt;") return str.replace(/</g, '&lt;').replace(/>/g, '&gt;');
} }
export function htmlDecode(input: string): string | null { export function htmlDecode(input: string): string | null {
@@ -36,8 +36,7 @@ export function ltrim(code: string): string {
let _uniqueIdCounter = 0; let _uniqueIdCounter = 0;
export function ensureUniqueId(el: HTMLElement) { export function ensureUniqueId(el: HTMLElement) {
if (el.id === "") if (el.id === '') el.id = `py-internal-${_uniqueIdCounter++}`;
el.id = `py-internal-${_uniqueIdCounter++}`;
} }
/* /*
@@ -48,7 +47,7 @@ export function ensureUniqueId(el: HTMLElement) {
export function showError(msg: string): void { export function showError(msg: string): void {
const warning = document.createElement('div'); const warning = document.createElement('div');
// XXX: the style should go to css instead of here probably // XXX: the style should go to css instead of here probably
warning.className = "py-error"; warning.className = 'py-error';
warning.style.backgroundColor = 'LightCoral'; warning.style.backgroundColor = 'LightCoral';
warning.style.alignContent = 'center'; warning.style.alignContent = 'center';
warning.style.margin = '4px'; warning.style.margin = '4px';
@@ -83,7 +82,7 @@ export function handleFetchError(e: Error, singleFile: string) {
export function readTextFromPath(path: string) { export function readTextFromPath(path: string) {
const request = new XMLHttpRequest(); const request = new XMLHttpRequest();
request.open("GET", path, false); request.open('GET', path, false);
request.send(); request.send();
const returnValue = request.responseText; const returnValue = request.responseText;
@@ -99,14 +98,14 @@ export function globalExport(name: string, obj: object) {
// visible everywhere. Should be used very sparingly! // visible everywhere. Should be used very sparingly!
// `window` in the browser, `global` in node // `window` in the browser, `global` in node
const _global = (window || global); const _global = window || global;
_global[name] = obj; _global[name] = obj;
} }
export function getAttribute(el:Element, attr:string ):string | null { export function getAttribute(el: Element, attr: string): string | null {
if( el.hasAttribute( attr ) ){ if (el.hasAttribute(attr)) {
const value = el.getAttribute(attr); const value = el.getAttribute(attr);
if( value ){ if (value) {
return value; return value;
} }
} }