Add "Hello, world" examples
This commit is contained in:
parent
b791aba860
commit
adeb13189b
|
@ -34,7 +34,7 @@ walkdir = "2.2.9"
|
|||
|
||||
[dev-dependencies]
|
||||
scraper = "0.10.1"
|
||||
serde_json = "1.0.40"
|
||||
|
||||
[features]
|
||||
database_tests = []
|
||||
sandbox_tests = []
|
||||
|
|
|
@ -11,6 +11,7 @@ class Editor {
|
|||
output: Output
|
||||
autodeleteText: HTMLSpanElement
|
||||
autodeleteCheckbox: HTMLLabelElement
|
||||
helloWorldLink: HTMLSpanElement
|
||||
submit: HTMLInputElement
|
||||
detailsElement: HTMLDetailsElement
|
||||
stdinElement: HTMLTextAreaElement
|
||||
|
@ -31,11 +32,12 @@ class Editor {
|
|||
this.output.display({}, {
|
||||
stdout: stdout.value,
|
||||
stderr: document.querySelector<HTMLInputElement>('#dbstderr').value,
|
||||
status: +document.querySelector<HTMLInputElement>('#dbstatus')?.value,
|
||||
status: +document.querySelector<HTMLInputElement>('#dbstatus') ?.value,
|
||||
})
|
||||
}
|
||||
this.autodeleteText = form.querySelector('#autodelete-text')
|
||||
this.autodeleteCheckbox = form.querySelector('#automatically-hidden-label')
|
||||
this.helloWorldLink = form.querySelector('#hello-world')
|
||||
this.submit = form.querySelector('[type=submit]')
|
||||
this.submit.disabled = true
|
||||
form.addEventListener('submit', () => {
|
||||
|
@ -58,7 +60,7 @@ class Editor {
|
|||
this.stdinElement.name = 'stdin'
|
||||
this.stdinElement.addEventListener('change', () => this.changeToLookLikeNewPaste())
|
||||
this.detailsElement.append(summary, this.stdinElement)
|
||||
const dbStdin = document.querySelector<HTMLInputElement>('#dbstdin')?.value
|
||||
const dbStdin = document.querySelector<HTMLInputElement>('#dbstdin') ?.value
|
||||
if (dbStdin) {
|
||||
this.stdinElement.value = dbStdin
|
||||
this.detailsElement.open = true
|
||||
|
@ -112,12 +114,19 @@ class Editor {
|
|||
|
||||
async updateLanguage() {
|
||||
this.wrapperButtons.clear()
|
||||
this.helloWorldLink.textContent = ''
|
||||
const identifier = this.getLanguageIdentifier()
|
||||
this.setLanguage(identifier)
|
||||
const isStillValid = () => identifier === this.getLanguageIdentifier()
|
||||
const language = await getLanguage(identifier, isStillValid)
|
||||
// This deals with user changing the language after asynchronous event
|
||||
if (isStillValid()) {
|
||||
if (language.helloWorldPaste) {
|
||||
const anchor = document.createElement('a')
|
||||
anchor.href = '/' + language.helloWorldPaste
|
||||
anchor.textContent = 'Hello world program'
|
||||
this.helloWorldLink.append(' | ', anchor)
|
||||
}
|
||||
this.detailsElement.style.display = language.implementations.length ? 'block' : 'none'
|
||||
this.wrapperButtons.update(language.implementations)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
{
|
||||
"identifier": "c",
|
||||
"name": "C",
|
||||
"helloworld": "#include <stdio.h>\n\nint main(void) {\n puts(\"Hello, world!\");\n return 0;\n}",
|
||||
"implementations": [
|
||||
{
|
||||
"label": "Clang",
|
||||
|
@ -34,6 +35,7 @@
|
|||
{
|
||||
"identifier": "cpp",
|
||||
"name": "C++",
|
||||
"helloworld": "#include <iostream>\n\nint main(void) {\n std::cout << \"Hello, world!\\n\";\n}",
|
||||
"implementations": [
|
||||
{
|
||||
"label": "Clang",
|
||||
|
@ -62,6 +64,7 @@
|
|||
{
|
||||
"identifier": "csharp",
|
||||
"name": "C#",
|
||||
"helloworld": "using System;\n\nclass Program {\n static void Main(string[] args) {\n Console.WriteLine(\"Hello, world!\");\n }\n}",
|
||||
"implementations": [
|
||||
{
|
||||
"label": ".NET Core",
|
||||
|
@ -79,6 +82,7 @@
|
|||
{
|
||||
"identifier": "haskell",
|
||||
"name": "Haskell",
|
||||
"helloworld": "main = putStrLn \"Hello, world!\"",
|
||||
"implementations": [
|
||||
{
|
||||
"label": "GHC",
|
||||
|
@ -100,6 +104,7 @@
|
|||
{
|
||||
"identifier": "java",
|
||||
"name": "Java",
|
||||
"helloworld": "class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello, world!\");\n }\n}",
|
||||
"implementations": [
|
||||
{
|
||||
"label": "OpenJDK 8",
|
||||
|
@ -128,6 +133,7 @@
|
|||
{
|
||||
"identifier": "javascript",
|
||||
"name": "JavaScript",
|
||||
"helloworld": "console.log(\"Hello, world!\");",
|
||||
"implementations": [
|
||||
{
|
||||
"label": "Node.js",
|
||||
|
@ -157,6 +163,7 @@
|
|||
{
|
||||
"identifier": "perl",
|
||||
"name": "Perl",
|
||||
"helloworld": "use v5.12;\nuse warnings;\n\nsay \"Hello, world!\";",
|
||||
"implementations": [
|
||||
{
|
||||
"label": "Perl",
|
||||
|
@ -174,6 +181,7 @@
|
|||
{
|
||||
"identifier": "php",
|
||||
"name": "PHP",
|
||||
"helloworld": "<?php\necho \"Hello, world!\\n\";",
|
||||
"implementations": [
|
||||
{
|
||||
"label": "PHP",
|
||||
|
@ -199,6 +207,7 @@
|
|||
{
|
||||
"identifier": "python",
|
||||
"name": "Python 3",
|
||||
"helloworld": "print(\"Hello, world!\")",
|
||||
"implementations": [
|
||||
{
|
||||
"label": "CPython",
|
||||
|
@ -222,6 +231,7 @@
|
|||
{
|
||||
"identifier": "raku",
|
||||
"name": "Raku",
|
||||
"helloworld": "say \"Hello, world!\"",
|
||||
"implementations": [
|
||||
{
|
||||
"label": "Rakudo",
|
||||
|
@ -239,6 +249,7 @@
|
|||
{
|
||||
"identifier": "rust",
|
||||
"name": "Rust",
|
||||
"helloworld": "fn main() {\n println!(\"Hello, world!\");\n}",
|
||||
"implementations": [
|
||||
{
|
||||
"label": "Stable",
|
||||
|
@ -268,6 +279,7 @@
|
|||
{
|
||||
"identifier": "sh",
|
||||
"name": "Sh",
|
||||
"helloworld": "echo Hello, world!",
|
||||
"implementations": [
|
||||
{
|
||||
"label": "sh",
|
||||
|
@ -289,6 +301,7 @@
|
|||
{
|
||||
"identifier": "sqlite",
|
||||
"name": "SQLite",
|
||||
"helloworld": "SELECT 'Hello, world!'",
|
||||
"implementations": [
|
||||
{
|
||||
"label": "SQLite",
|
||||
|
@ -306,6 +319,7 @@
|
|||
{
|
||||
"identifier": "typescript",
|
||||
"name": "TypeScript",
|
||||
"helloworld": "console.log(\"Hello, world!\");",
|
||||
"implementations": [
|
||||
{
|
||||
"label": "Node.js",
|
||||
|
|
1
migrations/2019-11-24-101842_hello-world/down.sql
Normal file
1
migrations/2019-11-24-101842_hello-world/down.sql
Normal file
|
@ -0,0 +1 @@
|
|||
ALTER TABLE languages DROP COLUMN hello_world_paste_id;
|
1
migrations/2019-11-24-101842_hello-world/up.sql
Normal file
1
migrations/2019-11-24-101842_hello-world/up.sql
Normal file
|
@ -0,0 +1 @@
|
|||
ALTER TABLE languages ADD COLUMN hello_world_paste_id int REFERENCES pastes;
|
|
@ -19,7 +19,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let pool = Pool::new(ConnectionManager::new(database_url))
|
||||
.expect("Couldn't create a connection connection");
|
||||
diesel_migrations::run_pending_migrations(&pool.get()?)?;
|
||||
migration::run(pool.get()?)?;
|
||||
migration::run(&pool.get()?)?;
|
||||
warp::serve(routes::routes(pool)).run(([127, 0, 0, 1], 8080));
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::schema::{implementation_wrappers, implementations, languages};
|
||||
use diesel::pg::Pg;
|
||||
use crate::models::paste;
|
||||
use crate::schema::{implementation_wrappers, implementations, languages, pastes};
|
||||
use crate::Connection;
|
||||
use diesel::prelude::*;
|
||||
use diesel::sql_types::{Bool, Integer, Text};
|
||||
use serde::Deserialize;
|
||||
|
@ -9,6 +10,7 @@ use std::fs;
|
|||
#[derive(Deserialize)]
|
||||
struct Language {
|
||||
identifier: String,
|
||||
helloworld: Option<String>,
|
||||
#[serde(default)]
|
||||
implementations: Vec<Implementation>,
|
||||
}
|
||||
|
@ -32,13 +34,42 @@ struct Wrapper {
|
|||
is_formatter: bool,
|
||||
}
|
||||
|
||||
pub fn run(connection: impl Connection<Backend = Pg>) -> Result<(), Box<dyn Error>> {
|
||||
pub fn run(connection: &Connection) -> Result<(), Box<dyn Error>> {
|
||||
let languages: Vec<Language> = serde_json::from_slice(&fs::read("languages.json")?)?;
|
||||
for Language {
|
||||
identifier: languages_identifier,
|
||||
helloworld,
|
||||
implementations,
|
||||
} in languages
|
||||
{
|
||||
if let Some(hello_world) = helloworld {
|
||||
let paste_id: Option<i32> = languages::table
|
||||
.filter(languages::identifier.eq(&languages_identifier))
|
||||
.select(languages::hello_world_paste_id)
|
||||
.get_result(connection)?;
|
||||
if paste_id.is_none() {
|
||||
let identifier = paste::insert(
|
||||
connection,
|
||||
None,
|
||||
&languages_identifier,
|
||||
hello_world,
|
||||
"".into(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
diesel::update(languages::table)
|
||||
.set(
|
||||
languages::hello_world_paste_id.eq(pastes::table
|
||||
.select(pastes::paste_id)
|
||||
.filter(pastes::identifier.eq(identifier))
|
||||
.single_value()),
|
||||
)
|
||||
.filter(languages::identifier.eq(&languages_identifier))
|
||||
.execute(connection)?;
|
||||
}
|
||||
}
|
||||
for Implementation {
|
||||
label,
|
||||
identifier: implementation_identifier,
|
||||
|
@ -61,7 +92,7 @@ pub fn run(connection: impl Connection<Backend = Pg>) -> Result<(), Box<dyn Erro
|
|||
.on_conflict((implementations::language_id, implementations::identifier))
|
||||
.do_update()
|
||||
.set(implementations::label.eq(&label))
|
||||
.execute(&connection)?;
|
||||
.execute(connection)?;
|
||||
for (
|
||||
i,
|
||||
Wrapper {
|
||||
|
@ -105,7 +136,7 @@ pub fn run(connection: impl Connection<Backend = Pg>) -> Result<(), Box<dyn Erro
|
|||
implementation_wrappers::is_formatter.eq(is_formatter),
|
||||
implementation_wrappers::ordering.eq(i),
|
||||
))
|
||||
.execute(&connection)?;
|
||||
.execute(connection)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::schema::{implementation_wrappers, implementations, languages};
|
||||
use crate::schema::{implementation_wrappers, implementations, languages, pastes};
|
||||
use crate::Connection;
|
||||
use diesel::prelude::*;
|
||||
use futures::Future;
|
||||
|
@ -8,6 +8,12 @@ use tokio_executor::blocking;
|
|||
use warp::http::header::CACHE_CONTROL;
|
||||
use warp::{Rejection, Reply};
|
||||
|
||||
#[derive(Queryable)]
|
||||
struct Language {
|
||||
id: i32,
|
||||
paste_identifier: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Queryable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Wrapper {
|
||||
|
@ -37,6 +43,7 @@ struct ImplementationWrapper {
|
|||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct JsonLanguage {
|
||||
hello_world_paste: Option<String>,
|
||||
implementations: Vec<JsonImplementation>,
|
||||
}
|
||||
|
||||
|
@ -51,16 +58,22 @@ pub fn api_language(
|
|||
identifier: String,
|
||||
) -> impl Future<Item = impl Reply, Error = Rejection> {
|
||||
blocking::run(move || {
|
||||
let id: i32 = languages::table
|
||||
let language: Language = languages::table
|
||||
.filter(languages::identifier.eq(identifier))
|
||||
.select(languages::language_id)
|
||||
.select((
|
||||
languages::language_id,
|
||||
pastes::table
|
||||
.select(pastes::identifier)
|
||||
.filter(languages::hello_world_paste_id.eq(pastes::paste_id.nullable()))
|
||||
.single_value(),
|
||||
))
|
||||
.get_result(&connection)
|
||||
.optional()
|
||||
.map_err(warp::reject::custom)?
|
||||
.ok_or_else(warp::reject::not_found)?;
|
||||
let implementations = implementations::table
|
||||
.select((implementations::implementation_id, implementations::label))
|
||||
.filter(implementations::language_id.eq(id))
|
||||
.filter(implementations::language_id.eq(language.id))
|
||||
.load(&connection)
|
||||
.map_err(warp::reject::custom)?;
|
||||
let implementation_wrappers = ImplementationWrapper::belonging_to(&implementations)
|
||||
|
@ -103,7 +116,10 @@ pub fn api_language(
|
|||
})
|
||||
.collect();
|
||||
Ok(warp::reply::with_header(
|
||||
warp::reply::json(&JsonLanguage { implementations }),
|
||||
warp::reply::json(&JsonLanguage {
|
||||
implementations,
|
||||
hello_world_paste: language.paste_identifier,
|
||||
}),
|
||||
CACHE_CONTROL,
|
||||
"max-age=14400",
|
||||
))
|
||||
|
|
|
@ -18,7 +18,7 @@ pub fn display_paste(
|
|||
Paste::delete_old(connection)?;
|
||||
let languages = Language::fetch(connection)?;
|
||||
let paste: Paste = pastes::table
|
||||
.inner_join(languages::table)
|
||||
.inner_join(languages::table.on(pastes::language_id.eq(languages::language_id)))
|
||||
.select((
|
||||
pastes::paste,
|
||||
pastes::language_id,
|
||||
|
|
|
@ -237,7 +237,7 @@ mod test {
|
|||
)))
|
||||
.expect("Couldn't create a connection connection");
|
||||
diesel_migrations::run_pending_migrations(&pool.get().unwrap()).unwrap();
|
||||
migration::run(pool.get().unwrap()).unwrap();
|
||||
migration::run(&pool.get().unwrap()).unwrap();
|
||||
routes(pool).map(Reply::into_response).boxed()
|
||||
};
|
||||
}
|
||||
|
@ -268,6 +268,22 @@ mod test {
|
|||
.to_string()
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
pub struct Implementation<'a> {
|
||||
label: &'a str,
|
||||
#[serde(borrow)]
|
||||
wrappers: Vec<Wrapper<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Wrapper<'a> {
|
||||
identifier: &'a str,
|
||||
label: &'a str,
|
||||
is_asm: bool,
|
||||
is_formatter: bool,
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "database_tests"), ignore)]
|
||||
fn test_language_api() {
|
||||
|
@ -276,23 +292,6 @@ mod test {
|
|||
#[serde(borrow)]
|
||||
implementations: Vec<Implementation<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
pub struct Implementation<'a> {
|
||||
label: &'a str,
|
||||
#[serde(borrow)]
|
||||
wrappers: Vec<Wrapper<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Wrapper<'a> {
|
||||
identifier: &'a str,
|
||||
label: &'a str,
|
||||
is_asm: bool,
|
||||
is_formatter: bool,
|
||||
}
|
||||
|
||||
let response = warp::test::request()
|
||||
.path(&format!("/api/v0/language/{}", get_sh_id()))
|
||||
.reply(&*ROUTES);
|
||||
|
@ -331,4 +330,62 @@ mod test {
|
|||
"abc"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "sandbox_tests"), ignore)]
|
||||
fn test_sandbox() {
|
||||
#[derive(Deserialize)]
|
||||
struct LanguageIdentifier<'a> {
|
||||
identifier: &'a str,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ApiLanguage<'a> {
|
||||
hello_world_paste: Option<String>,
|
||||
#[serde(borrow)]
|
||||
implementations: Vec<Implementation<'a>>,
|
||||
}
|
||||
let languages = warp::test::request()
|
||||
.path("/api/v1/languages")
|
||||
.reply(&*ROUTES);
|
||||
let languages =
|
||||
serde_json::from_slice::<Vec<LanguageIdentifier>>(languages.body()).unwrap();
|
||||
for LanguageIdentifier { identifier } in languages {
|
||||
let language = warp::test::request()
|
||||
.path(&format!("/api/v0/language/{}", identifier))
|
||||
.reply(&*ROUTES);
|
||||
if let ApiLanguage {
|
||||
hello_world_paste: Some(hello_world_paste),
|
||||
implementations,
|
||||
} = serde_json::from_slice(language.body()).unwrap()
|
||||
{
|
||||
let code = warp::test::request()
|
||||
.path(&format!("/{}.txt", hello_world_paste))
|
||||
.reply(&*ROUTES);
|
||||
let wrappers = implementations
|
||||
.into_iter()
|
||||
.flat_map(|i| i.wrappers)
|
||||
.filter(|w| w.label == "Run");
|
||||
for Wrapper { identifier, .. } in wrappers {
|
||||
let body = format!(
|
||||
"code={}&compilerOptions=&stdin=",
|
||||
str::from_utf8(code.body()).unwrap()
|
||||
);
|
||||
let out = warp::test::request()
|
||||
.path(&format!("/api/v0/run/{}", identifier))
|
||||
.method("POST")
|
||||
.header(CONTENT_LENGTH, body.len())
|
||||
.body(body)
|
||||
.reply(&*ROUTES);
|
||||
let body = str::from_utf8(out.body()).unwrap();
|
||||
assert!(
|
||||
body.contains(r#"Hello, world!\n""#),
|
||||
"{}: {}",
|
||||
identifier,
|
||||
body,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ table! {
|
|||
priority -> Int4,
|
||||
name -> Text,
|
||||
identifier -> Text,
|
||||
hello_world_paste_id -> Nullable<Int4>,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,6 +47,5 @@ table! {
|
|||
|
||||
joinable!(implementation_wrappers -> implementations (implementation_id));
|
||||
joinable!(implementations -> languages (language_id));
|
||||
joinable!(pastes -> languages (language_id));
|
||||
|
||||
allow_tables_to_appear_in_same_query!(implementations, implementation_wrappers, languages, pastes,);
|
||||
|
|
|
@ -15,9 +15,10 @@
|
|||
This paste will be automatically deleted on @delete_at.format("%Y-%m-%d %H:%M") UTC.
|
||||
</span>
|
||||
}
|
||||
<label id="automatically-hidden-label">
|
||||
<input type=checkbox name=autodelete checked> Automatically delete after 24 hours
|
||||
</label>
|
||||
<span id="automatically-hidden-label">
|
||||
<label><input type=checkbox name=autodelete checked> Automatically delete after 24 hours</label>
|
||||
<span id=hello-world></span>
|
||||
</span>
|
||||
</p>
|
||||
<p><textarea id=code name=code>@('\n')@paste.paste</textarea></p>
|
||||
@:buttons()
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
<label>
|
||||
<input type=checkbox name=autodelete checked> Automatically delete after 24 hours
|
||||
</label>
|
||||
<span id=hello-world></span>
|
||||
</p>
|
||||
<p><textarea id=code name=code></textarea></p>
|
||||
@:buttons()
|
||||
|
|
Loading…
Reference in New Issue
Block a user