mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
notebook example inspired by the now abandoned alpha.iodide.io, wasm + rustpython
This commit is contained in:
1 parent
b569ccf240
commit
6b8aea057d
5 files changed
+392
No files matched your search
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "app",
|
||||
"version": "1.0.0",
|
||||
"description": "Bindings to the RustPython library for WebAssembly",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"codemirror": "^5.42.0",
|
||||
"local-echo": "^0.2.0",
|
||||
"xterm": "^3.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@wasm-tool/wasm-pack-plugin": "^1.1.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"css-loader": "^3.4.1",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"raw-loader": "^4.0.0",
|
||||
"serve": "^11.0.2",
|
||||
"webpack": "^4.16.3",
|
||||
"webpack-cli": "^3.1.0",
|
||||
"webpack-dev-server": "^3.1.5"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "webpack-dev-server -d",
|
||||
"build": "webpack",
|
||||
"dist": "webpack --mode production",
|
||||
"test": "webpack --mode production && cd ../tests && pipenv run pytest"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/RustPython/RustPython.git"
|
||||
},
|
||||
"author": "Ryan Liddle",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/RustPython/RustPython/issues"
|
||||
},
|
||||
"homepage": "https://github.com/RustPython/RustPython#readme"
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link defer async href="https://fonts.googleapis.com/css?family=Fira+Sans|Sen:800&display=swap" rel="stylesheet">
|
||||
<title>🐍 RustPython Notebook</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- navigation bar -->
|
||||
<div class="nav-bar">
|
||||
<div>
|
||||
<button id="run-btn">run notebook ► </button>
|
||||
<button id="snippet-btn" class="secondary-btn mr-1">import code</button>
|
||||
</div>
|
||||
<div class="header">RustPython 🐍 😱 🤘</div>
|
||||
<div><a href="https://github.com/RustPython/">github</a></div>
|
||||
</div>
|
||||
|
||||
<!-- user interface elements for importing code -->
|
||||
<div id="url-container" class="d-none">
|
||||
<div><label for="url">public url: (uses github api file url for now)</label></div>
|
||||
<div>
|
||||
<input type="url" name="url" id="snippet-url">
|
||||
</div>
|
||||
<div>
|
||||
<button id="fetch-code" class="secondary-btn">fetch</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- code editor and output display -->
|
||||
<!-- split view -->
|
||||
<div class="split-view full-height">
|
||||
<textarea id="code"></textarea>
|
||||
<div id="console"></div>
|
||||
</div>
|
||||
|
||||
<!-- errors and keyboard shortcuts -->
|
||||
<div class="split-view">
|
||||
<div>
|
||||
<mark>errors</mark>
|
||||
<div id="error"></div>
|
||||
</div>
|
||||
<div class="text-small mt-1">
|
||||
<mark>Keyboard Shortcuts:</mark>
|
||||
<div>
|
||||
<div>Run code: 'Ctrl-Enter' or 'Cmd-Enter'</div>
|
||||
<div>Decrease Indent: 'Shift-Tab' </div>
|
||||
<div>Toggle comment: 'Ctrl-/' or 'Cmd-/'</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,114 @@
|
||||
import './style.css';
|
||||
import CodeMirror from 'codemirror';
|
||||
import 'codemirror/mode/python/python';
|
||||
import 'codemirror/addon/comment/comment';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
|
||||
let rp;
|
||||
|
||||
// UI elements
|
||||
const consoleElement = document.getElementById('console');
|
||||
const errorElement = document.getElementById('error');
|
||||
const fetchbtnElement = document.getElementById("fetch-code");
|
||||
const urlConainerElement = document.getElementById('url-container');
|
||||
|
||||
// A dependency graph that contains any wasm must be imported asynchronously.
|
||||
import('rustpython')
|
||||
.then(rustpy => {
|
||||
rp = rustpy;
|
||||
// so people can play around with it
|
||||
window.rp = rustpy;
|
||||
onReady();
|
||||
})
|
||||
.catch(e => {
|
||||
console.error('Error importing `rustpython`:', e);
|
||||
document.getElementById('error').textContent = e;
|
||||
});
|
||||
|
||||
// Code Mirror code editor
|
||||
const editor = CodeMirror.fromTextArea(document.getElementById('code'), {
|
||||
extraKeys: {
|
||||
'Ctrl-Enter': runCodeFromTextarea,
|
||||
'Cmd-Enter': runCodeFromTextarea,
|
||||
'Shift-Tab': 'indentLess',
|
||||
'Ctrl-/': 'toggleComment',
|
||||
'Cmd-/': 'toggleComment',
|
||||
Tab: editor => {
|
||||
var spaces = Array(editor.getOption('indentUnit') + 1).join(' ');
|
||||
editor.replaceSelection(spaces);
|
||||
}
|
||||
},
|
||||
lineNumbers: true,
|
||||
mode: 'text/x-python',
|
||||
indentUnit: 4,
|
||||
autofocus: true
|
||||
});
|
||||
|
||||
// Runs the code the the code editor
|
||||
function runCodeFromTextarea() {
|
||||
// Clean the console and errors
|
||||
consoleElement.innerHTML = '';
|
||||
errorElement.textContent = '';
|
||||
|
||||
const code = editor.getValue();
|
||||
try {
|
||||
rp.pyExec(code, {
|
||||
stdout: output => {
|
||||
consoleElement.innerHTML += output;
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof WebAssembly.RuntimeError) {
|
||||
err = window.__RUSTPYTHON_ERROR || err;
|
||||
}
|
||||
errorElement.textContent = err;
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
function onReady() {
|
||||
// snippets.addEventListener('change', updateSnippet);
|
||||
document
|
||||
.getElementById('run-btn')
|
||||
.addEventListener('click', runCodeFromTextarea);
|
||||
|
||||
// so that the test knows that we're ready
|
||||
const readyElement = document.createElement('div');
|
||||
readyElement.id = 'rp_loaded';
|
||||
document.head.appendChild(readyElement);
|
||||
}
|
||||
|
||||
// when clicking the import code button
|
||||
// show a UI with a url input + fetch button
|
||||
// only accepts api.github.com urls (for now)
|
||||
// add another function to parse a regular url
|
||||
fetchbtnElement.addEventListener("click", function () {
|
||||
// https://developer.github.com/v3/repos/contents/#get-repository-content
|
||||
// Format:
|
||||
// https://api.github.com/repos/username/reponame/contents/filename.py
|
||||
let url = document
|
||||
.getElementById('snippet-url')
|
||||
.value;
|
||||
// minimal js fetch code
|
||||
// needs better error handling
|
||||
fetch(url)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
// The Python code is in data.content
|
||||
// it is encoded with Base64. Use atob to decode it.
|
||||
//https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/atob
|
||||
var decodedData = atob(data.content);
|
||||
// set the value of the code editor
|
||||
editor.setValue(decodedData);
|
||||
urlConainerElement.classList.add("d-none");
|
||||
}).catch(err => {
|
||||
document
|
||||
.getElementById("errors")
|
||||
.innerHTML = "Couldn't fetch code. Make sure the link is public."
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
document.getElementById("snippet-btn").addEventListener("click", function () {
|
||||
urlConainerElement.classList.remove("d-none");
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
.header {
|
||||
font-family: 'Sen', sans-serif;
|
||||
;
|
||||
}
|
||||
|
||||
.d-flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.d-none {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mr-1 {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.mt-1 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.text-small {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
mark {
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
border-bottom: 2px solid #F74C00;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: sticky;
|
||||
top: 0px;
|
||||
background-color: #fff;
|
||||
height: 20px;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.split-view {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.split-view div {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
|
||||
.full-height {
|
||||
height: calc(90vh - 60px);
|
||||
}
|
||||
|
||||
#console {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
height: 90% !important;
|
||||
border-right: 1px solid gray;
|
||||
}
|
||||
|
||||
#run-btn,
|
||||
.secondary-btn {
|
||||
border: 0px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#run-btn {
|
||||
background-color: #F74C00;
|
||||
}
|
||||
|
||||
input[type="url"] {
|
||||
border: 1px solid black;
|
||||
border-radius: 0px;
|
||||
width: 55%;
|
||||
margin: 5px 0px 5px 0px;
|
||||
font-size: 0.7rem;
|
||||
padding: 4px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.url-container {
|
||||
padding: 10px;
|
||||
background-color: white;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.secondary-btn {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.secondary-btn.active,
|
||||
.secondary-btn:hover {
|
||||
background-color: #F74C00;
|
||||
}
|
||||
|
||||
#error {
|
||||
color: tomato;
|
||||
margin-top: 10px;
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
#code-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin');
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const interval = setInterval(() => console.log('keepalive'), 1000 * 60 * 5);
|
||||
|
||||
module.exports = (env = {}) => {
|
||||
const config = {
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: 'index.js'
|
||||
},
|
||||
mode: 'development',
|
||||
resolve: {
|
||||
alias: {
|
||||
rustpython: path.resolve(
|
||||
__dirname,
|
||||
env.rustpythonPkg || '../lib/pkg'
|
||||
)
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [MiniCssExtractPlugin.loader, 'css-loader']
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: 'src/index.ejs',
|
||||
templateParameters: {
|
||||
snippets: fs
|
||||
.readdirSync(path.join(__dirname, 'snippets'))
|
||||
.map(filename =>
|
||||
path.basename(filename, path.extname(filename))
|
||||
),
|
||||
defaultSnippetName: 'fibonacci',
|
||||
defaultSnippet: fs.readFileSync(
|
||||
path.join(__dirname, 'snippets/fibonacci.py')
|
||||
)
|
||||
}
|
||||
}),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'styles.css'
|
||||
}),
|
||||
{
|
||||
apply(compiler) {
|
||||
compiler.hooks.done.tap('clearInterval', () => {
|
||||
clearInterval(interval);
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
if (!env.noWasmPack) {
|
||||
config.plugins.push(
|
||||
new WasmPackPlugin({
|
||||
crateDirectory: path.join(__dirname, '../lib')
|
||||
})
|
||||
);
|
||||
}
|
||||
return config;
|
||||
};
|
||||
Reference in new issue
Block a user