UNPKG

@alauda/doom

Version:

Doctor Doom making docs.

260 lines (250 loc) 8.25 kB
<!doctype html> <html> <head> <meta charset="UTF-8" /> <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" /> <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> "use strict"; 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"); let term; globalThis.pyodide = await loadPyodide({ stdin: () => { let result = prompt(); echo(result); return result; }, }); let namespace = pyodide.globals.get("dict")(); pyodide.runPython( ` import sys from pyodide.ffi import to_js from pyodide.console import PyodideConsole, repr_shorten, BANNER import __main__ BANNER = "Welcome to the Pyodide terminal emulator 🐍\\n" + BANNER pyconsole = PyodideConsole(__main__.__dict__) import builtins async def await_fut(fut): res = await fut if res is not None: builtins._ = res return to_js([res], depth=1) def clear_console(): pyconsole.buffer = [] `, { globals: namespace }, ); let repr_shorten = namespace.get("repr_shorten"); let banner = namespace.get("BANNER"); let await_fut = namespace.get("await_fut"); let pyconsole = namespace.get("pyconsole"); let clear_console = namespace.get("clear_console"); const echo = (msg, ...opts) => term.echo( msg .replaceAll("]]", "&rsqb;&rsqb;") .replaceAll("[[", "&lsqb;&lsqb;"), ...opts, ); namespace.destroy(); let ps1 = ">>> ", ps2 = "... "; async function lock() { let resolve; let ready = term.ready; term.ready = new Promise((res) => (resolve = res)); await ready; return resolve; } async function interpreter(command) { let 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, " "); let 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. let wrapped = await_fut(fut); // complete case, get result / error and print it. try { let [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) { clear_console(); 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"); } } window.console_ready = main(); </script> </body> </html>