updates!
1. Removed all codes which adds ability to execute programs and display results 2. Added more options for the save duration
This commit is contained in:
parent
ba55a222f5
commit
be4da1823f
|
@ -46,19 +46,6 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ammonia"
|
|
||||||
version = "3.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "64e6d1c7838db705c9b756557ee27c384ce695a1c51a6fe528784cb1c6840170"
|
|
||||||
dependencies = [
|
|
||||||
"html5ever",
|
|
||||||
"maplit",
|
|
||||||
"once_cell",
|
|
||||||
"tendril",
|
|
||||||
"url",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android_system_properties"
|
name = "android_system_properties"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
@ -257,7 +244,7 @@ checksum = "29c39203181991a7dd4343b8005bd804e7a9a37afb8ac070e43771e8c820bbde"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"chrono-tz-build",
|
"chrono-tz-build",
|
||||||
"phf 0.11.1",
|
"phf",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -267,8 +254,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6f509c3a87b33437b05e2458750a0700e5bdd6956176773e6c7d6dd15a283a0c"
|
checksum = "6f509c3a87b33437b05e2458750a0700e5bdd6956176773e6c7d6dd15a283a0c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"parse-zoneinfo",
|
"parse-zoneinfo",
|
||||||
"phf 0.11.1",
|
"phf",
|
||||||
"phf_codegen 0.11.1",
|
"phf_codegen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -674,16 +661,6 @@ version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futf"
|
|
||||||
version = "0.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843"
|
|
||||||
dependencies = [
|
|
||||||
"mac",
|
|
||||||
"new_debug_unreachable",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.1.31"
|
version = "0.1.31"
|
||||||
|
@ -920,20 +897,6 @@ dependencies = [
|
||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "html5ever"
|
|
||||||
version = "0.26.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7"
|
|
||||||
dependencies = [
|
|
||||||
"log",
|
|
||||||
"mac",
|
|
||||||
"markup5ever",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
|
@ -1271,32 +1234,6 @@ dependencies = [
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mac"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "maplit"
|
|
||||||
version = "1.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "markup5ever"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016"
|
|
||||||
dependencies = [
|
|
||||||
"log",
|
|
||||||
"phf 0.10.1",
|
|
||||||
"phf_codegen 0.10.0",
|
|
||||||
"string_cache",
|
|
||||||
"string_cache_codegen",
|
|
||||||
"tendril",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchers"
|
name = "matchers"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -1460,12 +1397,6 @@ dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "new_debug_unreachable"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "5.1.2"
|
version = "5.1.2"
|
||||||
|
@ -1668,7 +1599,6 @@ dependencies = [
|
||||||
name = "pastebinrun"
|
name = "pastebinrun"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ammonia",
|
|
||||||
"chrono",
|
"chrono",
|
||||||
"diesel",
|
"diesel",
|
||||||
"diesel_migrations",
|
"diesel_migrations",
|
||||||
|
@ -1765,32 +1695,13 @@ dependencies = [
|
||||||
"sha2",
|
"sha2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "phf"
|
|
||||||
version = "0.10.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
|
|
||||||
dependencies = [
|
|
||||||
"phf_shared 0.10.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf"
|
name = "phf"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c"
|
checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"phf_shared 0.11.1",
|
"phf_shared",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "phf_codegen"
|
|
||||||
version = "0.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
|
|
||||||
dependencies = [
|
|
||||||
"phf_generator 0.10.0",
|
|
||||||
"phf_shared 0.10.0",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1799,18 +1710,8 @@ version = "0.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770"
|
checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"phf_generator 0.11.1",
|
"phf_generator",
|
||||||
"phf_shared 0.11.1",
|
"phf_shared",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "phf_generator"
|
|
||||||
version = "0.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
|
|
||||||
dependencies = [
|
|
||||||
"phf_shared 0.10.0",
|
|
||||||
"rand",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1819,19 +1720,10 @@ version = "0.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf"
|
checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"phf_shared 0.11.1",
|
"phf_shared",
|
||||||
"rand",
|
"rand",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "phf_shared"
|
|
||||||
version = "0.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
|
|
||||||
dependencies = [
|
|
||||||
"siphasher",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf_shared"
|
name = "phf_shared"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
|
@ -1887,12 +1779,6 @@ dependencies = [
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "precomputed-hash"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.51"
|
version = "1.0.51"
|
||||||
|
@ -2562,32 +2448,6 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "string_cache"
|
|
||||||
version = "0.8.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08"
|
|
||||||
dependencies = [
|
|
||||||
"new_debug_unreachable",
|
|
||||||
"once_cell",
|
|
||||||
"parking_lot 0.12.1",
|
|
||||||
"phf_shared 0.10.0",
|
|
||||||
"precomputed-hash",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "string_cache_codegen"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988"
|
|
||||||
dependencies = [
|
|
||||||
"phf_generator 0.10.0",
|
|
||||||
"phf_shared 0.10.0",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
@ -2625,17 +2485,6 @@ dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tendril"
|
|
||||||
version = "0.4.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0"
|
|
||||||
dependencies = [
|
|
||||||
"futf",
|
|
||||||
"mac",
|
|
||||||
"utf-8",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tera"
|
name = "tera"
|
||||||
version = "1.17.1"
|
version = "1.17.1"
|
||||||
|
@ -2771,7 +2620,6 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"mio 0.8.5",
|
"mio 0.8.5",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"parking_lot 0.12.1",
|
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2",
|
"socket2",
|
||||||
|
@ -3119,12 +2967,6 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "utf-8"
|
|
||||||
version = "0.7.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -7,7 +7,6 @@ license = "AGPL-3.0-or-later"
|
||||||
build = "buildSrc/build.rs"
|
build = "buildSrc/build.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ammonia = "3.3.0"
|
|
||||||
chrono = "0.4.23"
|
chrono = "0.4.23"
|
||||||
diesel = { version = "1.4.6", features = ["chrono"] }
|
diesel = { version = "1.4.6", features = ["chrono"] }
|
||||||
diesel_migrations = "1.4.0"
|
diesel_migrations = "1.4.0"
|
||||||
|
@ -31,7 +30,7 @@ serde = { version = "1.0.152", features = ["derive"] }
|
||||||
serde_json = "1.0.93"
|
serde_json = "1.0.93"
|
||||||
serde_with = "2.2.0"
|
serde_with = "2.2.0"
|
||||||
time-parse = "0.2.0"
|
time-parse = "0.2.0"
|
||||||
tokio = { version = "1.25.0", features = ["full"] }
|
tokio = { version = "1.25.0", features = ["rt"] }
|
||||||
tokio-signal = "0.2.9"
|
tokio-signal = "0.2.9"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
// pastebin.run
|
|
||||||
// Copyright (C) 2022 Konrad Borowski
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import { createSignal } from "solid-js";
|
|
||||||
import { render, screen } from "solid-testing-library";
|
|
||||||
import { expect, test } from "vitest";
|
|
||||||
import StandardInput from "../js/views/StandardInput";
|
|
||||||
|
|
||||||
test("StandardInput can become visible", async () => {
|
|
||||||
const [visible, setVisible] = createSignal(false);
|
|
||||||
const [, setStandardInput] = createSignal("");
|
|
||||||
render(() => (
|
|
||||||
<StandardInput visible={visible} setStandardInput={setStandardInput} />
|
|
||||||
));
|
|
||||||
expect(screen.queryByRole("textbox")).toBeNull();
|
|
||||||
setVisible(true);
|
|
||||||
expect(screen.getByRole("textbox")).toBeEmptyDOMElement();
|
|
||||||
});
|
|
|
@ -18,8 +18,6 @@ import { render } from "solid-js/web";
|
||||||
import App from "./views/App";
|
import App from "./views/App";
|
||||||
|
|
||||||
export default function createEditor(editor: Element) {
|
export default function createEditor(editor: Element) {
|
||||||
const markdown = editor.querySelector("#markdown") as Element;
|
|
||||||
const languages = editor.querySelector("select") as HTMLSelectElement;
|
|
||||||
const autoDelete = editor.querySelector(".autodelete-text") as Element;
|
const autoDelete = editor.querySelector(".autodelete-text") as Element;
|
||||||
const rawPasteElement = editor.querySelector(".rawpaste-text") as Element;
|
const rawPasteElement = editor.querySelector(".rawpaste-text") as Element;
|
||||||
const code = (editor.querySelector("textarea") as HTMLTextAreaElement).value;
|
const code = (editor.querySelector("textarea") as HTMLTextAreaElement).value;
|
||||||
|
@ -27,8 +25,6 @@ export default function createEditor(editor: Element) {
|
||||||
render(
|
render(
|
||||||
() => (
|
() => (
|
||||||
<App
|
<App
|
||||||
markdown={markdown}
|
|
||||||
languages={languages}
|
|
||||||
autoDelete={autoDelete}
|
autoDelete={autoDelete}
|
||||||
rawPasteElement={rawPasteElement}
|
rawPasteElement={rawPasteElement}
|
||||||
code={code}
|
code={code}
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
// pastebin.run
|
|
||||||
// Copyright (C) 2022 Konrad Borowski
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
type Wrapper = {
|
|
||||||
identifier: string;
|
|
||||||
label: string;
|
|
||||||
isAsm: boolean;
|
|
||||||
isFormatter: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Wrapper;
|
|
|
@ -1,23 +0,0 @@
|
||||||
// pastebin.run
|
|
||||||
// Copyright (C) 2022 Konrad Borowski
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import Wrapper from "./Wrapper";
|
|
||||||
|
|
||||||
type WrapperOptions = {
|
|
||||||
wrapper: Wrapper;
|
|
||||||
compilerOptions: string;
|
|
||||||
};
|
|
||||||
export default WrapperOptions;
|
|
|
@ -39,5 +39,5 @@ export function getEditorTypeSignal(): Signal<string> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTabIndentationSignal(): Signal<string> {
|
export function getTabIndentationSignal(): Signal<string> {
|
||||||
return createLocalStorageState("tabIndentation", "false");
|
return createLocalStorageState("tabIndentation", "true");
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,60 +15,36 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { createSignal, Show } from "solid-js";
|
import { createSignal, Show } from "solid-js";
|
||||||
import WrapperOptions from "../models/WrapperOptions";
|
|
||||||
import Editor from "./Editor";
|
import Editor from "./Editor";
|
||||||
import LanguageSelector from "./LanguageSelector";
|
|
||||||
import Output from "./Output";
|
|
||||||
import StandardInput from "./StandardInput";
|
|
||||||
import WrapperBar from "./WrapperBar";
|
|
||||||
|
|
||||||
export default function App({
|
export default function App({
|
||||||
markdown,
|
|
||||||
languages,
|
|
||||||
autoDelete,
|
autoDelete,
|
||||||
rawPasteElement,
|
rawPasteElement,
|
||||||
code,
|
code,
|
||||||
}: {
|
}: {
|
||||||
markdown: Element | null;
|
|
||||||
languages: HTMLSelectElement;
|
|
||||||
autoDelete: Element | null;
|
autoDelete: Element | null;
|
||||||
rawPasteElement: Element | null;
|
rawPasteElement: Element | null;
|
||||||
code: string;
|
code: string;
|
||||||
}) {
|
}) {
|
||||||
let form: HTMLFormElement | undefined;
|
let form: HTMLFormElement | undefined;
|
||||||
const [isPaste, setIsPaste] = createSignal(true);
|
const [isPaste, setIsPaste] = createSignal(true);
|
||||||
const [currentLanguage, setCurrentLanguage] = createSignal(
|
const [_, setCodeView] = createSignal({ code: "" });
|
||||||
languages.selectedOptions[0]?.value
|
|
||||||
);
|
|
||||||
const [standardInputVisible, setStandardInputVisible] = createSignal(false);
|
|
||||||
const [codeView, setCodeView] = createSignal({ code: "" });
|
|
||||||
const [standardInput, setStandardInput] = createSignal("");
|
|
||||||
const [wrapperOptions, setWrapperOptions] = createSignal<WrapperOptions>();
|
|
||||||
const [label, setLabel] = createSignal<Element>();
|
const [label, setLabel] = createSignal<Element>();
|
||||||
return (
|
return (
|
||||||
<form action="/" method="post" ref={(e) => (form = e)}>
|
<form action="/" method="post" ref={(e) => (form = e)}>
|
||||||
{markdown}
|
|
||||||
<Show when={isPaste()}>{autoDelete}</Show>
|
<Show when={isPaste()}>{autoDelete}</Show>
|
||||||
<div id="toolbar">
|
<div id="toolbar">
|
||||||
<LanguageSelector
|
|
||||||
setCurrentLanguage={setCurrentLanguage}
|
|
||||||
languages={languages}
|
|
||||||
/>
|
|
||||||
<WrapperBar
|
|
||||||
currentLanguage={currentLanguage}
|
|
||||||
setStandardInputVisible={setStandardInputVisible}
|
|
||||||
codeView={codeView}
|
|
||||||
runEvaluation={(wrapper, compilerOptions) => {
|
|
||||||
setWrapperOptions();
|
|
||||||
setWrapperOptions({ wrapper, compilerOptions });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Show when={isPaste()}>{rawPasteElement}</Show>
|
<Show when={isPaste()}>{rawPasteElement}</Show>
|
||||||
<span id="right-buttons">
|
<span id="right-buttons">
|
||||||
<button type="submit" name="share" value="share24">
|
<select name="duration" id="duration">
|
||||||
Share (delete after 24 hours)
|
<option value="1h">1 Hour</option>
|
||||||
</button>{" "}
|
<option value="3h">3 Hours</option>
|
||||||
<button type="submit" name="share" value="share">
|
<option value="24h">24 Hours</option>
|
||||||
|
<option value="3d">3 Days</option>
|
||||||
|
<option value="7d">7 Days</option>
|
||||||
|
<option value="90d">90 Days</option>
|
||||||
|
</select>{" "}
|
||||||
|
<button type="submit" name="share">
|
||||||
Share
|
Share
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
@ -81,29 +57,13 @@ export default function App({
|
||||||
code={code}
|
code={code}
|
||||||
onInput={() => {
|
onInput={() => {
|
||||||
setIsPaste(false);
|
setIsPaste(false);
|
||||||
setWrapperOptions();
|
|
||||||
}}
|
}}
|
||||||
currentLanguage={currentLanguage}
|
|
||||||
form={form as HTMLFormElement}
|
form={form as HTMLFormElement}
|
||||||
setCodeView={setCodeView}
|
setCodeView={setCodeView}
|
||||||
setLabel={setLabel}
|
setLabel={setLabel}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<StandardInput
|
|
||||||
visible={standardInputVisible}
|
|
||||||
setStandardInput={setStandardInput}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<Show when={wrapperOptions()} keyed>
|
|
||||||
{(wrapperOptions) => (
|
|
||||||
<Output
|
|
||||||
codeView={codeView()}
|
|
||||||
stdin={standardInput()}
|
|
||||||
wrapperOptions={wrapperOptions}
|
|
||||||
setWrapperOptions={setWrapperOptions}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,29 +14,26 @@
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { EditorView, basicSetup } from "codemirror";
|
|
||||||
import { indentWithTab } from "@codemirror/commands";
|
import { indentWithTab } from "@codemirror/commands";
|
||||||
import { indentUnit } from "@codemirror/language";
|
|
||||||
import { Compartment } from "@codemirror/state";
|
import { Compartment } from "@codemirror/state";
|
||||||
import { keymap } from "@codemirror/view";
|
import { keymap } from "@codemirror/view";
|
||||||
|
import { basicSetup, EditorView } from "codemirror";
|
||||||
import {
|
import {
|
||||||
Accessor,
|
Accessor,
|
||||||
createEffect,
|
createEffect,
|
||||||
createUniqueId,
|
createUniqueId,
|
||||||
JSXElement,
|
JSXElement,
|
||||||
onCleanup,
|
onCleanup,
|
||||||
Setter,
|
Setter
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
import CodeView from "../../models/CodeView";
|
import CodeView from "../../models/CodeView";
|
||||||
import { getTabIndentationSignal } from "../../options";
|
import { getTabIndentationSignal } from "../../options";
|
||||||
import "./codemirror.css";
|
import "./codemirror.css";
|
||||||
import languagesMap from "./languages";
|
|
||||||
|
|
||||||
export default function CodeMirrorEditor({
|
export default function CodeMirrorEditor({
|
||||||
code,
|
code,
|
||||||
setCode,
|
setCode,
|
||||||
onInput,
|
onInput,
|
||||||
currentLanguage,
|
|
||||||
form,
|
form,
|
||||||
setCodeView,
|
setCodeView,
|
||||||
setLabel,
|
setLabel,
|
||||||
|
@ -44,7 +41,6 @@ export default function CodeMirrorEditor({
|
||||||
code: Accessor<string>;
|
code: Accessor<string>;
|
||||||
setCode: Setter<string>;
|
setCode: Setter<string>;
|
||||||
onInput(): void;
|
onInput(): void;
|
||||||
currentLanguage: Accessor<string>;
|
|
||||||
form: HTMLFormElement;
|
form: HTMLFormElement;
|
||||||
setCodeView: Setter<CodeView>;
|
setCodeView: Setter<CodeView>;
|
||||||
setLabel: Setter<JSXElement>;
|
setLabel: Setter<JSXElement>;
|
||||||
|
@ -56,7 +52,6 @@ export default function CodeMirrorEditor({
|
||||||
: [];
|
: [];
|
||||||
}
|
}
|
||||||
const tabIndentation = new Compartment();
|
const tabIndentation = new Compartment();
|
||||||
const language = new Compartment();
|
|
||||||
let avoidChangeNotifications = false;
|
let avoidChangeNotifications = false;
|
||||||
const labelId = createUniqueId();
|
const labelId = createUniqueId();
|
||||||
const view = new EditorView({
|
const view = new EditorView({
|
||||||
|
@ -72,8 +67,6 @@ export default function CodeMirrorEditor({
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
EditorView.lineWrapping,
|
EditorView.lineWrapping,
|
||||||
indentUnit.of(" ".repeat(4)),
|
|
||||||
language.of([]),
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
|
@ -81,13 +74,6 @@ export default function CodeMirrorEditor({
|
||||||
effects: tabIndentation.reconfigure(getTabIndentationExtension()),
|
effects: tabIndentation.reconfigure(getTabIndentationExtension()),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
createEffect(async () => {
|
|
||||||
const callback = languagesMap[currentLanguage()];
|
|
||||||
const extension = callback ? await callback() : [];
|
|
||||||
view.dispatch({
|
|
||||||
effects: language.reconfigure(extension),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const getValue = () => view.state.doc.toString();
|
const getValue = () => view.state.doc.toString();
|
||||||
setCodeView({
|
setCodeView({
|
||||||
get code() {
|
get code() {
|
||||||
|
|
|
@ -1,110 +0,0 @@
|
||||||
// pastebin.run
|
|
||||||
// Copyright (C) 2022 Konrad Borowski
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import { StreamLanguage } from "@codemirror/language";
|
|
||||||
import { Extension } from "@codemirror/state";
|
|
||||||
|
|
||||||
const languagesMap: { [name: string]: () => Promise<Extension> } = {
|
|
||||||
async c() {
|
|
||||||
return (await import("@codemirror/lang-cpp")).cpp();
|
|
||||||
},
|
|
||||||
async cpp() {
|
|
||||||
return (await import("@codemirror/lang-cpp")).cpp();
|
|
||||||
},
|
|
||||||
async csharp() {
|
|
||||||
return StreamLanguage.define(
|
|
||||||
(await import("@codemirror/legacy-modes/mode/clike")).csharp
|
|
||||||
);
|
|
||||||
},
|
|
||||||
async go() {
|
|
||||||
return StreamLanguage.define(
|
|
||||||
(await import("@codemirror/legacy-modes/mode/go")).go
|
|
||||||
);
|
|
||||||
},
|
|
||||||
async haskell() {
|
|
||||||
return StreamLanguage.define(
|
|
||||||
(await import("@codemirror/legacy-modes/mode/haskell")).haskell
|
|
||||||
);
|
|
||||||
},
|
|
||||||
async html() {
|
|
||||||
return (await import("@codemirror/lang-html")).html();
|
|
||||||
},
|
|
||||||
async java() {
|
|
||||||
return (await import("@codemirror/lang-java")).java();
|
|
||||||
},
|
|
||||||
async javascript() {
|
|
||||||
return (await import("@codemirror/lang-javascript")).javascript();
|
|
||||||
},
|
|
||||||
async jinja2() {
|
|
||||||
return StreamLanguage.define(
|
|
||||||
(await import("@codemirror/legacy-modes/mode/jinja2")).jinja2
|
|
||||||
);
|
|
||||||
},
|
|
||||||
async jsx() {
|
|
||||||
return (await import("@codemirror/lang-javascript")).javascript({
|
|
||||||
jsx: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async markdown() {
|
|
||||||
return (await import("./markdown")).default;
|
|
||||||
},
|
|
||||||
async nix() {
|
|
||||||
return (await import("@replit/codemirror-lang-nix")).nix();
|
|
||||||
},
|
|
||||||
async perl() {
|
|
||||||
return StreamLanguage.define(
|
|
||||||
(await import("@codemirror/legacy-modes/mode/perl")).perl
|
|
||||||
);
|
|
||||||
},
|
|
||||||
async php() {
|
|
||||||
return (await import("@codemirror/lang-php")).php();
|
|
||||||
},
|
|
||||||
async postgresql() {
|
|
||||||
const { sql, PostgreSQL } = await import("@codemirror/lang-sql");
|
|
||||||
return sql({ dialect: PostgreSQL });
|
|
||||||
},
|
|
||||||
async python() {
|
|
||||||
return (await import("@codemirror/lang-python")).python();
|
|
||||||
},
|
|
||||||
async rust() {
|
|
||||||
return (await import("@codemirror/lang-rust")).rust();
|
|
||||||
},
|
|
||||||
async sh() {
|
|
||||||
return StreamLanguage.define(
|
|
||||||
(await import("@codemirror/legacy-modes/mode/shell")).shell
|
|
||||||
);
|
|
||||||
},
|
|
||||||
async sql() {
|
|
||||||
return (await import("@codemirror/lang-sql")).sql();
|
|
||||||
},
|
|
||||||
async sqlite() {
|
|
||||||
const { sql, SQLite } = await import("@codemirror/lang-sql");
|
|
||||||
return sql({ dialect: SQLite });
|
|
||||||
},
|
|
||||||
async typescript() {
|
|
||||||
return (await import("@codemirror/lang-javascript")).javascript({
|
|
||||||
typescript: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async tsx() {
|
|
||||||
return (await import("@codemirror/lang-javascript")).javascript({
|
|
||||||
jsx: true,
|
|
||||||
typescript: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default languagesMap;
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { markdown } from "@codemirror/lang-markdown";
|
|
||||||
import { LanguageDescription } from "@codemirror/language";
|
|
||||||
import { languages } from "@codemirror/language-data";
|
|
||||||
|
|
||||||
export default markdown({
|
|
||||||
codeLanguages: languages.concat(
|
|
||||||
LanguageDescription.of({
|
|
||||||
name: "Nix",
|
|
||||||
extensions: ["nix"],
|
|
||||||
async load() {
|
|
||||||
return (await import("@replit/codemirror-lang-nix")).nix();
|
|
||||||
},
|
|
||||||
})
|
|
||||||
),
|
|
||||||
});
|
|
|
@ -15,11 +15,10 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Accessor,
|
|
||||||
createResource,
|
createResource,
|
||||||
createSignal,
|
createSignal,
|
||||||
JSXElement,
|
JSXElement,
|
||||||
Setter,
|
Setter
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
import CodeView from "../models/CodeView";
|
import CodeView from "../models/CodeView";
|
||||||
import { getEditorTypeSignal } from "../options";
|
import { getEditorTypeSignal } from "../options";
|
||||||
|
@ -28,14 +27,12 @@ import TextAreaEditor from "./TextAreaEditor";
|
||||||
export default function Editor({
|
export default function Editor({
|
||||||
code: initialCode,
|
code: initialCode,
|
||||||
onInput,
|
onInput,
|
||||||
currentLanguage,
|
|
||||||
form,
|
form,
|
||||||
setCodeView: setCodeView,
|
setCodeView: setCodeView,
|
||||||
setLabel,
|
setLabel,
|
||||||
}: {
|
}: {
|
||||||
code: string;
|
code: string;
|
||||||
onInput(): void;
|
onInput(): void;
|
||||||
currentLanguage: Accessor<string>;
|
|
||||||
form: HTMLFormElement;
|
form: HTMLFormElement;
|
||||||
setCodeView: Setter<CodeView>;
|
setCodeView: Setter<CodeView>;
|
||||||
setLabel: Setter<JSXElement>;
|
setLabel: Setter<JSXElement>;
|
||||||
|
@ -56,7 +53,6 @@ export default function Editor({
|
||||||
code,
|
code,
|
||||||
setCode,
|
setCode,
|
||||||
onInput,
|
onInput,
|
||||||
currentLanguage,
|
|
||||||
form,
|
form,
|
||||||
setCodeView,
|
setCodeView,
|
||||||
setLabel,
|
setLabel,
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
// pastebin.run
|
|
||||||
// Copyright (C) 2022 Konrad Borowski
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import { For, Setter } from "solid-js";
|
|
||||||
|
|
||||||
export default function LanguageSelector({
|
|
||||||
setCurrentLanguage,
|
|
||||||
languages,
|
|
||||||
}: {
|
|
||||||
setCurrentLanguage: Setter<string>;
|
|
||||||
languages: HTMLSelectElement;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div class="group">
|
|
||||||
<label>
|
|
||||||
{"Language: "}
|
|
||||||
<select
|
|
||||||
id="language"
|
|
||||||
name="language"
|
|
||||||
onChange={(e) =>
|
|
||||||
setCurrentLanguage(e.currentTarget.selectedOptions[0].value)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<For each={[...languages.options]}>
|
|
||||||
{(option) => (
|
|
||||||
<option value={option.value} selected={option.selected}>
|
|
||||||
{option.textContent}
|
|
||||||
</option>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
// pastebin.run
|
|
||||||
// Copyright (C) 2022 Konrad Borowski
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import {
|
|
||||||
createEffect,
|
|
||||||
createResource,
|
|
||||||
Match,
|
|
||||||
onCleanup,
|
|
||||||
Setter,
|
|
||||||
Switch,
|
|
||||||
} from "solid-js";
|
|
||||||
import CodeView from "../models/CodeView";
|
|
||||||
import WrapperOptions from "../models/WrapperOptions";
|
|
||||||
import OutputBox from "./OutputBox";
|
|
||||||
import Spinner from "./Spinner";
|
|
||||||
|
|
||||||
export default function Output(props: {
|
|
||||||
codeView: CodeView;
|
|
||||||
stdin: string;
|
|
||||||
wrapperOptions: WrapperOptions;
|
|
||||||
setWrapperOptions: Setter<WrapperOptions | undefined>;
|
|
||||||
}) {
|
|
||||||
const abortController = new AbortController();
|
|
||||||
const [output, { refetch }] = createResource(async () => {
|
|
||||||
const body = new URLSearchParams();
|
|
||||||
body.append("compilerOptions", props.wrapperOptions.compilerOptions);
|
|
||||||
body.append("code", props.codeView.code);
|
|
||||||
body.append("stdin", props.stdin);
|
|
||||||
return (
|
|
||||||
await fetch(`/api/v0/run/${props.wrapperOptions.wrapper.identifier}`, {
|
|
||||||
method: "POST",
|
|
||||||
body,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
},
|
|
||||||
signal: abortController.signal,
|
|
||||||
})
|
|
||||||
).json();
|
|
||||||
});
|
|
||||||
createEffect(() => {
|
|
||||||
if (props.wrapperOptions.wrapper.isFormatter && output()?.status === 0) {
|
|
||||||
props.codeView.code = output().output.replace(
|
|
||||||
/\x7F(?:E[^\x7F]*|O)?/g,
|
|
||||||
""
|
|
||||||
);
|
|
||||||
if (!output().output.includes("\x7F")) {
|
|
||||||
props.setWrapperOptions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
onCleanup(() => abortController.abort());
|
|
||||||
return (
|
|
||||||
<div id="outputcontainer">
|
|
||||||
<div id="output">
|
|
||||||
<Switch
|
|
||||||
fallback={
|
|
||||||
<OutputBox
|
|
||||||
output={output()}
|
|
||||||
wrapper={props.wrapperOptions.wrapper}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Match when={output.loading}>
|
|
||||||
<Spinner />
|
|
||||||
</Match>
|
|
||||||
<Match when={output.error}>
|
|
||||||
{"An error occured while running the code. "}
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
refetch();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Try again
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
// pastebin.run
|
|
||||||
// Copyright (C) 2022 Konrad Borowski
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import { For, Show } from "solid-js";
|
|
||||||
import Wrapper from "../models/Wrapper";
|
|
||||||
import { createLocalStorageState } from "../options";
|
|
||||||
|
|
||||||
function parseOutput(wrapper: Wrapper, output: string) {
|
|
||||||
return (
|
|
||||||
<For
|
|
||||||
each={("O" + output)
|
|
||||||
.split("\x7F")
|
|
||||||
.filter(
|
|
||||||
(x) => x.length > 1 && (!wrapper.isFormatter || !x.startsWith("O"))
|
|
||||||
)}
|
|
||||||
fallback={<i>(no output)</i>}
|
|
||||||
>
|
|
||||||
{(item) => {
|
|
||||||
const firstChar = item[0];
|
|
||||||
if (firstChar === "O") {
|
|
||||||
return item.substring(1);
|
|
||||||
}
|
|
||||||
if (firstChar === "E") {
|
|
||||||
return <span class="error">{item.substring(1)}</span>;
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
}}
|
|
||||||
</For>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function runAsmFilter(output: string) {
|
|
||||||
return output.replace(
|
|
||||||
/(?:\t\.(?:text|file|section|globl|p2align|type|cfi_.*|size|section)\b|.Lfunc_end).*\n?/g,
|
|
||||||
""
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function OutputBox({
|
|
||||||
output,
|
|
||||||
wrapper,
|
|
||||||
}: {
|
|
||||||
output: { output: string; status: number | null };
|
|
||||||
wrapper: Wrapper;
|
|
||||||
}) {
|
|
||||||
const [filterAsmDirectivesOrig, setFilterAsmDirectives] =
|
|
||||||
createLocalStorageState("filterAsmDirectives", "true");
|
|
||||||
const filterAsmDirectives = () => JSON.parse(filterAsmDirectivesOrig());
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div class="stdout-header">
|
|
||||||
<Show when={output.status}>
|
|
||||||
<h2>{`Output (exit code ${output.status})`}</h2>
|
|
||||||
</Show>
|
|
||||||
<Show when={wrapper.isAsm}>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={filterAsmDirectives()}
|
|
||||||
onChange={(e) =>
|
|
||||||
setFilterAsmDirectives(e.currentTarget.checked.toString())
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{" Filter assembler directives"}
|
|
||||||
</label>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
<pre>
|
|
||||||
{parseOutput(
|
|
||||||
wrapper,
|
|
||||||
wrapper.isAsm && filterAsmDirectives()
|
|
||||||
? runAsmFilter(output.output)
|
|
||||||
: output.output
|
|
||||||
)}
|
|
||||||
</pre>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
// pastebin.run
|
|
||||||
// Copyright (C) 2022 Konrad Borowski
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import "./spinner.css";
|
|
||||||
|
|
||||||
export default function Spinner() {
|
|
||||||
return <div class="spinner" />;
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
@keyframes spinner {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
animation: 1s linear infinite spinner;
|
|
||||||
border: solid 3px #f5f2f0;
|
|
||||||
border-bottom-color: #c7c75c;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
// pastebin.run
|
|
||||||
// Copyright (C) 2022 Konrad Borowski
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import { Accessor, Setter, Show } from "solid-js";
|
|
||||||
|
|
||||||
export default function StandardInput({
|
|
||||||
visible,
|
|
||||||
setStandardInput,
|
|
||||||
}: {
|
|
||||||
visible: Accessor<boolean>;
|
|
||||||
setStandardInput: Setter<string>;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<Show when={visible()}>
|
|
||||||
<details>
|
|
||||||
<summary>Standard input</summary>
|
|
||||||
<textarea
|
|
||||||
name="stdin"
|
|
||||||
onInput={(e) => setStandardInput(e.currentTarget.value)}
|
|
||||||
/>
|
|
||||||
</details>
|
|
||||||
</Show>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,125 +0,0 @@
|
||||||
// pastebin.run
|
|
||||||
// Copyright (C) 2022 Konrad Borowski
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import {
|
|
||||||
Accessor,
|
|
||||||
createEffect,
|
|
||||||
createResource,
|
|
||||||
createSignal,
|
|
||||||
For,
|
|
||||||
Setter,
|
|
||||||
Show,
|
|
||||||
} from "solid-js";
|
|
||||||
import CodeView from "../models/CodeView";
|
|
||||||
import Wrapper from "../models/Wrapper";
|
|
||||||
import WrapperBarButton from "./WrapperBarButton";
|
|
||||||
|
|
||||||
type Language = {
|
|
||||||
helloWorld: string;
|
|
||||||
implementations: {
|
|
||||||
label: string;
|
|
||||||
wrappers: Wrapper[];
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
|
|
||||||
async function fetchLanguage(language: string): Promise<Language> {
|
|
||||||
return (await fetch(`/api/v0/language/${language}`)).json();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function WrapperBar({
|
|
||||||
currentLanguage,
|
|
||||||
setStandardInputVisible,
|
|
||||||
codeView,
|
|
||||||
runEvaluation,
|
|
||||||
}: {
|
|
||||||
currentLanguage: Accessor<string>;
|
|
||||||
setStandardInputVisible: Setter<boolean>;
|
|
||||||
codeView: Accessor<CodeView>;
|
|
||||||
runEvaluation: (wrapper: Wrapper, compilerOptions: string) => void;
|
|
||||||
}) {
|
|
||||||
const [data] = createResource(currentLanguage, fetchLanguage);
|
|
||||||
const [currentImplementationIndex, setCurrentImplementationIndex] =
|
|
||||||
createSignal(0);
|
|
||||||
let previousHelloWorld = "";
|
|
||||||
createEffect(() => {
|
|
||||||
const loadedData = data();
|
|
||||||
if (loadedData) {
|
|
||||||
setStandardInputVisible(loadedData.implementations.length > 0);
|
|
||||||
const code = codeView().code;
|
|
||||||
if (!code || code === previousHelloWorld) {
|
|
||||||
previousHelloWorld = codeView().code = loadedData.helloWorld;
|
|
||||||
} else {
|
|
||||||
previousHelloWorld = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let compilerOptions: HTMLInputElement;
|
|
||||||
return (
|
|
||||||
<Show when={data()} keyed>
|
|
||||||
{(data) => (
|
|
||||||
<>
|
|
||||||
<div class="group">
|
|
||||||
<For
|
|
||||||
each={
|
|
||||||
data.implementations[currentImplementationIndex()]?.wrappers
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{(wrapper, index) => (
|
|
||||||
<>
|
|
||||||
{" "}
|
|
||||||
<WrapperBarButton
|
|
||||||
index={index()}
|
|
||||||
runEvaluation={() => {
|
|
||||||
runEvaluation(wrapper, compilerOptions.value);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{wrapper.label}
|
|
||||||
</WrapperBarButton>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
<Show when={data.implementations.length > 1}>
|
|
||||||
<div class="group">
|
|
||||||
<label>
|
|
||||||
{"Implementation: "}
|
|
||||||
<select
|
|
||||||
onChange={(e) =>
|
|
||||||
setCurrentImplementationIndex(+e.currentTarget.value)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<For each={data.implementations}>
|
|
||||||
{({ label }, index) => (
|
|
||||||
<option value={index()}>{label}</option>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
<Show when={data.implementations.length}>
|
|
||||||
<div class="group">
|
|
||||||
<label>
|
|
||||||
{"Compiler options: "}
|
|
||||||
<input ref={(e) => (compilerOptions = e)} />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Show>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
// pastebin.run
|
|
||||||
// Copyright (C) 2022 Konrad Borowski
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import { onCleanup } from "solid-js";
|
|
||||||
|
|
||||||
export default function WrapperBarButton({
|
|
||||||
index,
|
|
||||||
runEvaluation,
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
index: number;
|
|
||||||
runEvaluation(): void;
|
|
||||||
children: string;
|
|
||||||
}) {
|
|
||||||
if (index === 0) {
|
|
||||||
const globalKeyEvent = (e: KeyboardEvent) => {
|
|
||||||
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
|
|
||||||
e.preventDefault();
|
|
||||||
runEvaluation();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.addEventListener("keydown", globalKeyEvent);
|
|
||||||
onCleanup(() => document.removeEventListener("keydown", globalKeyEvent));
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
runEvaluation();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE pastes DROP COLUMN output;
|
||||||
|
ALTER TABLE pastes ADD COLUMN stdout text, ADD COLUMN stderr text;
|
|
@ -0,0 +1,5 @@
|
||||||
|
ALTER TABLE pastes
|
||||||
|
DROP COLUMN language_id,
|
||||||
|
DROP COLUMN stdin,
|
||||||
|
DROP COLUMN exit_code,
|
||||||
|
DROP COLUMN output;
|
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
|
@ -7,20 +7,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/commands": "^6.2.0",
|
"@codemirror/commands": "^6.2.0",
|
||||||
"@codemirror/lang-cpp": "^6.0.2",
|
|
||||||
"@codemirror/lang-html": "^6.4.2",
|
|
||||||
"@codemirror/lang-java": "^6.0.1",
|
|
||||||
"@codemirror/lang-javascript": "^6.1.3",
|
|
||||||
"@codemirror/lang-markdown": "^6.0.5",
|
|
||||||
"@codemirror/lang-php": "^6.0.1",
|
|
||||||
"@codemirror/lang-python": "^6.1.1",
|
|
||||||
"@codemirror/lang-rust": "^6.0.1",
|
|
||||||
"@codemirror/lang-sql": "^6.4.0",
|
|
||||||
"@codemirror/language": "^6.5.0",
|
|
||||||
"@codemirror/language-data": "^6.1.0",
|
|
||||||
"@codemirror/legacy-modes": "^6.3.1",
|
|
||||||
"@codemirror/view": "^6.8.1",
|
"@codemirror/view": "^6.8.1",
|
||||||
"@replit/codemirror-lang-nix": "^6.0.0",
|
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"solid-js": "^1.6.10"
|
"solid-js": "^1.6.10"
|
||||||
},
|
},
|
||||||
|
|
48
src/main.rs
48
src/main.rs
|
@ -19,15 +19,12 @@ extern crate diesel;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
|
|
||||||
mod migration;
|
|
||||||
mod models;
|
mod models;
|
||||||
mod routes;
|
mod routes;
|
||||||
mod schema;
|
mod schema;
|
||||||
|
|
||||||
use crate::models::paste::ExtraPasteParameters;
|
|
||||||
use crate::routes::{
|
use crate::routes::{
|
||||||
api_insert_paste, api_language, api_languages, config, display_paste, index, insert_paste,
|
api_insert_paste, config, display_paste, index, insert_paste, metrics, raw_paste,
|
||||||
metrics, raw_paste, run,
|
|
||||||
};
|
};
|
||||||
use chrono::{Duration, Utc};
|
use chrono::{Duration, Utc};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
@ -39,8 +36,9 @@ use rocket_dyn_templates::tera::{self, Value};
|
||||||
use rocket_dyn_templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
use rocket_sync_db_pools::database;
|
use rocket_sync_db_pools::database;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::{Read, Write};
|
use std::net::SocketAddr;
|
||||||
use std::net::{SocketAddr, TcpListener, TcpStream};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
use tokio::signal;
|
use tokio::signal;
|
||||||
use tokio::sync::oneshot::Sender;
|
use tokio::sync::oneshot::Sender;
|
||||||
|
@ -131,31 +129,35 @@ async fn shutdown_monitor(s: Sender<i32>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn cli() {
|
async fn cli() {
|
||||||
|
println!("starting cli");
|
||||||
let addr = "0.0.0.0:9000".parse::<SocketAddr>().unwrap();
|
let addr = "0.0.0.0:9000".parse::<SocketAddr>().unwrap();
|
||||||
let listener = TcpListener::bind(addr).unwrap();
|
let listener = TcpListener::bind(addr).await.unwrap();
|
||||||
let mut db = PgConnection::establish("postgres://postgres:ifd783farq@localhost/pastebin")
|
|
||||||
|
let mut db = PgConnection::establish(&std::env::var("POSTGRES_URL").unwrap())
|
||||||
.expect("error in establishing connection with postgresql");
|
.expect("error in establishing connection with postgresql");
|
||||||
|
|
||||||
|
let host = std::env::var("HOST").unwrap();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (socket, details) = listener.accept().unwrap();
|
let (socket, details) = listener.accept().await.unwrap();
|
||||||
|
|
||||||
println!("received connection from {:?}", details);
|
println!("received connection from {:?}", details);
|
||||||
save_paste(&mut db, socket).unwrap();
|
save_paste(&mut db, &host, socket).await.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const BUFFER_SIZE: usize = 128;
|
const BUFFER_SIZE: usize = 128;
|
||||||
const HOST: &str = "https://p.ishan.pw";
|
|
||||||
|
|
||||||
fn save_paste(
|
async fn save_paste(
|
||||||
db: &mut PgConnection,
|
db: &mut PgConnection,
|
||||||
|
host: &str,
|
||||||
mut socket: TcpStream,
|
mut socket: TcpStream,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut paste = vec![];
|
let mut paste = vec![];
|
||||||
let mut buffer = [0; BUFFER_SIZE];
|
let mut buffer = [0; BUFFER_SIZE];
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let read = socket.read(&mut buffer)?;
|
let read = socket.read(&mut buffer).await?;
|
||||||
if read < BUFFER_SIZE {
|
if read < BUFFER_SIZE {
|
||||||
paste.extend(&buffer[..read]);
|
paste.extend(&buffer[..read]);
|
||||||
break;
|
break;
|
||||||
|
@ -170,20 +172,20 @@ fn save_paste(
|
||||||
db,
|
db,
|
||||||
// Terminal paste should stay for 3 days
|
// Terminal paste should stay for 3 days
|
||||||
Some(Utc::now() + Duration::days(3)),
|
Some(Utc::now() + Duration::days(3)),
|
||||||
"plaintext",
|
|
||||||
&String::from_utf8_lossy(&paste),
|
&String::from_utf8_lossy(&paste),
|
||||||
ExtraPasteParameters {
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
socket.write_fmt(format_args!("{HOST}/{v}\n"))?;
|
let output = format!("{}/{}\n", host, v);
|
||||||
socket.flush().unwrap();
|
let output: Vec<u8> = output.into_bytes();
|
||||||
|
|
||||||
|
socket.write(&output).await?;
|
||||||
|
socket.flush().await.unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rocket() -> Rocket<Build> {
|
fn rocket() -> Rocket<Build> {
|
||||||
|
println!("starting rocket");
|
||||||
let mut rocket = rocket::build()
|
let mut rocket = rocket::build()
|
||||||
.attach(Template::custom(|engines| {
|
.attach(Template::custom(|engines| {
|
||||||
engines.tera.register_function("js_path", js_path);
|
engines.tera.register_function("js_path", js_path);
|
||||||
|
@ -196,10 +198,7 @@ fn rocket() -> Rocket<Build> {
|
||||||
Db::get_one(&rocket)
|
Db::get_one(&rocket)
|
||||||
.await
|
.await
|
||||||
.expect("a database")
|
.expect("a database")
|
||||||
.run(|conn| {
|
.run(|conn| diesel_migrations::run_pending_migrations(conn))
|
||||||
diesel_migrations::run_pending_migrations(conn)?;
|
|
||||||
migration::run(conn)
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.expect("database to be migrated");
|
.expect("database to be migrated");
|
||||||
rocket
|
rocket
|
||||||
|
@ -212,11 +211,8 @@ fn rocket() -> Rocket<Build> {
|
||||||
.mount(
|
.mount(
|
||||||
"/",
|
"/",
|
||||||
routes![
|
routes![
|
||||||
api_language,
|
|
||||||
api_languages,
|
|
||||||
api_insert_paste,
|
api_insert_paste,
|
||||||
config,
|
config,
|
||||||
run,
|
|
||||||
index,
|
index,
|
||||||
insert_paste,
|
insert_paste,
|
||||||
display_paste,
|
display_paste,
|
||||||
|
|
160
src/migration.rs
160
src/migration.rs
|
@ -1,160 +0,0 @@
|
||||||
// pastebin.run
|
|
||||||
// Copyright (C) 2020 Konrad Borowski
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
use crate::schema::{implementation_wrappers, implementations, languages};
|
|
||||||
use diesel::prelude::*;
|
|
||||||
use diesel::sql_types::{Bool, Integer, Text};
|
|
||||||
use serde::Deserialize;
|
|
||||||
use std::error::Error;
|
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct JsonLanguage {
|
|
||||||
identifier: String,
|
|
||||||
name: String,
|
|
||||||
#[serde(default)]
|
|
||||||
helloworld: String,
|
|
||||||
#[serde(default)]
|
|
||||||
implementations: Vec<Implementation>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Insertable)]
|
|
||||||
struct Language<'a> {
|
|
||||||
identifier: &'a str,
|
|
||||||
name: String,
|
|
||||||
priority: i32,
|
|
||||||
hello_world: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct Implementation {
|
|
||||||
label: String,
|
|
||||||
identifier: String,
|
|
||||||
#[serde(default)]
|
|
||||||
wrappers: Vec<Wrapper>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct Wrapper {
|
|
||||||
identifier: String,
|
|
||||||
label: String,
|
|
||||||
code: String,
|
|
||||||
#[serde(default)]
|
|
||||||
is_asm: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
is_formatter: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(connection: &PgConnection) -> Result<(), Box<dyn Error + Send + Sync>> {
|
|
||||||
let languages: Vec<JsonLanguage> = serde_json::from_slice(&fs::read("languages.json")?)?;
|
|
||||||
for JsonLanguage {
|
|
||||||
identifier: languages_identifier,
|
|
||||||
name,
|
|
||||||
helloworld: hello_world,
|
|
||||||
implementations,
|
|
||||||
} in languages
|
|
||||||
{
|
|
||||||
diesel::insert_into(languages::table)
|
|
||||||
.values(Language {
|
|
||||||
identifier: &languages_identifier,
|
|
||||||
name,
|
|
||||||
priority: 10,
|
|
||||||
hello_world: &hello_world,
|
|
||||||
})
|
|
||||||
.on_conflict(languages::identifier)
|
|
||||||
.do_update()
|
|
||||||
.set(languages::hello_world.eq(&hello_world))
|
|
||||||
.execute(connection)?;
|
|
||||||
for (
|
|
||||||
i,
|
|
||||||
Implementation {
|
|
||||||
label,
|
|
||||||
identifier: implementation_identifier,
|
|
||||||
wrappers,
|
|
||||||
},
|
|
||||||
) in (1..).zip(implementations)
|
|
||||||
{
|
|
||||||
languages::table
|
|
||||||
.filter(languages::identifier.eq(&languages_identifier))
|
|
||||||
.select((
|
|
||||||
languages::language_id,
|
|
||||||
label.as_sql::<Text>(),
|
|
||||||
implementation_identifier.as_sql::<Text>(),
|
|
||||||
i.as_sql::<Integer>(),
|
|
||||||
))
|
|
||||||
.insert_into(implementations::table)
|
|
||||||
.into_columns((
|
|
||||||
implementations::language_id,
|
|
||||||
implementations::label,
|
|
||||||
implementations::identifier,
|
|
||||||
implementations::ordering,
|
|
||||||
))
|
|
||||||
.on_conflict((implementations::language_id, implementations::identifier))
|
|
||||||
.do_update()
|
|
||||||
.set((
|
|
||||||
implementations::label.eq(&label),
|
|
||||||
implementations::ordering.eq(i),
|
|
||||||
))
|
|
||||||
.execute(connection)?;
|
|
||||||
for (
|
|
||||||
i,
|
|
||||||
Wrapper {
|
|
||||||
identifier,
|
|
||||||
label,
|
|
||||||
code,
|
|
||||||
is_asm,
|
|
||||||
is_formatter,
|
|
||||||
},
|
|
||||||
) in (1..).zip(wrappers)
|
|
||||||
{
|
|
||||||
languages::table
|
|
||||||
.inner_join(implementations::table)
|
|
||||||
.filter(languages::identifier.eq(&languages_identifier))
|
|
||||||
.filter(implementations::identifier.eq(&implementation_identifier))
|
|
||||||
.select((
|
|
||||||
implementations::implementation_id,
|
|
||||||
identifier.as_sql::<Text>(),
|
|
||||||
label.as_sql::<Text>(),
|
|
||||||
code.as_sql::<Text>(),
|
|
||||||
is_asm.as_sql::<Bool>(),
|
|
||||||
is_formatter.as_sql::<Bool>(),
|
|
||||||
i.as_sql::<Integer>(),
|
|
||||||
))
|
|
||||||
.insert_into(implementation_wrappers::table)
|
|
||||||
.into_columns((
|
|
||||||
implementation_wrappers::implementation_id,
|
|
||||||
implementation_wrappers::identifier,
|
|
||||||
implementation_wrappers::label,
|
|
||||||
implementation_wrappers::code,
|
|
||||||
implementation_wrappers::is_asm,
|
|
||||||
implementation_wrappers::is_formatter,
|
|
||||||
implementation_wrappers::ordering,
|
|
||||||
))
|
|
||||||
.on_conflict(implementation_wrappers::identifier)
|
|
||||||
.do_update()
|
|
||||||
.set((
|
|
||||||
implementation_wrappers::label.eq(&label),
|
|
||||||
implementation_wrappers::code.eq(&code),
|
|
||||||
implementation_wrappers::is_asm.eq(is_asm),
|
|
||||||
implementation_wrappers::is_formatter.eq(is_formatter),
|
|
||||||
implementation_wrappers::ordering.eq(i),
|
|
||||||
))
|
|
||||||
.execute(connection)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
// pastebin.run
|
|
||||||
// Copyright (C) 2020-2021 Konrad Borowski
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
use crate::schema::languages::dsl::*;
|
|
||||||
use diesel::prelude::*;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
#[derive(Queryable, Serialize)]
|
|
||||||
pub struct Language {
|
|
||||||
pub id: i32,
|
|
||||||
pub identifier: String,
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Language {
|
|
||||||
pub fn fetch(connection: &PgConnection) -> Result<Vec<Language>, diesel::result::Error> {
|
|
||||||
languages
|
|
||||||
.select((language_id, identifier, name))
|
|
||||||
.order((priority.asc(), name.asc()))
|
|
||||||
.load(connection)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,7 +15,6 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
mod cors;
|
mod cors;
|
||||||
pub mod language;
|
|
||||||
pub mod paste;
|
pub mod paste;
|
||||||
|
|
||||||
pub use cors::Cors;
|
pub use cors::Cors;
|
||||||
|
|
|
@ -14,32 +14,21 @@
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use crate::schema::{languages, pastes};
|
use crate::schema::pastes;
|
||||||
use ammonia::Builder;
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use itertools::Itertools;
|
|
||||||
use log::info;
|
use log::info;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use pulldown_cmark::{Options, Parser};
|
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use rocket::http::Status;
|
|
||||||
use rocket::response::{self, Debug, Responder};
|
use rocket::response::{self, Debug, Responder};
|
||||||
use rocket::Request;
|
use rocket::Request;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
use std::iter;
|
|
||||||
|
|
||||||
#[derive(Queryable)]
|
#[derive(Queryable)]
|
||||||
pub struct Paste {
|
pub struct Paste {
|
||||||
pub identifier: String,
|
pub identifier: String,
|
||||||
pub paste: String,
|
pub paste: String,
|
||||||
pub language_id: i32,
|
|
||||||
pub delete_at: Option<DateTime<Utc>>,
|
pub delete_at: Option<DateTime<Utc>>,
|
||||||
pub language_identifier: String,
|
|
||||||
pub stdin: String,
|
|
||||||
pub exit_code: Option<i32>,
|
|
||||||
pub output: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Paste {
|
impl Paste {
|
||||||
|
@ -61,24 +50,12 @@ const CHARACTERS: &[u8] = b"23456789bcdfghjkmnpqrstvwxz-";
|
||||||
struct InsertPaste<'a> {
|
struct InsertPaste<'a> {
|
||||||
identifier: &'a str,
|
identifier: &'a str,
|
||||||
delete_at: Option<DateTime<Utc>>,
|
delete_at: Option<DateTime<Utc>>,
|
||||||
language_id: i32,
|
|
||||||
paste: &'a str,
|
paste: &'a str,
|
||||||
stdin: &'a str,
|
|
||||||
output: Option<&'a str>,
|
|
||||||
exit_code: Option<i32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct ExtraPasteParameters<'a> {
|
|
||||||
pub stdin: &'a str,
|
|
||||||
pub output: Option<&'a str>,
|
|
||||||
pub exit_code: Option<i32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum InsertionError {
|
pub enum InsertionError {
|
||||||
Diesel(diesel::result::Error),
|
Diesel(diesel::result::Error),
|
||||||
UnrecognizedLanguageIdentifier,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<diesel::result::Error> for InsertionError {
|
impl From<diesel::result::Error> for InsertionError {
|
||||||
|
@ -91,7 +68,6 @@ impl<'r> Responder<'r, 'static> for InsertionError {
|
||||||
fn respond_to(self, request: &'r Request<'_>) -> response::Result<'static> {
|
fn respond_to(self, request: &'r Request<'_>) -> response::Result<'static> {
|
||||||
match self {
|
match self {
|
||||||
Self::Diesel(e) => Debug(e).respond_to(request),
|
Self::Diesel(e) => Debug(e).respond_to(request),
|
||||||
Self::UnrecognizedLanguageIdentifier => Err(Status::BadRequest),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,32 +75,17 @@ impl<'r> Responder<'r, 'static> for InsertionError {
|
||||||
pub fn insert(
|
pub fn insert(
|
||||||
connection: &PgConnection,
|
connection: &PgConnection,
|
||||||
delete_at: Option<DateTime<Utc>>,
|
delete_at: Option<DateTime<Utc>>,
|
||||||
language: &str,
|
|
||||||
paste: &str,
|
paste: &str,
|
||||||
ExtraPasteParameters {
|
|
||||||
stdin,
|
|
||||||
output,
|
|
||||||
exit_code,
|
|
||||||
}: ExtraPasteParameters,
|
|
||||||
) -> Result<String, InsertionError> {
|
) -> Result<String, InsertionError> {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let identifier: String = (0..12)
|
let identifier: String = (0..12)
|
||||||
.map(|_| char::from(*CHARACTERS.choose(&mut rng).expect("a random character")))
|
.map(|_| char::from(*CHARACTERS.choose(&mut rng).expect("a random character")))
|
||||||
.collect();
|
.collect();
|
||||||
let language_id = languages::table
|
|
||||||
.select(languages::language_id)
|
|
||||||
.filter(languages::identifier.eq(language))
|
|
||||||
.get_result(connection)
|
|
||||||
.optional()?
|
|
||||||
.ok_or(InsertionError::UnrecognizedLanguageIdentifier)?;
|
|
||||||
let insert_paste = InsertPaste {
|
let insert_paste = InsertPaste {
|
||||||
identifier: &identifier,
|
identifier: &identifier,
|
||||||
delete_at,
|
delete_at,
|
||||||
language_id,
|
|
||||||
paste,
|
paste,
|
||||||
stdin,
|
|
||||||
output,
|
|
||||||
exit_code,
|
|
||||||
};
|
};
|
||||||
diesel::insert_into(pastes::table)
|
diesel::insert_into(pastes::table)
|
||||||
.values(&insert_paste)
|
.values(&insert_paste)
|
||||||
|
@ -137,12 +98,7 @@ pub fn insert(
|
||||||
pub struct ExternPaste {
|
pub struct ExternPaste {
|
||||||
pub identifier: Option<String>,
|
pub identifier: Option<String>,
|
||||||
pub paste: String,
|
pub paste: String,
|
||||||
pub language_id: i32,
|
|
||||||
pub delete_at: Option<String>,
|
pub delete_at: Option<String>,
|
||||||
pub markdown: String,
|
|
||||||
pub stdin: String,
|
|
||||||
pub exit_code: Option<i32>,
|
|
||||||
pub output: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExternPaste {
|
impl ExternPaste {
|
||||||
|
@ -150,98 +106,12 @@ impl ExternPaste {
|
||||||
let Paste {
|
let Paste {
|
||||||
identifier,
|
identifier,
|
||||||
paste,
|
paste,
|
||||||
language_id,
|
|
||||||
language_identifier,
|
|
||||||
delete_at,
|
delete_at,
|
||||||
stdin,
|
|
||||||
exit_code,
|
|
||||||
output,
|
|
||||||
} = paste;
|
} = paste;
|
||||||
let markdown = if language_identifier == "markdown" {
|
|
||||||
render_markdown(&paste)
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
};
|
|
||||||
Self {
|
Self {
|
||||||
identifier: Some(identifier),
|
identifier: Some(identifier),
|
||||||
paste,
|
paste,
|
||||||
language_id,
|
|
||||||
delete_at: delete_at.map(|delete_at| delete_at.format("%Y-%m-%d %H:%M").to_string()),
|
delete_at: delete_at.map(|delete_at| delete_at.format("%Y-%m-%d %H:%M").to_string()),
|
||||||
markdown,
|
|
||||||
stdin,
|
|
||||||
exit_code,
|
|
||||||
output,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_markdown(markdown: &str) -> String {
|
|
||||||
static FILTER: Lazy<Builder<'static>> = Lazy::new(|| {
|
|
||||||
let mut builder = Builder::new();
|
|
||||||
builder.link_rel(Some("noopener noreferrer nofollow"));
|
|
||||||
builder.add_generic_attributes(iter::once("class"));
|
|
||||||
builder.attribute_filter(|_, attribute, value| {
|
|
||||||
if attribute == "class" {
|
|
||||||
// class attribute must have a value that is a set of space-separate tokens
|
|
||||||
// https://html.spec.whatwg.org/#global-attributes
|
|
||||||
//
|
|
||||||
// A set of space-separated tokens is a string containing zero or more words
|
|
||||||
// (known as tokens) separated by one or more ASCII whitespace, where words
|
|
||||||
// consist of any string of one or more characters, none of which are ASCII
|
|
||||||
// whitespace.
|
|
||||||
// https://html.spec.whatwg.org/#space-separated-tokens
|
|
||||||
//
|
|
||||||
// Rust uses the WhatWG Infra Standard’s definition of ASCII whitespace.
|
|
||||||
// https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_whitespace
|
|
||||||
Some(
|
|
||||||
value
|
|
||||||
.split_ascii_whitespace()
|
|
||||||
.filter(|value| value.starts_with("language-"))
|
|
||||||
.join(" ")
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Some(value.into())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
builder
|
|
||||||
});
|
|
||||||
let mut output = String::new();
|
|
||||||
let options = Options::ENABLE_TABLES | Options::ENABLE_STRIKETHROUGH;
|
|
||||||
pulldown_cmark::html::push_html(&mut output, Parser::new_ext(markdown, options));
|
|
||||||
FILTER.clean(&output).to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::render_markdown;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn markdown_works() {
|
|
||||||
assert_eq!(
|
|
||||||
render_markdown("**bold**"),
|
|
||||||
"<p><strong>bold</strong></p>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn strikethrough_works() {
|
|
||||||
assert_eq!(render_markdown("~~strike~~"), "<p><del>strike</del></p>\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn code_blocks_work() {
|
|
||||||
assert_eq!(
|
|
||||||
render_markdown("```rust\nfn main() {}\n```"),
|
|
||||||
"<pre><code class=\"language-rust\">fn main() {}\n</code></pre>\n",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn only_language_classes_are_allowed() {
|
|
||||||
assert_eq!(
|
|
||||||
render_markdown(r#"<br class="language-a madoka language-b homura">"#),
|
|
||||||
"<br class=\"language-a language-b\">",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use crate::models::paste::{self, ExtraPasteParameters, InsertionError};
|
use crate::models::paste::{self, InsertionError};
|
||||||
use crate::models::Cors;
|
use crate::models::Cors;
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
|
@ -26,8 +26,6 @@ use std::error::Error;
|
||||||
pub struct PasteForm {
|
pub struct PasteForm {
|
||||||
#[field(default = Expiration(None))]
|
#[field(default = Expiration(None))]
|
||||||
expiration: Expiration,
|
expiration: Expiration,
|
||||||
#[field(default = "plaintext")]
|
|
||||||
language: String,
|
|
||||||
code: String,
|
code: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,13 +50,7 @@ pub async fn api_insert_paste(
|
||||||
paste::insert(
|
paste::insert(
|
||||||
conn,
|
conn,
|
||||||
form.expiration.0.map(|expiration| Utc::now() + expiration),
|
form.expiration.0.map(|expiration| Utc::now() + expiration),
|
||||||
&form.language,
|
|
||||||
&form.code,
|
&form.code,
|
||||||
ExtraPasteParameters {
|
|
||||||
stdin: "",
|
|
||||||
output: None,
|
|
||||||
exit_code: None,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
@ -1,133 +0,0 @@
|
||||||
// pastebin.run
|
|
||||||
// Copyright (C) 2020-2021 Konrad Borowski
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
use crate::schema::{implementation_wrappers, implementations, languages};
|
|
||||||
use crate::Db;
|
|
||||||
use diesel::prelude::*;
|
|
||||||
use rocket::response::Debug;
|
|
||||||
use rocket::serde::json::Json;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
#[derive(Queryable)]
|
|
||||||
struct Language {
|
|
||||||
id: i32,
|
|
||||||
hello_world: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Queryable)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct Wrapper {
|
|
||||||
identifier: String,
|
|
||||||
label: String,
|
|
||||||
is_asm: bool,
|
|
||||||
is_formatter: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Identifiable, Queryable)]
|
|
||||||
struct Implementation {
|
|
||||||
id: i32,
|
|
||||||
label: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Associations, Identifiable, Queryable)]
|
|
||||||
#[belongs_to(Implementation)]
|
|
||||||
struct ImplementationWrapper {
|
|
||||||
id: i32,
|
|
||||||
implementation_id: i32,
|
|
||||||
identifier: String,
|
|
||||||
label: String,
|
|
||||||
is_asm: bool,
|
|
||||||
is_formatter: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct JsonLanguage {
|
|
||||||
hello_world: Option<String>,
|
|
||||||
implementations: Vec<JsonImplementation>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct JsonImplementation {
|
|
||||||
label: String,
|
|
||||||
wrappers: Vec<Wrapper>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/api/v0/language/<identifier>")]
|
|
||||||
pub async fn api_language(
|
|
||||||
db: Db,
|
|
||||||
identifier: String,
|
|
||||||
) -> Result<Option<Json<JsonLanguage>>, Debug<diesel::result::Error>> {
|
|
||||||
db.run(|conn| {
|
|
||||||
let language: Option<Language> = languages::table
|
|
||||||
.filter(languages::identifier.eq(identifier))
|
|
||||||
.select((languages::language_id, languages::hello_world))
|
|
||||||
.get_result(conn)
|
|
||||||
.optional()?;
|
|
||||||
Ok(if let Some(language) = language {
|
|
||||||
let implementations = implementations::table
|
|
||||||
.select((implementations::implementation_id, implementations::label))
|
|
||||||
.filter(implementations::language_id.eq(language.id))
|
|
||||||
.order(implementations::ordering)
|
|
||||||
.load(conn)?;
|
|
||||||
let implementation_wrappers = ImplementationWrapper::belonging_to(&implementations)
|
|
||||||
.select((
|
|
||||||
implementation_wrappers::implementation_wrapper_id,
|
|
||||||
implementation_wrappers::implementation_id,
|
|
||||||
implementation_wrappers::identifier,
|
|
||||||
implementation_wrappers::label,
|
|
||||||
implementation_wrappers::is_asm,
|
|
||||||
implementation_wrappers::is_formatter,
|
|
||||||
))
|
|
||||||
.order(implementation_wrappers::ordering)
|
|
||||||
.load(conn)?;
|
|
||||||
let implementations = implementation_wrappers
|
|
||||||
.grouped_by(&implementations)
|
|
||||||
.into_iter()
|
|
||||||
.zip(implementations)
|
|
||||||
.map(|(wrappers, implementation)| JsonImplementation {
|
|
||||||
label: implementation.label,
|
|
||||||
wrappers: wrappers
|
|
||||||
.into_iter()
|
|
||||||
.map(
|
|
||||||
|ImplementationWrapper {
|
|
||||||
identifier,
|
|
||||||
label,
|
|
||||||
is_asm,
|
|
||||||
is_formatter,
|
|
||||||
..
|
|
||||||
}| {
|
|
||||||
Wrapper {
|
|
||||||
identifier,
|
|
||||||
label,
|
|
||||||
is_asm,
|
|
||||||
is_formatter,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.collect(),
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
Some(Json(JsonLanguage {
|
|
||||||
implementations,
|
|
||||||
hello_world: language.hello_world,
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
// pastebin.run
|
|
||||||
// Copyright (C) 2021 Konrad Borowski
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
use crate::models::Cors;
|
|
||||||
use crate::schema::languages;
|
|
||||||
use crate::Db;
|
|
||||||
use diesel::prelude::*;
|
|
||||||
use rocket::response::Debug;
|
|
||||||
use rocket::serde::json::Json;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
#[derive(Queryable, Serialize)]
|
|
||||||
pub struct Language {
|
|
||||||
identifier: String,
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/api/v1/languages")]
|
|
||||||
pub async fn api_languages(
|
|
||||||
db: Db,
|
|
||||||
) -> Result<Cors<Json<Vec<Language>>>, Debug<diesel::result::Error>> {
|
|
||||||
let languages = db
|
|
||||||
.run(|conn| {
|
|
||||||
languages::table
|
|
||||||
.select((languages::identifier, languages::name))
|
|
||||||
.load(conn)
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
Ok(Cors(Json(languages)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(test, feature = "database_tests"))]
|
|
||||||
mod test {
|
|
||||||
use rocket::http::hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN;
|
|
||||||
use rocket::http::Status;
|
|
||||||
use rocket::local::blocking::Client;
|
|
||||||
use rocket::uri;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cors() {
|
|
||||||
let rocket = Client::untracked(crate::rocket()).unwrap();
|
|
||||||
let response = rocket.get(uri!(super::api_languages)).dispatch();
|
|
||||||
assert_eq!(response.status(), Status::Ok);
|
|
||||||
assert_eq!(
|
|
||||||
response
|
|
||||||
.headers()
|
|
||||||
.get_one(ACCESS_CONTROL_ALLOW_ORIGIN.as_str()),
|
|
||||||
Some("*"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,10 +14,8 @@
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use super::WithTxt;
|
|
||||||
use crate::models::language::Language;
|
|
||||||
use crate::models::paste::{ExternPaste, Paste};
|
use crate::models::paste::{ExternPaste, Paste};
|
||||||
use crate::schema::{languages, pastes};
|
use crate::schema::pastes;
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use rocket::http::uri::Origin;
|
use rocket::http::uri::Origin;
|
||||||
|
@ -27,7 +25,6 @@ use serde::Serialize;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct DisplayPaste {
|
struct DisplayPaste {
|
||||||
languages: Vec<Language>,
|
|
||||||
description: String,
|
description: String,
|
||||||
paste: ExternPaste,
|
paste: ExternPaste,
|
||||||
raw_paste_url: Origin<'static>,
|
raw_paste_url: Origin<'static>,
|
||||||
|
@ -40,19 +37,9 @@ pub async fn display_paste(
|
||||||
) -> Result<Option<Template>, Debug<diesel::result::Error>> {
|
) -> Result<Option<Template>, Debug<diesel::result::Error>> {
|
||||||
db.run(move |conn| {
|
db.run(move |conn| {
|
||||||
Paste::delete_old(conn)?;
|
Paste::delete_old(conn)?;
|
||||||
let languages = Language::fetch(conn)?;
|
|
||||||
let paste: Option<Paste> = pastes::table
|
let paste: Option<Paste> = pastes::table
|
||||||
.inner_join(languages::table.on(pastes::language_id.eq(languages::language_id)))
|
.select((pastes::identifier, pastes::paste, pastes::delete_at))
|
||||||
.select((
|
|
||||||
pastes::identifier,
|
|
||||||
pastes::paste,
|
|
||||||
pastes::language_id,
|
|
||||||
pastes::delete_at,
|
|
||||||
languages::identifier,
|
|
||||||
pastes::stdin,
|
|
||||||
pastes::exit_code,
|
|
||||||
pastes::output,
|
|
||||||
))
|
|
||||||
.filter(pastes::identifier.eq(&identifier))
|
.filter(pastes::identifier.eq(&identifier))
|
||||||
.get_result(conn)
|
.get_result(conn)
|
||||||
.optional()?;
|
.optional()?;
|
||||||
|
@ -61,7 +48,6 @@ pub async fn display_paste(
|
||||||
Ok(Some(Template::render(
|
Ok(Some(Template::render(
|
||||||
"display-paste",
|
"display-paste",
|
||||||
&DisplayPaste {
|
&DisplayPaste {
|
||||||
languages,
|
|
||||||
description,
|
description,
|
||||||
paste: ExternPaste::from_paste(paste),
|
paste: ExternPaste::from_paste(paste),
|
||||||
raw_paste_url: uri!(super::raw_paste(identifier)),
|
raw_paste_url: uri!(super::raw_paste(identifier)),
|
||||||
|
|
|
@ -14,19 +14,15 @@
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use crate::models::language::Language;
|
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
use rocket::response::Debug;
|
use rocket::response::Debug;
|
||||||
use rocket_dyn_templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct Index {
|
struct Index {}
|
||||||
languages: Vec<Language>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
pub async fn index(db: Db) -> Result<Template, Debug<diesel::result::Error>> {
|
pub async fn index(_db: Db) -> Result<Template, Debug<diesel::result::Error>> {
|
||||||
let languages = db.run(|conn| Language::fetch(conn)).await?;
|
Ok(Template::render("index", &Index {}))
|
||||||
Ok(Template::render("index", &Index { languages }))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,49 +14,56 @@
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use crate::models::paste::{self, ExtraPasteParameters, InsertionError};
|
use crate::models::paste::{self, InsertionError};
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
use chrono::{Duration, Utc};
|
use chrono::{Duration, Utc};
|
||||||
use rocket::form::Form;
|
use rocket::form::{self, Form, FromForm, FromFormField};
|
||||||
use rocket::response::Redirect;
|
use rocket::response::Redirect;
|
||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
pub struct PasteForm {
|
pub struct PasteForm {
|
||||||
language: String,
|
#[field(validate = len(1..))]
|
||||||
code: String,
|
code: String,
|
||||||
share: Share,
|
duration: ShareDuration,
|
||||||
#[field(default = "")]
|
|
||||||
stdin: String,
|
|
||||||
output: Option<String>,
|
|
||||||
status: Option<i32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromFormField)]
|
pub enum ShareDuration {
|
||||||
pub enum Share {
|
OneHour,
|
||||||
Share,
|
ThreeHour,
|
||||||
Share24,
|
TwentyFourHour,
|
||||||
|
ThreeDays,
|
||||||
|
SevenDays,
|
||||||
|
NinetyDays,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'v> FromFormField<'v> for ShareDuration {
|
||||||
|
fn from_value(field: rocket::form::ValueField<'v>) -> rocket::form::Result<'v, Self> {
|
||||||
|
match field.value {
|
||||||
|
"1h" => Ok(ShareDuration::OneHour),
|
||||||
|
"3h" => Ok(ShareDuration::ThreeHour),
|
||||||
|
"24h" => Ok(ShareDuration::TwentyFourHour),
|
||||||
|
"3d" => Ok(ShareDuration::ThreeDays),
|
||||||
|
"7d" => Ok(ShareDuration::SevenDays),
|
||||||
|
"90d" => Ok(ShareDuration::NinetyDays),
|
||||||
|
|
||||||
|
_ => Err(form::Error::validation("invalid value for share duration"))?,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/", data = "<form>")]
|
#[post("/", data = "<form>")]
|
||||||
pub async fn insert_paste(db: Db, form: Form<PasteForm>) -> Result<Redirect, InsertionError> {
|
pub async fn insert_paste(db: Db, form: Form<PasteForm>) -> Result<Redirect, InsertionError> {
|
||||||
let delete_at = match form.share {
|
let delete_at = match form.duration {
|
||||||
Share::Share => None,
|
ShareDuration::OneHour => Some(Utc::now() + Duration::hours(1)),
|
||||||
Share::Share24 => Some(Utc::now() + Duration::hours(24)),
|
ShareDuration::ThreeHour => Some(Utc::now() + Duration::hours(3)),
|
||||||
|
ShareDuration::TwentyFourHour => Some(Utc::now() + Duration::hours(24)),
|
||||||
|
ShareDuration::ThreeDays => Some(Utc::now() + Duration::days(3)),
|
||||||
|
ShareDuration::SevenDays => Some(Utc::now() + Duration::days(7)),
|
||||||
|
ShareDuration::NinetyDays => Some(Utc::now() + Duration::days(90)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let identifier = db
|
let identifier = db
|
||||||
.run(move |conn| {
|
.run(move |conn| paste::insert(conn, delete_at, &form.code))
|
||||||
paste::insert(
|
|
||||||
conn,
|
|
||||||
delete_at,
|
|
||||||
&form.language,
|
|
||||||
&form.code,
|
|
||||||
ExtraPasteParameters {
|
|
||||||
stdin: &form.stdin,
|
|
||||||
output: form.output.as_deref(),
|
|
||||||
exit_code: form.status,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
Ok(Redirect::to(uri!(super::display_paste(identifier))))
|
Ok(Redirect::to(uri!(super::display_paste(identifier))))
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,23 +15,17 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
mod api_insert_paste_route;
|
mod api_insert_paste_route;
|
||||||
mod api_language_route;
|
|
||||||
mod api_languages_route;
|
|
||||||
mod config_route;
|
mod config_route;
|
||||||
mod display_paste_route;
|
mod display_paste_route;
|
||||||
mod index_route;
|
mod index_route;
|
||||||
mod insert_paste_route;
|
mod insert_paste_route;
|
||||||
mod metrics_route;
|
mod metrics_route;
|
||||||
mod raw_paste_route;
|
mod raw_paste_route;
|
||||||
mod run_route;
|
|
||||||
|
|
||||||
pub use api_insert_paste_route::*;
|
pub use api_insert_paste_route::*;
|
||||||
pub use api_language_route::*;
|
|
||||||
pub use api_languages_route::*;
|
|
||||||
pub use config_route::*;
|
pub use config_route::*;
|
||||||
pub use display_paste_route::*;
|
pub use display_paste_route::*;
|
||||||
pub use index_route::*;
|
pub use index_route::*;
|
||||||
pub use insert_paste_route::*;
|
pub use insert_paste_route::*;
|
||||||
pub use metrics_route::*;
|
pub use metrics_route::*;
|
||||||
pub use raw_paste_route::*;
|
pub use raw_paste_route::*;
|
||||||
pub use run_route::*;
|
|
||||||
|
|
|
@ -19,45 +19,10 @@ use crate::schema::pastes;
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use rocket::http::hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN;
|
use rocket::http::hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN;
|
||||||
use rocket::http::uri::fmt::{Formatter, FromUriParam, Path, UriDisplay};
|
use rocket::http::Header;
|
||||||
use rocket::http::{impl_from_uri_param_identity, Header};
|
use rocket::request::Request;
|
||||||
use rocket::request::{FromParam, Request};
|
|
||||||
use rocket::response::status::NotFound;
|
use rocket::response::status::NotFound;
|
||||||
use rocket::response::{self, Debug, Responder, Response};
|
use rocket::response::{self, Debug, Responder, Response};
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
pub struct WithTxt(String);
|
|
||||||
|
|
||||||
impl<'a> FromParam<'a> for WithTxt {
|
|
||||||
type Error = &'a str;
|
|
||||||
|
|
||||||
fn from_param(param: &str) -> Result<Self, &str> {
|
|
||||||
if let Some(param) = param.strip_suffix(".txt") {
|
|
||||||
Ok(WithTxt(
|
|
||||||
String::from_param(param).unwrap_or_else(|e| match e {}),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Err(param)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UriDisplay<Path> for WithTxt {
|
|
||||||
fn fmt(&self, f: &mut Formatter<Path>) -> fmt::Result {
|
|
||||||
self.0.fmt(f)?;
|
|
||||||
f.write_raw(".txt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_from_uri_param_identity!([Path] WithTxt);
|
|
||||||
|
|
||||||
impl FromUriParam<Path, String> for WithTxt {
|
|
||||||
type Target = WithTxt;
|
|
||||||
|
|
||||||
fn from_uri_param(param: String) -> WithTxt {
|
|
||||||
WithTxt(param)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum RawPasteResponse {
|
pub enum RawPasteResponse {
|
||||||
Found(String),
|
Found(String),
|
||||||
|
@ -80,17 +45,17 @@ impl<'r> Responder<'r, 'static> for RawPasteResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<identifier>")]
|
#[get("/raw/<identifier>")]
|
||||||
pub async fn raw_paste(
|
pub async fn raw_paste(
|
||||||
db: Db,
|
db: Db,
|
||||||
identifier: WithTxt,
|
identifier: String,
|
||||||
) -> Result<RawPasteResponse, Debug<diesel::result::Error>> {
|
) -> Result<RawPasteResponse, Debug<diesel::result::Error>> {
|
||||||
Ok(db
|
Ok(db
|
||||||
.run(move |conn| {
|
.run(move |conn| {
|
||||||
Paste::delete_old(conn)?;
|
Paste::delete_old(conn)?;
|
||||||
pastes::table
|
pastes::table
|
||||||
.select(pastes::paste)
|
.select(pastes::paste)
|
||||||
.filter(pastes::identifier.eq(&identifier.0))
|
.filter(pastes::identifier.eq(&identifier))
|
||||||
.get_result(conn)
|
.get_result(conn)
|
||||||
.optional()
|
.optional()
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,106 +0,0 @@
|
||||||
// pastebin.run
|
|
||||||
// Copyright (C) 2021 Konrad Borowski
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
use crate::schema::implementation_wrappers;
|
|
||||||
use crate::Db;
|
|
||||||
use diesel::prelude::*;
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use reqwest::Client;
|
|
||||||
use rocket::form::Form;
|
|
||||||
use rocket::response::Debug;
|
|
||||||
use rocket::serde::json::Json;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::env;
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
static CLIENT: Lazy<Client> = Lazy::new(Client::new);
|
|
||||||
static SANDBOX_URL: Lazy<String> = Lazy::new(|| env::var("SANDBOX_URL").unwrap());
|
|
||||||
|
|
||||||
#[derive(FromForm)]
|
|
||||||
pub struct RunForm {
|
|
||||||
code: String,
|
|
||||||
#[field(name = "compilerOptions")]
|
|
||||||
compiler_options: String,
|
|
||||||
stdin: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct Request {
|
|
||||||
files: Files,
|
|
||||||
stdin: String,
|
|
||||||
code: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct Files {
|
|
||||||
code: File,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct File {
|
|
||||||
contents: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
|
||||||
pub struct Output {
|
|
||||||
status: Option<i32>,
|
|
||||||
output: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/api/v0/run/<identifier>", data = "<form>")]
|
|
||||||
pub async fn run(
|
|
||||||
db: Db,
|
|
||||||
identifier: String,
|
|
||||||
form: Form<RunForm>,
|
|
||||||
) -> Result<Option<Json<Output>>, Debug<Box<dyn Error + Send + Sync>>> {
|
|
||||||
let run = || async {
|
|
||||||
let language_code = db
|
|
||||||
.run(|conn| {
|
|
||||||
implementation_wrappers::table
|
|
||||||
.filter(implementation_wrappers::identifier.eq(identifier))
|
|
||||||
.select(implementation_wrappers::code)
|
|
||||||
.get_result(conn)
|
|
||||||
.optional()
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
let language_code: String = if let Some(code) = language_code {
|
|
||||||
code
|
|
||||||
} else {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
let RunForm {
|
|
||||||
code,
|
|
||||||
compiler_options,
|
|
||||||
stdin,
|
|
||||||
} = form.into_inner();
|
|
||||||
let json: Output = CLIENT
|
|
||||||
.post(SANDBOX_URL.as_str())
|
|
||||||
.json(&Request {
|
|
||||||
files: Files {
|
|
||||||
code: File { contents: code },
|
|
||||||
},
|
|
||||||
stdin,
|
|
||||||
code: language_code.replace("%s", &compiler_options),
|
|
||||||
})
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.json()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Some(Json(json)))
|
|
||||||
};
|
|
||||||
run().await.map_err(Debug)
|
|
||||||
}
|
|
|
@ -14,39 +14,22 @@ table! {
|
||||||
table! {
|
table! {
|
||||||
implementations (implementation_id) {
|
implementations (implementation_id) {
|
||||||
implementation_id -> Int4,
|
implementation_id -> Int4,
|
||||||
language_id -> Int4,
|
|
||||||
identifier -> Text,
|
identifier -> Text,
|
||||||
label -> Text,
|
label -> Text,
|
||||||
ordering -> Int4,
|
ordering -> Int4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table! {
|
|
||||||
languages (language_id) {
|
|
||||||
language_id -> Int4,
|
|
||||||
priority -> Int4,
|
|
||||||
name -> Text,
|
|
||||||
identifier -> Text,
|
|
||||||
hello_world -> Nullable<Text>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
pastes (paste_id) {
|
pastes (paste_id) {
|
||||||
paste_id -> Int4,
|
paste_id -> Int4,
|
||||||
identifier -> Text,
|
identifier -> Text,
|
||||||
delete_at -> Nullable<Timestamptz>,
|
delete_at -> Nullable<Timestamptz>,
|
||||||
created_at -> Timestamptz,
|
created_at -> Timestamptz,
|
||||||
language_id -> Int4,
|
|
||||||
paste -> Text,
|
paste -> Text,
|
||||||
stdin -> Text,
|
|
||||||
exit_code -> Nullable<Int4>,
|
|
||||||
output -> Nullable<Text>,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
joinable!(implementation_wrappers -> implementations (implementation_id));
|
joinable!(implementation_wrappers -> implementations (implementation_id));
|
||||||
joinable!(implementations -> languages (language_id));
|
|
||||||
joinable!(pastes -> languages (language_id));
|
|
||||||
|
|
||||||
allow_tables_to_appear_in_same_query!(implementation_wrappers, implementations, languages, pastes,);
|
allow_tables_to_appear_in_same_query!(implementation_wrappers, implementations, pastes,);
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
<html lang=en>
|
<html lang=en>
|
||||||
<meta charset=utf-8>
|
<meta charset=utf-8>
|
||||||
<meta name=viewport content="width=device-width, initial-scale=1">
|
<meta name=viewport content="width=device-width, initial-scale=1">
|
||||||
<title>pastebin.run</title>
|
<title>p.ishan.pw</title>
|
||||||
{{ css_stylesheet() | safe }}
|
{{ css_stylesheet() | safe }}
|
||||||
<meta name=description content="{% block description %}Compile and share code in multiple programming languages{% endblock description %}">
|
<meta name=description content="{% block description %}Compile and share code{% endblock description %}">
|
||||||
<script type=module src="{{ js_path() }}"></script>
|
<script type=module src="{{ js_path() }}"></script>
|
||||||
<header id=header>
|
<header id=header>
|
||||||
<a href="/">
|
<a href="/">
|
||||||
|
@ -12,7 +12,8 @@
|
||||||
</a>
|
</a>
|
||||||
<nav id="menu-buttons">
|
<nav id="menu-buttons">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="https://pastebin.run/api">API</a
|
<!--TODO: do not hard code -->
|
||||||
|
<li><a href="https://p.ishan.pw/api">API</a
|
||||||
><li><a href="https://github.com/pastebinrun/pastebinrun/">Source code</a
|
><li><a href="https://github.com/pastebinrun/pastebinrun/">Source code</a
|
||||||
></ul>
|
></ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
@ -3,33 +3,13 @@
|
||||||
{% block description %}{{ description }}{% endblock %}
|
{% block description %}{{ description }}{% endblock %}
|
||||||
|
|
||||||
{% block paste_header %}
|
{% block paste_header %}
|
||||||
<div id=markdown>
|
|
||||||
{{ paste.markdown | safe }}
|
|
||||||
</div>
|
|
||||||
{% if paste.delete_at %}
|
{% if paste.delete_at %}
|
||||||
<div class="autodelete-text">
|
<div class="autodelete-text">
|
||||||
This paste will be automatically deleted on {{ paste.delete_at }} UTC.
|
This paste will be automatically deleted on {{ paste.delete_at }} UTC
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if paste.exit_code is defined %}
|
|
||||||
<input type=hidden id=dbstatus value="{{ paste.exit_code }}">
|
|
||||||
{% endif %}
|
|
||||||
{% if paste.output is defined %}
|
|
||||||
<input type=hidden id=dboutput value="{{ paste.output }}">
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block code %}{{ paste.paste }}{% endblock %}
|
{% block code %}{{ paste.paste }}{% endblock %}
|
||||||
|
|
||||||
{% block languages %}
|
|
||||||
{% for language in languages %}
|
|
||||||
<option value="{{ language.identifier }}"
|
|
||||||
{% if paste.language_id == language.id %}
|
|
||||||
selected
|
|
||||||
{% endif %}
|
|
||||||
>{{ language.name }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block rawpaste %}<div class="group rawpaste-text"><a href="{{ raw_paste_url }}">raw paste</a></div>{% endblock %}
|
{% block rawpaste %}<div class="group rawpaste-text"><a href="{{ raw_paste_url }}">raw paste</a></div>{% endblock %}
|
||||||
|
|
|
@ -4,13 +4,6 @@
|
||||||
<form method="post" action="/">
|
<form method="post" action="/">
|
||||||
{% block paste_header %}{% endblock %}
|
{% block paste_header %}{% endblock %}
|
||||||
<div id="toolbar">
|
<div id="toolbar">
|
||||||
<div class=group><label>Language: <select id=language name=language>
|
|
||||||
{% block languages %}
|
|
||||||
{% for language in languages %}
|
|
||||||
<option value="{{ language.identifier }}">{{ language.name }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
{% endblock languages %}
|
|
||||||
</select></label></div>
|
|
||||||
{% block rawpaste %}{% endblock %}
|
{% block rawpaste %}{% endblock %}
|
||||||
<span id="right-buttons">
|
<span id="right-buttons">
|
||||||
<button type=submit name=share value=share24>Share (delete after 24 hours)</button>
|
<button type=submit name=share value=share24>Share (delete after 24 hours)</button>
|
||||||
|
|
Loading…
Reference in New Issue