mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-19 18:27:29 -05:00
Format the TypeScript files (#877)
This commit is contained in:
@@ -47,13 +47,13 @@ export class PyBox extends HTMLElement {
|
||||
// now we need to set widths
|
||||
this.widths = [];
|
||||
|
||||
const widthsAttr = getAttribute( this, "widths" );
|
||||
const widthsAttr = getAttribute(this, 'widths');
|
||||
if (widthsAttr) {
|
||||
for (const w of widthsAttr.split(';')) {
|
||||
if (w.includes('/')){
|
||||
this.widths.push(w.split('/')[0])
|
||||
}else{
|
||||
this.widths.push(w)
|
||||
if (w.includes('/')) {
|
||||
this.widths.push(w.split('/')[0]);
|
||||
} else {
|
||||
this.widths.push(w);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -63,7 +63,7 @@ export class PyBox extends HTMLElement {
|
||||
this.widths.forEach((width, index) => {
|
||||
const node: ChildNode = mainDiv.childNodes[index];
|
||||
(<HTMLElement>node).style.flex = width;
|
||||
addClasses((<HTMLElement>node), ['py-box-child']);
|
||||
addClasses(<HTMLElement>node, ['py-box-child']);
|
||||
});
|
||||
|
||||
this.appendChild(mainDiv);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getAttribute, addClasses, htmlDecode, ensureUniqueId } from '../utils';
|
||||
import { getLogger } from '../logger'
|
||||
import { getLogger } from '../logger';
|
||||
import type { Runtime } from '../runtime';
|
||||
|
||||
const logger = getLogger('py-button');
|
||||
@@ -18,14 +18,14 @@ export function make_PyButton(runtime: Runtime) {
|
||||
|
||||
this.defaultClass = ['py-button'];
|
||||
|
||||
const label = getAttribute(this, "label");
|
||||
const label = getAttribute(this, 'label');
|
||||
if (label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
// Styling does the same thing as class in normal HTML. Using the name "class" makes the style to malfunction
|
||||
const styling = getAttribute(this, "styling");
|
||||
if ( styling ) {
|
||||
const styling = getAttribute(this, 'styling');
|
||||
if (styling) {
|
||||
const klass = styling.trim();
|
||||
if (klass === '') {
|
||||
this.class = this.defaultClass;
|
||||
@@ -43,7 +43,7 @@ export function make_PyButton(runtime: Runtime) {
|
||||
|
||||
async connectedCallback() {
|
||||
ensureUniqueId(this);
|
||||
this.code = htmlDecode(this.innerHTML) || "";
|
||||
this.code = htmlDecode(this.innerHTML) || '';
|
||||
this.mount_name = this.id.split('-').join('_');
|
||||
this.innerHTML = '';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getAttribute, addClasses, htmlDecode, ensureUniqueId } from '../utils';
|
||||
import { getLogger } from '../logger'
|
||||
import { getLogger } from '../logger';
|
||||
import type { Runtime } from '../runtime';
|
||||
|
||||
const logger = getLogger('py-inputbox');
|
||||
@@ -14,7 +14,7 @@ export function make_PyInputBox(runtime: Runtime) {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const label = getAttribute( this, "label");
|
||||
const label = getAttribute(this, 'label');
|
||||
if (label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { basicSetup, EditorView } from 'codemirror';
|
||||
import { python } from '@codemirror/lang-python';
|
||||
import { indentUnit } from '@codemirror/language'
|
||||
import { indentUnit } from '@codemirror/language';
|
||||
import { Compartment } from '@codemirror/state';
|
||||
import { keymap } from '@codemirror/view';
|
||||
import { defaultKeymap } from '@codemirror/commands';
|
||||
@@ -14,7 +14,6 @@ import { getLogger } from '../logger';
|
||||
const logger = getLogger('py-repl');
|
||||
|
||||
export function make_PyRepl(runtime: Runtime) {
|
||||
|
||||
/* High level structure of py-repl DOM, and the corresponding JS names.
|
||||
|
||||
this <py-repl>
|
||||
@@ -63,7 +62,7 @@ export function make_PyRepl(runtime: Runtime) {
|
||||
makeEditor(pySrc: string): EditorView {
|
||||
const languageConf = new Compartment();
|
||||
const extensions = [
|
||||
indentUnit.of(" "),
|
||||
indentUnit.of(' '),
|
||||
basicSetup,
|
||||
languageConf.of(python()),
|
||||
keymap.of([
|
||||
@@ -181,22 +180,20 @@ export function make_PyRepl(runtime: Runtime) {
|
||||
}
|
||||
|
||||
getOutputElement(): HTMLElement {
|
||||
const outputID = getAttribute(this, "output");
|
||||
const outputID = getAttribute(this, 'output');
|
||||
if (outputID !== null) {
|
||||
const el = document.getElementById(outputID);
|
||||
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;
|
||||
return undefined;
|
||||
}
|
||||
return el;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return this.outDiv;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// XXX the autogenerate logic is very messy. We should redo it, and it
|
||||
// should be the default.
|
||||
autogenerateMaybe(): void {
|
||||
@@ -210,19 +207,19 @@ export function make_PyRepl(runtime: Runtime) {
|
||||
newPyRepl.setAttribute('root', this.getAttribute('root'));
|
||||
newPyRepl.id = this.getAttribute('root') + '-' + nextExecId.toString();
|
||||
|
||||
if(this.hasAttribute('auto-generate')) {
|
||||
if (this.hasAttribute('auto-generate')) {
|
||||
newPyRepl.setAttribute('auto-generate', '');
|
||||
this.removeAttribute('auto-generate');
|
||||
}
|
||||
|
||||
const outputMode = getAttribute( this, 'output-mode')
|
||||
if(outputMode) {
|
||||
const outputMode = getAttribute(this, 'output-mode');
|
||||
if (outputMode) {
|
||||
newPyRepl.setAttribute('output-mode', outputMode);
|
||||
}
|
||||
|
||||
const addReplAttribute = (attribute: string) => {
|
||||
const attr = getAttribute( this, attribute)
|
||||
if(attr) {
|
||||
const attr = getAttribute(this, attribute);
|
||||
if (attr) {
|
||||
newPyRepl.setAttribute(attribute, attr);
|
||||
}
|
||||
};
|
||||
@@ -230,13 +227,12 @@ export function make_PyRepl(runtime: Runtime) {
|
||||
addReplAttribute('output');
|
||||
|
||||
newPyRepl.setAttribute('exec-id', nextExecId.toString());
|
||||
if( this.parentElement ){
|
||||
if (this.parentElement) {
|
||||
this.parentElement.appendChild(newPyRepl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return PyRepl
|
||||
return PyRepl;
|
||||
}
|
||||
|
||||
@@ -6,9 +6,7 @@ import { pyExec } from '../pyexec';
|
||||
const logger = getLogger('py-script');
|
||||
|
||||
export function make_PyScript(runtime: Runtime) {
|
||||
|
||||
class PyScript extends HTMLElement {
|
||||
|
||||
async connectedCallback() {
|
||||
ensureUniqueId(this);
|
||||
const pySrc = await this.getPySrc();
|
||||
@@ -24,8 +22,7 @@ export function make_PyScript(runtime: Runtime) {
|
||||
const url = this.getAttribute('src');
|
||||
const response = await fetch(url);
|
||||
return await response.text();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return htmlDecode(this.innerHTML);
|
||||
}
|
||||
}
|
||||
@@ -37,97 +34,97 @@ export function make_PyScript(runtime: Runtime) {
|
||||
/** Defines all possible py-on* and their corresponding event types */
|
||||
const pyAttributeToEvent: Map<string, string> = new Map<string, string>([
|
||||
// Leaving pys-onClick and pys-onKeyDown for backward compatibility
|
||||
["pys-onClick", "click"],
|
||||
["pys-onKeyDown", "keydown"],
|
||||
["py-onClick", "click"],
|
||||
["py-onKeyDown", "keydown"],
|
||||
['pys-onClick', 'click'],
|
||||
['pys-onKeyDown', 'keydown'],
|
||||
['py-onClick', 'click'],
|
||||
['py-onKeyDown', 'keydown'],
|
||||
// Window Events
|
||||
["py-afterprint", "afterprint"],
|
||||
["py-beforeprint", "beforeprint"],
|
||||
["py-beforeunload", "beforeunload"],
|
||||
["py-error", "error"],
|
||||
["py-hashchange", "hashchange"],
|
||||
["py-load", "load"],
|
||||
["py-message", "message"],
|
||||
["py-offline", "offline"],
|
||||
["py-online", "online"],
|
||||
["py-pagehide", "pagehide"],
|
||||
["py-pageshow", "pageshow"],
|
||||
["py-popstate", "popstate"],
|
||||
["py-resize", "resize"],
|
||||
["py-storage", "storage"],
|
||||
["py-unload", "unload"],
|
||||
['py-afterprint', 'afterprint'],
|
||||
['py-beforeprint', 'beforeprint'],
|
||||
['py-beforeunload', 'beforeunload'],
|
||||
['py-error', 'error'],
|
||||
['py-hashchange', 'hashchange'],
|
||||
['py-load', 'load'],
|
||||
['py-message', 'message'],
|
||||
['py-offline', 'offline'],
|
||||
['py-online', 'online'],
|
||||
['py-pagehide', 'pagehide'],
|
||||
['py-pageshow', 'pageshow'],
|
||||
['py-popstate', 'popstate'],
|
||||
['py-resize', 'resize'],
|
||||
['py-storage', 'storage'],
|
||||
['py-unload', 'unload'],
|
||||
|
||||
// Form Events
|
||||
["py-blur", "blur"],
|
||||
["py-change", "change"],
|
||||
["py-contextmenu", "contextmenu"],
|
||||
["py-focus", "focus"],
|
||||
["py-input", "input"],
|
||||
["py-invalid", "invalid"],
|
||||
["py-reset", "reset"],
|
||||
["py-search", "search"],
|
||||
["py-select", "select"],
|
||||
["py-submit", "submit"],
|
||||
['py-blur', 'blur'],
|
||||
['py-change', 'change'],
|
||||
['py-contextmenu', 'contextmenu'],
|
||||
['py-focus', 'focus'],
|
||||
['py-input', 'input'],
|
||||
['py-invalid', 'invalid'],
|
||||
['py-reset', 'reset'],
|
||||
['py-search', 'search'],
|
||||
['py-select', 'select'],
|
||||
['py-submit', 'submit'],
|
||||
|
||||
// Keyboard Events
|
||||
["py-keydown", "keydown"],
|
||||
["py-keypress", "keypress"],
|
||||
["py-keyup", "keyup"],
|
||||
['py-keydown', 'keydown'],
|
||||
['py-keypress', 'keypress'],
|
||||
['py-keyup', 'keyup'],
|
||||
|
||||
// Mouse Events
|
||||
["py-click", "click"],
|
||||
["py-dblclick", "dblclick"],
|
||||
["py-mousedown", "mousedown"],
|
||||
["py-mousemove", "mousemove"],
|
||||
["py-mouseout", "mouseout"],
|
||||
["py-mouseover", "mouseover"],
|
||||
["py-mouseup", "mouseup"],
|
||||
["py-mousewheel", "mousewheel"],
|
||||
["py-wheel", "wheel"],
|
||||
['py-click', 'click'],
|
||||
['py-dblclick', 'dblclick'],
|
||||
['py-mousedown', 'mousedown'],
|
||||
['py-mousemove', 'mousemove'],
|
||||
['py-mouseout', 'mouseout'],
|
||||
['py-mouseover', 'mouseover'],
|
||||
['py-mouseup', 'mouseup'],
|
||||
['py-mousewheel', 'mousewheel'],
|
||||
['py-wheel', 'wheel'],
|
||||
|
||||
// Drag Events
|
||||
["py-drag", "drag"],
|
||||
["py-dragend", "dragend"],
|
||||
["py-dragenter", "dragenter"],
|
||||
["py-dragleave", "dragleave"],
|
||||
["py-dragover", "dragover"],
|
||||
["py-dragstart", "dragstart"],
|
||||
["py-drop", "drop"],
|
||||
["py-scroll", "scroll"],
|
||||
['py-drag', 'drag'],
|
||||
['py-dragend', 'dragend'],
|
||||
['py-dragenter', 'dragenter'],
|
||||
['py-dragleave', 'dragleave'],
|
||||
['py-dragover', 'dragover'],
|
||||
['py-dragstart', 'dragstart'],
|
||||
['py-drop', 'drop'],
|
||||
['py-scroll', 'scroll'],
|
||||
|
||||
// Clipboard Events
|
||||
["py-copy", "copy"],
|
||||
["py-cut", "cut"],
|
||||
["py-paste", "paste"],
|
||||
['py-copy', 'copy'],
|
||||
['py-cut', 'cut'],
|
||||
['py-paste', 'paste'],
|
||||
|
||||
// Media Events
|
||||
["py-abort", "abort"],
|
||||
["py-canplay", "canplay"],
|
||||
["py-canplaythrough", "canplaythrough"],
|
||||
["py-cuechange", "cuechange"],
|
||||
["py-durationchange", "durationchange"],
|
||||
["py-emptied", "emptied"],
|
||||
["py-ended", "ended"],
|
||||
["py-loadeddata", "loadeddata"],
|
||||
["py-loadedmetadata", "loadedmetadata"],
|
||||
["py-loadstart", "loadstart"],
|
||||
["py-pause", "pause"],
|
||||
["py-play", "play"],
|
||||
["py-playing", "playing"],
|
||||
["py-progress", "progress"],
|
||||
["py-ratechange", "ratechange"],
|
||||
["py-seeked", "seeked"],
|
||||
["py-seeking", "seeking"],
|
||||
["py-stalled", "stalled"],
|
||||
["py-suspend", "suspend"],
|
||||
["py-timeupdate", "timeupdate"],
|
||||
["py-volumechange", "volumechange"],
|
||||
["py-waiting", "waiting"],
|
||||
['py-abort', 'abort'],
|
||||
['py-canplay', 'canplay'],
|
||||
['py-canplaythrough', 'canplaythrough'],
|
||||
['py-cuechange', 'cuechange'],
|
||||
['py-durationchange', 'durationchange'],
|
||||
['py-emptied', 'emptied'],
|
||||
['py-ended', 'ended'],
|
||||
['py-loadeddata', 'loadeddata'],
|
||||
['py-loadedmetadata', 'loadedmetadata'],
|
||||
['py-loadstart', 'loadstart'],
|
||||
['py-pause', 'pause'],
|
||||
['py-play', 'play'],
|
||||
['py-playing', 'playing'],
|
||||
['py-progress', 'progress'],
|
||||
['py-ratechange', 'ratechange'],
|
||||
['py-seeked', 'seeked'],
|
||||
['py-seeking', 'seeking'],
|
||||
['py-stalled', 'stalled'],
|
||||
['py-suspend', 'suspend'],
|
||||
['py-timeupdate', 'timeupdate'],
|
||||
['py-volumechange', 'volumechange'],
|
||||
['py-waiting', 'waiting'],
|
||||
|
||||
// Misc Events
|
||||
["py-toggle", "toggle"],
|
||||
]);
|
||||
['py-toggle', 'toggle'],
|
||||
]);
|
||||
|
||||
/** Initialize all elements with py-* handlers attributes */
|
||||
export async function initHandlers(runtime: Runtime) {
|
||||
@@ -142,22 +139,27 @@ async function createElementsWithEventListeners(runtime: Runtime, pyAttribute: s
|
||||
const matches: NodeListOf<HTMLElement> = document.querySelectorAll(`[${pyAttribute}]`);
|
||||
for (const el of matches) {
|
||||
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 event = pyAttributeToEvent.get(pyAttribute);
|
||||
|
||||
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.")
|
||||
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.',
|
||||
);
|
||||
const source = `
|
||||
from pyodide.ffi import create_proxy
|
||||
Element("${el.id}").element.addEventListener("${event}", create_proxy(${handlerCode}))
|
||||
`;
|
||||
await runtime.run(source);
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
el.addEventListener(event, () => {
|
||||
(async() => {await runtime.run(handlerCode)})();
|
||||
(async () => {
|
||||
await runtime.run(handlerCode);
|
||||
})();
|
||||
});
|
||||
}
|
||||
// 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);
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Mount all elements with attribute py-mount into the Python namespace */
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { Runtime } from '../runtime';
|
||||
import type {PyProxy} from "pyodide"
|
||||
import type { PyProxy } from 'pyodide';
|
||||
import { getLogger } from '../logger';
|
||||
|
||||
const logger = getLogger('py-register-widget');
|
||||
|
||||
|
||||
function createWidget(runtime: Runtime, name: string, code: string, klass: string) {
|
||||
class CustomWidget extends HTMLElement {
|
||||
shadow: ShadowRoot;
|
||||
@@ -65,14 +64,14 @@ export function make_PyWidget(runtime: Runtime) {
|
||||
this.wrapper = document.createElement('slot');
|
||||
this.shadow.appendChild(this.wrapper);
|
||||
|
||||
this.addAttributes('src','name','klass');
|
||||
this.addAttributes('src', 'name', 'klass');
|
||||
}
|
||||
|
||||
addAttributes(...attrs:string[]){
|
||||
for (const each of attrs){
|
||||
const property = each === "src" ? "source" : each;
|
||||
addAttributes(...attrs: string[]) {
|
||||
for (const each of attrs) {
|
||||
const property = each === 'src' ? 'source' : each;
|
||||
if (this.hasAttribute(each)) {
|
||||
this[property]=this.getAttribute(each);
|
||||
this[property] = this.getAttribute(each);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,14 +42,14 @@ function getLogger(prefix: string): Logger {
|
||||
}
|
||||
|
||||
function _makeLogger(prefix: string): Logger {
|
||||
prefix = "[" + prefix + "] ";
|
||||
prefix = '[' + prefix + '] ';
|
||||
|
||||
function make(level: string) {
|
||||
const out_fn = console[level].bind(console);
|
||||
function fn(fmt: string, ...args: unknown[]) {
|
||||
out_fn(prefix + fmt, ...args);
|
||||
}
|
||||
return fn
|
||||
return fn;
|
||||
}
|
||||
|
||||
// 'log' is intentionally omitted
|
||||
@@ -58,7 +58,7 @@ function _makeLogger(prefix: string): Logger {
|
||||
const warn = make('warn');
|
||||
const error = make('error');
|
||||
|
||||
return {debug, info, warn, error};
|
||||
return { debug, info, warn, error };
|
||||
}
|
||||
|
||||
export { getLogger };
|
||||
|
||||
@@ -7,18 +7,16 @@ import { make_PyScript, initHandlers, mountElements } from './components/pyscrip
|
||||
import { PyLoader } from './components/pyloader';
|
||||
import { PyodideRuntime } from './pyodide';
|
||||
import { getLogger } from './logger';
|
||||
import { handleFetchError, showError, globalExport } from './utils'
|
||||
import { handleFetchError, showError, globalExport } from './utils';
|
||||
import { createCustomElements } from './components/elements';
|
||||
|
||||
type ImportType = { [key: string]: unknown }
|
||||
type ImportType = { [key: string]: unknown };
|
||||
type ImportMapType = {
|
||||
imports: ImportType | null
|
||||
}
|
||||
|
||||
imports: ImportType | null;
|
||||
};
|
||||
|
||||
const logger = getLogger('pyscript/main');
|
||||
|
||||
|
||||
/* High-level overview of the lifecycle of a PyScript App:
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
|
||||
class PyScriptApp {
|
||||
|
||||
config: AppConfig;
|
||||
loader: PyLoader;
|
||||
runtime: Runtime;
|
||||
@@ -75,15 +71,16 @@ class PyScriptApp {
|
||||
logger.info('searching for <py-config>');
|
||||
const elements = document.getElementsByTagName('py-config');
|
||||
let el: Element | null = null;
|
||||
if (elements.length > 0)
|
||||
el = elements[0];
|
||||
if (elements.length > 0) el = elements[0];
|
||||
if (elements.length >= 2) {
|
||||
// XXX: ideally, I would like to have a way to raise "fatal
|
||||
// errors" and stop the computation, but currently our life cycle
|
||||
// is too messy to implement it reliably. We might want to revisit
|
||||
// this once it's in a better shape.
|
||||
showError("Multiple <py-config> tags detected. Only the first is " +
|
||||
"going to be parsed, all the others will be ignored");
|
||||
showError(
|
||||
'Multiple <py-config> tags detected. Only the first is ' +
|
||||
'going to be parsed, all the others will be ignored',
|
||||
);
|
||||
}
|
||||
this.config = loadConfigFromElement(el);
|
||||
logger.info('config loaded:\n' + JSON.stringify(this.config, null, 2));
|
||||
@@ -102,17 +99,15 @@ class PyScriptApp {
|
||||
loadRuntime() {
|
||||
logger.info('Initializing runtime');
|
||||
if (this.config.runtimes.length == 0) {
|
||||
showError("Fatal error: config.runtimes is empty");
|
||||
showError('Fatal error: config.runtimes is empty');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.config.runtimes.length > 1) {
|
||||
showError("Multiple runtimes are not supported yet. " +
|
||||
"Only the first will be used");
|
||||
showError('Multiple runtimes are not supported yet. ' + 'Only the first will be used');
|
||||
}
|
||||
const runtime_cfg = this.config.runtimes[0];
|
||||
this.runtime = new PyodideRuntime(this.config, runtime_cfg.src,
|
||||
runtime_cfg.name, runtime_cfg.lang);
|
||||
this.runtime = new PyodideRuntime(this.config, runtime_cfg.src, runtime_cfg.name, runtime_cfg.lang);
|
||||
this.loader.log(`Downloading ${runtime_cfg.name}...`);
|
||||
const script = document.createElement('script'); // create a script DOM node
|
||||
script.src = this.runtime.src;
|
||||
@@ -160,13 +155,12 @@ class PyScriptApp {
|
||||
logger.info('PyScript page fully initialized');
|
||||
}
|
||||
|
||||
|
||||
// lifecycle (6)
|
||||
async setupVirtualEnv(runtime: Runtime): Promise<void> {
|
||||
// XXX: maybe the following calls could be parallelized, instead of
|
||||
// await()ing immediately. For now I'm using await to be 100%
|
||||
// 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 this.fetchPaths(runtime);
|
||||
}
|
||||
@@ -178,7 +172,7 @@ class PyScriptApp {
|
||||
// initialized. But we could easily do it in JS in parallel with the
|
||||
// download/startup of pyodide.
|
||||
const paths = this.config.paths;
|
||||
logger.info("Paths to fetch: ", paths)
|
||||
logger.info('Paths to fetch: ', paths);
|
||||
for (const singleFile of paths) {
|
||||
logger.info(` fetching path: ${singleFile}`);
|
||||
try {
|
||||
@@ -188,7 +182,7 @@ class PyScriptApp {
|
||||
handleFetchError(<Error>e, singleFile);
|
||||
}
|
||||
}
|
||||
logger.info("All paths fetched");
|
||||
logger.info('All paths fetched');
|
||||
}
|
||||
|
||||
// lifecycle (7)
|
||||
@@ -239,7 +233,6 @@ class PyScriptApp {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function pyscript_get_config() {
|
||||
@@ -251,4 +244,4 @@ globalExport('pyscript_get_config', pyscript_get_config);
|
||||
const globalApp = new PyScriptApp();
|
||||
globalApp.main();
|
||||
|
||||
export const runtime = globalApp.runtime
|
||||
export const runtime = globalApp.runtime;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import toml from '../src/toml'
|
||||
import toml from '../src/toml';
|
||||
import { getLogger } from './logger';
|
||||
import { version } from './runtime';
|
||||
import { getAttribute, readTextFromPath, showError } from './utils'
|
||||
import { getAttribute, readTextFromPath, showError } from './utils';
|
||||
|
||||
const logger = getLogger('py-config');
|
||||
|
||||
@@ -31,29 +31,30 @@ export type RuntimeConfig = {
|
||||
export type PyScriptMetadata = {
|
||||
version?: string;
|
||||
time?: string;
|
||||
}
|
||||
};
|
||||
|
||||
const allKeys = {
|
||||
"string": ["name", "description", "version", "type", "author_name", "author_email", "license"],
|
||||
"number": ["schema_version"],
|
||||
"boolean": ["autoclose_loader"],
|
||||
"array": ["runtimes", "packages", "paths", "plugins"]
|
||||
string: ['name', 'description', 'version', 'type', 'author_name', 'author_email', 'license'],
|
||||
number: ['schema_version'],
|
||||
boolean: ['autoclose_loader'],
|
||||
array: ['runtimes', 'packages', 'paths', 'plugins'],
|
||||
};
|
||||
|
||||
export const defaultConfig: AppConfig = {
|
||||
"schema_version": 1,
|
||||
"type": "app",
|
||||
"autoclose_loader": true,
|
||||
"runtimes": [{
|
||||
"src": "https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js",
|
||||
"name": "pyodide-0.21.3",
|
||||
"lang": "python"
|
||||
}],
|
||||
"packages":[],
|
||||
"paths":[],
|
||||
"plugins": []
|
||||
}
|
||||
|
||||
schema_version: 1,
|
||||
type: 'app',
|
||||
autoclose_loader: true,
|
||||
runtimes: [
|
||||
{
|
||||
src: 'https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js',
|
||||
name: 'pyodide-0.21.3',
|
||||
lang: 'python',
|
||||
},
|
||||
],
|
||||
packages: [],
|
||||
paths: [],
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
export function loadConfigFromElement(el: Element): AppConfig {
|
||||
let srcConfig: AppConfig;
|
||||
@@ -61,47 +62,41 @@ export function loadConfigFromElement(el: Element): AppConfig {
|
||||
if (el === null) {
|
||||
srcConfig = {};
|
||||
inlineConfig = {};
|
||||
}
|
||||
else {
|
||||
const configType = getAttribute(el, "type") || "toml";
|
||||
} else {
|
||||
const configType = getAttribute(el, 'type') || 'toml';
|
||||
srcConfig = extractFromSrc(el, configType);
|
||||
inlineConfig = extractFromInline(el, configType);
|
||||
}
|
||||
srcConfig = mergeConfig(srcConfig, defaultConfig);
|
||||
const result = mergeConfig(inlineConfig, srcConfig);
|
||||
result.pyscript = {
|
||||
"version": version,
|
||||
"time": new Date().toISOString()
|
||||
version: version,
|
||||
time: new Date().toISOString(),
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
function extractFromSrc(el: Element, configType: string) {
|
||||
const src = getAttribute(el, "src")
|
||||
const src = getAttribute(el, 'src');
|
||||
if (src) {
|
||||
logger.info('loading ', src)
|
||||
logger.info('loading ', src);
|
||||
return validateConfig(readTextFromPath(src), configType);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
function extractFromInline(el: Element, configType: string) {
|
||||
if (el.innerHTML !== '')
|
||||
{
|
||||
if (el.innerHTML !== '') {
|
||||
logger.info('loading <py-config> content');
|
||||
return validateConfig(el.innerHTML, configType);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function fillUserData(inputConfig: AppConfig, resultConfig: AppConfig): AppConfig
|
||||
{
|
||||
for (const key in inputConfig)
|
||||
{
|
||||
function fillUserData(inputConfig: AppConfig, resultConfig: AppConfig): AppConfig {
|
||||
for (const key in inputConfig) {
|
||||
// fill in all extra keys ignored by the validator
|
||||
if (!(key in defaultConfig))
|
||||
{
|
||||
if (!(key in defaultConfig)) {
|
||||
resultConfig[key] = inputConfig[key];
|
||||
}
|
||||
}
|
||||
@@ -109,32 +104,22 @@ function fillUserData(inputConfig: AppConfig, resultConfig: AppConfig): AppConfi
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
else if (Object.keys(inlineConfig).length === 0)
|
||||
{
|
||||
} else if (Object.keys(inlineConfig).length === 0) {
|
||||
return externalConfig;
|
||||
}
|
||||
else if(Object.keys(externalConfig).length === 0)
|
||||
{
|
||||
} else if (Object.keys(externalConfig).length === 0) {
|
||||
return inlineConfig;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
let merged: AppConfig = {};
|
||||
|
||||
for (const keyType in allKeys)
|
||||
{
|
||||
for (const keyType in allKeys) {
|
||||
const keys: string[] = allKeys[keyType];
|
||||
keys.forEach(function(item: string){
|
||||
if (keyType === "boolean")
|
||||
{
|
||||
merged[item] = (typeof inlineConfig[item] !== "undefined") ? inlineConfig[item] : externalConfig[item];
|
||||
}
|
||||
else
|
||||
{
|
||||
keys.forEach(function (item: string) {
|
||||
if (keyType === 'boolean') {
|
||||
merged[item] =
|
||||
typeof inlineConfig[item] !== 'undefined' ? inlineConfig[item] : externalConfig[item];
|
||||
} else {
|
||||
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;
|
||||
if (configType === "toml") {
|
||||
if (configType === 'toml') {
|
||||
try {
|
||||
// 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`;
|
||||
showError(`<p>${errMessage}</p>`);
|
||||
throw Error(errMessage);
|
||||
}
|
||||
config = toml.parse(configText);
|
||||
}
|
||||
catch (err) {
|
||||
} catch (err) {
|
||||
const errMessage: string = err.toString();
|
||||
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
|
||||
@@ -175,52 +158,42 @@ function parseConfig(configText: string, configType = "toml") {
|
||||
// it's correctly handled by playwright.
|
||||
throw SyntaxError(errMessage);
|
||||
}
|
||||
}
|
||||
else if (configType === "json") {
|
||||
} else if (configType === 'json') {
|
||||
try {
|
||||
config = JSON.parse(configText);
|
||||
}
|
||||
catch (err) {
|
||||
} catch (err) {
|
||||
const errMessage: string = err.toString();
|
||||
showError(`<p>config supplied: ${configText} is an invalid JSON and cannot be parsed: ${errMessage}</p>`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
showError(`<p>type of config supplied is: ${configType}, supported values are ["toml", "json"].</p>`);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
function validateConfig(configText: string, configType = "toml") {
|
||||
function validateConfig(configText: string, configType = 'toml') {
|
||||
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];
|
||||
keys.forEach(function(item: string){
|
||||
if (validateParamInConfig(item, keyType, config))
|
||||
{
|
||||
if (item === "runtimes")
|
||||
{
|
||||
keys.forEach(function (item: string) {
|
||||
if (validateParamInConfig(item, keyType, config)) {
|
||||
if (item === 'runtimes') {
|
||||
finalConfig[item] = [];
|
||||
const runtimes = config[item] as object[];
|
||||
runtimes.forEach(function(eachRuntime: object){
|
||||
runtimes.forEach(function (eachRuntime: object) {
|
||||
const runtimeConfig: object = {};
|
||||
for (const eachRuntimeParam in eachRuntime)
|
||||
{
|
||||
if (validateParamInConfig(eachRuntimeParam, "string", eachRuntime))
|
||||
{
|
||||
for (const eachRuntimeParam in eachRuntime) {
|
||||
if (validateParamInConfig(eachRuntimeParam, 'string', eachRuntime)) {
|
||||
runtimeConfig[eachRuntimeParam] = eachRuntime[eachRuntimeParam];
|
||||
}
|
||||
}
|
||||
finalConfig[item].push(runtimeConfig);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
finalConfig[item] = config[item];
|
||||
}
|
||||
}
|
||||
@@ -231,9 +204,8 @@ function validateConfig(configText: string, configType = "toml") {
|
||||
}
|
||||
|
||||
function validateParamInConfig(paramName: string, paramType: string, config: object): boolean {
|
||||
if (paramName in config)
|
||||
{
|
||||
return paramType === "array" ? Array.isArray(config[paramName]) : typeof config[paramName] === paramType;
|
||||
if (paramName in config) {
|
||||
return paramType === 'array' ? Array.isArray(config[paramName]) : typeof config[paramName] === paramType;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,7 @@ import type { Runtime } from './runtime';
|
||||
|
||||
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
|
||||
const set_current_display_target = runtime.globals.get('set_current_display_target');
|
||||
ensureUniqueId(outElem);
|
||||
@@ -13,16 +12,14 @@ export async function pyExec(runtime: Runtime, pysrc: string, outElem: HTMLEleme
|
||||
try {
|
||||
try {
|
||||
return await runtime.run(pysrc);
|
||||
}
|
||||
catch (err) {
|
||||
} catch (err) {
|
||||
// XXX: currently we display exceptions in the same position as
|
||||
// the output. But we probably need a better way to do that,
|
||||
// e.g. allowing plugins to intercept exceptions and display them
|
||||
// in a configurable way.
|
||||
displayPyException(err, outElem);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
} finally {
|
||||
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) {
|
||||
const display = runtime.globals.get('display');
|
||||
if (kwargs === undefined)
|
||||
display(obj);
|
||||
if (kwargs === undefined) display(obj);
|
||||
else {
|
||||
display.callKwargs(obj, kwargs);
|
||||
}
|
||||
@@ -46,18 +42,17 @@ export function pyDisplay(runtime: Runtime, obj: any, kwargs: object) {
|
||||
function displayPyException(err: any, errElem: HTMLElement) {
|
||||
//addClasses(errElem, ['py-error'])
|
||||
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
|
||||
// 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;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// this is very likely a normal JS exception. The best we can do is to
|
||||
// display it as is.
|
||||
logger.error("Non-python exception:\n" + err);
|
||||
logger.error('Non-python exception:\n' + err);
|
||||
pre.innerText = err;
|
||||
}
|
||||
errElem.appendChild(pre);
|
||||
|
||||
@@ -80,9 +80,7 @@ export class PyodideRuntime extends Runtime {
|
||||
|
||||
async loadPackage(names: string | string[]): Promise<void> {
|
||||
logger.info(`pyodide.loadPackage: ${names.toString()}`);
|
||||
await this.interpreter.loadPackage(names,
|
||||
logger.info.bind(logger),
|
||||
logger.info.bind(logger));
|
||||
await this.interpreter.loadPackage(names, logger.info.bind(logger), logger.info.bind(logger));
|
||||
}
|
||||
|
||||
async installPackage(package_name: string | string[]): Promise<void> {
|
||||
@@ -97,15 +95,13 @@ export class PyodideRuntime extends Runtime {
|
||||
async loadFromFile(path: string): Promise<void> {
|
||||
const pathArr = path.split('/');
|
||||
const filename = pathArr.pop();
|
||||
for(let i=0; i<pathArr.length; i++)
|
||||
{
|
||||
const eachPath = pathArr.slice(0, i+1).join('/');
|
||||
const {exists, parentExists} = this.interpreter.FS.analyzePath(eachPath);
|
||||
for (let i = 0; i < pathArr.length; i++) {
|
||||
const eachPath = pathArr.slice(0, i + 1).join('/');
|
||||
const { exists, parentExists } = this.interpreter.FS.analyzePath(eachPath);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,9 @@ import { getLogger } from './logger';
|
||||
|
||||
const logger = getLogger('pyscript/runtime');
|
||||
|
||||
export const version = "<<VERSION>>";
|
||||
export const version = '<<VERSION>>';
|
||||
export type RuntimeInterpreter = PyodideInterface | null;
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Runtime class is a super class that all different runtimes must respect
|
||||
and adhere to.
|
||||
@@ -65,8 +63,8 @@ export abstract class Runtime extends Object {
|
||||
* */
|
||||
async runButDontRaise(code: string): Promise<unknown> {
|
||||
return this.run(code).catch(err => {
|
||||
const error = err as Error
|
||||
logger.error("Error:", error);
|
||||
const error = err as Error;
|
||||
logger.error('Error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export function removeClasses(element: HTMLElement, classes: string[]) {
|
||||
}
|
||||
|
||||
export function escape(str: string): string {
|
||||
return str.replace(/</g, "<").replace(/>/g, ">")
|
||||
return str.replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
export function htmlDecode(input: string): string | null {
|
||||
@@ -36,8 +36,7 @@ export function ltrim(code: string): string {
|
||||
|
||||
let _uniqueIdCounter = 0;
|
||||
export function ensureUniqueId(el: HTMLElement) {
|
||||
if (el.id === "")
|
||||
el.id = `py-internal-${_uniqueIdCounter++}`;
|
||||
if (el.id === '') el.id = `py-internal-${_uniqueIdCounter++}`;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -48,7 +47,7 @@ export function ensureUniqueId(el: HTMLElement) {
|
||||
export function showError(msg: string): void {
|
||||
const warning = document.createElement('div');
|
||||
// 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.alignContent = 'center';
|
||||
warning.style.margin = '4px';
|
||||
@@ -83,7 +82,7 @@ export function handleFetchError(e: Error, singleFile: string) {
|
||||
|
||||
export function readTextFromPath(path: string) {
|
||||
const request = new XMLHttpRequest();
|
||||
request.open("GET", path, false);
|
||||
request.open('GET', path, false);
|
||||
request.send();
|
||||
const returnValue = request.responseText;
|
||||
|
||||
@@ -99,14 +98,14 @@ export function globalExport(name: string, obj: object) {
|
||||
// visible everywhere. Should be used very sparingly!
|
||||
|
||||
// `window` in the browser, `global` in node
|
||||
const _global = (window || global);
|
||||
const _global = window || global;
|
||||
_global[name] = obj;
|
||||
}
|
||||
|
||||
export function getAttribute(el:Element, attr:string ):string | null {
|
||||
if( el.hasAttribute( attr ) ){
|
||||
export function getAttribute(el: Element, attr: string): string | null {
|
||||
if (el.hasAttribute(attr)) {
|
||||
const value = el.getAttribute(attr);
|
||||
if( value ){
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user