Files
pyscript/pyscriptjs/src/stdio.ts
Andrea Giammarchi 82613d016a Fix #1429 - Use basic-devtools module (#1430)
This MR brings in `$`, `$$`, and `$x` browsers devtools' utilities to our code so we can use these whenever we find it convenient.
2023-05-02 15:12:28 +02:00

128 lines
3.6 KiB
TypeScript

import { $ } from 'basic-devtools';
import { createSingularWarning, escape } from './utils';
export interface Stdio {
stdout_writeline: (msg: string) => void;
stderr_writeline: (msg: string) => void;
}
/** Default implementation of Stdio: stdout and stderr are both sent to the
* console
*/
export const DEFAULT_STDIO: Stdio = {
stdout_writeline: console.log,
stderr_writeline: console.log,
};
/** Stdio provider which captures and store the messages.
* Useful for tests.
*/
export class CaptureStdio implements Stdio {
captured_stdout: string;
captured_stderr: string;
constructor() {
this.reset();
}
reset() {
this.captured_stdout = '';
this.captured_stderr = '';
}
stdout_writeline(msg: string) {
this.captured_stdout += msg + '\n';
}
stderr_writeline(msg: string) {
this.captured_stderr += msg + '\n';
}
}
/** Stdio provider for sending output to DOM element
* specified by ID. Used with "output" keyword.
*
*/
export class TargetedStdio implements Stdio {
source_element: HTMLElement;
source_attribute: string;
capture_stdout: boolean;
capture_stderr: boolean;
constructor(source_element: HTMLElement, source_attribute: string, capture_stdout = true, capture_stderr = true) {
this.source_element = source_element;
this.source_attribute = source_attribute;
this.capture_stdout = capture_stdout;
this.capture_stderr = capture_stderr;
}
/** Writes the given msg to an element with a given ID. The ID is the value an attribute
* of the source_element specified by source_attribute.
* Both the element to be targeted and the ID of the element to write to
* are determined at write-time, not when the TargetdStdio object is
* created. This way, if either the 'output' attribute of the HTML tag
* or the ID of the target element changes during execution of the Python
* code, the output is still routed (or not) as expected
*
* @param msg The output to be written
*/
writeline_by_attribute(msg: string) {
const target_id = this.source_element.getAttribute(this.source_attribute);
const target = $('#' + target_id, document);
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>';
}
target.innerHTML += 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);
}
}
}
/** Redirect stdio streams to multiple listeners
*/
export class StdioMultiplexer implements Stdio {
_listeners: Stdio[];
constructor() {
this._listeners = [];
}
addListener(obj: Stdio) {
this._listeners.push(obj);
}
removeListener(obj: Stdio) {
const index = this._listeners.indexOf(obj);
if (index > -1) {
this._listeners.splice(index, 1);
}
}
stdout_writeline(msg: string) {
for (const obj of this._listeners) obj.stdout_writeline(msg);
}
stderr_writeline(msg: string) {
for (const obj of this._listeners) obj.stderr_writeline(msg);
}
}