Apply prettier to css, html, js, md, ts, and yml (#1249)

* Apply prettier to css, js, html, md, ts, and yml

As a followup I will add prettier to the .pre-commit config.
This patch is 100% generated by prettier.
I used a forked version of prettier that understands the
py-script tag.
See https://github.com/hoodmane/pyscript-prettier-precommit
for more info.

* Apply old pre-commit

* Revert some problems

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Revert some changes

* More changes

* Fix pre-commit

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Hood Chatham
2023-03-06 15:20:21 +01:00
committed by GitHub
parent 7ffe6a598e
commit 08f34f748b
108 changed files with 4571 additions and 3802 deletions

View File

@@ -1 +1 @@
module.exports = "";
module.exports = '';

View File

@@ -10,7 +10,7 @@
* nothing.
*/
console.warn(`.py files that are not explicitly mocked in \
console.warn(`.py files that are not explicitly mocked in \
jest.config.js and /__mocks__/ are mocked as empty strings`);
module.exports = "";
module.exports = '';

View File

@@ -17,7 +17,7 @@ module.exports = {
url: 'http://localhost',
},
moduleNameMapper: {
'^.*?pyscript\.py$': '<rootDir>/__mocks__/_pyscript.js',
'^.*?pyscript.py$': '<rootDir>/__mocks__/_pyscript.js',
'^[./a-zA-Z0-9$_-]+\\.py$': '<rootDir>/__mocks__/fileMock.js',
'\\.(css)$': '<rootDir>/__mocks__/cssMock.js',
},

View File

@@ -1,54 +1,54 @@
{
"name": "pyscript",
"version": "0.0.1",
"scripts": {
"build-min": "NODE_ENV=production rollup -c",
"build": "rollup -c",
"dev": "rollup -c -w",
"tsc": "tsc --noEmit",
"format:check": "prettier --check './src/**/*.{js,html,ts}'",
"format": "prettier --write './src/**/*.{js,html,ts}'",
"lint": "eslint './src/**/*.{js,html,ts}'",
"lint:fix": "eslint --fix './src/**/*.{js,html,ts}'",
"xprelint": "npm run format",
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage",
"test:watch": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --watch"
},
"devDependencies": {
"@jest/globals": "29.1.2",
"@rollup/plugin-commonjs": "22.0.2",
"@rollup/plugin-legacy": "2.2.0",
"@rollup/plugin-node-resolve": "14.1.0",
"@rollup/plugin-typescript": "8.5.0",
"@types/codemirror": "^5.60.5",
"@types/jest": "29.1.2",
"@types/node": "18.8.3",
"@typescript-eslint/eslint-plugin": "5.39.0",
"@typescript-eslint/parser": "5.39.0",
"cross-env": "7.0.3",
"eslint": "8.25.0",
"jest": "29.1.2",
"jest-environment-jsdom": "29.1.2",
"prettier": "2.7.1",
"pyodide": "0.22.1",
"rollup": "2.79.1",
"rollup-plugin-copy": "3.4.0",
"rollup-plugin-css-only": "3.1.0",
"rollup-plugin-livereload": "2.0.5",
"rollup-plugin-serve": "2.0.1",
"rollup-plugin-string": "3.0.0",
"rollup-plugin-terser": "7.0.2",
"ts-jest": "29.0.3",
"tslib": "2.4.0",
"typescript": "4.8.4"
},
"dependencies": {
"@codemirror/commands": "6.1.1",
"@codemirror/lang-python": "6.0.2",
"@codemirror/language": "6.2.1",
"@codemirror/state": "6.1.2",
"@codemirror/theme-one-dark": "6.1.0",
"@codemirror/view": "6.3.0",
"codemirror": "6.0.1"
}
"name": "pyscript",
"version": "0.0.1",
"scripts": {
"build-min": "NODE_ENV=production rollup -c",
"build": "rollup -c",
"dev": "rollup -c -w",
"tsc": "tsc --noEmit",
"format:check": "prettier --check './src/**/*.{js,html,ts}'",
"format": "prettier --write './src/**/*.{js,html,ts}'",
"lint": "eslint './src/**/*.{js,html,ts}'",
"lint:fix": "eslint --fix './src/**/*.{js,html,ts}'",
"xprelint": "npm run format",
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage",
"test:watch": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --watch"
},
"devDependencies": {
"@jest/globals": "29.1.2",
"@rollup/plugin-commonjs": "22.0.2",
"@rollup/plugin-legacy": "2.2.0",
"@rollup/plugin-node-resolve": "14.1.0",
"@rollup/plugin-typescript": "8.5.0",
"@types/codemirror": "^5.60.5",
"@types/jest": "29.1.2",
"@types/node": "18.8.3",
"@typescript-eslint/eslint-plugin": "5.39.0",
"@typescript-eslint/parser": "5.39.0",
"cross-env": "7.0.3",
"eslint": "8.25.0",
"jest": "29.1.2",
"jest-environment-jsdom": "29.1.2",
"prettier": "2.7.1",
"pyodide": "0.22.1",
"rollup": "2.79.1",
"rollup-plugin-copy": "3.4.0",
"rollup-plugin-css-only": "3.1.0",
"rollup-plugin-livereload": "2.0.5",
"rollup-plugin-serve": "2.0.1",
"rollup-plugin-string": "3.0.0",
"rollup-plugin-terser": "7.0.2",
"ts-jest": "29.0.3",
"tslib": "2.4.0",
"typescript": "4.8.4"
},
"dependencies": {
"@codemirror/commands": "6.1.1",
"@codemirror/lang-python": "6.0.2",
"@codemirror/language": "6.2.1",
"@codemirror/state": "6.1.2",
"@codemirror/theme-one-dark": "6.1.0",
"@codemirror/view": "6.3.0",
"codemirror": "6.0.1"
}
}

View File

@@ -1,34 +1,34 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://unpkg.com/mvp.css@1.12/mvp.css" />
<link rel="stylesheet" href="pyscript.css" />
<script defer src="pyscript.min.js"></script>
<title>PyScript</title>
</head>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/mvp.css@1.12/mvp.css">
<link rel="stylesheet" href="pyscript.css">
<script defer src="pyscript.min.js"></script>
<title>PyScript</title>
</head>
<body>
<main>
<h1>&lt;py-script&gt;</h1>
<ul>
<li><a href="pyscript.js">pyscript.js</a></li>
<li><a href="pyscript.min.js">pyscript.min.js</a></li>
<li><a href="pyscript.css">pyscript.css</a></li>
<li><a href="pyscript.min.js.map">pyscript.min.js.map</a></li>
<li><a href="pyscript.js.map">pyscript.js.map</a></li>
</ul>
<div id="out"></div>
<py-script std-out="out">
import sys
print(sys.version)
</py-script>
<body>
<main>
<h1>&lt;py-script&gt;</h1>
<ul>
<li><a href="pyscript.js">pyscript.js</a></li>
<li><a href="pyscript.min.js">pyscript.min.js</a></li>
<li><a href="pyscript.css">pyscript.css</a></li>
<li><a href="pyscript.min.js.map">pyscript.min.js.map</a></li>
<li><a href="pyscript.js.map">pyscript.js.map</a></li>
</ul>
<div id="out"></div>
<py-script std-out="out">
import sys
print(sys.version)
</py-script>
<h2>Example</h2>
<pre style="padding:1em;border:1px solid #000000;">&lt;!DOCTYPE html&gt;
<h2>Example</h2>
<pre style="padding: 1em; border: 1px solid #000000">
&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;utf-8&quot; /&gt;
@@ -47,8 +47,8 @@ now = datetime.now()
now.strftime(&quot;%m/%d/%Y, %H:%M:%S&quot;)
&lt;/py-script&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
</main>
</body>
&lt;/html&gt;</pre
>
</main>
</body>
</html>

View File

@@ -1,69 +1,70 @@
import commonjs from "@rollup/plugin-commonjs";
import resolve from "@rollup/plugin-node-resolve";
import { terser } from "rollup-plugin-terser";
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import { terser } from 'rollup-plugin-terser';
import legacy from '@rollup/plugin-legacy';
import typescript from "@rollup/plugin-typescript";
import css from "rollup-plugin-css-only";
import serve from "rollup-plugin-serve";
import { string } from "rollup-plugin-string";
import copy from 'rollup-plugin-copy'
import typescript from '@rollup/plugin-typescript';
import css from 'rollup-plugin-css-only';
import serve from 'rollup-plugin-serve';
import { string } from 'rollup-plugin-string';
import copy from 'rollup-plugin-copy';
const production = !process.env.ROLLUP_WATCH || (process.env.NODE_ENV === "production");
const production = !process.env.ROLLUP_WATCH || process.env.NODE_ENV === 'production';
const copy_targets = {
targets: [
{ src: 'public/index.html', dest: 'build' },
{ src: 'src/plugins/*', dest: 'build/plugins' }
]
}
targets: [
{ src: 'public/index.html', dest: 'build' },
{ src: 'src/plugins/*', dest: 'build/plugins' },
],
};
if( !production ){
copy_targets.targets.push({ src: 'build/*', dest: 'examples/build' })
if (!production) {
copy_targets.targets.push({ src: 'build/*', dest: 'examples/build' });
}
export default {
input: "src/main.ts",
output:[
{
file: "build/pyscript.js",
format: "iife",
sourcemap: true,
inlineDynamicImports: true,
name: "pyscript",
input: 'src/main.ts',
output: [
{
file: 'build/pyscript.js',
format: 'iife',
sourcemap: true,
inlineDynamicImports: true,
name: 'pyscript',
},
{
file: 'build/pyscript.min.js',
format: 'iife',
sourcemap: true,
inlineDynamicImports: true,
name: 'pyscript',
plugins: [terser()],
},
],
plugins: [
css({ output: 'pyscript.css' }),
// Bundle all the Python files into the output file
string({
include: './src/**/*.py',
}),
legacy({ 'src/toml.js': 'toml' }),
resolve({
browser: true,
}),
commonjs(),
typescript({
sourceMap: !production,
inlineSources: !production,
}),
// This will make sure that examples will always get the latest build folder
copy(copy_targets),
// production && terser(),
!production &&
serve({
port: 8080,
contentBase: 'examples',
}),
],
watch: {
clearScreen: false,
},
{
file: "build/pyscript.min.js",
format: "iife",
sourcemap: true,
inlineDynamicImports: true,
name: "pyscript",
plugins: [terser()],
},
],
plugins: [
css({ output: "pyscript.css" }),
// Bundle all the Python files into the output file
string({
include: "./src/**/*.py",
}),
legacy({ 'src/toml.js': 'toml' }),
resolve({
browser: true,
}),
commonjs(),
typescript({
sourceMap: !production,
inlineSources: !production,
}),
// This will make sure that examples will always get the latest build folder
copy(copy_targets),
// production && terser(),
!production && serve({
port: 8080,
contentBase: 'examples'}
)
],
watch: {
clearScreen: false,
},
};

View File

@@ -34,11 +34,16 @@ export function make_PyScript(interpreter: InterpreterClient, app: PyScriptApp)
const pySrc = await this.getPySrc();
this.innerHTML = '';
app.plugins.beforePyScriptExec({interpreter: interpreter, src: pySrc, pyScriptTag: this});
app.plugins.beforePyScriptExec({ interpreter: interpreter, src: pySrc, pyScriptTag: this });
const result = (await pyExec(interpreter, pySrc, this)).result;
app.plugins.afterPyScriptExec({interpreter: interpreter, src: pySrc, pyScriptTag: this, result: result});
app.plugins.afterPyScriptExec({
interpreter: interpreter,
src: pySrc,
pyScriptTag: this,
result: result,
});
} finally {
releaseLock()
releaseLock();
}
}
@@ -199,8 +204,7 @@ function createElementsWithEventListeners(interpreter: InterpreterClient, pyAttr
void (async () => {
try {
await interpreter.run(handlerCode);
}
catch (err) {
} catch (err) {
displayPyException(err, el.parentElement);
}
})();

View File

@@ -12,51 +12,49 @@ The convention is:
*/
export enum ErrorCode {
GENERIC = "PY0000", // Use this only for development then change to a more specific error code
FETCH_ERROR = "PY0001",
FETCH_NAME_ERROR = "PY0002",
// Currently these are created depending on error code received from fetching
FETCH_UNAUTHORIZED_ERROR = "PY0401",
FETCH_FORBIDDEN_ERROR = "PY0403",
FETCH_NOT_FOUND_ERROR = "PY0404",
FETCH_SERVER_ERROR = "PY0500",
FETCH_UNAVAILABLE_ERROR = "PY0503",
BAD_CONFIG = "PY1000",
MICROPIP_INSTALL_ERROR = "PY1001",
BAD_PLUGIN_FILE_EXTENSION = "PY2000",
NO_DEFAULT_EXPORT = "PY2001",
TOP_LEVEL_AWAIT = "PY9000"
GENERIC = 'PY0000', // Use this only for development then change to a more specific error code
FETCH_ERROR = 'PY0001',
FETCH_NAME_ERROR = 'PY0002',
// Currently these are created depending on error code received from fetching
FETCH_UNAUTHORIZED_ERROR = 'PY0401',
FETCH_FORBIDDEN_ERROR = 'PY0403',
FETCH_NOT_FOUND_ERROR = 'PY0404',
FETCH_SERVER_ERROR = 'PY0500',
FETCH_UNAVAILABLE_ERROR = 'PY0503',
BAD_CONFIG = 'PY1000',
MICROPIP_INSTALL_ERROR = 'PY1001',
BAD_PLUGIN_FILE_EXTENSION = 'PY2000',
NO_DEFAULT_EXPORT = 'PY2001',
TOP_LEVEL_AWAIT = 'PY9000',
}
export class UserError extends Error {
messageType: MessageType;
errorCode: ErrorCode;
messageType: MessageType;
errorCode: ErrorCode;
constructor(errorCode: ErrorCode, message: string, t: MessageType = "text") {
super(message);
this.errorCode = errorCode;
this.name = "UserError";
this.messageType = t;
this.message = `(${errorCode}): ${message}`;
}
constructor(errorCode: ErrorCode, message: string, t: MessageType = 'text') {
super(message);
this.errorCode = errorCode;
this.name = 'UserError';
this.messageType = t;
this.message = `(${errorCode}): ${message}`;
}
}
export class FetchError extends UserError {
errorCode: ErrorCode;
constructor(errorCode: ErrorCode, message: string) {
super(errorCode, message)
this.name = "FetchError";
}
errorCode: ErrorCode;
constructor(errorCode: ErrorCode, message: string) {
super(errorCode, message);
this.name = 'FetchError';
}
}
export class InstallError extends UserError {
errorCode: ErrorCode;
constructor(errorCode: ErrorCode, message: string) {
super(errorCode, message)
this.name = "InstallError";
}
errorCode: ErrorCode;
constructor(errorCode: ErrorCode, message: string) {
super(errorCode, message);
this.name = 'InstallError';
}
}
export function _createAlertBanner(

View File

@@ -11,7 +11,6 @@ InterpreterClient class is responsible to request code execution
(among other things) from a `RemoteInterpreter`
*/
export class InterpreterClient extends Object {
_remote: RemoteInterpreter;
config: AppConfig;
/**
@@ -41,7 +40,7 @@ export class InterpreterClient extends Object {
* the remote interpreter.
* Python exceptions are turned into JS exceptions.
* */
async run(code: string): Promise<{result: any}> {
async run(code: string): Promise<{ result: any }> {
return await this._remote.run(code);
}

View File

@@ -42,7 +42,7 @@ 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);

View File

@@ -208,7 +208,7 @@ export class PyScriptApp {
logger.info('importing pyscript');
// Save and load pyscript.py from FS
interpreter._remote.interface.FS.mkdirTree("/home/pyodide/pyscript");
interpreter._remote.interface.FS.mkdirTree('/home/pyodide/pyscript');
interpreter._remote.interface.FS.writeFile('pyscript/__init__.py', pyscript);
//Refresh the module cache so Python consistently finds pyscript module
interpreter._remote.invalidate_module_path_cache();

View File

@@ -5,7 +5,7 @@ import { make_PyScript } from './components/pyscript';
import { InterpreterClient } from './interpreter_client';
const logger = getLogger('plugin');
type PyScriptTag = InstanceType<ReturnType<typeof make_PyScript>>;
type PyScriptTag = InstanceType<ReturnType<typeof make_PyScript>>;
export class Plugin {
/** Validate the configuration of the plugin and handle default values.
@@ -48,7 +48,7 @@ export class Plugin {
* @param options.src {string} The Python source code to be evaluated
* @param options.pyScriptTag The <py-script> HTML tag that originated the evaluation
*/
beforePyScriptExec(options: {interpreter: InterpreterClient, src: string, pyScriptTag: PyScriptTag}) {}
beforePyScriptExec(options: { interpreter: InterpreterClient; src: string; pyScriptTag: PyScriptTag }) {}
/** The Python in a <py-script> has just been evaluated, but control
* has not been ceded back to the JavaScript event loop yet
@@ -58,7 +58,12 @@ export class Plugin {
* @param options.pyScriptTag The <py-script> HTML tag that originated the evaluation
* @param options.result The returned result of evaluating the Python (if any)
*/
afterPyScriptExec(options: {interpreter: InterpreterClient, src: string, pyScriptTag: PyScriptTag, result: any}) {}
afterPyScriptExec(options: {
interpreter: InterpreterClient;
src: string;
pyScriptTag: PyScriptTag;
result: any;
}) {}
/** Startup complete. The interpreter is initialized and ready, user
* scripts have been executed: the main initialization logic ends here and
@@ -122,13 +127,13 @@ export class PluginManager {
for (const p of this._pythonPlugins) p.afterStartup?.(interpreter);
}
beforePyScriptExec(options: {interpreter: InterpreterClient, src: string, pyScriptTag: PyScriptTag}) {
beforePyScriptExec(options: { interpreter: InterpreterClient; src: string; pyScriptTag: PyScriptTag }) {
for (const p of this._plugins) p.beforePyScriptExec?.(options);
for (const p of this._pythonPlugins) p.beforePyScriptExec?.callKwargs(options);
}
afterPyScriptExec(options: {interpreter: InterpreterClient, src: string, pyScriptTag: PyScriptTag, result: any}) {
afterPyScriptExec(options: { interpreter: InterpreterClient; src: string; pyScriptTag: PyScriptTag; result: any }) {
for (const p of this._plugins) p.afterPyScriptExec?.(options);
for (const p of this._pythonPlugins) p.afterPyScriptExec?.callKwargs(options);

View File

@@ -1,5 +1,5 @@
import { joinPaths } from '../utils';
import { FetchConfig } from "../pyconfig";
import { FetchConfig } from '../pyconfig';
import { UserError, ErrorCode } from '../exceptions';
export function calculatePaths(fetch_cfg: FetchConfig[]) {
@@ -10,14 +10,9 @@ export function calculatePaths(fetch_cfg: FetchConfig[]) {
const to_folder = each_fetch_cfg.to_folder || '.';
const to_file = each_fetch_cfg.to_file;
const files = each_fetch_cfg.files;
if (files !== undefined)
{
if (to_file !== undefined)
{
throw new UserError(
ErrorCode.BAD_CONFIG,
`Cannot use 'to_file' and 'files' parameters together!`
);
if (files !== undefined) {
if (to_file !== undefined) {
throw new UserError(ErrorCode.BAD_CONFIG, `Cannot use 'to_file' and 'files' parameters together!`);
}
for (const each_f of files) {
const each_fetch_path = joinPaths([from, each_f]);
@@ -31,10 +26,9 @@ export function calculatePaths(fetch_cfg: FetchConfig[]) {
if (filename === '') {
throw new UserError(
ErrorCode.BAD_CONFIG,
`Couldn't determine the filename from the path ${from}, please supply 'to_file' parameter.`
`Couldn't determine the filename from the path ${from}, please supply 'to_file' parameter.`,
);
}
else {
} else {
paths.push(joinPaths([to_folder, filename]));
}
}

View File

@@ -1,9 +1,9 @@
import { Plugin } from "../plugin";
import { TargetedStdio, StdioMultiplexer } from "../stdio";
import { make_PyScript } from "../components/pyscript";
import { InterpreterClient } from "../interpreter_client";
import { Plugin } from '../plugin';
import { TargetedStdio, StdioMultiplexer } from '../stdio';
import { make_PyScript } from '../components/pyscript';
import { InterpreterClient } from '../interpreter_client';
type PyScriptTag = InstanceType<ReturnType<typeof make_PyScript>>;
type PyScriptTag = InstanceType<ReturnType<typeof make_PyScript>>;
/**
* The StdioDirector plugin captures the output to Python's sys.stdio and
@@ -17,8 +17,8 @@ export class StdioDirector extends Plugin {
_stdioMultiplexer: StdioMultiplexer;
constructor(stdio: StdioMultiplexer) {
super()
this._stdioMultiplexer = stdio
super();
this._stdioMultiplexer = stdio;
}
/** Prior to a <py-script> tag being evaluated, if that tag itself has
@@ -27,30 +27,35 @@ export class StdioDirector extends Plugin {
* with that ID for the duration of the evaluation.
*
*/
beforePyScriptExec(options: {interpreter: InterpreterClient, src: string, pyScriptTag: PyScriptTag}): void {
if (options.pyScriptTag.hasAttribute("output")){
const targeted_io = new TargetedStdio(options.pyScriptTag, "output", true, true)
options.pyScriptTag.stdout_manager = targeted_io
this._stdioMultiplexer.addListener(targeted_io)
beforePyScriptExec(options: { interpreter: InterpreterClient; src: string; pyScriptTag: PyScriptTag }): void {
if (options.pyScriptTag.hasAttribute('output')) {
const targeted_io = new TargetedStdio(options.pyScriptTag, 'output', true, true);
options.pyScriptTag.stdout_manager = targeted_io;
this._stdioMultiplexer.addListener(targeted_io);
}
if (options.pyScriptTag.hasAttribute("stderr")){
const targeted_io = new TargetedStdio(options.pyScriptTag, "stderr", false, true)
options.pyScriptTag.stderr_manager = targeted_io
this._stdioMultiplexer.addListener(targeted_io)
if (options.pyScriptTag.hasAttribute('stderr')) {
const targeted_io = new TargetedStdio(options.pyScriptTag, 'stderr', false, true);
options.pyScriptTag.stderr_manager = targeted_io;
this._stdioMultiplexer.addListener(targeted_io);
}
}
/** After a <py-script> tag is evaluated, if that tag has a 'stdout_manager'
* (presumably TargetedStdio, or some other future IO handler), it is removed.
*/
afterPyScriptExec(options: {interpreter: InterpreterClient, src: string, pyScriptTag: PyScriptTag, result: any}): void {
if (options.pyScriptTag.stdout_manager != null){
this._stdioMultiplexer.removeListener(options.pyScriptTag.stdout_manager)
options.pyScriptTag.stdout_manager = null
afterPyScriptExec(options: {
interpreter: InterpreterClient;
src: string;
pyScriptTag: PyScriptTag;
result: any;
}): void {
if (options.pyScriptTag.stdout_manager != null) {
this._stdioMultiplexer.removeListener(options.pyScriptTag.stdout_manager);
options.pyScriptTag.stdout_manager = null;
}
if (options.pyScriptTag.stderr_manager != null){
this._stdioMultiplexer.removeListener(options.pyScriptTag.stderr_manager)
options.pyScriptTag.stderr_manager = null
if (options.pyScriptTag.stderr_manager != null) {
this._stdioMultiplexer.removeListener(options.pyScriptTag.stderr_manager);
options.pyScriptTag.stderr_manager = null;
}
}
}

View File

@@ -22,14 +22,14 @@ export async function pyExec(interpreter: InterpreterClient, pysrc: string, outE
'\nSee https://docs.pyscript.net/latest/guides/asyncio.html for more information.',
);
}
return (await interpreter.run(pysrc));
return await interpreter.run(pysrc);
} 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);
return {result: undefined};
return { result: undefined };
}
} finally {
pyscript_py.set_current_display_target(undefined);

View File

@@ -37,9 +37,7 @@ export class RemoteInterpreter extends Object {
// TODO: Remove this once `runtimes` is removed!
interpreter: InterpreterInterface;
constructor(
src = 'https://cdn.jsdelivr.net/pyodide/v0.22.1/full/pyodide.js'
) {
constructor(src = 'https://cdn.jsdelivr.net/pyodide/v0.22.1/full/pyodide.js') {
super();
this.src = src;
}
@@ -87,11 +85,11 @@ export class RemoteInterpreter extends Object {
await this.loadPackage('micropip');
}
logger.info('pyodide loaded and initialized');
await this.run('print("Python initialization complete")')
await this.run('print("Python initialization complete")');
}
/* eslint-disable */
async run(code: string): Promise<{result: any}> {
async run(code: string): Promise<{ result: any }> {
/**
* eslint wants `await` keyword to be used i.e.
* { result: await this.interface.runPython(code) }
@@ -134,10 +132,12 @@ export class RemoteInterpreter extends Object {
// for which the signature of `loadPackage` accepts the above params as args i.e.
// the call uses `logger.info.bind(logger), logger.info.bind(logger)`.
const pyodide_version = (await this.run("import sys; sys.modules['pyodide'].__version__")).result.toString();
if (pyodide_version.startsWith("0.22")) {
await this.interface.loadPackage(names, { messageCallback: logger.info.bind(logger), errorCallback: logger.info.bind(logger) });
}
else {
if (pyodide_version.startsWith('0.22')) {
await this.interface.loadPackage(names, {
messageCallback: logger.info.bind(logger),
errorCallback: logger.info.bind(logger),
});
} else {
await this.interface.loadPackage(names, logger.info.bind(logger), logger.info.bind(logger));
}
}

View File

@@ -1,4 +1,4 @@
import { createSingularWarning, escape } from "./utils";
import { createSingularWarning, escape } from './utils';
export interface Stdio {
stdout_writeline: (msg: string) => void;
@@ -42,8 +42,7 @@ export class CaptureStdio implements Stdio {
* specified by ID. Used with "output" keyword.
*
*/
export class TargetedStdio implements Stdio{
export class TargetedStdio implements Stdio {
source_element: HTMLElement;
source_attribute: string;
capture_stdout: boolean;
@@ -66,31 +65,32 @@ export class TargetedStdio implements Stdio{
*
* @param msg The output to be written
*/
writeline_by_attribute(msg:string){
const target_id = this.source_element.getAttribute(this.source_attribute)
const target = document.getElementById(target_id)
if (target === null) { // No matching ID
createSingularWarning(`${this.source_attribute} = "${target_id}" does not match the id of any element on the page.`)
writeline_by_attribute(msg: string) {
const target_id = this.source_element.getAttribute(this.source_attribute);
const target = document.getElementById(target_id);
if (target === null) {
// No matching ID
createSingularWarning(
`${this.source_attribute} = "${target_id}" does not match the id of any element on the page.`,
);
} else {
msg = escape(msg).replace('\n', '<br>');
if (!msg.endsWith('<br/>') && !msg.endsWith('<br>')) {
msg = msg + '<br>';
}
else {
msg = escape(msg).replace("\n", "<br>")
if (!msg.endsWith("<br/>") && !msg.endsWith("<br>")){
msg = msg + "<br>"
}
target.innerHTML += msg
}
}
stdout_writeline (msg: string) {
if (this.capture_stdout){
this.writeline_by_attribute(msg)
target.innerHTML += msg;
}
}
stderr_writeline (msg: string) {
if (this.capture_stderr){
this.writeline_by_attribute(msg)
stdout_writeline(msg: string) {
if (this.capture_stdout) {
this.writeline_by_attribute(msg);
}
}
stderr_writeline(msg: string) {
if (this.capture_stderr) {
this.writeline_by_attribute(msg);
}
}
}
@@ -109,9 +109,9 @@ export class StdioMultiplexer implements Stdio {
}
removeListener(obj: Stdio) {
const index = this._listeners.indexOf(obj)
if (index > -1){
this._listeners.splice(index, 1)
const index = this._listeners.indexOf(obj);
if (index > -1) {
this._listeners.splice(index, 1);
}
}

View File

@@ -1,34 +1,35 @@
/* py-config - not a component */
py-config {
display: none
display: none;
}
/* py-{el} - components not defined */
py-script:not(:defined) {
display: none
display: none;
}
py-repl:not(:defined) {
display: none
display: none;
}
py-title:not(:defined) {
display: none
display: none;
}
py-inputbox:not(:defined) {
display: none
display: none;
}
py-button:not(:defined) {
display: none
display: none;
}
py-box:not(:defined) {
display: none
display: none;
}
html {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
line-height: 1.5;
}
@@ -110,7 +111,7 @@ html {
/* Pop-up second layer end */
.alert-banner {
position: relative;
padding: .5rem 1.5rem .5rem .5rem;
padding: 0.5rem 1.5rem 0.5rem 0.5rem;
margin: 5px 0;
}
@@ -118,8 +119,8 @@ html {
margin: 0;
}
.py-error{
background-color: #FFE9E8;
.py-error {
background-color: #ffe9e8;
border: solid;
border-color: #f0625f;
color: #9d041c;
@@ -132,24 +133,24 @@ html {
color: #794700;
}
.alert-banner.py-error>#alert-close-button {
.alert-banner.py-error > #alert-close-button {
color: #9d041c;
}
.alert-banner.py-warning>#alert-close-button {
color: #794700
.alert-banner.py-warning > #alert-close-button {
color: #794700;
}
#alert-close-button {
position: absolute;
right: .5rem;
top: .5rem;
cursor: pointer;
background: transparent;
border: none;
position: absolute;
right: 0.5rem;
top: 0.5rem;
cursor: pointer;
background: transparent;
border: none;
}
.py-box{
.py-box {
display: flex;
flex-direction: row;
justify-content: flex-start;
@@ -168,10 +169,7 @@ border: none;
border-color: rgba(209, 213, 219, var(--tw-border-opacity));
border-width: 1px;
position: relative;
--tw-ring-inset: var(--tw-empty,
/*!*/
/*!*/
);
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgba(59, 130, 246, 0.5);
@@ -183,7 +181,7 @@ border: none;
box-sizing: border-box;
border-width: 1px;
border-style: solid;
border-color: rgb(209, 213, 219)
border-color: rgb(209, 213, 219);
}
.editor-box:hover button {

View File

@@ -1,4 +1,4 @@
import { _createAlertBanner } from "./exceptions"
import { _createAlertBanner } from './exceptions';
export function addClasses(element: HTMLElement, classes: string[]) {
for (const entry of classes) {
@@ -127,11 +127,11 @@ export function createLock() {
* @private
*/
async function acquireLock() {
const old_lock = _lock;
let releaseLock: () => void;
_lock = new Promise((resolve) => (releaseLock = resolve));
await old_lock;
return releaseLock;
const old_lock = _lock;
let releaseLock: () => void;
_lock = new Promise(resolve => (releaseLock = resolve));
await old_lock;
return releaseLock;
}
return acquireLock;
}
}

View File

@@ -1,127 +1,123 @@
import { expect, it, jest, describe, afterEach } from "@jest/globals"
import { _createAlertBanner, UserError, FetchError, ErrorCode } from "../../src/exceptions"
import { expect, it, jest, describe, afterEach } from '@jest/globals';
import { _createAlertBanner, UserError, FetchError, ErrorCode } from '../../src/exceptions';
describe("Test _createAlertBanner", () => {
afterEach(() => {
// Ensure we always have a clean body
document.body.innerHTML = `<div>Hello World</div>`;
})
describe('Test _createAlertBanner', () => {
afterEach(() => {
// Ensure we always have a clean body
document.body.innerHTML = `<div>Hello World</div>`;
});
it("error level shouldn't contain close button", async () => {
_createAlertBanner('Something went wrong!', 'error');
it("error level shouldn't contain close button", async () => {
_createAlertBanner("Something went wrong!", "error");
const banner = document.getElementsByClassName('alert-banner');
const closeButton = document.getElementById('alert-close-button');
expect(banner.length).toBe(1);
expect(banner[0].innerHTML).toBe('Something went wrong!');
expect(closeButton).toBeNull();
});
const banner = document.getElementsByClassName("alert-banner");
const closeButton = document.getElementById("alert-close-button");
expect(banner.length).toBe(1);
expect(banner[0].innerHTML).toBe("Something went wrong!");
expect(closeButton).toBeNull();
})
it('warning level should contain close button', async () => {
_createAlertBanner('This is a warning', 'warning');
it("warning level should contain close button", async () => {
_createAlertBanner("This is a warning", "warning");
const banner = document.getElementsByClassName('alert-banner');
const closeButton = document.getElementById('alert-close-button');
expect(banner.length).toBe(1);
expect(banner[0].innerHTML).toContain('This is a warning');
expect(closeButton).not.toBeNull();
});
const banner = document.getElementsByClassName("alert-banner");
const closeButton = document.getElementById("alert-close-button");
expect(banner.length).toBe(1);
expect(banner[0].innerHTML).toContain("This is a warning");
expect(closeButton).not.toBeNull();
})
it('error level banner should log to console', async () => {
const logSpy = jest.spyOn(console, 'error');
it("error level banner should log to console", async () => {
const logSpy = jest.spyOn(console, "error");
_createAlertBanner('Something went wrong!');
_createAlertBanner("Something went wrong!");
expect(logSpy).toHaveBeenCalledWith('Something went wrong!');
});
expect(logSpy).toHaveBeenCalledWith("Something went wrong!");
it('warning level banner should log to console', async () => {
const logSpy = jest.spyOn(console, 'warn');
})
_createAlertBanner('This warning', 'warning');
it("warning level banner should log to console", async () => {
const logSpy = jest.spyOn(console, "warn");
expect(logSpy).toHaveBeenCalledWith('This warning');
});
_createAlertBanner("This warning", "warning");
it('close button should remove element from page', async () => {
let banner = document.getElementsByClassName('alert-banner');
expect(banner.length).toBe(0);
expect(logSpy).toHaveBeenCalledWith("This warning");
})
_createAlertBanner('Warning!', 'warning');
it("close button should remove element from page", async () => {
let banner = document.getElementsByClassName("alert-banner");
expect(banner.length).toBe(0);
// Just a sanity check
banner = document.getElementsByClassName('alert-banner');
expect(banner.length).toBe(1);
_createAlertBanner("Warning!", "warning");
const closeButton = document.getElementById('alert-close-button');
if (closeButton) {
closeButton.click();
// Confirm that clicking the close button, removes the element
banner = document.getElementsByClassName('alert-banner');
expect(banner.length).toBe(0);
} else {
fail('Unable to find close button on the page, but should exist');
}
});
// Just a sanity check
banner = document.getElementsByClassName("alert-banner");
expect(banner.length).toBe(1);
it("toggling logging off on error alert shouldn't log to console", async () => {
const errorLogSpy = jest.spyOn(console, 'error');
const closeButton = document.getElementById("alert-close-button");
if(closeButton) {
closeButton.click();
// Confirm that clicking the close button, removes the element
banner = document.getElementsByClassName("alert-banner");
expect(banner.length).toBe(0);
} else {
fail("Unable to find close button on the page, but should exist");
}
_createAlertBanner('Test error', 'error', 'text', false);
expect(errorLogSpy).not.toHaveBeenCalledWith('Test error');
});
})
it("toggling logging off on warning alert shouldn't log to console", async () => {
const warnLogSpy = jest.spyOn(console, 'warn');
_createAlertBanner('Test warning', 'warning', 'text', false);
expect(warnLogSpy).not.toHaveBeenCalledWith('Test warning');
});
it("toggling logging off on error alert shouldn't log to console", async () => {
const errorLogSpy = jest.spyOn(console, "error");
it('_createAlertbanner messageType text writes message to content', async () => {
let banner = document.getElementsByClassName('alert-banner');
expect(banner.length).toBe(0);
_createAlertBanner("Test error", "error", "text", false);
expect(errorLogSpy).not.toHaveBeenCalledWith("Test error");
})
const message = '<p>Test message</p>';
_createAlertBanner(message, 'error', 'text');
banner = document.getElementsByClassName('alert-banner');
it("toggling logging off on warning alert shouldn't log to console", async () => {
const warnLogSpy = jest.spyOn(console, "warn");
_createAlertBanner("Test warning", "warning", "text", false);
expect(warnLogSpy).not.toHaveBeenCalledWith("Test warning");
})
expect(banner.length).toBe(1);
expect(banner[0].innerHTML).toBe('&lt;p&gt;Test message&lt;/p&gt;');
expect(banner[0].textContent).toBe(message);
});
it('_createAlertbanner messageType html writes message to innerHTML', async () => {
let banner = document.getElementsByClassName('alert-banner');
expect(banner.length).toBe(0);
it('_createAlertbanner messageType text writes message to content', async () => {
let banner = document.getElementsByClassName("alert-banner");
expect(banner.length).toBe(0);
const message = '<p>Test message</p>';
_createAlertBanner(message, 'error', 'html');
banner = document.getElementsByClassName('alert-banner');
const message = '<p>Test message</p>'
_createAlertBanner(message, 'error', 'text');
banner = document.getElementsByClassName("alert-banner");
expect(banner.length).toBe(1);
expect(banner[0].innerHTML).toBe(message);
expect(banner[0].textContent).toBe('Test message');
});
});
expect(banner.length).toBe(1);
expect(banner[0].innerHTML).toBe("&lt;p&gt;Test message&lt;/p&gt;");
expect(banner[0].textContent).toBe(message);
})
describe('Test Exceptions', () => {
it('UserError contains errorCode and shows in message', async () => {
const errorCode = ErrorCode.BAD_CONFIG;
const message = 'Test error';
const userError = new UserError(ErrorCode.BAD_CONFIG, message);
expect(userError.errorCode).toBe(errorCode);
expect(userError.message).toBe(`(${errorCode}): ${message}`);
});
it('_createAlertbanner messageType html writes message to innerHTML', async () => {
let banner = document.getElementsByClassName("alert-banner");
expect(banner.length).toBe(0);
const message = '<p>Test message</p>';
_createAlertBanner(message, 'error', 'html');
banner = document.getElementsByClassName("alert-banner");
expect(banner.length).toBe(1);
expect(banner[0].innerHTML).toBe(message);
expect(banner[0].textContent).toBe("Test message");
})
})
describe("Test Exceptions", () => {
it('UserError contains errorCode and shows in message', async() => {
const errorCode = ErrorCode.BAD_CONFIG;
const message = 'Test error';
const userError = new UserError(ErrorCode.BAD_CONFIG, message);
expect(userError.errorCode).toBe(errorCode);
expect(userError.message).toBe(`(${errorCode}): ${message}`);
})
it('FetchError contains errorCode and shows in message', async() => {
const errorCode = ErrorCode.FETCH_NOT_FOUND_ERROR;
const message = 'Test error';
const fetchError = new FetchError(errorCode, message);
expect(fetchError.errorCode).toBe(errorCode);
expect(fetchError.message).toBe(`(${errorCode}): ${message}`);
})
})
it('FetchError contains errorCode and shows in message', async () => {
const errorCode = ErrorCode.FETCH_NOT_FOUND_ERROR;
const message = 'Test error';
const fetchError = new FetchError(errorCode, message);
expect(fetchError.errorCode).toBe(errorCode);
expect(fetchError.message).toBe(`(${errorCode}): ${message}`);
});
});

View File

@@ -1,111 +1,107 @@
import { describe, it, expect, jest } from '@jest/globals'
import { FetchError, ErrorCode } from "../../src/exceptions"
import { robustFetch } from "../../src/fetch"
import { describe, it, expect, jest } from '@jest/globals';
import { FetchError, ErrorCode } from '../../src/exceptions';
import { robustFetch } from '../../src/fetch';
import { Response } from 'node-fetch';
describe("robustFetch", () => {
describe('robustFetch', () => {
it('should return a response object', async () => {
global.fetch = jest.fn(() => Promise.resolve(new Response((status = '200'), 'Hello World')));
it("should return a response object", async () => {
global.fetch = jest.fn(() => (Promise.resolve(new Response(status="200", "Hello World"))));
const response = await robustFetch('https://pyscript.net');
expect(response).toBeInstanceOf(Response);
expect(response.status).toBe(200);
});
const response = await robustFetch("https://pyscript.net");
expect(response).toBeInstanceOf(Response);
expect(response.status).toBe(200);
})
it('receiving a 404 when fetching should throw FetchError with the right errorCode', async () => {
global.fetch = jest.fn(() => Promise.resolve(new Response('Not Found', { status: 404 })));
it('receiving a 404 when fetching should throw FetchError with the right errorCode', async () => {
global.fetch = jest.fn(() => (Promise.resolve(new Response("Not Found", {status: 404}))));
const url = 'https://pyscript.net/non-existent-page';
const expectedError = new FetchError(
ErrorCode.FETCH_NOT_FOUND_ERROR,
`Fetching from URL ${url} failed with error 404 (Not Found). ` + `Are your filename and path correct?`,
);
const url = "https://pyscript.net/non-existent-page"
const expectedError = new FetchError(
ErrorCode.FETCH_NOT_FOUND_ERROR,
`Fetching from URL ${url} failed with error 404 (Not Found). ` +
`Are your filename and path correct?`
)
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
});
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
})
it('receiving a 401 when fetching should throw FetchError with the right errorCode', async () => {
global.fetch = jest.fn(() => Promise.resolve(new Response('', { status: 401 })));
it('receiving a 401 when fetching should throw FetchError with the right errorCode', async () => {
global.fetch = jest.fn(() => (Promise.resolve(new Response("", {status: 401}))));
const url = 'https://pyscript.net/protected-page';
const expectedError = new FetchError(
ErrorCode.FETCH_UNAUTHORIZED_ERROR,
`Fetching from URL ${url} failed with error 401 (Unauthorized). ` + `Are your filename and path correct?`,
);
const url = "https://pyscript.net/protected-page"
const expectedError = new FetchError(
ErrorCode.FETCH_UNAUTHORIZED_ERROR,
`Fetching from URL ${url} failed with error 401 (Unauthorized). ` +
`Are your filename and path correct?`
)
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
});
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
})
it('receiving a 403 when fetching should throw FetchError with the right errorCode', async () => {
global.fetch = jest.fn(() => Promise.resolve(new Response('', { status: 403 })));
it('receiving a 403 when fetching should throw FetchError with the right errorCode', async () => {
global.fetch = jest.fn(() => (Promise.resolve(new Response("", {status: 403}))));
const url = 'https://pyscript.net/secret-page';
const expectedError = new FetchError(
ErrorCode.FETCH_FORBIDDEN_ERROR,
`Fetching from URL ${url} failed with error 403 (Forbidden). ` + `Are your filename and path correct?`,
);
const url = "https://pyscript.net/secret-page"
const expectedError = new FetchError(
ErrorCode.FETCH_FORBIDDEN_ERROR,
`Fetching from URL ${url} failed with error 403 (Forbidden). ` +
`Are your filename and path correct?`
)
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
});
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
})
it('receiving a 500 when fetching should throw FetchError with the right errorCode', async () => {
global.fetch = jest.fn(() => Promise.resolve(new Response('Not Found', { status: 500 })));
it('receiving a 500 when fetching should throw FetchError with the right errorCode', async () => {
global.fetch = jest.fn(() => (Promise.resolve(new Response("Not Found", {status: 500}))));
const url = 'https://pyscript.net/protected-page';
const expectedError = new FetchError(
ErrorCode.FETCH_SERVER_ERROR,
`Fetching from URL ${url} failed with error 500 (Internal Server Error). ` +
`Are your filename and path correct?`,
);
const url = "https://pyscript.net/protected-page"
const expectedError = new FetchError(
ErrorCode.FETCH_SERVER_ERROR,
`Fetching from URL ${url} failed with error 500 (Internal Server Error). ` +
`Are your filename and path correct?`
)
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
});
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
})
it('receiving a 503 when fetching should throw FetchError with the right errorCode', async () => {
global.fetch = jest.fn(() => Promise.resolve(new Response('Not Found', { status: 503 })));
it('receiving a 503 when fetching should throw FetchError with the right errorCode', async () => {
global.fetch = jest.fn(() => (Promise.resolve(new Response("Not Found", {status: 503}))));
const url = 'https://pyscript.net/protected-page';
const expectedError = new FetchError(
ErrorCode.FETCH_UNAVAILABLE_ERROR,
`Fetching from URL ${url} failed with error 503 (Service Unavailable). ` +
`Are your filename and path correct?`,
);
const url = "https://pyscript.net/protected-page"
const expectedError = new FetchError(
ErrorCode.FETCH_UNAVAILABLE_ERROR,
`Fetching from URL ${url} failed with error 503 (Service Unavailable). ` +
`Are your filename and path correct?`
)
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
});
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
})
it('handle TypeError when using a bad url', async () => {
global.fetch = jest.fn(() => Promise.reject(new TypeError('Failed to fetch')));
it('handle TypeError when using a bad url', async () => {
global.fetch = jest.fn(() => (Promise.reject(new TypeError("Failed to fetch"))));
const url = 'https://pyscript.net/protected-page';
const expectedError = new FetchError(
ErrorCode.FETCH_ERROR,
`Fetching from URL ${url} failed with error 'Failed to fetch'. Are your filename and path correct?`,
);
const url = "https://pyscript.net/protected-page"
const expectedError = new FetchError(
ErrorCode.FETCH_ERROR,
`Fetching from URL ${url} failed with error 'Failed to fetch'. Are your filename and path correct?`
)
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
});
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
})
it('handle failed to fetch when using local file', async () => {
global.fetch = jest.fn(() => Promise.reject(new TypeError('Failed to fetch')));
it('handle failed to fetch when using local file', async () => {
global.fetch = jest.fn(() => (Promise.reject(new TypeError("Failed to fetch"))));
const url = './my-awesome-pyscript.py';
const url = "./my-awesome-pyscript.py"
const expectedError = new FetchError(
ErrorCode.FETCH_ERROR,
`PyScript: Access to local files
const expectedError = new FetchError(
ErrorCode.FETCH_ERROR,
`PyScript: Access to local files
(using "Paths:" in &lt;py-config&gt;)
is not available when directly opening a HTML file;
you must use a webserver to serve the additional files.
See <a style="text-decoration: underline;" href="https://github.com/pyscript/pyscript/issues/257#issuecomment-1119595062">this reference</a>
on starting a simple webserver with Python.
`
)
`,
);
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
})
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
});
});

View File

@@ -1,49 +1,55 @@
import { calculatePaths } from "../../src/plugins/fetch";
import { FetchConfig } from "../../src/pyconfig";
import { calculatePaths } from '../../src/plugins/fetch';
import { FetchConfig } from '../../src/pyconfig';
describe("CalculateFetchPaths", () => {
it("should calculate paths when only from is provided", () => {
const fetch_cfg: FetchConfig[] = [{from: "http://a.com/data.csv" }];
const [paths, fetchPaths] = calculatePaths(fetch_cfg);
expect(paths).toStrictEqual(["./data.csv"]);
expect(fetchPaths).toStrictEqual(["http://a.com/data.csv"]);
})
describe('CalculateFetchPaths', () => {
it('should calculate paths when only from is provided', () => {
const fetch_cfg: FetchConfig[] = [{ from: 'http://a.com/data.csv' }];
const [paths, fetchPaths] = calculatePaths(fetch_cfg);
expect(paths).toStrictEqual(['./data.csv']);
expect(fetchPaths).toStrictEqual(['http://a.com/data.csv']);
});
it("should calculate paths when only files is provided", () => {
const fetch_cfg: FetchConfig[] = [{files: ["foo/__init__.py", "foo/mod.py"] }];
const [paths, fetchPaths] = calculatePaths(fetch_cfg);
expect(paths).toStrictEqual(["./foo/__init__.py", "./foo/mod.py"]);
expect(fetchPaths).toStrictEqual(["foo/__init__.py", "foo/mod.py"]);
})
it('should calculate paths when only files is provided', () => {
const fetch_cfg: FetchConfig[] = [{ files: ['foo/__init__.py', 'foo/mod.py'] }];
const [paths, fetchPaths] = calculatePaths(fetch_cfg);
expect(paths).toStrictEqual(['./foo/__init__.py', './foo/mod.py']);
expect(fetchPaths).toStrictEqual(['foo/__init__.py', 'foo/mod.py']);
});
it("should calculate paths when files and to_folder is provided", () => {
const fetch_cfg: FetchConfig[] = [{files: ["foo/__init__.py", "foo/mod.py"], to_folder: "/my/lib/"}];
const [paths, fetchPaths] = calculatePaths(fetch_cfg);
expect(paths).toStrictEqual(["/my/lib/foo/__init__.py", "/my/lib/foo/mod.py"]);
expect(fetchPaths).toStrictEqual(["foo/__init__.py", "foo/mod.py"]);
})
it('should calculate paths when files and to_folder is provided', () => {
const fetch_cfg: FetchConfig[] = [{ files: ['foo/__init__.py', 'foo/mod.py'], to_folder: '/my/lib/' }];
const [paths, fetchPaths] = calculatePaths(fetch_cfg);
expect(paths).toStrictEqual(['/my/lib/foo/__init__.py', '/my/lib/foo/mod.py']);
expect(fetchPaths).toStrictEqual(['foo/__init__.py', 'foo/mod.py']);
});
it("should calculate paths when from and files and to_folder is provided", () => {
const fetch_cfg: FetchConfig[] = [{from: "http://a.com/download/", files: ["foo/__init__.py", "foo/mod.py"], to_folder: "/my/lib/"}];
const [paths, fetchPaths] = calculatePaths(fetch_cfg);
expect(paths).toStrictEqual(["/my/lib/foo/__init__.py", "/my/lib/foo/mod.py"]);
expect(fetchPaths).toStrictEqual(["http://a.com/download/foo/__init__.py", "http://a.com/download/foo/mod.py"]);
})
it('should calculate paths when from and files and to_folder is provided', () => {
const fetch_cfg: FetchConfig[] = [
{ from: 'http://a.com/download/', files: ['foo/__init__.py', 'foo/mod.py'], to_folder: '/my/lib/' },
];
const [paths, fetchPaths] = calculatePaths(fetch_cfg);
expect(paths).toStrictEqual(['/my/lib/foo/__init__.py', '/my/lib/foo/mod.py']);
expect(fetchPaths).toStrictEqual(['http://a.com/download/foo/__init__.py', 'http://a.com/download/foo/mod.py']);
});
it("should error out while calculating paths when filename cannot be determined from 'from'", () => {
const fetch_cfg: FetchConfig[] = [{from: "http://google.com/", to_folder: "/tmp"}];
expect(()=>calculatePaths(fetch_cfg)).toThrowError("Couldn't determine the filename from the path http://google.com/");
})
const fetch_cfg: FetchConfig[] = [{ from: 'http://google.com/', to_folder: '/tmp' }];
expect(() => calculatePaths(fetch_cfg)).toThrowError(
"Couldn't determine the filename from the path http://google.com/",
);
});
it("should calculate paths when to_file is explicitly supplied", () => {
const fetch_cfg: FetchConfig[] = [{from: "http://a.com/data.csv?version=1", to_file: "pkg/tmp/data.csv"}];
const [paths, fetchPaths] = calculatePaths(fetch_cfg);
expect(paths).toStrictEqual(["./pkg/tmp/data.csv"]);
expect(fetchPaths).toStrictEqual(["http://a.com/data.csv?version=1"]);
})
it('should calculate paths when to_file is explicitly supplied', () => {
const fetch_cfg: FetchConfig[] = [{ from: 'http://a.com/data.csv?version=1', to_file: 'pkg/tmp/data.csv' }];
const [paths, fetchPaths] = calculatePaths(fetch_cfg);
expect(paths).toStrictEqual(['./pkg/tmp/data.csv']);
expect(fetchPaths).toStrictEqual(['http://a.com/data.csv?version=1']);
});
it("should error out when both to_file and files parameters are provided", () => {
const fetch_cfg: FetchConfig[] = [{from: "http://a.com/data.csv?version=1", to_file: "pkg/tmp/data.csv", files: ["a.py", "b.py"]}];
expect(()=>calculatePaths(fetch_cfg)).toThrowError("Cannot use 'to_file' and 'files' parameters together!");
})
})
it('should error out when both to_file and files parameters are provided', () => {
const fetch_cfg: FetchConfig[] = [
{ from: 'http://a.com/data.csv?version=1', to_file: 'pkg/tmp/data.csv', files: ['a.py', 'b.py'] },
];
expect(() => calculatePaths(fetch_cfg)).toThrowError("Cannot use 'to_file' and 'files' parameters together!");
});
});

View File

@@ -15,20 +15,16 @@ describe('getLogger', () => {
console.info = jest.fn();
const logger = getLogger('prefix1');
logger.info('hello world')
expect(console.info).toHaveBeenCalledWith(
'[prefix1] hello world'
)
logger.info('hello world');
expect(console.info).toHaveBeenCalledWith('[prefix1] hello world');
});
it('logger.info handles multiple args', () => {
console.info = jest.fn();
const logger = getLogger('prefix2');
logger.info('hello', 'world', 1, 2, 3)
expect(console.info).toHaveBeenCalledWith(
'[prefix2] hello', 'world', 1, 2, 3
)
logger.info('hello', 'world', 1, 2, 3);
expect(console.info).toHaveBeenCalledWith('[prefix2] hello', 'world', 1, 2, 3);
});
it('logger.{debug,warn,error} also works', () => {
@@ -43,15 +39,8 @@ describe('getLogger', () => {
logger.error('this is an error');
expect(console.info).not.toHaveBeenCalled();
expect(console.debug).toHaveBeenCalledWith(
'[prefix3] this is a debug'
)
expect(console.warn).toHaveBeenCalledWith(
'[prefix3] this is a warning'
)
expect(console.error).toHaveBeenCalledWith(
'[prefix3] this is an error'
)
expect(console.debug).toHaveBeenCalledWith('[prefix3] this is a debug');
expect(console.warn).toHaveBeenCalledWith('[prefix3] this is a warning');
expect(console.error).toHaveBeenCalledWith('[prefix3] this is an error');
});
});

View File

@@ -1,9 +1,8 @@
import { describe, it, beforeEach, expect } from "@jest/globals"
import { UserError, ErrorCode } from "../../src/exceptions"
import { PyScriptApp } from "../../src/main"
describe("Test withUserErrorHandler", () => {
import { describe, it, beforeEach, expect } from '@jest/globals';
import { UserError, ErrorCode } from '../../src/exceptions';
import { PyScriptApp } from '../../src/main';
describe('Test withUserErrorHandler', () => {
class MyApp extends PyScriptApp {
myRealMain: any;
@@ -24,46 +23,46 @@ describe("Test withUserErrorHandler", () => {
it("userError doesn't stop execution", () => {
function myRealMain() {
throw new UserError(ErrorCode.GENERIC, "Computer says no");
throw new UserError(ErrorCode.GENERIC, 'Computer says no');
}
const app = new MyApp(myRealMain);
app.main();
const banners = document.getElementsByClassName("alert-banner");
const banners = document.getElementsByClassName('alert-banner');
expect(banners.length).toBe(1);
expect(banners[0].innerHTML).toBe("(PY0000): Computer says no");
expect(banners[0].innerHTML).toBe('(PY0000): Computer says no');
});
it("userError escapes by default", () => {
it('userError escapes by default', () => {
function myRealMain() {
throw new UserError(ErrorCode.GENERIC, "hello <br>");
throw new UserError(ErrorCode.GENERIC, 'hello <br>');
}
const app = new MyApp(myRealMain);
app.main();
const banners = document.getElementsByClassName("alert-banner");
const banners = document.getElementsByClassName('alert-banner');
expect(banners.length).toBe(1);
expect(banners[0].innerHTML).toBe("(PY0000): hello &lt;br&gt;");
expect(banners[0].innerHTML).toBe('(PY0000): hello &lt;br&gt;');
});
it("userError messageType=html don't escape", () => {
function myRealMain() {
throw new UserError(ErrorCode.GENERIC, "hello <br>", "html");
throw new UserError(ErrorCode.GENERIC, 'hello <br>', 'html');
}
const app = new MyApp(myRealMain);
app.main();
const banners = document.getElementsByClassName("alert-banner");
const banners = document.getElementsByClassName('alert-banner');
expect(banners.length).toBe(1);
expect(banners[0].innerHTML).toBe("(PY0000): hello <br>");
expect(banners[0].innerHTML).toBe('(PY0000): hello <br>');
});
it("any other exception should stop execution and raise", () => {
it('any other exception should stop execution and raise', () => {
function myRealMain() {
throw new Error("Explosions!");
throw new Error('Explosions!');
}
const app = new MyApp(myRealMain);
expect(() => app.main()).toThrow(new Error("Explosions!"))
expect(() => app.main()).toThrow(new Error('Explosions!'));
});
});

View File

@@ -11,7 +11,7 @@ describe('RemoteInterpreter', () => {
let interpreter: InterpreterClient;
let stdio: CaptureStdio = new CaptureStdio();
beforeAll(async () => {
const config: AppConfig = {interpreters: [{src: "../pyscriptjs/node_modules/pyodide/pyodide.js"}]};
const config: AppConfig = { interpreters: [{ src: '../pyscriptjs/node_modules/pyodide/pyodide.js' }] };
interpreter = new InterpreterClient(config, stdio);
/**

View File

@@ -4,31 +4,29 @@ import { type Stdio, CaptureStdio, StdioMultiplexer, TargetedStdio } from '../..
describe('CaptureStdio', () => {
it('captured streams are initialized to empty string', () => {
let stdio = new CaptureStdio();
expect(stdio.captured_stdout).toBe("");
expect(stdio.captured_stderr).toBe("");
expect(stdio.captured_stdout).toBe('');
expect(stdio.captured_stderr).toBe('');
});
it('stdout() and stderr() captures', () => {
let stdio = new CaptureStdio();
stdio.stdout_writeline("hello");
stdio.stdout_writeline("world");
stdio.stderr_writeline("this is an error");
expect(stdio.captured_stdout).toBe("hello\nworld\n");
expect(stdio.captured_stderr).toBe("this is an error\n");
stdio.stdout_writeline('hello');
stdio.stdout_writeline('world');
stdio.stderr_writeline('this is an error');
expect(stdio.captured_stdout).toBe('hello\nworld\n');
expect(stdio.captured_stderr).toBe('this is an error\n');
});
it('reset() works', () => {
let stdio = new CaptureStdio();
stdio.stdout_writeline("aaa");
stdio.stderr_writeline("bbb");
stdio.stdout_writeline('aaa');
stdio.stderr_writeline('bbb');
stdio.reset();
expect(stdio.captured_stdout).toBe("");
expect(stdio.captured_stderr).toBe("");
expect(stdio.captured_stdout).toBe('');
expect(stdio.captured_stderr).toBe('');
});
});
describe('StdioMultiplexer', () => {
let a: CaptureStdio;
let b: CaptureStdio;
@@ -44,10 +42,10 @@ describe('StdioMultiplexer', () => {
// no listeners, messages are ignored
multi.stdout_writeline('out 1');
multi.stderr_writeline('err 1');
expect(a.captured_stdout).toBe("");
expect(a.captured_stderr).toBe("");
expect(b.captured_stdout).toBe("");
expect(b.captured_stderr).toBe("");
expect(a.captured_stdout).toBe('');
expect(a.captured_stderr).toBe('');
expect(b.captured_stdout).toBe('');
expect(b.captured_stderr).toBe('');
});
it('redirects to multiple listeners', () => {
@@ -59,11 +57,11 @@ describe('StdioMultiplexer', () => {
multi.stdout_writeline('out 2');
multi.stderr_writeline('err 2');
expect(a.captured_stdout).toBe("out 1\nout 2\n");
expect(a.captured_stderr).toBe("err 1\nerr 2\n");
expect(a.captured_stdout).toBe('out 1\nout 2\n');
expect(a.captured_stderr).toBe('err 1\nerr 2\n');
expect(b.captured_stdout).toBe("out 2\n");
expect(b.captured_stderr).toBe("err 2\n");
expect(b.captured_stdout).toBe('out 2\n');
expect(b.captured_stderr).toBe('err 2\n');
});
});
@@ -75,32 +73,30 @@ describe('TargetedStdio', () => {
beforeEach(() => {
//DOM element to capture stdout and stderr
let target_div = document.getElementById("output-id");
let target_div = document.getElementById('output-id');
if (target_div=== null){
if (target_div === null) {
target_div = document.createElement('div');
target_div.id = "output-id";
target_div.id = 'output-id';
document.body.appendChild(target_div);
}
else {
target_div.innerHTML = "";
} else {
target_div.innerHTML = '';
}
//DOM element to capture stderr
let error_div = document.getElementById("error-id");
let error_div = document.getElementById('error-id');
if (error_div=== null){
if (error_div === null) {
error_div = document.createElement('div');
error_div.id = "error-id";
error_div.id = 'error-id';
document.body.appendChild(error_div);
}
else {
error_div.innerHTML = "";
} else {
error_div.innerHTML = '';
}
const tag = document.createElement('div');
tag.setAttribute("output", "output-id");
tag.setAttribute("stderr", "error-id");
tag.setAttribute('output', 'output-id');
tag.setAttribute('stderr', 'error-id');
capture = new CaptureStdio();
targeted = new TargetedStdio(tag, 'output', true, true);
@@ -112,29 +108,29 @@ describe('TargetedStdio', () => {
multi.addListener(error_targeted);
});
it('targeted id is set by constructor', () =>{
expect(targeted.source_attribute).toBe("output");
it('targeted id is set by constructor', () => {
expect(targeted.source_attribute).toBe('output');
});
it('targeted stdio/stderr also goes to multiplexer', () =>{
multi.stdout_writeline("out 1");
multi.stderr_writeline("out 2");
expect(capture.captured_stdout).toBe("out 1\n");
expect(capture.captured_stderr).toBe("out 2\n");
expect(document.getElementById("output-id")?.innerHTML).toBe("out 1<br>out 2<br>");
expect(document.getElementById("error-id")?.innerHTML).toBe("out 2<br>");
it('targeted stdio/stderr also goes to multiplexer', () => {
multi.stdout_writeline('out 1');
multi.stderr_writeline('out 2');
expect(capture.captured_stdout).toBe('out 1\n');
expect(capture.captured_stderr).toBe('out 2\n');
expect(document.getElementById('output-id')?.innerHTML).toBe('out 1<br>out 2<br>');
expect(document.getElementById('error-id')?.innerHTML).toBe('out 2<br>');
});
it('Add and remove targeted listener', () => {
multi.stdout_writeline("out 1");
multi.stdout_writeline('out 1');
multi.removeListener(targeted);
multi.stdout_writeline("out 2");
multi.stdout_writeline('out 2');
multi.addListener(targeted);
multi.stdout_writeline("out 3");
multi.stdout_writeline('out 3');
//all three should be captured by multiplexer
expect(capture.captured_stdout).toBe("out 1\nout 2\nout 3\n");
expect(capture.captured_stdout).toBe('out 1\nout 2\nout 3\n');
//out 2 should not be present in the DOM element
expect(document.getElementById("output-id")?.innerHTML).toBe("out 1<br>out 3<br>");
expect(document.getElementById('output-id')?.innerHTML).toBe('out 1<br>out 3<br>');
});
});

View File

@@ -1,73 +1,78 @@
import { beforeEach, expect, describe, it } from "@jest/globals"
import { ensureUniqueId, joinPaths, createSingularWarning} from "../../src/utils"
import { beforeEach, expect, describe, it } from '@jest/globals';
import { ensureUniqueId, joinPaths, createSingularWarning } from '../../src/utils';
describe("Utils", () => {
describe('Utils', () => {
let element: HTMLElement;
let element: HTMLElement;
beforeEach(() => {
element = document.createElement('div');
});
beforeEach(() => {
element = document.createElement("div");
})
it('ensureUniqueId sets unique id on element', async () => {
expect(element.id).toBe('');
it("ensureUniqueId sets unique id on element", async () => {
expect(element.id).toBe("")
ensureUniqueId(element);
ensureUniqueId(element)
expect(element.id).toBe('py-internal-0');
});
expect(element.id).toBe("py-internal-0")
})
it('ensureUniqueId sets unique id with increasing counter', async () => {
const secondElement = document.createElement('div');
it("ensureUniqueId sets unique id with increasing counter", async () => {
const secondElement = document.createElement("div")
expect(element.id).toBe('');
expect(secondElement.id).toBe('');
expect(element.id).toBe("")
expect(secondElement.id).toBe("")
ensureUniqueId(element);
ensureUniqueId(secondElement);
ensureUniqueId(element)
ensureUniqueId(secondElement)
// The counter will have been incremented on
// the previous test, make sure it keeps increasing
expect(element.id).toBe('py-internal-1');
expect(secondElement.id).toBe('py-internal-2');
});
});
// The counter will have been incremented on
// the previous test, make sure it keeps increasing
expect(element.id).toBe("py-internal-1")
expect(secondElement.id).toBe("py-internal-2")
})
})
describe('JoinPaths', () => {
it('should remove trailing slashes from the beginning and the end', () => {
const paths: string[] = ['///abc/d/e///'];
const joinedPath = joinPaths(paths);
expect(joinedPath).toStrictEqual('/abc/d/e');
});
describe("JoinPaths", () => {
it("should remove trailing slashes from the beginning and the end", () => {
const paths: string[] = ['///abc/d/e///'];
const joinedPath = joinPaths(paths);
expect(joinedPath).toStrictEqual('/abc/d/e');
})
it('should not remove slashes from the middle to preserve protocols such as http', () => {
const paths: string[] = ['http://google.com', '///data.txt'];
const joinedPath = joinPaths(paths);
expect(joinedPath).toStrictEqual('http://google.com/data.txt');
});
it("should not remove slashes from the middle to preserve protocols such as http", () => {
const paths: string[] = ['http://google.com', '///data.txt'];
const joinedPath = joinPaths(paths);
expect(joinedPath).toStrictEqual('http://google.com/data.txt');
})
it('should not join paths when they are empty strings', () => {
const paths: string[] = ['', '///hhh/ll/pp///', '', 'kkk'];
const joinedPath = joinPaths(paths);
expect(joinedPath).toStrictEqual('hhh/ll/pp/kkk');
});
it("should not join paths when they are empty strings", () => {
const paths: string[] = ['', '///hhh/ll/pp///', '', 'kkk'];
const joinedPath = joinPaths(paths);
expect(joinedPath).toStrictEqual('hhh/ll/pp/kkk');
})
describe('createSingularBanner', () => {
it('should create one and new banner containing the sentinel text, and not duplicate it', () => {
//One warning banner with the desired text should be created
createSingularWarning('A unique error message', 'unique');
expect(document.getElementsByClassName('alert-banner')?.length).toEqual(1);
expect(document.getElementsByClassName('alert-banner')[0].textContent).toEqual(
expect.stringContaining('A unique error message'),
);
describe("createSingularBanner", () => {
it("should create one and new banner containing the sentinel text, and not duplicate it", () => {
//One warning banner with the desired text should be created
createSingularWarning("A unique error message", "unique")
expect(document.getElementsByClassName("alert-banner")?.length).toEqual(1)
expect(document.getElementsByClassName("alert-banner")[0].textContent).toEqual(expect.stringContaining("A unique error message"))
//Should still only be one banner, since the second uses the existing sentinel value "unique"
createSingularWarning('This banner should not appear', 'unique');
expect(document.getElementsByClassName('alert-banner')?.length).toEqual(1);
expect(document.getElementsByClassName('alert-banner')[0].textContent).toEqual(
expect.stringContaining('A unique error message'),
);
//Should still only be one banner, since the second uses the existing sentinel value "unique"
createSingularWarning("This banner should not appear", "unique")
expect(document.getElementsByClassName("alert-banner")?.length).toEqual(1)
expect(document.getElementsByClassName("alert-banner")[0].textContent).toEqual(expect.stringContaining("A unique error message"))
//If the sentinel value is not provided, the entire msg is used as the sentinel
createSingularWarning("A unique error message", null)
expect(document.getElementsByClassName("alert-banner")?.length).toEqual(1)
expect(document.getElementsByClassName("alert-banner")[0].textContent).toEqual(expect.stringContaining("A unique error message"))
})
})
})
//If the sentinel value is not provided, the entire msg is used as the sentinel
createSingularWarning('A unique error message', null);
expect(document.getElementsByClassName('alert-banner')?.length).toEqual(1);
expect(document.getElementsByClassName('alert-banner')[0].textContent).toEqual(
expect.stringContaining('A unique error message'),
);
});
});
});

View File

@@ -1,22 +1,18 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"_version": "3.0.0",
"$schema": "https://json.schemastore.org/tsconfig",
"_version": "3.0.0",
"include": ["src/**/*"],
"exclude": ["node_modules/*", "__sapper__/*", "public/*"],
"compilerOptions": {
"moduleResolution": "node",
"target": "ES2020",
"module": "ES2020",
"types": ["jest", "node"],
"strict": false,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"lib": [
"es2017",
"dom",
"DOM.Iterable"
]
}
"include": ["src/**/*"],
"exclude": ["node_modules/*", "__sapper__/*", "public/*"],
"compilerOptions": {
"moduleResolution": "node",
"target": "ES2020",
"module": "ES2020",
"types": ["jest", "node"],
"strict": false,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"lib": ["es2017", "dom", "DOM.Iterable"]
}
}