From f4178588b73e9798dfd79165d22072f0f64bd881 Mon Sep 17 00:00:00 2001 From: Radon Rosborough Date: Sat, 6 Jun 2020 15:47:12 -0600 Subject: [PATCH] You can run Python code now --- backend/src/api.ts | 48 ++++++++++++++++-- backend/src/langs.ts | 35 +++++++------ frontend/src/app.ts | 3 ++ package.json | 3 +- yarn.lock | 118 ++++++++----------------------------------- 5 files changed, 85 insertions(+), 122 deletions(-) diff --git a/backend/src/api.ts b/backend/src/api.ts index 00aa8ae..c4e4baf 100644 --- a/backend/src/api.ts +++ b/backend/src/api.ts @@ -1,10 +1,14 @@ +import * as fs from "fs"; import * as pty from "node-pty"; import { IPty } from "node-pty"; +import * as path from "path"; +import * as tmp from "tmp"; import * as WebSocket from "ws"; import { LangConfig, langs } from "./langs"; export class Session { + code: string; config: LangConfig; term: IPty; ws: WebSocket; @@ -13,13 +17,14 @@ export class Session { this.ws = ws; this.config = langs[lang]; this.term = null; + this.code = ""; this.ws.send( JSON.stringify({ event: "setMonacoLanguage", monacoLanguage: this.config.monacoLang, }) ); - this.run(); + this.run().catch(console.error); ws.on("message", this.handleClientMessage); } handleClientMessage = (event: string) => { @@ -30,7 +35,7 @@ export class Session { console.error(`failed to parse client message: ${msg}`); return; } - switch (msg.event) { + switch (msg?.event) { case "terminalInput": if (!this.term) { console.error(`terminalInput: no terminal`); @@ -40,19 +45,52 @@ export class Session { this.term.write(msg.input); } break; + case "runCode": + if (typeof msg.code !== "string") { + console.error(`runCode: missing or malformed code field`); + } else { + this.code = msg.code; + this.run(); + } + break; default: console.error(`unknown client message type: ${msg.event}`); break; } }; - run = () => { - const cmdline = this.config.cmdline; + run = async () => { + const { repl, file, run } = this.config; if (this.term) { this.term.kill(); } + this.ws.send(JSON.stringify({ event: "terminalClear" })); + const tmpdir: string = await new Promise((resolve, reject) => + tmp.dir({ unsafeCleanup: true }, (err, path) => { + if (err) { + reject(err); + } else { + resolve(path); + } + }) + ); + let cmdline: string[]; + if (this.code || !repl) { + await new Promise((resolve, reject) => + fs.writeFile(path.resolve(tmpdir, file), this.code, (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }) + ); + cmdline = run; + } else { + cmdline = repl; + } this.term = pty.spawn(cmdline[0], cmdline.slice(1), { name: "xterm-color", - cwd: process.env.PWD, + cwd: tmpdir, env: process.env, }); this.term.on("data", (data) => diff --git a/backend/src/langs.ts b/backend/src/langs.ts index a364d54..812ae57 100644 --- a/backend/src/langs.ts +++ b/backend/src/langs.ts @@ -1,92 +1,91 @@ export interface LangConfig { - cmdline: string[]; + repl?: string[]; + file?: string; + run?: string[]; monacoLang: string; name: string; } export const langs = { bash: { - cmdline: ["bash"], + repl: ["bash"], name: "Bash", monacoLang: "shell", }, c: { - cmdline: ["echo", "not implemented"], name: "C", monacoLang: "c", }, "c++": { - cmdline: ["echo", "not implemented"], name: "C++", monacoLang: "cpp", }, clojure: { - cmdline: ["clojure"], + repl: ["clojure"], name: "Clojure", monacoLang: "clojure", }, emacs: { - cmdline: ["emacs"], + repl: ["emacs"], name: "Emacs Lisp", monacoLang: "plaintext", }, fish: { - cmdline: ["env", "SHELL=/usr/bin/fish", "fish"], + repl: ["env", "SHELL=/usr/bin/fish", "fish"], name: "Fish", monacoLang: "plaintext", }, go: { - cmdline: ["echo", "not implemented"], name: "Go", monacoLang: "go", }, haskell: { - cmdline: ["ghci"], + repl: ["ghci"], name: "Haskell", monacoLang: "plaintext", }, java: { - cmdline: ["echo", "not implemented"], name: "Java", monacoLang: "java", }, julia: { - cmdline: ["julia"], + repl: ["julia"], name: "Julia", monacoLang: "plaintext", }, lua: { - cmdline: ["lua"], + repl: ["lua"], name: "Lua", monacoLang: "lua", }, nodejs: { - cmdline: ["node"], + repl: ["node"], name: "Node.js", monacoLang: "javascript", }, python: { - cmdline: ["python3"], + repl: ["python3", "-u"], + file: "main.py", + run: ["python3", "-u", "-i", "main.py"], name: "Python", monacoLang: "python", }, ruby: { - cmdline: ["irb"], + repl: ["irb"], name: "Ruby", monacoLang: "ruby", }, rust: { - cmdline: ["echo", "not implemented"], name: "Rust", monacoLang: "rust", }, vim: { - cmdline: ["vim"], + repl: ["vim"], name: "Vimscript", monacoLang: "plaintext", }, zsh: { - cmdline: ["env", "SHELL=/usr/bin/zsh", "zsh"], + repl: ["env", "SHELL=/usr/bin/zsh", "zsh"], name: "Zsh", monacoLang: "shell", }, diff --git a/frontend/src/app.ts b/frontend/src/app.ts index 0fc0f12..db206e3 100644 --- a/frontend/src/app.ts +++ b/frontend/src/app.ts @@ -32,6 +32,9 @@ socket.addEventListener("message", (event) => { return; } switch (message?.event) { + case "terminalClear": + term.reset(); + return; case "terminalOutput": if (typeof message.output !== "string") { console.error("Unexpected message from server:", message); diff --git a/package.json b/package.json index aa21f70..44516e6 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@types/app-root-path": "^1.2.4", "@types/express": "^4.17.6", "@types/express-ws": "^3.0.0", + "@types/tmp": "^0.2.0", "app-root-path": "^3.0.0", "css-loader": "^3.5.3", "express": "^4.17.1", @@ -16,8 +17,8 @@ "monaco-editor": "^0.20.0", "node-pty": "^0.9.0", "style-loader": "^1.2.1", + "tmp": "^0.2.1", "ts-loader": "^7.0.5", - "ttf-loader": "^1.0.2", "typescript": "^3.9.5", "webpack": "^4.43.0", "webpack-cli": "^3.3.11", diff --git a/yarn.lock b/yarn.lock index 76eeb4e..1759012 100644 --- a/yarn.lock +++ b/yarn.lock @@ -83,6 +83,11 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" +"@types/tmp@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.0.tgz#e3f52b4d7397eaa9193592ef3fdd44dc0af4298c" + integrity sha512-flgpHJjntpBAdJD43ShRosQvNC0ME97DCfGvZEDlAThQmnerRXrLbX6YgzRBQCZTthET9eAWFAMaYP0m0Y4HzQ== + "@types/ws@*": version "7.2.5" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.2.5.tgz#513f28b04a1ea1aa9dc2cad3f26e8e37c88aae49" @@ -316,13 +321,6 @@ aproba@^1.1.1: resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== -argparse@^1.0.6: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -385,14 +383,6 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -babel-runtime@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" - balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -817,11 +807,6 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= -core-js@^2.4.0: - version "2.6.11" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" - integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== - core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -1751,11 +1736,6 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-in-browser@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" - integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU= - is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -1812,11 +1792,6 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -"js-tokens@^3.0.0 || ^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -1841,15 +1816,6 @@ json5@^2.1.2: dependencies: minimist "^1.2.5" -jss@^9.5.1: - version "9.8.7" - resolved "https://registry.yarnpkg.com/jss/-/jss-9.8.7.tgz#ed9763fc0f2f0260fc8260dac657af61e622ce05" - integrity sha512-awj3XRZYxbrmmrx9LUSj5pXSUfm12m8xzi/VKeqI1ZwWBtQ0kVPTs3vYs32t4rFw83CgFDukA8wKzOE9sMQnoQ== - dependencies: - is-in-browser "^1.1.3" - symbol-observable "^1.1.0" - warning "^3.0.0" - kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -1921,13 +1887,6 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" -loose-envify@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -2011,11 +1970,6 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -microbuffer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/microbuffer/-/microbuffer-1.0.0.tgz#8b3832ed40c87d51f47bb234913a698a756d19d2" - integrity sha1-izgy7UDIfVH0e7I0kTppinVtGdI= - micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" @@ -2335,7 +2289,7 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -pako@^1.0.0, pako@~1.0.5: +pako@~1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== @@ -2658,11 +2612,6 @@ readdirp@~3.4.0: dependencies: picomatch "^2.2.1" -regenerator-runtime@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" - integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== - regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" @@ -2733,6 +2682,13 @@ rimraf@^2.5.4, rimraf@^2.6.3: dependencies: glob "^7.1.3" +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -2960,11 +2916,6 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - ssri@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" @@ -3074,11 +3025,6 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -symbol-observable@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" - integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== - tapable@^1.0.0, tapable@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" @@ -3123,6 +3069,13 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" +tmp@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" @@ -3181,25 +3134,6 @@ tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== -ttf-loader@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/ttf-loader/-/ttf-loader-1.0.2.tgz#ac1d5824ac9ff2fe3daaa9836170072e1fb6f6f2" - integrity sha512-IMlcqjkSxqfOD4UpPrJ+LGm98JQ5URDFami19mR7lfjNR1XAEoG93Xd3loGYUMSxuhfzSTxsnLXu8emIawx/6A== - dependencies: - babel-runtime "^6.26.0" - jss "^9.5.1" - ttf2woff "^2.0.1" - uuid "^3.2.1" - -ttf2woff@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/ttf2woff/-/ttf2woff-2.0.2.tgz#09a7cee59abd3c15282b57ed84ac7c7770749f1f" - integrity sha512-X68badwBjAy/+itU49scLjXUL094up+rHuYk+YAOTTBYSUMOmLZ7VyhZJuqQESj1gnyLAC2/5V8Euv+mExmyPA== - dependencies: - argparse "^1.0.6" - microbuffer "^1.0.0" - pako "^1.0.0" - tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" @@ -3319,11 +3253,6 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@^3.2.1: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - v8-compile-cache@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" @@ -3339,13 +3268,6 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== -warning@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c" - integrity sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w= - dependencies: - loose-envify "^1.0.0" - watchpack-chokidar2@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0"