Skip to content

Commit 69830bb

Browse files
authored
Merge pull request RustPython#1675 from RustPython/coolreader18/demo-improvements
Demo improvements
2 parents 9e936eb + 049df44 commit 69830bb

10 files changed

Lines changed: 364 additions & 230 deletions

File tree

‎wasm/demo/package.json‎

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,13 @@
99
"xterm": "^3.8.0"
1010
},
1111
"devDependencies": {
12-
"@wasm-tool/wasm-pack-plugin": "0.2.0",
13-
"copy-webpack-plugin": "^4.5.2",
14-
"css-loader": "^2.0.1",
12+
"@wasm-tool/wasm-pack-plugin": "^1.1.0",
13+
"clean-webpack-plugin": "^3.0.0",
14+
"css-loader": "^3.4.1",
1515
"html-webpack-plugin": "^3.2.0",
16-
"mini-css-extract-plugin": "^0.5.0",
17-
"raw-loader": "^1.0.0",
16+
"mini-css-extract-plugin": "^0.9.0",
17+
"raw-loader": "^4.0.0",
1818
"serve": "^11.0.2",
19-
"start-server-and-test": "^1.7.11",
2019
"webpack": "^4.16.3",
2120
"webpack-cli": "^3.1.0",
2221
"webpack-dev-server": "^3.1.5"
@@ -25,8 +24,7 @@
2524
"dev": "webpack-dev-server -d",
2625
"build": "webpack",
2726
"dist": "webpack --mode production",
28-
"test": "cd ../tests; pipenv run pytest",
29-
"ci": "start-server-and-test 'npm run build && serve dist/ -n -p 8080' :8080 test"
27+
"test": "webpack && cd ../tests && pipenv run pytest"
3028
},
3129
"repository": {
3230
"type": "git",

‎wasm/demo/snippets/asyncbrowser.py‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import browser
2+
import functools
3+
4+
5+
# just setting up the framework, skip to the bottom to see the real code
6+
7+
ready = object()
8+
go = object()
9+
10+
11+
def run(coro, *, payload=None, error=False):
12+
send = coro.throw if error else coro.send
13+
try:
14+
cmd = send(payload)
15+
except StopIteration:
16+
return
17+
if cmd is ready:
18+
coro.send(
19+
(
20+
lambda *args: run(coro, payload=args),
21+
lambda *args: run(coro, payload=args, error=True),
22+
)
23+
)
24+
elif cmd is go:
25+
pass
26+
else:
27+
raise RuntimeError(f"expected cmd to be ready or go, got {cmd}")
28+
29+
30+
class JSFuture:
31+
def __init__(self, prom):
32+
self._prom = prom
33+
34+
def __await__(self):
35+
done, error = yield ready
36+
self._prom.then(done, error)
37+
res, = yield go
38+
return res
39+
40+
41+
def wrap_prom_func(func):
42+
@functools.wraps(func)
43+
async def wrapper(*args, **kwargs):
44+
return await JSFuture(func(*args, **kwargs))
45+
46+
return wrapper
47+
48+
49+
fetch = wrap_prom_func(browser.fetch)
50+
51+
###################
52+
# Real code start #
53+
###################
54+
55+
56+
async def main(delay):
57+
url = f"https://httpbin.org/delay/{delay}"
58+
print(f"fetching {url}...")
59+
res = await fetch(
60+
url, response_format="json", headers={"X-Header-Thing": "rustpython is neat!"}
61+
)
62+
print(f"got res from {res['url']}:")
63+
print(res, end="\n\n")
64+
65+
66+
for delay in range(3):
67+
run(main(delay))
68+
print()

‎wasm/demo/src/index.ejs‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
<p>
1010
RustPython is a Python interpreter written in Rust. This demo is
1111
compiled from Rust to WebAssembly so it runs in the browser.<br>
12-
Please input your Python code below and click <kbd>Run</kbd>
12+
Input your Python code below and click <kbd>Run</kbd>
1313
(or <kbd>Ctrl+Enter</kbd>), or you can open up your
14-
browser's devtools and play with <code>rp.pyEval('print("a")')</code>
14+
browser's devtools and play with <code>rp.pyEval('1 + 1')</code>
1515
</p>
1616
<div id="code-wrapper">
1717
<textarea id="code">
@@ -31,9 +31,13 @@
3131
<textarea id="console" readonly>Loading...</textarea>
3232

3333
<h3>Interactive shell</h3>
34-
<div id="terminal"></div>
34+
<div id="terminal"></div>
3535

36-
<p>Here's some info regarding the <code>rp.pyEval()</code> function</p>
36+
<p>
37+
Here's some info regarding the <code>rp.pyEval()</code>,
38+
<code>rp.pyExec()</code>, and <code>rp.pyExecSingle()</code>
39+
functions
40+
</p>
3741
<ul>
3842
<li>
3943
You can return variables from python and get them returned to

‎wasm/demo/src/index.js‎

Lines changed: 154 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,158 @@
11
import './style.css';
2-
import 'codemirror/lib/codemirror.css';
32
import 'xterm/lib/xterm.css';
3+
import CodeMirror from 'codemirror';
4+
import 'codemirror/mode/python/python';
5+
import 'codemirror/addon/comment/comment';
6+
import 'codemirror/lib/codemirror.css';
7+
import { Terminal } from 'xterm';
8+
import LocalEchoController from 'local-echo';
9+
10+
let rp;
411

5-
// A dependency graph that contains any wasm must all be imported
6-
// asynchronously. This `index.js` file does the single async import, so
7-
// that no one else needs to worry about it again.
8-
import('./main.js').catch(e => {
9-
console.error('Error importing `main.js`:', e);
10-
document.getElementById('error').textContent = e;
12+
// A dependency graph that contains any wasm must be imported asynchronously.
13+
import('rustpython')
14+
.then(rustpy => {
15+
rp = rustpy;
16+
// so people can play around with it
17+
window.rp = rustpy;
18+
onReady();
19+
})
20+
.catch(e => {
21+
console.error('Error importing `rustpython`:', e);
22+
document.getElementById('error').textContent = e;
23+
});
24+
25+
const editor = CodeMirror.fromTextArea(document.getElementById('code'), {
26+
extraKeys: {
27+
'Ctrl-Enter': runCodeFromTextarea,
28+
'Cmd-Enter': runCodeFromTextarea,
29+
'Shift-Tab': 'indentLess',
30+
'Ctrl-/': 'toggleComment',
31+
'Cmd-/': 'toggleComment',
32+
Tab: editor => {
33+
var spaces = Array(editor.getOption('indentUnit') + 1).join(' ');
34+
editor.replaceSelection(spaces);
35+
}
36+
},
37+
lineNumbers: true,
38+
mode: 'text/x-python',
39+
indentUnit: 4,
40+
autofocus: true
1141
});
42+
43+
const consoleElement = document.getElementById('console');
44+
const errorElement = document.getElementById('error');
45+
46+
function runCodeFromTextarea() {
47+
// Clean the console and errors
48+
consoleElement.value = '';
49+
errorElement.textContent = '';
50+
51+
const code = editor.getValue();
52+
try {
53+
rp.pyExec(code, {
54+
stdout: output => {
55+
const shouldScroll =
56+
consoleElement.scrollHeight - consoleElement.scrollTop ===
57+
consoleElement.clientHeight;
58+
consoleElement.value += output;
59+
if (shouldScroll) {
60+
consoleElement.scrollTop = consoleElement.scrollHeight;
61+
}
62+
}
63+
});
64+
} catch (err) {
65+
if (err instanceof WebAssembly.RuntimeError) {
66+
err = window.__RUSTPYTHON_ERROR || err;
67+
}
68+
errorElement.textContent = err;
69+
console.error(err);
70+
}
71+
}
72+
73+
const snippets = document.getElementById('snippets');
74+
75+
function updateSnippet() {
76+
const selected = snippets.value;
77+
78+
// the require here creates a webpack context; it's fine to use it
79+
// dynamically.
80+
// https://webpack.js.org/guides/dependency-management/
81+
const {
82+
default: snippet
83+
} = require(`raw-loader!../snippets/${selected}.py`);
84+
85+
editor.setValue(snippet);
86+
runCodeFromTextarea();
87+
}
88+
89+
const term = new Terminal();
90+
term.open(document.getElementById('terminal'));
91+
92+
const localEcho = new LocalEchoController(term);
93+
94+
let terminalVM;
95+
96+
function getPrompt(name) {
97+
terminalVM.exec(`
98+
try:
99+
import sys as __sys
100+
__prompt = __sys.${name}
101+
except:
102+
__prompt = ''
103+
finally:
104+
del __sys
105+
`);
106+
return String(terminalVM.eval('__prompt'));
107+
}
108+
109+
async function readPrompts() {
110+
let continuing = false;
111+
112+
while (true) {
113+
const ps1 = getPrompt('ps1');
114+
const ps2 = getPrompt('ps2');
115+
let input;
116+
if (continuing) {
117+
const prom = localEcho.read(ps2, ps2);
118+
localEcho._activePrompt.prompt = ps1;
119+
localEcho._input = localEcho.history.entries.pop() + '\n';
120+
localEcho._cursor = localEcho._input.length;
121+
localEcho._active = true;
122+
input = await prom;
123+
if (!input.endsWith('\n')) continue;
124+
} else {
125+
input = await localEcho.read(ps1, ps2);
126+
}
127+
try {
128+
terminalVM.execSingle(input);
129+
} catch (err) {
130+
if (err instanceof SyntaxError && err.message.includes('EOF')) {
131+
continuing = true;
132+
continue;
133+
} else if (err instanceof WebAssembly.RuntimeError) {
134+
err = window.__RUSTPYTHON_ERROR || err;
135+
}
136+
localEcho.println(err);
137+
}
138+
continuing = false;
139+
}
140+
}
141+
142+
function onReady() {
143+
snippets.addEventListener('change', updateSnippet);
144+
document
145+
.getElementById('run-btn')
146+
.addEventListener('click', runCodeFromTextarea);
147+
// Run once for demo
148+
runCodeFromTextarea();
149+
150+
terminalVM = rp.vmStore.init('term_vm');
151+
terminalVM.setStdout(data => localEcho.println(data));
152+
readPrompts().catch(err => console.error(err));
153+
154+
// so that the test knows that we're ready
155+
const readyElement = document.createElement('div');
156+
readyElement.id = 'rp_loaded';
157+
document.head.appendChild(readyElement);
158+
}

0 commit comments

Comments
 (0)