├── .gitignore ├── README.md ├── language ├── python │ └── python.go ├── javascript │ └── javascript.go ├── java │ └── java.go ├── c │ └── c.go ├── cpp │ └── cpp.go └── language.go ├── util └── util.go ├── cmd └── cmd.go ├── LICENSE └── runner.go /.gitignore: -------------------------------------------------------------------------------- 1 | runner 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gorunner 2 | 3 | Adapted from https://github.com/prasmussen/glot-code-runner 4 | -------------------------------------------------------------------------------- /language/python/python.go: -------------------------------------------------------------------------------- 1 | package python 2 | 3 | import ( 4 | "github.com/stephengrider/gorunner/cmd" 5 | "path/filepath" 6 | ) 7 | 8 | func Run(files []string, stdin string) (string, string, error) { 9 | workDir := filepath.Dir(files[0]) 10 | return cmd.RunStdin(workDir, stdin, "python", files[0]) 11 | } 12 | -------------------------------------------------------------------------------- /language/javascript/javascript.go: -------------------------------------------------------------------------------- 1 | package javascript 2 | 3 | import ( 4 | "github.com/stephengrider/gorunner/cmd" 5 | "path/filepath" 6 | ) 7 | 8 | func Run(files []string, stdin string) (string, string, error) { 9 | workDir := filepath.Dir(files[0]) 10 | return cmd.RunStdin(workDir, stdin, "node", files[0]) 11 | } 12 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "path/filepath" 5 | ) 6 | 7 | func FilterByExtension(files []string, ext string) []string { 8 | var newFiles []string 9 | suffix := "." + ext 10 | 11 | for _, file := range files { 12 | if filepath.Ext(file) == suffix { 13 | newFiles = append(newFiles, file) 14 | } 15 | } 16 | 17 | return newFiles 18 | } 19 | -------------------------------------------------------------------------------- /language/java/java.go: -------------------------------------------------------------------------------- 1 | package java 2 | 3 | import ( 4 | "github.com/stephengrider/gorunner/cmd" 5 | "path/filepath" 6 | ) 7 | 8 | func Run(files []string, stdin string) (string, string, error) { 9 | workDir := filepath.Dir(files[0]) 10 | fname := filepath.Base(files[0]) 11 | 12 | stdout, stderr, err := cmd.Run(workDir, "javac", fname) 13 | if err != nil { 14 | return stdout, stderr, err 15 | } 16 | 17 | return cmd.RunStdin(workDir, stdin, "java", className(fname)) 18 | } 19 | 20 | func className(fname string) string { 21 | ext := filepath.Ext(fname) 22 | return fname[0 : len(fname)-len(ext)] 23 | } 24 | -------------------------------------------------------------------------------- /language/c/c.go: -------------------------------------------------------------------------------- 1 | package c 2 | 3 | import ( 4 | "github.com/stephengrider/gorunner/cmd" 5 | "github.com/stephengrider/gorunner/util" 6 | "path/filepath" 7 | ) 8 | 9 | func Run(files []string, stdin string) (string, string, error) { 10 | workDir := filepath.Dir(files[0]) 11 | binName := "a.out" 12 | 13 | sourceFiles := util.FilterByExtension(files, "c") 14 | args := append([]string{"clang", "-o", binName, "-lm"}, sourceFiles...) 15 | stdout, stderr, err := cmd.Run(workDir, args...) 16 | if err != nil { 17 | return stdout, stderr, err 18 | } 19 | 20 | binPath := filepath.Join(workDir, binName) 21 | return cmd.RunStdin(workDir, stdin, binPath) 22 | } 23 | -------------------------------------------------------------------------------- /language/cpp/cpp.go: -------------------------------------------------------------------------------- 1 | package cpp 2 | 3 | import ( 4 | "github.com/stephengrider/gorunner/cmd" 5 | "github.com/stephengrider/gorunner/util" 6 | "path/filepath" 7 | ) 8 | 9 | func Run(files []string, stdin string) (string, string, error) { 10 | workDir := filepath.Dir(files[0]) 11 | binName := "a.out" 12 | 13 | sourceFiles := util.FilterByExtension(files, "cpp") 14 | args := append([]string{"clang++", "-std=c++11", "-o", binName}, sourceFiles...) 15 | stdout, stderr, err := cmd.Run(workDir, args...) 16 | if err != nil { 17 | return stdout, stderr, err 18 | } 19 | 20 | binPath := filepath.Join(workDir, binName) 21 | return cmd.RunStdin(workDir, stdin, binPath) 22 | } 23 | -------------------------------------------------------------------------------- /language/language.go: -------------------------------------------------------------------------------- 1 | package language 2 | 3 | import ( 4 | "github.com/stephengrider/gorunner/language/c" 5 | "github.com/stephengrider/gorunner/language/cpp" 6 | "github.com/stephengrider/gorunner/language/java" 7 | "github.com/stephengrider/gorunner/language/javascript" 8 | "github.com/stephengrider/gorunner/language/python" 9 | ) 10 | 11 | type runFn func([]string, string) (string, string, error) 12 | 13 | var languages = map[string]runFn{ 14 | "c": c.Run, 15 | "cpp": cpp.Run, 16 | "java": java.Run, 17 | "javascript": javascript.Run, 18 | "python": python.Run, 19 | } 20 | 21 | func IsSupported(lang string) bool { 22 | _, supported := languages[lang] 23 | return supported 24 | } 25 | 26 | func Run(lang string, files []string, stdin string) (string, string, error) { 27 | return languages[lang](files, stdin) 28 | } 29 | -------------------------------------------------------------------------------- /cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "os/exec" 6 | "strings" 7 | ) 8 | 9 | func Run(workDir string, args ...string) (string, string, error) { 10 | return RunStdin(workDir, "", args...) 11 | } 12 | 13 | func RunStdin(workDir, stdin string, args ...string) (string, string, error) { 14 | var stdout bytes.Buffer 15 | var stderr bytes.Buffer 16 | 17 | cmd := exec.Command(args[0], args[1:]...) 18 | cmd.Dir = workDir 19 | cmd.Stdin = strings.NewReader(stdin) 20 | cmd.Stdout = &stdout 21 | cmd.Stderr = &stderr 22 | err := cmd.Run() 23 | 24 | return stdout.String(), stderr.String(), err 25 | } 26 | 27 | func RunBash(workDir, command string) (string, string, error) { 28 | return Run(workDir, "bash", "-c", command) 29 | } 30 | 31 | func RunBashStdin(workDir, command, stdin string) (string, string, error) { 32 | return RunStdin(workDir, stdin, "bash", "-c", command) 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Petter Rasmussen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /runner.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/stephengrider/gorunner/cmd" 7 | "github.com/stephengrider/gorunner/language" 8 | "io/ioutil" 9 | "os" 10 | "path/filepath" 11 | ) 12 | 13 | type Payload struct { 14 | Language string `json:"language"` 15 | Files []*InMemoryFile `json:"files"` 16 | Stdin string `json:"stdin"` 17 | Command string `json:"command"` 18 | } 19 | 20 | type InMemoryFile struct { 21 | Name string `json:"name"` 22 | Content string `json:"content"` 23 | } 24 | 25 | type Result struct { 26 | Stdout string `json:"stdout"` 27 | Stderr string `json:"stderr"` 28 | Error string `json:"error"` 29 | } 30 | 31 | func main() { 32 | payload := &Payload{} 33 | err := json.NewDecoder(os.Stdin).Decode(payload) 34 | 35 | if err != nil { 36 | exitF("Failed to parse input json (%s)\n", err.Error()) 37 | } 38 | 39 | // Ensure that we have at least one file 40 | if len(payload.Files) == 0 { 41 | exitF("No files given\n") 42 | } 43 | 44 | // Check if we support given language 45 | if !language.IsSupported(payload.Language) { 46 | exitF("Language '%s' is not supported\n", payload.Language) 47 | } 48 | 49 | // Write files to disk 50 | filepaths, err := writeFiles(payload.Files) 51 | if err != nil { 52 | exitF("Failed to write file to disk (%s)", err.Error()) 53 | } 54 | 55 | var stdout, stderr string 56 | 57 | // Execute the given command or run the code with 58 | // the language runner if no command is given 59 | if payload.Command == "" { 60 | stdout, stderr, err = language.Run(payload.Language, filepaths, payload.Stdin) 61 | } else { 62 | workDir := filepath.Dir(filepaths[0]) 63 | stdout, stderr, err = cmd.RunBashStdin(workDir, payload.Command, payload.Stdin) 64 | } 65 | printResult(stdout, stderr, err) 66 | } 67 | 68 | // Writes files to disk, returns list of absolute filepaths 69 | func writeFiles(files []*InMemoryFile) ([]string, error) { 70 | // Create temp dir 71 | tmpPath, err := ioutil.TempDir("", "") 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | paths := make([]string, len(files), len(files)) 77 | for i, file := range files { 78 | path, err := writeFile(tmpPath, file) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | paths[i] = path 84 | 85 | } 86 | return paths, nil 87 | } 88 | 89 | // Writes a single file to disk 90 | func writeFile(basePath string, file *InMemoryFile) (string, error) { 91 | // Get absolute path to file inside basePath 92 | absPath := filepath.Join(basePath, file.Name) 93 | 94 | // Create all parent dirs 95 | err := os.MkdirAll(filepath.Dir(absPath), 0775) 96 | if err != nil { 97 | return "", err 98 | } 99 | 100 | // Write file to disk 101 | err = ioutil.WriteFile(absPath, []byte(file.Content), 0664) 102 | if err != nil { 103 | return "", err 104 | } 105 | 106 | // Return absolute path to file 107 | return absPath, nil 108 | } 109 | 110 | func exitF(format string, a ...interface{}) { 111 | fmt.Fprintf(os.Stderr, format, a...) 112 | os.Exit(1) 113 | } 114 | 115 | func printResult(stdout, stderr string, err error) { 116 | result := &Result{ 117 | Stdout: stdout, 118 | Stderr: stderr, 119 | Error: errToStr(err), 120 | } 121 | json.NewEncoder(os.Stdout).Encode(result) 122 | } 123 | 124 | func errToStr(err error) string { 125 | if err != nil { 126 | return err.Error() 127 | } 128 | 129 | return "" 130 | } 131 | --------------------------------------------------------------------------------