use std::{ fs::File, io::{Read, Write}, path::PathBuf, process::Command, }; use axum::{ Json, Router, body::Bytes, extract::{Request, State}, http::{HeaderMap, HeaderValue, StatusCode, header::CONTENT_TYPE}, middleware::{self, Next}, response::IntoResponse, routing::get, }; use include_dir::{Dir, include_dir}; use serde::Serialize; static PUBLIC_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/public"); #[derive(Serialize)] struct OutputResponse { pub stdout: Vec, pub stderr: Vec, } async fn serve_mem(request: Request, _next: Next) -> impl IntoResponse { let uri = request.uri(); let filename = if *uri == *"/" { "index.html".to_string() } else { uri.to_string().trim_start_matches("/").into() }; if let Some(file) = PUBLIC_DIR.get_file(filename) { let ext = file.path().extension(); let mut headers = HeaderMap::new(); let mime = if let Some(Some(ext)) = ext.map(|f| f.to_str()) { match ext { "html" => "text/html", "js" => "application/javascript", "css" => "text/css", "svg" => "image/svg+xml", _ => "text/plain", } } else { "text/plain" }; headers.insert(CONTENT_TYPE, HeaderValue::from_str(mime).unwrap()); (headers, file.contents()).into_response() } else { StatusCode::NOT_FOUND.into_response() } } async fn get_code(State(state): State) -> impl IntoResponse { let mut path = state.path.clone(); path.push("src/main.rs"); let mut file = File::open(path).unwrap(); let mut contents = String::new(); file.read_to_string(&mut contents).unwrap(); contents.into_response() } async fn put_code(State(state): State, body: Bytes) -> impl IntoResponse { let mut path = state.path.clone(); path.push("src/main.rs"); let mut file = File::options() .write(true) .truncate(true) .open(path) .unwrap(); file.write_all(&body[..]).unwrap(); StatusCode::OK.into_response() } async fn run_code(State(state): State) -> impl IntoResponse { let mut path = state.path.clone(); path.push("Cargo.toml"); let cargo = std::env::var("CARGO").unwrap(); let output = Command::new(cargo) .args(["run", "--manifest-path", path.to_str().unwrap()]) .output() .unwrap(); let res = OutputResponse { stdout: output.stdout, stderr: output.stderr, }; Json(res) } #[derive(Clone)] struct ServerState { pub path: PathBuf, } pub async fn start(path: PathBuf) { let app = Router::new() .layer(middleware::from_fn(serve_mem)) .route("/code", get(get_code).post(run_code).put(put_code)) .with_state(ServerState { path }); let listener = tokio::net::TcpListener::bind("127.0.0.1:4555") .await .unwrap(); axum::serve(listener, app).await.unwrap(); }