Commit 6bf43ec9 authored by Konrad Borowski's avatar Konrad Borowski

Move language migrations to JSON

parent cbc70576
Pipeline #97967389 passed with stages
in 19 minutes and 23 seconds
......@@ -17,9 +17,10 @@ build:release: &build
- cargo build --release --verbose
artifacts:
paths:
- target/release/pastebinrun
- languages.json
- migrations
- static
- target/release/pastebinrun
cache:
key: release
paths:
......
......@@ -23,6 +23,7 @@ pulldown-cmark = "0.5.0"
rand = "0.7.0"
reqwest = "0.9.19"
serde = { version = "1.0.88", features = ["derive"] }
serde_json = "1.0.41"
time-parse = "0.1.2"
tokio-executor = { version = "0.2.0-alpha.4", features = ["blocking"] }
warp = "0.1.15"
......
......@@ -87,7 +87,7 @@ class Editor {
return this.languageSelector.selectedOptions[0].value
}
async run(implementationIdentifier, wrapper, compilerOptions) {
async run(wrapper, compilerOptions) {
this.output.clear()
if (this.abortEval) {
this.abortEval.abort()
......@@ -104,8 +104,7 @@ class Editor {
},
signal: this.abortEval.signal,
}
const languageIdentifier = this.getLanguageIdentifier()
const path = `/api/v0/run/${languageIdentifier}/${implementationIdentifier}/${wrapper.identifier}`
const path = `/api/v0/run/${wrapper.identifier}`
let response
try {
response = await (await fetch(path, parameters)).json()
......
......@@ -4,7 +4,7 @@ export default class WrapperButtons {
buttonsContainer: HTMLSpanElement
compilerOptions: HTMLInputElement
buttons: HTMLSpanElement
run: (identifier: string, wrapper: Wrapper, compilerOptions: string) => void;
run: (wrapper: Wrapper, compilerOptions: string) => void;
abortController: AbortController
select: HTMLSelectElement
optionMap = new WeakMap<HTMLOptionElement, { identifier: string, wrappers: Wrapper[] }>()
......@@ -50,13 +50,12 @@ export default class WrapperButtons {
}
if (options.length !== 0) {
const option = options[0]
const { wrappers, identifier } = this.optionMap.get(option)
for (const wrapper of wrappers) {
for (const wrapper of this.optionMap.get(option).wrappers) {
const button = document.createElement('button')
button.textContent = wrapper.label
button.addEventListener('click', e => {
e.preventDefault()
this.run(identifier, wrapper, this.compilerOptions.value)
this.run(wrapper, this.compilerOptions.value)
})
this.buttons.append(button)
}
......
[
{
"identifier": "plaintext",
"name": "Plain text"
},
{
"identifier": "c",
"name": "C",
"implementations": [
{
"label": "Clang",
"identifier": "clang",
"wrappers": [
{
"identifier": "clang",
"label": "Run",
"code": "mv code code.cpp; clang++ %s code.cpp && ./a.out"
}
]
},
{
"label": "gcc",
"identifier": "gcc",
"wrappers": [
{
"identifier": "gcc",
"label": "Run",
"code": "mv code code.c; gcc %s code.c && ./a.out"
}
]
}
]
},
{
"identifier": "cpp",
"name": "C++",
"implementations": [
{
"label": "Clang",
"identifier": "clang",
"wrappers": [
{
"identifier": "clangcpp",
"label": "Run",
"code": "mv code code.cpp; clang++ %s code.cpp && ./a.out"
}
]
},
{
"label": "g++",
"identifier": "gcc",
"wrappers": [
{
"identifier": "gpp",
"label": "Run",
"code": "mv code code.cpp; g++ %s code.cpp && ./a.out"
}
]
}
]
},
{
"identifier": "csharp",
"name": "C#"
},
{
"identifier": "haskell",
"name": "Haskell"
},
{
"identifier": "html",
"name": "HTML"
},
{
"identifier": "java",
"name": "Java"
},
{
"identifier": "javascript",
"name": "JavaScript"
},
{
"identifier": "jinja2",
"name": "Jinja2"
},
{
"identifier": "jsx",
"name": "JSX"
},
{
"identifier": "markdown",
"name": "Markdown"
},
{
"identifier": "perl",
"name": "Perl",
"implementations": [
{
"label": "Perl",
"identifier": "perl",
"wrappers": [
{
"identifier": "perl",
"label": "Run",
"code": "perl %s code"
}
]
}
]
},
{
"identifier": "php",
"name": "PHP"
},
{
"identifier": "postgresql",
"name": "PostgreSQL"
},
{
"identifier": "python2",
"name": "Python 2"
},
{
"identifier": "python",
"name": "Python 3",
"implementations": [
{
"label": "CPython",
"identifier": "cpython",
"wrappers": [
{
"identifier": "cpython",
"label": "Run",
"code": "python3 %s code"
},
{
"identifier": "black",
"label": "Format (black)",
"code": "black code; cat code",
"is_formatter": true
}
]
}
]
},
{
"identifier": "raku",
"name": "Raku",
"implementations": [
{
"label": "Rakudo",
"identifier": "rakudo",
"wrappers": [
{
"identifier": "rakudo",
"label": "Run",
"code": "perl6 %s code"
}
]
}
]
},
{
"identifier": "rust",
"name": "Rust",
"implementations": [
{
"label": "Stable",
"identifier": "stable",
"wrappers": [
{
"identifier": "rustc-stable",
"label": "Run",
"code": "mv code code.rs && rustc %s code.rs && ./code"
},
{
"identifier": "rustc-asm-stable",
"label": "ASM",
"code": "rustc --emit asm --crate-type rlib %s code && cat code.s",
"is_asm": true
},
{
"identifier": "rustfmt-stable",
"label": "Rustfmt",
"code": "rustfmt code; cat code",
"is_formatter": true
}
]
}
]
},
{
"identifier": "sh",
"name": "Sh",
"implementations": [
{
"label": "sh",
"identifier": "sh",
"wrappers": [
{
"identifier": "sh",
"label": "Run",
"code": "sh %s code"
}
]
}
]
},
{
"identifier": "sql",
"name": "SQL"
},
{
"identifier": "sqlite",
"name": "SQLite",
"implementations": [
{
"label": "SQLite",
"identifier": "sqlite",
"wrappers": [
{
"identifier": "sqlite",
"label": "Run",
"code": "sqlite3 %s < code"
}
]
}
]
},
{
"identifier": "typescript",
"name": "TypeScript"
},
{
"identifier": "tsx",
"name": "TypeScript-JSX"
}
]
TRUNCATE implementation_wrappers;
DELETE FROM implementations;
ALTER TABLE implementation_wrappers
DROP CONSTRAINT implementation_wrappers_identifier_key,
ADD CONSTRAINT implementation_wrappers_implementation_id_identifier_key
UNIQUE (implementation_id, identifier);
WITH impl (identifier, implid, impllabel) AS (
VALUES
('cpp', 'clang', 'Clang'),
('cpp', 'g-plus-plus', 'g++'),
('c', 'clang', 'Clang'),
('c', 'gcc', 'gcc'),
('python', 'cpython', 'CPython'),
('rust', 'rustc', 'rustc'),
('sqlite', 'sqlite', 'SQLite'),
('sh', 'sh', 'sh'),
('raku', 'rakudo', 'Rakudo'),
('perl', 'perl', 'perl')
)
INSERT INTO implementations (language_id, identifier, label)
SELECT language_id, implid, impllabel
FROM impl
JOIN languages USING (identifier);
WITH implwrap (langidentifier, implidentifier, wrapidentifier, wraplabel, code, ordering, is_formatter, is_asm) AS (
VALUES
('cpp', 'clang', 'run', 'Run', 'mv code code.cpp; clang++ %s code.cpp && ./a.out', 1, FALSE, FALSE),
('cpp', 'g-plus-plus', 'run', 'Run', 'mv code code.cpp; g++ %s code.cpp && ./a.out', 1, FALSE, FALSE),
('c', 'clang', 'run', 'Run', 'mv code code.c; clang %s code.c && ./a.out', 1, FALSE, FALSE),
('c', 'gcc', 'run', 'Run', 'mv code code.c; gcc %s code.c && ./a.out', 1, FALSE, FALSE),
('python', 'cpython', 'format', 'Format (black)', 'black code; cat code', 2, TRUE, FALSE),
('python', 'cpython', 'run', 'Run', 'python3 %s code', 1, FALSE, FALSE),
('sh', 'sh', 'run', 'Run', 'sh %s code', 1, FALSE, FALSE),
('sqlite', 'sqlite', 'run', 'Run', 'sqlite3 %s < code', 1, FALSE, FALSE),
('rust', 'rustc', 'run', 'Run', 'mv code code.rs && rustc %s code.rs && ./code', 1, FALSE, FALSE),
('raku', 'rakudo', 'run', 'Run', 'perl6 %s code', 1, FALSE, FALSE),
('rust', 'rustc', 'format', 'Rustfmt', 'rustfmt code; cat code', 3, TRUE, FALSE),
('rust', 'rustc', 'asm', 'ASM', 'rustc --emit asm --crate-type rlib code && cat code.s', 2, FALSE, TRUE),
('perl', 'perl', 'run', 'Run', 'perl %s code', 1, FALSE, FALSE)
)
INSERT INTO implementation_wrappers (implementation_id, identifier, label, code, ordering, is_formatter, is_asm)
SELECT implementation_id, wrapidentifier, wraplabel, code, ordering, is_formatter, is_asm
FROM implwrap
JOIN implementations ON implidentifier = implementations.identifier
JOIN languages ON implementations.language_id = languages.language_id AND langidentifier = languages.identifier;
TRUNCATE implementation_wrappers;
DELETE FROM implementations;
ALTER TABLE implementation_wrappers
DROP CONSTRAINT implementation_wrappers_implementation_id_identifier_key,
ADD CONSTRAINT implementation_wrappers_identifier_key
UNIQUE (identifier);
#[macro_use]
extern crate diesel;
mod migration;
mod models;
mod routes;
mod schema;
......@@ -18,6 +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()?)?;
warp::serve(routes::routes(pool)).run(([127, 0, 0, 1], 8080));
Ok(())
}
......
use crate::schema::{implementation_wrappers, implementations, languages};
use diesel::pg::Pg;
use diesel::prelude::*;
use diesel::sql_types::{Bool, Integer, Text};
use serde::Deserialize;
use std::error::Error;
use std::fs;
#[derive(Deserialize)]
struct Language {
identifier: String,
#[serde(default)]
implementations: Vec<Implementation>,
}
#[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: impl Connection<Backend = Pg>) -> Result<(), Box<dyn Error>> {
let languages: Vec<Language> = serde_json::from_slice(&fs::read("languages.json")?)?;
for Language {
identifier: languages_identifier,
implementations,
} in languages
{
for Implementation {
label,
identifier: implementation_identifier,
wrappers,
} in implementations
{
languages::table
.filter(languages::identifier.eq(&languages_identifier))
.select((
languages::language_id,
label.as_sql::<Text>(),
implementation_identifier.as_sql::<Text>(),
))
.insert_into(implementations::table)
.into_columns((
implementations::language_id,
implementations::label,
implementations::identifier,
))
.on_conflict((implementations::language_id, implementations::identifier))
.do_update()
.set(implementations::label.eq(&label))
.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(())
}
......@@ -20,7 +20,6 @@ struct Wrapper {
#[derive(Identifiable, Queryable)]
struct Implementation {
id: i32,
identifier: String,
label: String,
}
......@@ -43,7 +42,6 @@ struct JsonLanguage {
#[derive(Serialize)]
struct JsonImplementation {
identifier: String,
label: String,
wrappers: Vec<Wrapper>,
}
......@@ -61,11 +59,7 @@ pub fn api_language(
.map_err(warp::reject::custom)?
.ok_or_else(warp::reject::not_found)?;
let implementations = implementations::table
.select((
implementations::implementation_id,
implementations::identifier,
implementations::label,
))
.select((implementations::implementation_id, implementations::label))
.filter(implementations::language_id.eq(id))
.load(&connection)
.map_err(warp::reject::custom)?;
......@@ -86,7 +80,6 @@ pub fn api_language(
.into_iter()
.zip(implementations)
.map(|(wrappers, implementation)| JsonImplementation {
identifier: implementation.identifier,
label: implementation.label,
wrappers: wrappers
.into_iter()
......
......@@ -103,11 +103,10 @@ fn api_v0(pool: PgPool) -> BoxedFilter<(impl Reply,)> {
.and(warp::get2())
.and_then(api_language::api_language);
let run = root
.and(path!("run" / String / String))
.and(path!("run" / String))
.and(warp::post2())
.and(warp::body::content_length_limit(1_000_000))
.and(warp::body::form())
.and(path!(String))
.and_then(run::run);
language.or(run).boxed()
}
......@@ -214,6 +213,7 @@ fn not_found(pool: PgPool) -> impl Clone + Fn(Rejection) -> NotFoundFuture {
#[cfg(test)]
mod test {
use super::routes;
use crate::migration;
use diesel::r2d2::{ConnectionManager, CustomizeConnection, Pool};
use diesel::Connection;
use lazy_static::lazy_static;
......@@ -237,6 +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();
routes(pool).map(Reply::into_response).boxed()
};
}
......@@ -278,7 +279,6 @@ mod test {
#[derive(Debug, Deserialize, PartialEq)]
pub struct Implementation<'a> {
identifier: &'a str,
label: &'a str,
#[serde(borrow)]
wrappers: Vec<Wrapper<'a>>,
......@@ -300,10 +300,9 @@ mod test {
serde_json::from_slice::<ApiLanguage>(response.body()).unwrap(),
ApiLanguage {
implementations: vec![Implementation {
identifier: "sh",
label: "sh",
wrappers: vec![Wrapper {
identifier: "run",
identifier: "sh",
label: "Run",
is_asm: false,
is_formatter: false,
......
use crate::schema::{implementation_wrappers, implementations, languages};
use crate::schema::implementation_wrappers;
use crate::Connection;
use diesel::prelude::*;
use futures::Future;
......@@ -44,20 +44,14 @@ struct Output {
pub fn run(
connection: Connection,
language: String,
implementation: String,
identifier: String,
Form {
code,
compiler_options,
}: Form,
identifier: String,
) -> impl Future<Item = impl Reply, Error = Rejection> {
blocking::run(move || {
implementations::table
.inner_join(implementation_wrappers::table)
.inner_join(languages::table)
.filter(languages::identifier.eq(language))
.filter(implementations::identifier.eq(implementation))
implementation_wrappers::table
.filter(implementation_wrappers::identifier.eq(identifier))
.select(implementation_wrappers::code)
.get_result(&connection)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment