mirror of
https://github.com/pyscript/pyscript.git
synced 2022-05-01 19:47:48 +03:00
Merge pull request #28 from anaconda/pys-19/allow_out_err_redirect
[PYS-19] allow out err redirect
This commit is contained in:
@@ -23,7 +23,7 @@
|
||||
<h1>Bokeh Example</h1>
|
||||
<div id="myplot"></div>
|
||||
|
||||
<py-script>
|
||||
<py-script id="main">
|
||||
import json
|
||||
import pyodide
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<h1>Bokeh Example</h1>
|
||||
<div id="myplot"></div>
|
||||
|
||||
<py-script>
|
||||
<py-script id="main">
|
||||
import asyncio
|
||||
import json
|
||||
import pyodide
|
||||
|
||||
@@ -20,9 +20,12 @@
|
||||
</py-env>
|
||||
|
||||
<body>
|
||||
<h1 class="font-semibold text-2xl ml-5">Custom REPL</h1>
|
||||
<py-box widths="2/3;1/3">
|
||||
<py-repl id="my-repl" auto-generate="true" target="output"> </py-repl>
|
||||
<py-repl id="my-repl" auto-generate="true" std-out="output" std-err="err-div"> </py-repl>
|
||||
<div id="output"></div>
|
||||
</py-box>
|
||||
<footer id="err-div" class="bg-red-700 text-white text-center border-t-4 border-gree-500 fixed inset-x-0 bottom-0 p-4 hidden">
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<body>
|
||||
<div id="outputDiv" class="font-mono" style="background-color:yellow"></div>
|
||||
<py-script target="outputDiv">
|
||||
<py-script output="outputDiv">
|
||||
from datetime import datetime
|
||||
now = datetime.now()
|
||||
now.strftime("%m/%d/%Y, %H:%M:%S")
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<div class="font-mono">start time: <label id="outputDiv"></label></div>
|
||||
<div id="outputDiv2" class="font-mono"></div>
|
||||
<div id="outputDiv3" class="font-mono"></div>
|
||||
<py-script target="outputDiv">
|
||||
<py-script output="outputDiv">
|
||||
import utils
|
||||
utils.now()
|
||||
</py-script>
|
||||
|
||||
153
pyscriptjs/src/components/base.ts
Normal file
153
pyscriptjs/src/components/base.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import { pyodideLoaded, loadedEnvironments, componentDetailsNavOpen, mode } from '../stores';
|
||||
|
||||
// Premise used to connect to the first available pyodide interpreter
|
||||
let pyodideReadyPromise;
|
||||
let environments;
|
||||
let currentMode;
|
||||
let Element;
|
||||
|
||||
pyodideLoaded.subscribe(value => {
|
||||
pyodideReadyPromise = value;
|
||||
});
|
||||
loadedEnvironments.subscribe(value => {
|
||||
environments = value;
|
||||
});
|
||||
|
||||
let propertiesNavOpen;
|
||||
componentDetailsNavOpen.subscribe(value => {
|
||||
propertiesNavOpen = value;
|
||||
});
|
||||
|
||||
mode.subscribe(value => {
|
||||
currentMode = value;
|
||||
});
|
||||
|
||||
// TODO: use type declaractions
|
||||
type PyodideInterface = {
|
||||
registerJsModule(name: string, module: object): void
|
||||
}
|
||||
|
||||
|
||||
export class BaseEvalElement extends HTMLElement {
|
||||
shadow: ShadowRoot;
|
||||
wrapper: HTMLElement;
|
||||
code: string;
|
||||
source: string;
|
||||
btnConfig: HTMLElement;
|
||||
btnRun: HTMLElement;
|
||||
outputElement: HTMLElement;
|
||||
errorElement: HTMLElement;
|
||||
theme: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// attach shadow so we can preserve the element original innerHtml content
|
||||
this.shadow = this.attachShadow({ mode: 'open'});
|
||||
this.wrapper = document.createElement('slot');
|
||||
this.shadow.appendChild(this.wrapper);
|
||||
}
|
||||
|
||||
addToOutput(s: string) {
|
||||
this.outputElement.innerHTML += "<div>"+s+"</div>";
|
||||
this.outputElement.hidden = false;
|
||||
}
|
||||
|
||||
postEvaluate(){
|
||||
|
||||
}
|
||||
|
||||
getSourceFromElement(): string{
|
||||
return "";
|
||||
}
|
||||
|
||||
async getSourceFromFile(s: string): Promise<string>{
|
||||
let pyodide = await pyodideReadyPromise;
|
||||
let response = await fetch(s);
|
||||
this.code = await response.text();
|
||||
return this.code;
|
||||
}
|
||||
|
||||
protected async _register_esm(pyodide: PyodideInterface): Promise<void> {
|
||||
const imports: {[key: string]: unknown} = {}
|
||||
|
||||
for (const node of document.querySelectorAll("script[type='importmap']")) {
|
||||
const importmap = (() => {
|
||||
try {
|
||||
return JSON.parse(node.textContent)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
})()
|
||||
|
||||
if (importmap?.imports == null)
|
||||
continue
|
||||
|
||||
for (const [name, url] of Object.entries(importmap.imports)) {
|
||||
if (typeof name != "string" || typeof url != "string")
|
||||
continue
|
||||
|
||||
try {
|
||||
// XXX: pyodide doesn't like Module(), failing with
|
||||
// "can't read 'name' of undefined" at import time
|
||||
imports[name] = {...await import(url)}
|
||||
} catch {
|
||||
console.error(`failed to fetch '${url}' for '${name}'`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pyodide.registerJsModule("esm", imports)
|
||||
}
|
||||
|
||||
async evaluate(): Promise<void> {
|
||||
console.log('evaluate');
|
||||
let pyodide = await pyodideReadyPromise;
|
||||
let source: string;
|
||||
let output;
|
||||
try {
|
||||
// @ts-ignore
|
||||
if (this.source){
|
||||
source = await this.getSourceFromFile(this.source);
|
||||
}else{
|
||||
source = this.getSourceFromElement();
|
||||
}
|
||||
|
||||
await this._register_esm(pyodide);
|
||||
|
||||
if (source.includes("asyncio")){
|
||||
await pyodide.runPythonAsync(`output_manager.change("`+this.outputElement.id+`", "`+this.errorElement.id+`")`);
|
||||
output = await pyodide.runPythonAsync(source);
|
||||
await pyodide.runPythonAsync(`output_manager.revert()`)
|
||||
}else{
|
||||
output = pyodide.runPython(`output_manager.change("`+this.outputElement.id+`", "`+this.errorElement.id+`")`);
|
||||
output = pyodide.runPython(source);
|
||||
pyodide.runPython(`output_manager.revert()`)
|
||||
}
|
||||
|
||||
if (output !== undefined){
|
||||
if (Element === undefined){
|
||||
Element = pyodide.globals.get('Element');
|
||||
}
|
||||
const out = Element(this.outputElement.id);
|
||||
// @ts-ignore
|
||||
out.write.callKwargs(output, { append : true});
|
||||
|
||||
this.outputElement.hidden = false;
|
||||
this.outputElement.style.display = 'block';
|
||||
}
|
||||
|
||||
this.postEvaluate()
|
||||
|
||||
} catch (err) {
|
||||
if (Element === undefined){
|
||||
Element = pyodide.globals.get('Element');
|
||||
}
|
||||
const out = Element(this.errorElement.id);
|
||||
// @ts-ignore
|
||||
out.write.callKwargs(err, { append : true});
|
||||
this.errorElement.hidden = false;
|
||||
this.errorElement.style.display = 'block';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ export class PyBox extends HTMLElement {
|
||||
|
||||
for (let i in this.widths) {
|
||||
// @ts-ignore
|
||||
addClasses(mainDiv.childNodes[parseInt(i)], [this.widths[i]]);
|
||||
addClasses(mainDiv.childNodes[parseInt(i)], [this.widths[i], 'mx-4']);
|
||||
}
|
||||
|
||||
this.appendChild(mainDiv);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { oneDarkTheme } from "@codemirror/theme-one-dark";
|
||||
|
||||
import { pyodideLoaded, loadedEnvironments, componentDetailsNavOpen, currentComponentDetails, mode } from '../stores';
|
||||
import { addClasses } from '../utils';
|
||||
import { BaseEvalElement } from './base';
|
||||
|
||||
// Premise used to connect to the first available pyodide interpreter
|
||||
let pyodideReadyPromise;
|
||||
@@ -43,27 +44,13 @@ function createCmdHandler(el){
|
||||
}
|
||||
|
||||
|
||||
export class PyRepl extends HTMLElement {
|
||||
shadow: ShadowRoot;
|
||||
wrapper: HTMLElement;
|
||||
export class PyRepl extends BaseEvalElement {
|
||||
editor: EditorView;
|
||||
editorNode: HTMLElement;
|
||||
code: string;
|
||||
cm: any;
|
||||
btnConfig: HTMLElement;
|
||||
btnRun: HTMLElement;
|
||||
editorOut: HTMLElement; //HTMLTextAreaElement;
|
||||
theme: string;
|
||||
// editorState: EditorState;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// attach shadow so we can preserve the element original innerHtml content
|
||||
this.shadow = this.attachShadow({ mode: 'open'});
|
||||
|
||||
this.wrapper = document.createElement('slot');
|
||||
|
||||
// add an extra div where we can attach the codemirror editor
|
||||
this.editorNode = document.createElement('div');
|
||||
addClasses(this.editorNode, ["editor-box"])
|
||||
@@ -111,7 +98,7 @@ export class PyRepl extends HTMLElement {
|
||||
})
|
||||
|
||||
let mainDiv = document.createElement('div');
|
||||
addClasses(mainDiv, ["parentBox", "group", "flex", "flex-col", "mt-10", "border-2", "border-gray-200", "rounded-lg"])
|
||||
addClasses(mainDiv, ["parentBox", "group", "flex", "flex-col", "mt-2", "border-2", "border-gray-200", "rounded-lg"])
|
||||
// add Editor to main PyScript div
|
||||
|
||||
// Butons DIV
|
||||
@@ -145,7 +132,7 @@ export class PyRepl extends HTMLElement {
|
||||
|
||||
currentComponentDetails.set([
|
||||
{key: "auto-generate", value: true},
|
||||
{key:"target", value: "default"},
|
||||
{key:"output", value: "default"},
|
||||
{key: "source", value: "self"},
|
||||
{key: "output-mode", value: "clear"}
|
||||
])
|
||||
@@ -171,78 +158,73 @@ export class PyRepl extends HTMLElement {
|
||||
this.setAttribute("root", this.id);
|
||||
}
|
||||
|
||||
if (this.hasAttribute('target')) {
|
||||
this.editorOut = document.getElementById(this.getAttribute('target'));
|
||||
if (this.hasAttribute('output')) {
|
||||
this.errorElement = this.outputElement = document.getElementById(this.getAttribute('output'));
|
||||
|
||||
// in this case, the default output-mode is append, if hasn't been specified
|
||||
if (!this.hasAttribute('output-mode')) {
|
||||
this.setAttribute('output-mode', 'append');
|
||||
}
|
||||
}else{
|
||||
// Editor Output Div
|
||||
this.editorOut = document.createElement('div');
|
||||
this.editorOut.classList.add("output");
|
||||
this.editorOut.hidden = true;
|
||||
this.editorOut.id = this.id + "-" + this.getAttribute("exec-id");
|
||||
if (this.hasAttribute('std-out')){
|
||||
this.outputElement = document.getElementById(this.getAttribute('std-out'));
|
||||
}else{
|
||||
// In this case neither output or std-out have been provided so we need
|
||||
// to create a new output div to output to
|
||||
this.outputElement = document.createElement('div');
|
||||
this.outputElement.classList.add("output");
|
||||
this.outputElement.hidden = true;
|
||||
this.outputElement.id = this.id + "-" + this.getAttribute("exec-id");
|
||||
|
||||
// add the output div id there's not target
|
||||
mainDiv.appendChild(this.editorOut);
|
||||
// add the output div id if there's not output pre-defined
|
||||
mainDiv.appendChild(this.outputElement);
|
||||
}
|
||||
|
||||
if (this.hasAttribute('std-err')){
|
||||
this.errorElement = document.getElementById(this.getAttribute('std-err'));
|
||||
}else{
|
||||
this.errorElement = this.outputElement;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.appendChild(mainDiv);
|
||||
this.editor.focus();
|
||||
console.log('connected');
|
||||
}
|
||||
|
||||
addToOutput(s: string) {
|
||||
this.editorOut.innerHTML += "<div>"+s+"</div>";
|
||||
this.editorOut.hidden = false;
|
||||
this.outputElement.innerHTML += "<div>"+s+"</div>";
|
||||
this.outputElement.hidden = false;
|
||||
}
|
||||
|
||||
async evaluate() {
|
||||
console.log('evaluate');
|
||||
let pyodide = await pyodideReadyPromise;
|
||||
// debugger
|
||||
try {
|
||||
// @ts-ignore
|
||||
let source = this.editor.state.doc.toString();
|
||||
let output;
|
||||
if (source.includes("asyncio")){
|
||||
output = await pyodide.runPythonAsync(source);
|
||||
}else{
|
||||
output = pyodide.runPython(source);
|
||||
}
|
||||
postEvaluate(): void {
|
||||
if (this.hasAttribute('auto-generate')) {
|
||||
let nextExecId = parseInt(this.getAttribute('exec-id')) + 1;
|
||||
const newPyRepl = document.createElement("py-repl");
|
||||
newPyRepl.setAttribute('root', this.getAttribute('root'));
|
||||
newPyRepl.id = this.getAttribute('root') + "-" + nextExecId.toString();
|
||||
newPyRepl.setAttribute('auto-generate', null);
|
||||
if (this.hasAttribute('output')){
|
||||
newPyRepl.setAttribute('output', this.getAttribute('output'));
|
||||
}
|
||||
if (this.hasAttribute('std-out')){
|
||||
newPyRepl.setAttribute('std-out', this.getAttribute('std-out'));
|
||||
}
|
||||
if (this.hasAttribute('std-err')){
|
||||
newPyRepl.setAttribute('std-err', this.getAttribute('std-err'));
|
||||
}
|
||||
|
||||
if (output !== undefined){
|
||||
let Element = pyodide.globals.get('Element');
|
||||
let out = Element(this.editorOut.id);
|
||||
// @ts-ignore
|
||||
out.write(output);
|
||||
out.write.callKwargs(output, { append : false});
|
||||
|
||||
if (!this.hasAttribute('target')) {
|
||||
this.editorOut.hidden = false;
|
||||
}
|
||||
// this.addToOutput(output);
|
||||
}
|
||||
|
||||
if (this.hasAttribute('auto-generate')) {
|
||||
let nextExecId = parseInt(this.getAttribute('exec-id')) + 1;
|
||||
const newPyRepl = document.createElement("py-repl");
|
||||
newPyRepl.setAttribute('root', this.getAttribute('root'));
|
||||
newPyRepl.id = this.getAttribute('root') + "-" + nextExecId.toString();
|
||||
newPyRepl.setAttribute('auto-generate', null);
|
||||
if (this.hasAttribute('target')){
|
||||
newPyRepl.setAttribute('target', this.getAttribute('target'));
|
||||
}
|
||||
|
||||
newPyRepl.setAttribute('exec-id', nextExecId.toString());
|
||||
this.parentElement.appendChild(newPyRepl);
|
||||
}
|
||||
} catch (err) {
|
||||
this.addToOutput(err);
|
||||
}
|
||||
newPyRepl.setAttribute('exec-id', nextExecId.toString());
|
||||
this.parentElement.appendChild(newPyRepl);
|
||||
}
|
||||
}
|
||||
|
||||
getSourceFromElement(): string {
|
||||
const sourceStrings = [`output_manager.change("`+this.outputElement.id+`")`,
|
||||
...this.editor.state.doc.toString().split("\n")];
|
||||
return sourceStrings.join('\n')
|
||||
}
|
||||
|
||||
render(){
|
||||
console.log('rendered');
|
||||
@@ -250,4 +232,3 @@ export class PyRepl extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { oneDarkTheme } from "@codemirror/theme-one-dark";
|
||||
|
||||
import { pyodideLoaded, loadedEnvironments, componentDetailsNavOpen, currentComponentDetails, mode, addToScriptsQueue, addInitializer, addPostInitializer } from '../stores';
|
||||
import { addClasses } from '../utils';
|
||||
import { BaseEvalElement } from './base';
|
||||
|
||||
// Premise used to connect to the first available pyodide interpreter
|
||||
let pyodideReadyPromise;
|
||||
@@ -50,13 +51,15 @@ type PyodideInterface = {
|
||||
registerJsModule(name: string, module: object): void
|
||||
}
|
||||
|
||||
// TODO: This should be used as base for generic scripts that need exectutoin
|
||||
// from PyScript to initializers, etc...
|
||||
class Script {
|
||||
source: string;
|
||||
state: string;
|
||||
target: string;
|
||||
output: string;
|
||||
|
||||
constructor(source: string, target: string) {
|
||||
this.target = target;
|
||||
constructor(source: string, output: string) {
|
||||
this.output = output;
|
||||
this.source = source;
|
||||
this.state = 'waiting';
|
||||
}
|
||||
@@ -75,7 +78,7 @@ class Script {
|
||||
output = pyodide.runPython(this.source);
|
||||
}
|
||||
|
||||
if (this.target){
|
||||
if (this.output){
|
||||
// this.editorOut.innerHTML = s;
|
||||
}
|
||||
// if (output !== undefined){
|
||||
@@ -90,30 +93,12 @@ class Script {
|
||||
}
|
||||
}
|
||||
|
||||
export class PyScript extends HTMLElement {
|
||||
shadow: ShadowRoot;
|
||||
wrapper: HTMLElement;
|
||||
editor: EditorView;
|
||||
editorNode: HTMLElement;
|
||||
code: string;
|
||||
cm: any;
|
||||
btnConfig: HTMLElement;
|
||||
btnRun: HTMLElement;
|
||||
editorOut: HTMLElement; //HTMLTextAreaElement;
|
||||
source: string;
|
||||
// editorState: EditorState;
|
||||
export class PyScript extends BaseEvalElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// attach shadow so we can preserve the element original innerHtml content
|
||||
this.shadow = this.attachShadow({ mode: 'open'});
|
||||
|
||||
this.wrapper = document.createElement('slot');
|
||||
|
||||
// add an extra div where we can attach the codemirror editor
|
||||
this.editorNode = document.createElement('div');
|
||||
addClasses(this.editorNode, ["editor-box"])
|
||||
this.shadow.appendChild(this.wrapper);
|
||||
}
|
||||
|
||||
@@ -140,11 +125,6 @@ export class PyScript extends HTMLElement {
|
||||
]
|
||||
})
|
||||
|
||||
this.editor = new EditorView({
|
||||
state: startState,
|
||||
parent: this.editorNode
|
||||
})
|
||||
|
||||
let mainDiv = document.createElement('div');
|
||||
addClasses(mainDiv, ["parentBox", "flex", "flex-col", "border-4", "border-dashed", "border-gray-200", "rounded-lg"])
|
||||
// add Editor to main PyScript div
|
||||
@@ -180,7 +160,7 @@ export class PyScript extends HTMLElement {
|
||||
|
||||
currentComponentDetails.set([
|
||||
{key: "auto-generate", value: true},
|
||||
{key:"target", value: "default"},
|
||||
{key:"output", value: "default"},
|
||||
{key: "source", value: "self"}
|
||||
])
|
||||
}
|
||||
@@ -190,18 +170,34 @@ export class PyScript extends HTMLElement {
|
||||
eDiv.appendChild(this.btnConfig);
|
||||
|
||||
mainDiv.appendChild(eDiv);
|
||||
mainDiv.appendChild(this.editorNode);
|
||||
|
||||
if (this.hasAttribute('target')) {
|
||||
this.editorOut = document.getElementById(this.getAttribute('target'));
|
||||
if (this.hasAttribute('output')) {
|
||||
this.errorElement = this.outputElement = document.getElementById(this.getAttribute('output'));
|
||||
|
||||
// in this case, the default output-mode is append, if hasn't been specified
|
||||
if (!this.hasAttribute('output-mode')) {
|
||||
this.setAttribute('output-mode', 'append');
|
||||
}
|
||||
}else{
|
||||
// Editor Output Div
|
||||
this.editorOut = document.createElement('div');
|
||||
this.editorOut.classList.add("output");
|
||||
this.editorOut.hidden = true;
|
||||
if (this.hasAttribute('std-out')){
|
||||
this.outputElement = document.getElementById(this.getAttribute('std-out'));
|
||||
}else{
|
||||
// In this case neither output or std-out have been provided so we need
|
||||
// to create a new output div to output to
|
||||
this.outputElement = document.createElement('div');
|
||||
this.outputElement.classList.add("output");
|
||||
this.outputElement.hidden = true;
|
||||
this.outputElement.id = this.id + "-" + this.getAttribute("exec-id");
|
||||
|
||||
// add the output div id there's not target
|
||||
mainDiv.appendChild(this.editorOut);
|
||||
// add the output div id if there's not output pre-defined
|
||||
mainDiv.appendChild(this.outputElement);
|
||||
}
|
||||
|
||||
if (this.hasAttribute('std-err')){
|
||||
this.outputElement = document.getElementById(this.getAttribute('std-err'));
|
||||
}else{
|
||||
this.errorElement = this.outputElement;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentMode=="edit"){
|
||||
@@ -217,35 +213,6 @@ export class PyScript extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
addToOutput(s: string) {
|
||||
this.editorOut.innerHTML = s;
|
||||
this.editorOut.hidden = false;
|
||||
}
|
||||
|
||||
async loadFromFile(s: string){
|
||||
let pyodide = await pyodideReadyPromise;
|
||||
let response = await fetch(s);
|
||||
this.code = await response.text();
|
||||
|
||||
await pyodide.runPythonAsync(this.code);
|
||||
await pyodide.runPythonAsync(`
|
||||
from pyodide.http import pyfetch
|
||||
from pyodide import eval_code
|
||||
response = await pyfetch("`+s+`")
|
||||
content = await response.bytes()
|
||||
|
||||
with open("todo.py", "wb") as f:
|
||||
print(content)
|
||||
f.write(content)
|
||||
print("done writing")
|
||||
`)
|
||||
// let pkg = pyodide.pyimport("todo");
|
||||
// pyodide.runPython(`
|
||||
// import todo
|
||||
// `)
|
||||
// pkg.do_something();
|
||||
}
|
||||
|
||||
protected async _register_esm(pyodide: PyodideInterface): Promise<void> {
|
||||
for (const node of document.querySelectorAll("script[type='importmap']")) {
|
||||
const importmap = (() => {
|
||||
@@ -278,64 +245,8 @@ export class PyScript extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
async evaluate(): Promise<void> {
|
||||
console.log('evaluate');
|
||||
|
||||
if (this.source){
|
||||
this.loadFromFile(this.source)
|
||||
}else{
|
||||
const pyodide = await pyodideReadyPromise;
|
||||
await this._register_esm(pyodide)
|
||||
// debugger
|
||||
try {
|
||||
function ltrim(code: string): string {
|
||||
const lines = code.split("\n")
|
||||
if (lines.length == 0)
|
||||
return code
|
||||
|
||||
const lengths = lines
|
||||
.filter((line) => line.trim().length != 0)
|
||||
.map((line) => {
|
||||
const [prefix] = line.match(/^\s*/)
|
||||
return prefix.length
|
||||
})
|
||||
|
||||
const k = Math.min(...lengths)
|
||||
|
||||
if (k != 0)
|
||||
return lines.map((line) => line.substring(k)).join("\n")
|
||||
else
|
||||
return code
|
||||
}
|
||||
|
||||
const str = this.editor.state.doc.toString()
|
||||
const source = htmlDecode(ltrim(str))
|
||||
|
||||
let output
|
||||
if (source.includes("asyncio"))
|
||||
output = await pyodide.runPythonAsync(source)
|
||||
else
|
||||
output = pyodide.runPython(source)
|
||||
|
||||
if (output !== undefined) {
|
||||
this.addToOutput(output)
|
||||
}
|
||||
|
||||
if (this.hasAttribute('auto-generate') && this.parentElement.lastChild === this) {
|
||||
const newPyscript = document.createElement("py-script");
|
||||
newPyscript.setAttribute('auto-generate', null);
|
||||
this.parentElement.appendChild(newPyscript);
|
||||
}
|
||||
} catch (err) {
|
||||
this.addToOutput(err);
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
console.log('rendered');
|
||||
|
||||
getSourceFromElement(): string {
|
||||
return htmlDecode(this.code);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ let pyodide;
|
||||
let additional_definitions = `
|
||||
from js import document, setInterval, console
|
||||
import asyncio
|
||||
import io, base64
|
||||
import io, base64, sys
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
@@ -22,6 +22,8 @@ class PyScript:
|
||||
if append:
|
||||
child = document.createElement('div');
|
||||
element = document.querySelector(f'#{element_id}');
|
||||
if not element:
|
||||
return
|
||||
exec_id = exec_id or element.childElementCount + 1
|
||||
element_id = child.id = f"{element_id}-{exec_id}";
|
||||
element.appendChild(child);
|
||||
@@ -34,11 +36,9 @@ class PyScript:
|
||||
img_str = 'data:image/png;base64,' + base64.b64encode(buf.read()).decode('UTF-8')
|
||||
document.getElementById(element_id).innerHTML = f'<div><img id="plt" src="{img_str}"/></div>'
|
||||
elif hasattr(value, "startswith") and value.startswith("data:image"):
|
||||
console.log(f"DATA/IMAGE: {value}")
|
||||
document.getElementById(element_id).innerHTML = f'<div><img id="plt" src="{value}"/></div>'
|
||||
else:
|
||||
document.getElementById(element_id).innerHTML = value;
|
||||
console.log(f"ELSE: {append} ==> {element_id} --> {value}")
|
||||
|
||||
@staticmethod
|
||||
def run_until_complete(f):
|
||||
@@ -95,7 +95,57 @@ class Element:
|
||||
|
||||
return Element(clone.id, clone)
|
||||
|
||||
class OutputCtxManager:
|
||||
def __init__(self, out=None, output_to_console=True, append=True):
|
||||
self._out = out
|
||||
self._prev = out
|
||||
self.output_to_console = output_to_console
|
||||
self._append = append
|
||||
|
||||
def change(self, out=None, err=None, output_to_console=True, append=True):
|
||||
self._prevt = self._out
|
||||
self._out = out
|
||||
self.output_to_console = output_to_console
|
||||
self._append = append
|
||||
console.log("----> changed out to", self._out, self._append)
|
||||
|
||||
def revert(self):
|
||||
console.log("----> reverted")
|
||||
self._out = self._prev
|
||||
|
||||
def write(self, txt):
|
||||
console.log('writing to', self._out, txt, self._append)
|
||||
if self._out:
|
||||
pyscript.write(self._out, txt, append=self._append)
|
||||
if self.output_to_console:
|
||||
console.log(self._out, txt)
|
||||
|
||||
class OutputManager:
|
||||
def __init__(self, out=None, err=None, output_to_console=True, append=True):
|
||||
sys.stdout = self._out_manager = OutputCtxManager(out, output_to_console, append)
|
||||
sys.strerr = self._err_manager = OutputCtxManager(err, output_to_console, append)
|
||||
self.output_to_console = output_to_console
|
||||
self._append = append
|
||||
|
||||
def change(self, out=None, err=None, output_to_console=True, append=True):
|
||||
self._out_manager.change(out, output_to_console, append)
|
||||
sys.stdout = self._out_manager
|
||||
self._err_manager.change(err, output_to_console, append)
|
||||
sys.stderr = self._err_manager
|
||||
self.output_to_console = output_to_console
|
||||
self.append = append
|
||||
|
||||
def revert(self):
|
||||
self._out_manager.revert()
|
||||
self._err_manager.revert()
|
||||
sys.stdout = self._out_manager
|
||||
sys.stdout = self._err_manager
|
||||
console.log("----> reverted")
|
||||
|
||||
|
||||
pyscript = PyScript()
|
||||
output_manager = OutputManager()
|
||||
|
||||
`
|
||||
|
||||
let loadInterpreter = async function(): Promise<any> {
|
||||
|
||||
@@ -2,5 +2,28 @@
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules/*", "__sapper__/*", "public/*"]
|
||||
"exclude": ["node_modules/*", "__sapper__/*", "public/*"],
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"target": "es2017",
|
||||
"module": "esnext",
|
||||
/**
|
||||
Svelte Preprocess cannot figure out whether you have a value or a type, so tell TypeScript
|
||||
to enforce using `import type` instead of `import` for Types.
|
||||
*/
|
||||
"importsNotUsedAsValues": "error",
|
||||
"isolatedModules": true,
|
||||
/**
|
||||
To have warnings/errors of the Svelte compiler at the correct position,
|
||||
enable source maps by default.
|
||||
*/
|
||||
"sourceMap": true,
|
||||
/** Requests the runtime types from the svelte modules by default. Needed for TS files or else you get errors. */
|
||||
"types": ["svelte"],
|
||||
|
||||
"strict": false,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user