Spaces:
Runtime error
Runtime error
| <html> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta | |
| http-equiv="origin-trial" | |
| content="Aq6vv/4syIkcyMszFgCc9LlH0kX88jdE7SXfCFnh2RQN0nhhL8o6PCQ2oE3a7n3mC7+d9n89Repw5HYBtjarDw4AAAB3eyJvcmlnaW4iOiJodHRwczovL3B5b2RpZGUub3JnOjQ0MyIsImZlYXR1cmUiOiJXZWJBc3NlbWJseUpTUHJvbWlzZUludGVncmF0aW9uIiwiZXhwaXJ5IjoxNzMwMjQ2Mzk5LCJpc1N1YmRvbWFpbiI6dHJ1ZX0=" | |
| /> | |
| <meta | |
| http-equiv="origin-trial" | |
| content="Ai8IXb0XqedlM/Q2guWXFfBkKiYY9uaPZpdjHqc8y0ZvpAfK9SKzp/dIuFH+txG/HEKxt59uIkk39hhWrhNgbw4AAABieyJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjgwMDAiLCJmZWF0dXJlIjoiV2ViQXNzZW1ibHlKU1Byb21pc2VJbnRlZ3JhdGlvbiIsImV4cGlyeSI6MTczMDI0NjM5OX0=" | |
| /> | |
| <script src="https://cdn.jsdelivr.net/npm/jquery"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/jquery.terminal@2.35.2/js/jquery.terminal.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/jquery.terminal@2.35.2/js/unix_formatting.min.js"></script> | |
| <link | |
| href="https://cdn.jsdelivr.net/npm/jquery.terminal@2.35.2/css/jquery.terminal.min.css" | |
| rel="stylesheet" | |
| /> | |
| <link | |
| href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🐍</text></svg>" | |
| rel="icon" | |
| /> | |
| <style> | |
| .terminal { | |
| --size: 1.5; | |
| --color: rgba(255, 255, 255, 0.8); | |
| } | |
| .noblink { | |
| --animation: terminal-none; | |
| } | |
| body { | |
| background-color: black; | |
| } | |
| #jquery-terminal-logo { | |
| color: white; | |
| border-color: white; | |
| position: absolute; | |
| top: 7px; | |
| right: 18px; | |
| z-index: 2; | |
| } | |
| #jquery-terminal-logo a { | |
| color: gray; | |
| text-decoration: none; | |
| font-size: 0.7em; | |
| } | |
| #loading { | |
| display: inline-block; | |
| width: 50px; | |
| height: 50px; | |
| position: fixed; | |
| top: 50%; | |
| left: 50%; | |
| border: 3px solid rgba(172, 237, 255, 0.5); | |
| border-radius: 50%; | |
| border-top-color: #fff; | |
| animation: spin 1s ease-in-out infinite; | |
| -webkit-animation: spin 1s ease-in-out infinite; | |
| } | |
| @keyframes spin { | |
| to { | |
| -webkit-transform: rotate(360deg); | |
| } | |
| } | |
| @-webkit-keyframes spin { | |
| to { | |
| -webkit-transform: rotate(360deg); | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="jquery-terminal-logo"> | |
| <a href="https://terminal.jcubic.pl/">jQuery Terminal</a> | |
| </div> | |
| <div id="loading"></div> | |
| <script> | |
| ; | |
| function sleep(s) { | |
| return new Promise((resolve) => setTimeout(resolve, s)); | |
| } | |
| async function main() { | |
| let indexURL = "./"; | |
| const urlParams = new URLSearchParams(window.location.search); | |
| const buildParam = urlParams.get("build"); | |
| if (buildParam) { | |
| if (["full", "debug", "pyc"].includes(buildParam)) { | |
| indexURL = indexURL.replace( | |
| "/full/", | |
| "/" + urlParams.get("build") + "/", | |
| ); | |
| } else { | |
| console.warn( | |
| 'Invalid URL parameter: build="' + | |
| buildParam + | |
| '". Using default "full".', | |
| ); | |
| } | |
| } | |
| const { loadPyodide } = await import(indexURL + "pyodide.mjs"); | |
| // to facilitate debugging | |
| globalThis.loadPyodide = loadPyodide; | |
| let term; | |
| globalThis.pyodide = await loadPyodide({ | |
| stdin: () => { | |
| let result = prompt(); | |
| echo(result); | |
| return result; | |
| }, | |
| }); | |
| let { repr_shorten, BANNER, PyodideConsole } = | |
| pyodide.pyimport("pyodide.console"); | |
| BANNER = | |
| `Welcome to the Pyodide ${pyodide.version} terminal emulator 🐍\n` + | |
| BANNER; | |
| const pyconsole = PyodideConsole(pyodide.globals); | |
| const namespace = pyodide.globals.get("dict")(); | |
| const await_fut = pyodide.runPython( | |
| ` | |
| import builtins | |
| from pyodide.ffi import to_js | |
| async def await_fut(fut): | |
| res = await fut | |
| if res is not None: | |
| builtins._ = res | |
| return to_js([res], depth=1) | |
| await_fut | |
| `, | |
| { globals: namespace }, | |
| ); | |
| namespace.destroy(); | |
| const echo = (msg, ...opts) => | |
| term.echo( | |
| msg | |
| .replaceAll("]]", "]]") | |
| .replaceAll("[[", "[["), | |
| ...opts, | |
| ); | |
| const ps1 = ">>> "; | |
| const ps2 = "... "; | |
| async function lock() { | |
| let resolve; | |
| const ready = term.ready; | |
| term.ready = new Promise((res) => (resolve = res)); | |
| await ready; | |
| return resolve; | |
| } | |
| async function interpreter(command) { | |
| const unlock = await lock(); | |
| term.pause(); | |
| // multiline should be split (useful when pasting) | |
| for (const c of command.split("\n")) { | |
| const escaped = c.replaceAll(/\u00a0/g, " "); | |
| const fut = pyconsole.push(escaped); | |
| term.set_prompt(fut.syntax_check === "incomplete" ? ps2 : ps1); | |
| switch (fut.syntax_check) { | |
| case "syntax-error": | |
| term.error(fut.formatted_error.trimEnd()); | |
| continue; | |
| case "incomplete": | |
| continue; | |
| case "complete": | |
| break; | |
| default: | |
| throw new Error(`Unexpected type ${ty}`); | |
| } | |
| // In JavaScript, await automatically also awaits any results of | |
| // awaits, so if an async function returns a future, it will await | |
| // the inner future too. This is not what we want so we | |
| // temporarily put it into a list to protect it. | |
| const wrapped = await_fut(fut); | |
| // complete case, get result / error and print it. | |
| try { | |
| const [value] = await wrapped; | |
| if (value !== undefined) { | |
| echo( | |
| repr_shorten.callKwargs(value, { | |
| separator: "\n<long output truncated>\n", | |
| }), | |
| ); | |
| } | |
| if (value instanceof pyodide.ffi.PyProxy) { | |
| value.destroy(); | |
| } | |
| } catch (e) { | |
| if (e.constructor.name === "PythonError") { | |
| const message = fut.formatted_error || e.message; | |
| term.error(message.trimEnd()); | |
| } else { | |
| throw e; | |
| } | |
| } finally { | |
| fut.destroy(); | |
| wrapped.destroy(); | |
| } | |
| } | |
| term.resume(); | |
| await sleep(10); | |
| unlock(); | |
| } | |
| term = $("body").terminal(interpreter, { | |
| greetings: BANNER, | |
| prompt: ps1, | |
| completionEscape: false, | |
| completion: function (command, callback) { | |
| callback(pyconsole.complete(command).toJs()[0]); | |
| }, | |
| keymap: { | |
| "CTRL+C": async function (event, original) { | |
| pyconsole.buffer.clear(); | |
| term.enter(); | |
| echo("KeyboardInterrupt"); | |
| term.set_command(""); | |
| term.set_prompt(ps1); | |
| }, | |
| TAB: (event, original) => { | |
| const command = term.before_cursor(); | |
| // Disable completion for whitespaces. | |
| if (command.trim() === "") { | |
| term.insert("\t"); | |
| return false; | |
| } | |
| return original(event); | |
| }, | |
| }, | |
| }); | |
| window.term = term; | |
| pyconsole.stdout_callback = (s) => echo(s, { newline: false }); | |
| pyconsole.stderr_callback = (s) => { | |
| term.error(s.trimEnd()); | |
| }; | |
| term.ready = Promise.resolve(); | |
| pyodide._api.on_fatal = async (e) => { | |
| if (e.name === "Exit") { | |
| term.error(e); | |
| term.error("Pyodide exited and can no longer be used."); | |
| } else { | |
| term.error( | |
| "Pyodide has suffered a fatal error. Please report this to the Pyodide maintainers.", | |
| ); | |
| term.error("The cause of the fatal error was:"); | |
| term.error(e); | |
| term.error("Look in the browser console for more details."); | |
| } | |
| await term.ready; | |
| term.pause(); | |
| await sleep(15); | |
| term.pause(); | |
| }; | |
| const searchParams = new URLSearchParams(window.location.search); | |
| if (searchParams.has("noblink")) { | |
| $(".cmd-cursor").addClass("noblink"); | |
| } | |
| let idbkvPromise; | |
| async function getIDBKV() { | |
| if (!idbkvPromise) { | |
| idbkvPromise = await import( | |
| "https://unpkg.com/idb-keyval@5.0.2/dist/esm/index.js" | |
| ); | |
| } | |
| return idbkvPromise; | |
| } | |
| async function mountDirectory(pyodideDirectory, directoryKey) { | |
| if (pyodide.FS.analyzePath(pyodideDirectory).exists) { | |
| return; | |
| } | |
| const { get, set } = await getIDBKV(); | |
| const opts = { | |
| id: "mountdirid", | |
| mode: "readwrite", | |
| }; | |
| let directoryHandle = await get(directoryKey); | |
| if (!directoryHandle) { | |
| directoryHandle = await showDirectoryPicker(opts); | |
| await set(directoryKey, directoryHandle); | |
| } | |
| const permissionStatus = | |
| await directoryHandle.requestPermission(opts); | |
| if (permissionStatus !== "granted") { | |
| throw new Error("readwrite access to directory not granted"); | |
| } | |
| await pyodide.mountNativeFS(pyodideDirectory, directoryHandle); | |
| } | |
| globalThis.mountDirectory = mountDirectory; | |
| } | |
| window.console_ready = main(); | |
| </script> | |
| </body> | |
| </html> | |