make it possible to implement another lanaguage than python

This commit is contained in:
b1ek 2024-02-18 15:16:55 +10:00
parent 62f9981325
commit 888743f4de
Signed by: blek
GPG Key ID: 14546221E3595D0C
7 changed files with 128 additions and 40 deletions

30
src/executor.rs Normal file
View File

@ -0,0 +1,30 @@
use crate::executor::python::PythonExecutor;
pub mod python;
pub mod helper;
pub fn executors() -> Vec<Box<dyn Executor>> {
vec![
Box::new(PythonExecutor {}),
]
}
pub fn executors_id() -> Vec<String> {
executors()
.iter()
.map(|x| x.id())
.collect()
}
pub fn get_executor<'a, T: Into<String>>(id: T, executors: &'a Vec<Box<dyn Executor>>) -> Option<&'a Box<dyn Executor>> {
let id: String = id.into();
let found = executors.iter().find(|x| x.id() == id);
found
}
pub trait Executor {
fn id(&self) -> String;
fn exec(&self, code: String) -> Result<String, String>;
}

30
src/executor/helper.rs Normal file
View File

@ -0,0 +1,30 @@
use std::{fs::create_dir_all, process::ExitStatus};
use rand::{distributions::Alphanumeric, Rng, thread_rng};
pub fn assure_dir_exists() -> Result<(), std::io::Error> {
create_dir_all("/tmp/sandy-tmp")?;
Ok(())
}
pub fn create_path() -> String {
let name = thread_rng()
.sample_iter(&Alphanumeric)
.take(64)
.map(char::from)
.collect::<String>();
format!("/tmp/sandy-tmp/{name}")
}
pub fn exit_code_msg(code: ExitStatus) -> String {
let status = code.code();
if let Some(status) = status {
if status != 0 {
format!("<span style='color:red;font-weight:bolder'>Command exited with code {status}</span>")
} else {
"Command exited with code 0".into()
}
} else {
"Command exited with unknown code".into()
}
}

51
src/executor/python.rs Normal file
View File

@ -0,0 +1,51 @@
use std::fs;
use std::io::Read;
use std::process::Command;
use std::process::Stdio;
use super::Executor;
use super::helper::*;
#[derive(Debug, Clone, Copy)]
pub struct PythonExecutor {}
impl Executor for PythonExecutor {
fn id(&self) -> String {
"python".into()
}
fn exec(&self, code: String) -> Result<String, String> {
assure_dir_exists().map_err(|x| x.to_string())?;
let path = create_path();
// 1. Save the code
fs::write(path.clone(), code).map_err(|x| x.to_string())?;
// 2. Run
let mut out = Command::new("python")
.arg(path.clone())
.stdout(Stdio::piped())
.spawn()
.unwrap();
// 3. Grab the stuff and exit
let exit_status = out.wait().map_err(|x| x.to_string())?;
let mut stdout: String = "".into();
if let Some(mut sout) = out.stdout {
let mut buf = vec![];
sout.read_to_end(&mut buf).map_err(|x| x.to_string())?;
stdout += String::from_utf8(buf).map_err(|x| x.to_string())?.as_str();
} else {
stdout += "Couldn't get stdout from the process"
}
stdout += "\n\n";
stdout += exit_code_msg(exit_status).as_str();
Ok(stdout)
}
}

View File

@ -3,8 +3,10 @@
use std::{fs::create_dir_all, net::SocketAddr, process::Stdio};
use web::state::{SharedState, TemplateState};
use executor::executors_id;
mod web;
pub mod executor;
fn check() {
let res = std::process::Command::new("python")
@ -26,7 +28,9 @@ async fn main() {
check();
let state = SharedState {
template: TemplateState {}
template: TemplateState {
langs: executors_id()
}
};
let routes = web::routes(state);

View File

@ -1,11 +1,8 @@
use std::{fs::{self, remove_file}, io::Read, process::{Command, Stdio}};
use rand::{distributions::Alphanumeric, Rng};
use serde::{Serialize, Deserialize};
use warp::{reject::Rejection, reply::{json, with_status, Reply}, Filter, http::StatusCode};
use crate::SharedState;
use crate::{executor::{executors, executors_id, get_executor, python::PythonExecutor, Executor}, SharedState};
#[derive(Serialize, Deserialize)]
struct ExecutorData {
@ -15,49 +12,25 @@ struct ExecutorData {
async fn executor(_state: SharedState, data: ExecutorData) -> Result<Box<dyn Reply>, Rejection> {
if data.lang != "python" {
let lang = data.lang;
let execs = executors();
let found = get_executor(&lang, &execs);
if found.is_none() {
return Ok(
Box::new(
with_status(
json(&"only python supported".to_string()),
json(&format!("{lang} is not supported")),
StatusCode::BAD_REQUEST
)
)
)
}
let name = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(64)
.map(char::from)
.collect::<String>();
let path = format!("/tmp/sandy-tmp/{name}");
let lang = found.unwrap();
let out = lang.exec(data.code).unwrap();
fs::write(path.clone(), &data.code).unwrap();
let mut out = Command::new("python")
.arg(path.clone())
.stdout(Stdio::piped())
.spawn()
.unwrap();
let exit_status = out.wait().unwrap().code().unwrap();
let mut buf = vec![];
out.stdout.unwrap().read_to_end(&mut buf).unwrap();
let mut stdout = String::from_utf8(buf).unwrap();
stdout += "\n---\n";
if exit_status != 0 {
stdout += format!("<span style='color:red;font-weight:bolder'>Command exited with code {}</span>", exit_status).as_str();
} else {
stdout += "Command exited with code 0";
}
let stdout = stdout.replace('\n', "<br>");
remove_file(path).unwrap();
Ok(Box::new(warp::reply::json(&stdout)))
Ok(Box::new(warp::reply::json(&out)))
}
fn executor_f(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {

View File

@ -9,7 +9,7 @@ use super::state::TemplateState;
#[derive(Template)]
#[template( path = "index.html" )]
pub struct Index {
pub state: TemplateState
state: TemplateState
}
impl Index {

View File

@ -6,5 +6,5 @@ pub struct SharedState {
#[derive(Debug, Clone)]
pub struct TemplateState {
pub langs: Vec<String>
}