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:
Ishan Jain 2023-02-11 21:37:33 +05:30
parent ba55a222f5
commit be4da1823f
Signed by: ishan
GPG Key ID: 0506DB2A1CC75C27
42 changed files with 106 additions and 2845 deletions

172
Cargo.lock generated
View File

@ -46,19 +46,6 @@ dependencies = [
"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]]
name = "android_system_properties"
version = "0.1.5"
@ -257,7 +244,7 @@ checksum = "29c39203181991a7dd4343b8005bd804e7a9a37afb8ac070e43771e8c820bbde"
dependencies = [
"chrono",
"chrono-tz-build",
"phf 0.11.1",
"phf",
]
[[package]]
@ -267,8 +254,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f509c3a87b33437b05e2458750a0700e5bdd6956176773e6c7d6dd15a283a0c"
dependencies = [
"parse-zoneinfo",
"phf 0.11.1",
"phf_codegen 0.11.1",
"phf",
"phf_codegen",
]
[[package]]
@ -674,16 +661,6 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "futures"
version = "0.1.31"
@ -920,20 +897,6 @@ dependencies = [
"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]]
name = "http"
version = "0.2.8"
@ -1271,32 +1234,6 @@ dependencies = [
"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]]
name = "matchers"
version = "0.1.0"
@ -1460,12 +1397,6 @@ dependencies = [
"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]]
name = "nom"
version = "5.1.2"
@ -1668,7 +1599,6 @@ dependencies = [
name = "pastebinrun"
version = "0.1.0"
dependencies = [
"ammonia",
"chrono",
"diesel",
"diesel_migrations",
@ -1765,32 +1695,13 @@ dependencies = [
"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]]
name = "phf"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c"
dependencies = [
"phf_shared 0.11.1",
]
[[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",
"phf_shared",
]
[[package]]
@ -1799,18 +1710,8 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770"
dependencies = [
"phf_generator 0.11.1",
"phf_shared 0.11.1",
]
[[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",
"phf_generator",
"phf_shared",
]
[[package]]
@ -1819,19 +1720,10 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf"
dependencies = [
"phf_shared 0.11.1",
"phf_shared",
"rand",
]
[[package]]
name = "phf_shared"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
dependencies = [
"siphasher",
]
[[package]]
name = "phf_shared"
version = "0.11.1"
@ -1887,12 +1779,6 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "precomputed-hash"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "proc-macro2"
version = "1.0.51"
@ -2562,32 +2448,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "strsim"
version = "0.10.0"
@ -2625,17 +2485,6 @@ dependencies = [
"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]]
name = "tera"
version = "1.17.1"
@ -2771,7 +2620,6 @@ dependencies = [
"memchr",
"mio 0.8.5",
"num_cpus",
"parking_lot 0.12.1",
"pin-project-lite",
"signal-hook-registry",
"socket2",
@ -3119,12 +2967,6 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "valuable"
version = "0.1.0"

View File

@ -7,7 +7,6 @@ license = "AGPL-3.0-or-later"
build = "buildSrc/build.rs"
[dependencies]
ammonia = "3.3.0"
chrono = "0.4.23"
diesel = { version = "1.4.6", features = ["chrono"] }
diesel_migrations = "1.4.0"
@ -31,7 +30,7 @@ serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.93"
serde_with = "2.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"
[build-dependencies]

View File

@ -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();
});

View File

@ -18,8 +18,6 @@ import { render } from "solid-js/web";
import App from "./views/App";
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 rawPasteElement = editor.querySelector(".rawpaste-text") as Element;
const code = (editor.querySelector("textarea") as HTMLTextAreaElement).value;
@ -27,8 +25,6 @@ export default function createEditor(editor: Element) {
render(
() => (
<App
markdown={markdown}
languages={languages}
autoDelete={autoDelete}
rawPasteElement={rawPasteElement}
code={code}

View File

@ -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;

View File

@ -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;

View File

@ -39,5 +39,5 @@ export function getEditorTypeSignal(): Signal<string> {
}
export function getTabIndentationSignal(): Signal<string> {
return createLocalStorageState("tabIndentation", "false");
return createLocalStorageState("tabIndentation", "true");
}

View File

@ -15,60 +15,36 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import { createSignal, Show } from "solid-js";
import WrapperOptions from "../models/WrapperOptions";
import Editor from "./Editor";
import LanguageSelector from "./LanguageSelector";
import Output from "./Output";
import StandardInput from "./StandardInput";
import WrapperBar from "./WrapperBar";
export default function App({
markdown,
languages,
autoDelete,
rawPasteElement,
code,
}: {
markdown: Element | null;
languages: HTMLSelectElement;
autoDelete: Element | null;
rawPasteElement: Element | null;
code: string;
}) {
let form: HTMLFormElement | undefined;
const [isPaste, setIsPaste] = createSignal(true);
const [currentLanguage, setCurrentLanguage] = createSignal(
languages.selectedOptions[0]?.value
);
const [standardInputVisible, setStandardInputVisible] = createSignal(false);
const [codeView, setCodeView] = createSignal({ code: "" });
const [standardInput, setStandardInput] = createSignal("");
const [wrapperOptions, setWrapperOptions] = createSignal<WrapperOptions>();
const [_, setCodeView] = createSignal({ code: "" });
const [label, setLabel] = createSignal<Element>();
return (
<form action="/" method="post" ref={(e) => (form = e)}>
{markdown}
<Show when={isPaste()}>{autoDelete}</Show>
<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>
<span id="right-buttons">
<button type="submit" name="share" value="share24">
Share (delete after 24 hours)
</button>{" "}
<button type="submit" name="share" value="share">
<select name="duration" id="duration">
<option value="1h">1 Hour</option>
<option value="3h">3 Hours</option>
<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
</button>
</span>
@ -81,29 +57,13 @@ export default function App({
code={code}
onInput={() => {
setIsPaste(false);
setWrapperOptions();
}}
currentLanguage={currentLanguage}
form={form as HTMLFormElement}
setCodeView={setCodeView}
setLabel={setLabel}
/>
</div>
<StandardInput
visible={standardInputVisible}
setStandardInput={setStandardInput}
/>
</div>
<Show when={wrapperOptions()} keyed>
{(wrapperOptions) => (
<Output
codeView={codeView()}
stdin={standardInput()}
wrapperOptions={wrapperOptions}
setWrapperOptions={setWrapperOptions}
/>
)}
</Show>
</div>
</form>
);

View File

@ -14,29 +14,26 @@
// 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 { EditorView, basicSetup } from "codemirror";
import { indentWithTab } from "@codemirror/commands";
import { indentUnit } from "@codemirror/language";
import { Compartment } from "@codemirror/state";
import { keymap } from "@codemirror/view";
import { basicSetup, EditorView } from "codemirror";
import {
Accessor,
createEffect,
createUniqueId,
JSXElement,
onCleanup,
Setter,
Setter
} from "solid-js";
import CodeView from "../../models/CodeView";
import { getTabIndentationSignal } from "../../options";
import "./codemirror.css";
import languagesMap from "./languages";
export default function CodeMirrorEditor({
code,
setCode,
onInput,
currentLanguage,
form,
setCodeView,
setLabel,
@ -44,7 +41,6 @@ export default function CodeMirrorEditor({
code: Accessor<string>;
setCode: Setter<string>;
onInput(): void;
currentLanguage: Accessor<string>;
form: HTMLFormElement;
setCodeView: Setter<CodeView>;
setLabel: Setter<JSXElement>;
@ -56,7 +52,6 @@ export default function CodeMirrorEditor({
: [];
}
const tabIndentation = new Compartment();
const language = new Compartment();
let avoidChangeNotifications = false;
const labelId = createUniqueId();
const view = new EditorView({
@ -72,8 +67,6 @@ export default function CodeMirrorEditor({
}
}),
EditorView.lineWrapping,
indentUnit.of(" ".repeat(4)),
language.of([]),
],
});
createEffect(() => {
@ -81,13 +74,6 @@ export default function CodeMirrorEditor({
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();
setCodeView({
get code() {

View File

@ -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;

View File

@ -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();
},
})
),
});

View File

@ -15,11 +15,10 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import {
Accessor,
createResource,
createSignal,
JSXElement,
Setter,
Setter
} from "solid-js";
import CodeView from "../models/CodeView";
import { getEditorTypeSignal } from "../options";
@ -28,14 +27,12 @@ import TextAreaEditor from "./TextAreaEditor";
export default function Editor({
code: initialCode,
onInput,
currentLanguage,
form,
setCodeView: setCodeView,
setLabel,
}: {
code: string;
onInput(): void;
currentLanguage: Accessor<string>;
form: HTMLFormElement;
setCodeView: Setter<CodeView>;
setLabel: Setter<JSXElement>;
@ -56,7 +53,6 @@ export default function Editor({
code,
setCode,
onInput,
currentLanguage,
form,
setCodeView,
setLabel,

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
</>
);
}

View File

@ -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" />;
}

View File

@ -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;
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -0,0 +1,2 @@
ALTER TABLE pastes DROP COLUMN output;
ALTER TABLE pastes ADD COLUMN stdout text, ADD COLUMN stderr text;

View File

@ -0,0 +1,5 @@
ALTER TABLE pastes
DROP COLUMN language_id,
DROP COLUMN stdin,
DROP COLUMN exit_code,
DROP COLUMN output;

1089
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,20 +7,7 @@
},
"dependencies": {
"@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",
"@replit/codemirror-lang-nix": "^6.0.0",
"codemirror": "^6.0.1",
"solid-js": "^1.6.10"
},

View File

@ -19,15 +19,12 @@ extern crate diesel;
#[macro_use]
extern crate rocket;
mod migration;
mod models;
mod routes;
mod schema;
use crate::models::paste::ExtraPasteParameters;
use crate::routes::{
api_insert_paste, api_language, api_languages, config, display_paste, index, insert_paste,
metrics, raw_paste, run,
api_insert_paste, config, display_paste, index, insert_paste, metrics, raw_paste,
};
use chrono::{Duration, Utc};
use diesel::prelude::*;
@ -39,8 +36,9 @@ use rocket_dyn_templates::tera::{self, Value};
use rocket_dyn_templates::Template;
use rocket_sync_db_pools::database;
use std::collections::HashMap;
use std::io::{Read, Write};
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::net::SocketAddr;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::runtime::Runtime;
use tokio::signal;
use tokio::sync::oneshot::Sender;
@ -131,31 +129,35 @@ async fn shutdown_monitor(s: Sender<i32>) {
}
async fn cli() {
println!("starting cli");
let addr = "0.0.0.0:9000".parse::<SocketAddr>().unwrap();
let listener = TcpListener::bind(addr).unwrap();
let mut db = PgConnection::establish("postgres://postgres:ifd783farq@localhost/pastebin")
let listener = TcpListener::bind(addr).await.unwrap();
let mut db = PgConnection::establish(&std::env::var("POSTGRES_URL").unwrap())
.expect("error in establishing connection with postgresql");
let host = std::env::var("HOST").unwrap();
loop {
let (socket, details) = listener.accept().unwrap();
let (socket, details) = listener.accept().await.unwrap();
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 HOST: &str = "https://p.ishan.pw";
fn save_paste(
async fn save_paste(
db: &mut PgConnection,
host: &str,
mut socket: TcpStream,
) -> Result<(), Box<dyn std::error::Error>> {
let mut paste = vec![];
let mut buffer = [0; BUFFER_SIZE];
loop {
let read = socket.read(&mut buffer)?;
let read = socket.read(&mut buffer).await?;
if read < BUFFER_SIZE {
paste.extend(&buffer[..read]);
break;
@ -170,20 +172,20 @@ fn save_paste(
db,
// Terminal paste should stay for 3 days
Some(Utc::now() + Duration::days(3)),
"plaintext",
&String::from_utf8_lossy(&paste),
ExtraPasteParameters {
..Default::default()
},
)
.unwrap();
socket.write_fmt(format_args!("{HOST}/{v}\n"))?;
socket.flush().unwrap();
let output = format!("{}/{}\n", host, v);
let output: Vec<u8> = output.into_bytes();
socket.write(&output).await?;
socket.flush().await.unwrap();
Ok(())
}
fn rocket() -> Rocket<Build> {
println!("starting rocket");
let mut rocket = rocket::build()
.attach(Template::custom(|engines| {
engines.tera.register_function("js_path", js_path);
@ -196,10 +198,7 @@ fn rocket() -> Rocket<Build> {
Db::get_one(&rocket)
.await
.expect("a database")
.run(|conn| {
diesel_migrations::run_pending_migrations(conn)?;
migration::run(conn)
})
.run(|conn| diesel_migrations::run_pending_migrations(conn))
.await
.expect("database to be migrated");
rocket
@ -212,11 +211,8 @@ fn rocket() -> Rocket<Build> {
.mount(
"/",
routes![
api_language,
api_languages,
api_insert_paste,
config,
run,
index,
insert_paste,
display_paste,

View File

@ -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(())
}

View File

@ -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)
}
}

View File

@ -15,7 +15,6 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
mod cors;
pub mod language;
pub mod paste;
pub use cors::Cors;

View File

@ -14,32 +14,21 @@
// 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, pastes};
use ammonia::Builder;
use crate::schema::pastes;
use chrono::{DateTime, Utc};
use diesel::prelude::*;
use itertools::Itertools;
use log::info;
use once_cell::sync::Lazy;
use pulldown_cmark::{Options, Parser};
use rand::seq::SliceRandom;
use rocket::http::Status;
use rocket::response::{self, Debug, Responder};
use rocket::Request;
use serde::Serialize;
use serde_with::skip_serializing_none;
use std::iter;
#[derive(Queryable)]
pub struct Paste {
pub identifier: String,
pub paste: String,
pub language_id: i32,
pub delete_at: Option<DateTime<Utc>>,
pub language_identifier: String,
pub stdin: String,
pub exit_code: Option<i32>,
pub output: Option<String>,
}
impl Paste {
@ -61,24 +50,12 @@ const CHARACTERS: &[u8] = b"23456789bcdfghjkmnpqrstvwxz-";
struct InsertPaste<'a> {
identifier: &'a str,
delete_at: Option<DateTime<Utc>>,
language_id: i32,
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)]
pub enum InsertionError {
Diesel(diesel::result::Error),
UnrecognizedLanguageIdentifier,
}
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> {
match self {
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(
connection: &PgConnection,
delete_at: Option<DateTime<Utc>>,
language: &str,
paste: &str,
ExtraPasteParameters {
stdin,
output,
exit_code,
}: ExtraPasteParameters,
) -> Result<String, InsertionError> {
let mut rng = rand::thread_rng();
let identifier: String = (0..12)
.map(|_| char::from(*CHARACTERS.choose(&mut rng).expect("a random character")))
.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 {
identifier: &identifier,
delete_at,
language_id,
paste,
stdin,
output,
exit_code,
};
diesel::insert_into(pastes::table)
.values(&insert_paste)
@ -137,12 +98,7 @@ pub fn insert(
pub struct ExternPaste {
pub identifier: Option<String>,
pub paste: String,
pub language_id: i32,
pub delete_at: Option<String>,
pub markdown: String,
pub stdin: String,
pub exit_code: Option<i32>,
pub output: Option<String>,
}
impl ExternPaste {
@ -150,98 +106,12 @@ impl ExternPaste {
let Paste {
identifier,
paste,
language_id,
language_identifier,
delete_at,
stdin,
exit_code,
output,
} = paste;
let markdown = if language_identifier == "markdown" {
render_markdown(&paste)
} else {
String::new()
};
Self {
identifier: Some(identifier),
paste,
language_id,
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 Standards 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\">",
);
}
}

View File

@ -14,7 +14,7 @@
// 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::paste::{self, ExtraPasteParameters, InsertionError};
use crate::models::paste::{self, InsertionError};
use crate::models::Cors;
use crate::Db;
use chrono::Duration;
@ -26,8 +26,6 @@ use std::error::Error;
pub struct PasteForm {
#[field(default = Expiration(None))]
expiration: Expiration,
#[field(default = "plaintext")]
language: String,
code: String,
}
@ -52,13 +50,7 @@ pub async fn api_insert_paste(
paste::insert(
conn,
form.expiration.0.map(|expiration| Utc::now() + expiration),
&form.language,
&form.code,
ExtraPasteParameters {
stdin: "",
output: None,
exit_code: None,
},
)
})
.await?;

View File

@ -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
}

View File

@ -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("*"),
);
}
}

View File

@ -14,10 +14,8 @@
// 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 super::WithTxt;
use crate::models::language::Language;
use crate::models::paste::{ExternPaste, Paste};
use crate::schema::{languages, pastes};
use crate::schema::pastes;
use crate::Db;
use diesel::prelude::*;
use rocket::http::uri::Origin;
@ -27,7 +25,6 @@ use serde::Serialize;
#[derive(Serialize)]
struct DisplayPaste {
languages: Vec<Language>,
description: String,
paste: ExternPaste,
raw_paste_url: Origin<'static>,
@ -40,19 +37,9 @@ pub async fn display_paste(
) -> Result<Option<Template>, Debug<diesel::result::Error>> {
db.run(move |conn| {
Paste::delete_old(conn)?;
let languages = Language::fetch(conn)?;
let paste: Option<Paste> = pastes::table
.inner_join(languages::table.on(pastes::language_id.eq(languages::language_id)))
.select((
pastes::identifier,
pastes::paste,
pastes::language_id,
pastes::delete_at,
languages::identifier,
pastes::stdin,
pastes::exit_code,
pastes::output,
))
.select((pastes::identifier, pastes::paste, pastes::delete_at))
.filter(pastes::identifier.eq(&identifier))
.get_result(conn)
.optional()?;
@ -61,7 +48,6 @@ pub async fn display_paste(
Ok(Some(Template::render(
"display-paste",
&DisplayPaste {
languages,
description,
paste: ExternPaste::from_paste(paste),
raw_paste_url: uri!(super::raw_paste(identifier)),

View File

@ -14,19 +14,15 @@
// 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::language::Language;
use crate::Db;
use rocket::response::Debug;
use rocket_dyn_templates::Template;
use serde::Serialize;
#[derive(Serialize)]
struct Index {
languages: Vec<Language>,
}
struct Index {}
#[get("/")]
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 { languages }))
pub async fn index(_db: Db) -> Result<Template, Debug<diesel::result::Error>> {
Ok(Template::render("index", &Index {}))
}

View File

@ -14,49 +14,56 @@
// 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::paste::{self, ExtraPasteParameters, InsertionError};
use crate::models::paste::{self, InsertionError};
use crate::Db;
use chrono::{Duration, Utc};
use rocket::form::Form;
use rocket::form::{self, Form, FromForm, FromFormField};
use rocket::response::Redirect;
#[derive(FromForm)]
pub struct PasteForm {
language: String,
#[field(validate = len(1..))]
code: String,
share: Share,
#[field(default = "")]
stdin: String,
output: Option<String>,
status: Option<i32>,
duration: ShareDuration,
}
#[derive(FromFormField)]
pub enum Share {
Share,
Share24,
pub enum ShareDuration {
OneHour,
ThreeHour,
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>")]
pub async fn insert_paste(db: Db, form: Form<PasteForm>) -> Result<Redirect, InsertionError> {
let delete_at = match form.share {
Share::Share => None,
Share::Share24 => Some(Utc::now() + Duration::hours(24)),
let delete_at = match form.duration {
ShareDuration::OneHour => Some(Utc::now() + Duration::hours(1)),
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
.run(move |conn| {
paste::insert(
conn,
delete_at,
&form.language,
&form.code,
ExtraPasteParameters {
stdin: &form.stdin,
output: form.output.as_deref(),
exit_code: form.status,
},
)
})
.run(move |conn| paste::insert(conn, delete_at, &form.code))
.await?;
Ok(Redirect::to(uri!(super::display_paste(identifier))))
}

View File

@ -15,23 +15,17 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
mod api_insert_paste_route;
mod api_language_route;
mod api_languages_route;
mod config_route;
mod display_paste_route;
mod index_route;
mod insert_paste_route;
mod metrics_route;
mod raw_paste_route;
mod run_route;
pub use api_insert_paste_route::*;
pub use api_language_route::*;
pub use api_languages_route::*;
pub use config_route::*;
pub use display_paste_route::*;
pub use index_route::*;
pub use insert_paste_route::*;
pub use metrics_route::*;
pub use raw_paste_route::*;
pub use run_route::*;

View File

@ -19,45 +19,10 @@ use crate::schema::pastes;
use crate::Db;
use diesel::prelude::*;
use rocket::http::hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN;
use rocket::http::uri::fmt::{Formatter, FromUriParam, Path, UriDisplay};
use rocket::http::{impl_from_uri_param_identity, Header};
use rocket::request::{FromParam, Request};
use rocket::http::Header;
use rocket::request::Request;
use rocket::response::status::NotFound;
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 {
Found(String),
@ -80,17 +45,17 @@ impl<'r> Responder<'r, 'static> for RawPasteResponse {
}
}
#[get("/<identifier>")]
#[get("/raw/<identifier>")]
pub async fn raw_paste(
db: Db,
identifier: WithTxt,
identifier: String,
) -> Result<RawPasteResponse, Debug<diesel::result::Error>> {
Ok(db
.run(move |conn| {
Paste::delete_old(conn)?;
pastes::table
.select(pastes::paste)
.filter(pastes::identifier.eq(&identifier.0))
.filter(pastes::identifier.eq(&identifier))
.get_result(conn)
.optional()
})

View File

@ -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)
}

View File

@ -14,39 +14,22 @@ table! {
table! {
implementations (implementation_id) {
implementation_id -> Int4,
language_id -> Int4,
identifier -> Text,
label -> Text,
ordering -> Int4,
}
}
table! {
languages (language_id) {
language_id -> Int4,
priority -> Int4,
name -> Text,
identifier -> Text,
hello_world -> Nullable<Text>,
}
}
table! {
pastes (paste_id) {
paste_id -> Int4,
identifier -> Text,
delete_at -> Nullable<Timestamptz>,
created_at -> Timestamptz,
language_id -> Int4,
paste -> Text,
stdin -> Text,
exit_code -> Nullable<Int4>,
output -> Nullable<Text>,
}
}
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,);

View File

@ -2,9 +2,9 @@
<html lang=en>
<meta charset=utf-8>
<meta name=viewport content="width=device-width, initial-scale=1">
<title>pastebin.run</title>
<title>p.ishan.pw</title>
{{ 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>
<header id=header>
<a href="/">
@ -12,7 +12,8 @@
</a>
<nav id="menu-buttons">
<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
></ul>
</nav>

View File

@ -3,33 +3,13 @@
{% block description %}{{ description }}{% endblock %}
{% block paste_header %}
<div id=markdown>
{{ paste.markdown | safe }}
</div>
{% if paste.delete_at %}
<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>
{% 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 %}
{% 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 %}

View File

@ -4,13 +4,6 @@
<form method="post" action="/">
{% block paste_header %}{% endblock %}
<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 %}
<span id="right-buttons">
<button type=submit name=share value=share24>Share (delete after 24 hours)</button>