found, adding one");
const termElem = document.createElement("py-terminal");
if (auto)
termElem.setAttribute("auto", "");
if (docked)
termElem.setAttribute("docked", "");
if (xterm)
termElem.setAttribute("xterm", "");
document.body.appendChild(termElem);
}
}
afterSetup(_interpreter) {
const PyTerminal = _interpreter.config.xterm ? make_PyTerminal_xterm(this.app) : make_PyTerminal_pre(this.app);
customElements.define("py-terminal", PyTerminal);
}
};
var PyTerminalBaseClass = class extends HTMLElement {
isAuto() {
return this.hasAttribute("auto");
}
isDocked() {
return this.hasAttribute("docked");
}
setupPosition(app) {
if (this.isAuto()) {
this.classList.add("py-terminal-hidden");
this.autoShowOnNextLine = true;
} else {
this.autoShowOnNextLine = false;
}
if (this.isDocked()) {
this.classList.add("py-terminal-docked");
}
logger7.info("Registering stdio listener");
app.registerStdioListener(this);
}
};
function make_PyTerminal_pre(app) {
class PyTerminalPre extends PyTerminalBaseClass {
connectedCallback() {
this.outElem = document.createElement("pre");
this.outElem.classList.add("py-terminal");
this.appendChild(this.outElem);
this.setupPosition(app);
}
// implementation of the Stdio interface
stdout_writeline(msg) {
this.outElem.innerText += msg + "\n";
if (this.isDocked()) {
this.scrollTop = this.scrollHeight;
}
if (this.autoShowOnNextLine) {
this.classList.remove("py-terminal-hidden");
this.autoShowOnNextLine = false;
}
}
stderr_writeline(msg) {
this.stdout_writeline(msg);
}
// end of the Stdio interface
}
return PyTerminalPre;
}
function make_PyTerminal_xterm(app) {
class PyTerminalXterm extends PyTerminalBaseClass {
constructor() {
super();
this._xterm_cdn_base_url = "https://cdn.jsdelivr.net/npm/xterm@5.1.0";
this.cachedStdOut = [];
this.cachedStdErr = [];
this._moduleResolved = false;
this.style.width = "100%";
this.style.height = "100%";
}
async connectedCallback() {
if (knownPyTerminalTags.has(this))
return;
knownPyTerminalTags.add(this);
this.outElem = document.createElement("div");
this.appendChild(this.outElem);
this.setupPosition(app);
this.xtermReady = this._setupXterm();
await this.xtermReady;
}
/**
* Fetch the xtermjs library from CDN an initialize it.
* @private
* @returns the associated xterm.js Terminal
*/
async _setupXterm() {
if (this.xterm == void 0) {
if (globalThis.Terminal == void 0) {
await import(this._xterm_cdn_base_url + "/lib/xterm.js");
const cssTag = document.createElement("link");
cssTag.type = "text/css";
cssTag.rel = "stylesheet";
cssTag.href = this._xterm_cdn_base_url + "/css/xterm.css";
document.head.appendChild(cssTag);
}
this.xterm = new Terminal({ screenReaderMode: true, cols: 80 });
if (!this.autoShowOnNextLine)
this.xterm.open(this);
this._moduleResolved = true;
this.cachedStdOut.forEach((value) => this.stdout_writeline(value));
this.cachedStdErr.forEach((value) => this.stderr_writeline(value));
} else {
this._moduleResolved = true;
}
return this.xterm;
}
// implementation of the Stdio interface
stdout_writeline(msg) {
if (this._moduleResolved) {
this.xterm.writeln(msg);
if (this.isDocked()) {
this.scrollTop = this.scrollHeight;
}
if (this.autoShowOnNextLine) {
this.classList.remove("py-terminal-hidden");
this.autoShowOnNextLine = false;
this.xterm.open(this);
}
} else {
this.cachedStdOut.push(msg);
}
}
stderr_writeline(msg) {
this.stdout_writeline(msg);
}
// end of the Stdio interface
}
return PyTerminalXterm;
}
// src/plugins/splashscreen.ts
var logger8 = getLogger("py-splashscreen");
var AUTOCLOSE_LOADER_DEPRECATED = `
The setting autoclose_loader is deprecated. Please use the
following instead:
<py-config>
[splashscreen]
autoclose = false
</py-config>
`;
var SplashscreenPlugin = class extends Plugin {
configure(config2) {
this.autoclose = true;
this.enabled = true;
if ("autoclose_loader" in config2) {
this.autoclose = config2.autoclose_loader;
showWarning(AUTOCLOSE_LOADER_DEPRECATED, "html");
}
if (config2.splashscreen) {
this.autoclose = config2.splashscreen.autoclose ?? true;
this.enabled = config2.splashscreen.enabled ?? true;
}
}
beforeLaunch(_config) {
if (!this.enabled) {
return;
}
logger8.info("add py-splashscreen");
customElements.define("py-splashscreen", PySplashscreen);
this.elem = document.createElement("py-splashscreen");
document.body.append(this.elem);
document.addEventListener("py-status-message", (e) => {
const msg = e.detail;
this.elem.log(msg);
});
}
afterStartup(_interpreter) {
if (this.autoclose && this.enabled) {
this.elem.close();
}
}
onUserError(_error) {
if (this.elem !== void 0 && this.enabled) {
this.elem.close();
}
}
};
var PySplashscreen = class extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.innerHTML = ``;
this.mount_name = this.id.split("-").join("_");
this.operation = $("#pyscript-operation", document);
this.details = $("#pyscript-operation-details", document);
}
log(msg) {
const newLog = document.createElement("p");
newLog.innerText = msg;
this.details.appendChild(newLog);
}
close() {
logger8.info("Closing");
this.remove();
}
};
// src/plugins/importmap.ts
var logger9 = getLogger("plugins/importmap");
var ImportmapPlugin = class extends Plugin {
async afterSetup(interpreter2) {
for (const node of $$("script[type='importmap']", document)) {
const importmap = (() => {
try {
return JSON.parse(node.textContent);
} catch (e) {
const error = e;
showWarning("Failed to parse import map: " + error.message);
}
})();
if (importmap?.imports == null)
continue;
for (const [name2, url] of Object.entries(importmap.imports)) {
if (typeof name2 != "string" || typeof url != "string")
continue;
let exports;
try {
exports = { ...await import(url) };
} catch {
logger9.warn(`failed to fetch '${url}' for '${name2}'`);
continue;
}
logger9.info("Registering JS module", name2);
await interpreter2._remote.registerJsModule(name2, exports);
}
}
}
};
// src/plugins/stdiodirector.ts
var StdioDirector = class extends Plugin {
constructor(stdio) {
super();
this._stdioMultiplexer = stdio;
}
/** Prior to a tag being evaluated, if that tag itself has
* an 'output' attribute, a new TargetedStdio object is created and added
* to the stdioMultiplexer to route sys.stdout and sys.stdout to the DOM object
* with that ID for the duration of the evaluation.
*
*/
beforePyScriptExec(options) {
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);
}
}
/** After a tag is evaluated, if that tag has a 'stdout_manager'
* (presumably TargetedStdio, or some other future IO handler), it is removed.
*/
afterPyScriptExec(options) {
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;
}
}
beforePyReplExec(options) {
if (options.pyReplTag.getAttribute("output-mode") != "append") {
options.outEl.innerHTML = "";
}
let output_targeted_io;
if (options.pyReplTag.hasAttribute("output")) {
output_targeted_io = new TargetedStdio(options.pyReplTag, "output", true, true);
} else {
output_targeted_io = new TargetedStdio(options.pyReplTag.outDiv, "id", true, true);
}
options.pyReplTag.stdout_manager = output_targeted_io;
this._stdioMultiplexer.addListener(output_targeted_io);
if (options.pyReplTag.hasAttribute("stderr")) {
const stderr_targeted_io = new TargetedStdio(options.pyReplTag, "stderr", false, true);
options.pyReplTag.stderr_manager = stderr_targeted_io;
this._stdioMultiplexer.addListener(stderr_targeted_io);
}
}
async afterPyReplExec(options) {
if (options.result !== void 0) {
const outputId = options.pyReplTag.getAttribute("output");
if (outputId) {
if ($("#" + outputId, document)) {
await pyDisplay(options.interpreter, options.result, { target: outputId });
} else {
createSingularWarning(`output = "${outputId}" does not match the id of any element on the page.`);
}
} else {
await pyDisplay(options.interpreter, options.result, { target: options.outEl.id });
}
}
if (options.pyReplTag.stdout_manager != null) {
this._stdioMultiplexer.removeListener(options.pyReplTag.stdout_manager);
options.pyReplTag.stdout_manager = null;
}
if (options.pyReplTag.stderr_manager != null) {
this._stdioMultiplexer.removeListener(options.pyReplTag.stderr_manager);
options.pyReplTag.stderr_manager = null;
}
}
};
// bundlePyscriptPythonPlugin:dummy
var dummy_default = { dirs: ["pyscript"], files: [["pyscript/_html.py", `from textwrap import dedent
import js
from _pyscript_js import deepQuerySelector
from . import _internal
from ._mime import format_mime as _format_mime
class HTML:
"""
Wrap a string so that display() can render it as plain HTML
"""
def __init__(self, html):
self._html = html
def _repr_html_(self):
return self._html
def write(element_id, value, append=False, exec_id=0):
"""Writes value to the element with id "element_id"""
Element(element_id).write(value=value, append=append)
js.console.warn(
dedent(
"""PyScript Deprecation Warning: PyScript.write is
marked as deprecated and will be removed sometime soon. Please, use
Element().write instead."""
)
)
def display(*values, target=None, append=True):
if target is None:
target = _internal.DISPLAY_TARGET
if target is None:
raise Exception(
"Implicit target not allowed here. Please use display(..., target=...)"
)
for v in values:
Element(target).write(v, append=append)
class Element:
def __init__(self, element_id, element=None):
self._id = element_id
self._element = element
@property
def id(self):
return self._id
@property
def element(self):
"""Return the dom element"""
if not self._element:
self._element = deepQuerySelector(f"#{self._id}")
return self._element
@property
def value(self):
return self.element.value
@property
def innerHtml(self):
return self.element.innerHTML
def write(self, value, append=False):
html, mime_type = _format_mime(value)
if html == "\\n":
return
if append:
child = js.document.createElement("div")
self.element.appendChild(child)
if append and self.element.children:
out_element = self.element.children[-1]
else:
out_element = self.element
if mime_type in ("application/javascript", "text/html"):
script_element = js.document.createRange().createContextualFragment(html)
out_element.appendChild(script_element)
else:
out_element.innerHTML = html
def clear(self):
if hasattr(self.element, "value"):
self.element.value = ""
else:
self.write("", append=False)
def select(self, query, from_content=False):
el = self.element
if from_content:
el = el.content
_el = el.querySelector(query)
if _el:
return Element(_el.id, _el)
else:
js.console.warn(f"WARNING: can't find element matching query {query}")
def clone(self, new_id=None, to=None):
if new_id is None:
new_id = self.element.id
clone = self.element.cloneNode(True)
clone.id = new_id
if to:
to.element.appendChild(clone)
# Inject it into the DOM
to.element.after(clone)
else:
# Inject it into the DOM
self.element.after(clone)
return Element(clone.id, clone)
def remove_class(self, classname):
classList = self.element.classList
if isinstance(classname, list):
classList.remove(*classname)
else:
classList.remove(classname)
def add_class(self, classname):
classList = self.element.classList
if isinstance(classname, list):
classList.add(*classname)
else:
self.element.classList.add(classname)
def add_classes(element, class_list):
classList = element.classList
classList.add(*class_list.split(" "))
def create(what, id_=None, classes=""):
element = js.document.createElement(what)
if id_:
element.id = id_
add_classes(element, classes)
return Element(id_, element)
`], ["pyscript/_plugin.py", 'from _pyscript_js import define_custom_element\nfrom js import console\nfrom pyodide.ffi import create_proxy\n\n\nclass Plugin:\n def __init__(self, name=None):\n if not name:\n name = self.__class__.__name__\n\n self.name = name\n self._custom_elements = []\n self.app = None\n\n def init(self, app):\n self.app = app\n\n def configure(self, config):\n pass\n\n def afterSetup(self, interpreter):\n pass\n\n def afterStartup(self, interpreter):\n pass\n\n def beforePyScriptExec(self, interpreter, src, pyScriptTag):\n pass\n\n def afterPyScriptExec(self, interpreter, src, pyScriptTag, result):\n pass\n\n def beforePyReplExec(self, interpreter, src, outEl, pyReplTag):\n pass\n\n def afterPyReplExec(self, interpreter, src, outEl, pyReplTag, result):\n pass\n\n def onUserError(self, error):\n pass\n\n def register_custom_element(self, tag):\n """\n Decorator to register a new custom element as part of a Plugin and associate\n tag to it. Internally, it delegates the registration to the PyScript internal\n [JS] plugin manager, who actually creates the JS custom element that can be\n attached to the page and instantiate an instance of the class passing the custom\n element to the plugin constructor.\n\n Exammple:\n >> plugin = Plugin("PyTutorial")\n >> @plugin.register_custom_element("py-tutor")\n >> class PyTutor:\n >> def __init__(self, element):\n >> self.element = element\n """\n # TODO: Ideally would be better to use the logger.\n console.info(f"Defining new custom element {tag}")\n\n def wrapper(class_):\n # TODO: this is very pyodide specific but will have to do\n # until we have JS interface that works across interpreters\n define_custom_element(tag, create_proxy(class_)) # noqa: F821\n\n self._custom_elements.append(tag)\n return create_proxy(wrapper)\n'], ["pyscript/_mime.py", `import base64
import html
import io
import re
from js import console
MIME_METHODS = {
"__repr__": "text/plain",
"_repr_html_": "text/html",
"_repr_markdown_": "text/markdown",
"_repr_svg_": "image/svg+xml",
"_repr_png_": "image/png",
"_repr_pdf_": "application/pdf",
"_repr_jpeg_": "image/jpeg",
"_repr_latex": "text/latex",
"_repr_json_": "application/json",
"_repr_javascript_": "application/javascript",
"savefig": "image/png",
}
def render_image(mime, value, meta):
# If the image value is using bytes we should convert it to base64
# otherwise it will return raw bytes and the browser will not be able to
# render it.
if isinstance(value, bytes):
value = base64.b64encode(value).decode("utf-8")
# This is the pattern of base64 strings
base64_pattern = re.compile(
r"^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$"
)
# If value doesn't match the base64 pattern we should encode it to base64
if len(value) > 0 and not base64_pattern.match(value):
value = base64.b64encode(value.encode("utf-8")).decode("utf-8")
data = f"data:{mime};charset=utf-8;base64,{value}"
attrs = " ".join(['{k}="{v}"' for k, v in meta.items()])
return f''
def identity(value, meta):
return value
MIME_RENDERERS = {
"text/plain": html.escape,
"text/html": identity,
"image/png": lambda value, meta: render_image("image/png", value, meta),
"image/jpeg": lambda value, meta: render_image("image/jpeg", value, meta),
"image/svg+xml": identity,
"application/json": identity,
"application/javascript": lambda value, meta: f"