Skip to content

Commit 5b9def9

Browse files
committed
feat: Add initial version of the code-runner with only few languages
1 parent 7573bc8 commit 5b9def9

21 files changed

Lines changed: 451 additions & 1 deletion

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "code-runner-rust"
3+
version = "0.1.0"
4+
authors = ["Johnny Eric Amancio <johnnyeric@gmail.com>"]
5+
6+
[dependencies]
7+
serde = { version = "1.0", features = ["derive"] }
8+
serde_derive = "1.0"
9+
serde_json = "1.0"
10+
phf = { version = "0.7.24", features = ["macros"] }
11+
tempfile = "3"

README.md

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,112 @@
11
# code-runner-rust
2-
Code Runner written in Rust
2+
3+
This is my attempt to port the [glot-code-runner](https://github.com/prasmussen/glot-code-runner) to Rust.
4+
5+
## Overview
6+
code-runner-rust is a command line application that reads code as a
7+
json payload from stdin – compiles and runs the code – and writes
8+
the result as json to stdout.
9+
10+
## Prerequisites
11+
code-runner-rust requires that the compiler / interpreter for the languages
12+
you want to run is installed and is in PATH.
13+
14+
## Supported languages
15+
- golang
16+
- javascript
17+
- python
18+
- rust
19+
20+
## Input (stdin)
21+
The input is required to be a json object containing the properties `language`
22+
and `files`. `language` must be a lowecase string matching one of the supported
23+
languages. `files` must be an array with at least one object containing the
24+
properties `name` and `content`. `name` is the name of the file and can include
25+
forward slashes to create the file in a subdirectory relative to the base
26+
directory. All files are written into the same base directory under the OS's
27+
temp dir.
28+
29+
In addition, one may optionally provide the `stdin` and `command` properties to
30+
provide stdin data to the running code and to run the code with a custom command.
31+
See examples below.
32+
33+
## Output (stdout)
34+
The output is a json object containing the properties `stdout`, `stderr` and
35+
`error`. `stdout` and `stderr` is captured from the output of the ran code.
36+
`error` is popuplated if there is a compiler / interpreter error.
37+
Note that glot-code-runner will exit with a non-zero code if invalid input is
38+
given or if the files cannot be written to disk (permissions, disk space, etc).
39+
No json will be written to stdout in those cases. Otherwise the exit code is 0.
40+
41+
## Examples
42+
43+
### Simple example
44+
##### Input
45+
```javascript
46+
{
47+
"language": "python",
48+
"files": [
49+
{
50+
"name": "main.py",
51+
"content": "print(42)"
52+
}
53+
]
54+
}
55+
```
56+
57+
##### Output
58+
```javascript
59+
{
60+
"stdout": "42\n",
61+
"stderr": "",
62+
"error": ""
63+
}
64+
```
65+
66+
### Read from stdin
67+
##### Input
68+
```javascript
69+
{
70+
"language": "python",
71+
"stdin": "42",
72+
"files": [
73+
{
74+
"name": "main.py",
75+
"content": "print(input('Number from stdin: '))"
76+
}
77+
]
78+
}
79+
```
80+
81+
##### Output
82+
```javascript
83+
{
84+
"stdout": "Number from stdin: 42\n",
85+
"stderr": "",
86+
"error": ""
87+
}
88+
```
89+
90+
### Custom run command
91+
##### Input
92+
```javascript
93+
{
94+
"language": "bash",
95+
"command": "bash main.sh 42",
96+
"files": [
97+
{
98+
"name": "main.sh",
99+
"content": "echo Number from arg: $1"
100+
}
101+
]
102+
}
103+
```
104+
105+
##### Output
106+
```javascript
107+
{
108+
"stdout": "Number from arg: 42\n",
109+
"stderr": "",
110+
"error": ""
111+
}
112+
```

src/executor.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use std::io::{Write, Error};
2+
use std::process::{Command, Stdio};
3+
use std::result::Result;
4+
use types::ExecutorResult;
5+
6+
pub fn run_stdin(work_dir: &str, stdin: &str, args: &[&str]) -> Result<ExecutorResult, Error> {
7+
let mut command = Command::new(&args[0]);
8+
command
9+
.args(&args[1..])
10+
.current_dir(work_dir)
11+
.stdin(Stdio::piped())
12+
.stdout(Stdio::piped())
13+
.stderr(Stdio::piped());
14+
15+
let mut child = command
16+
.spawn()
17+
.expect("Failed to spawn child process");
18+
19+
{
20+
let stdin_stream = child.stdin.as_mut().expect("Failed to open stdin");
21+
stdin_stream.write_all(stdin.as_bytes()).expect("Failed to write to stdin");
22+
}
23+
24+
let output = child.wait_with_output().expect("Failed to read stdout");
25+
26+
let code = match output.status.code() {
27+
Some(code) => code,
28+
None => -1
29+
};
30+
31+
Ok(ExecutorResult{
32+
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
33+
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
34+
status_code: code.to_string()
35+
})
36+
}
37+
38+
pub fn run(work_dir: &str, args: &[&str]) -> Result<ExecutorResult, Error>{
39+
run_stdin(work_dir, "", &args)
40+
}
41+
42+
pub fn run_bash_stdin(work_dir: &str, command: &str, stdin: &str) -> Result<ExecutorResult, Error> {
43+
run_stdin(work_dir, stdin, &["bash", "-c", command])
44+
}
45+
46+
/* pub fn run_bash(work_dir: &str, command: &str) -> Result<ExecutorResult, Error> {
47+
run(work_dir, &["bash", "-c", command])
48+
} */

src/languages/golang.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use std::io::{Error};
2+
use types::ExecutorResult;
3+
use std::path::Path;
4+
use executor;
5+
6+
pub fn run(files: &Vec<String>, stdin: &str) -> Result<ExecutorResult, Error> {
7+
let parent = Path::new(&files[0]).parent();
8+
9+
match parent {
10+
Some(work_dir) => {
11+
executor::run_stdin(&work_dir.to_string_lossy(), &stdin, &["go", "run", &files[0]])
12+
}
13+
None => {
14+
panic!("Error finding work dir");
15+
}
16+
}
17+
}

src/languages/javascript.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use std::io::{Error};
2+
use types::ExecutorResult;
3+
use std::path::Path;
4+
use executor;
5+
6+
pub fn run(files: &Vec<String>, stdin: &str) -> Result<ExecutorResult, Error> {
7+
let parent = Path::new(&files[0]).parent();
8+
9+
match parent {
10+
Some(work_dir) => {
11+
executor::run_stdin(&work_dir.to_string_lossy(), &stdin, &["node", &files[0]])
12+
}
13+
None => {
14+
panic!("Error finding work dir");
15+
}
16+
}
17+
18+
}

src/languages/languages.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use phf::phf_map;
2+
use std::io::{Error};
3+
use types::ExecutorResult;
4+
use languages;
5+
6+
type RunnerFunction = fn(&Vec<String>, &str) -> Result<ExecutorResult, Error>;
7+
8+
static RUNNERS: phf::Map<&'static str, RunnerFunction> = phf_map! {
9+
"javascript" => languages::javascript::run,
10+
"python" => languages::python::run,
11+
"go" => languages::golang::run,
12+
"rust" => languages::rust::run,
13+
};
14+
15+
pub fn is_supported(language: &str) -> bool {
16+
RUNNERS.contains_key(language)
17+
}
18+
19+
pub fn run(language: &str, files: &Vec<String>, stdin: &str) -> Result<ExecutorResult, Error> {
20+
RUNNERS[language](files, stdin)
21+
}

src/languages/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
pub use self::languages::{is_supported, run};
2+
3+
mod languages;
4+
5+
// Language Runners
6+
mod javascript;
7+
mod golang;
8+
mod python;
9+
mod rust;

src/languages/python.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use std::io::{Error};
2+
use types::ExecutorResult;
3+
use std::path::Path;
4+
use executor;
5+
6+
pub fn run(files: &Vec<String>, stdin: &str) -> Result<ExecutorResult, Error> {
7+
let parent = Path::new(&files[0]).parent();
8+
9+
match parent {
10+
Some(work_dir) => {
11+
executor::run_stdin(&work_dir.to_string_lossy(), &stdin, &["python", &files[0]])
12+
}
13+
None => {
14+
panic!("Error finding work dir");
15+
}
16+
}
17+
}

src/languages/rust.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use std::io::{Error};
2+
use types::ExecutorResult;
3+
use std::path::Path;
4+
use executor;
5+
6+
pub fn run(files: &Vec<String>, stdin: &str) -> Result<ExecutorResult, Error> {
7+
let parent = Path::new(&files[0]).parent();
8+
let bin_name: String = "a.out".to_string();
9+
10+
match parent {
11+
Some(work_dir) => {
12+
let result = executor::run(&work_dir.to_string_lossy(), &["rustc", "-o", &bin_name, &files[0]]);
13+
14+
match result {
15+
Ok(_compiler_result) => {
16+
let path = Path::new(work_dir).join(bin_name);
17+
executor::run_stdin(&work_dir.to_string_lossy(), &stdin, &[&path.as_path().to_string_lossy()])
18+
}
19+
Err(_e) => {
20+
panic!("Error to execute rust code {:?}", _e)
21+
}
22+
}
23+
}
24+
None => {
25+
panic!("Error finding work dir");
26+
}
27+
}
28+
}

src/main.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#![feature(proc_macro_hygiene)]
2+
#[macro_use]
3+
extern crate serde_derive;
4+
extern crate serde;
5+
extern crate serde_json;
6+
extern crate phf;
7+
extern crate tempfile;
8+
9+
mod types;
10+
mod utils;
11+
mod executor;
12+
mod languages;
13+
14+
use std::path::Path;
15+
use std::io::{self, Read};
16+
use utils::parse_payload;
17+
use utils::write_files;
18+
use tempfile::tempdir;
19+
20+
fn main() -> io::Result<()> {
21+
let mut buffer = String::new();
22+
io::stdin().read_to_string(&mut buffer)?;
23+
24+
let temp_dir = tempdir()?;
25+
let payload = parse_payload(&buffer)?;
26+
let filepaths = write_files(&temp_dir, &payload.files)?;
27+
28+
let executor_result = if payload.command == "" {
29+
languages::run(&payload.language, &filepaths, &payload.stdin)
30+
} else {
31+
let parent = Path::new(&filepaths[0]).parent();
32+
33+
match parent {
34+
Some(work_dir) => {
35+
executor::run_bash_stdin(&work_dir.to_string_lossy(), &payload.command, &payload.stdin)
36+
}
37+
None => {
38+
panic!("Error finding work dir");
39+
}
40+
}
41+
};
42+
43+
let mut result = types::Result::new();
44+
45+
match executor_result {
46+
Ok(output) => {
47+
result.stdout = output.stdout;
48+
result.stderr = output.stderr;
49+
50+
let json = serde_json::to_string(&result)?;
51+
println!("{}", json);
52+
}
53+
Err(_e) => {
54+
result.error = format!("{}", _e);
55+
56+
let json = serde_json::to_string(&result)?;
57+
println!("{}", json);
58+
}
59+
}
60+
61+
temp_dir.close()?;
62+
63+
Ok(())
64+
}

0 commit comments

Comments
 (0)