Execute code block for supported languages (bash,go,ruby,python)

This commit is contained in:
Maas Lalani
2021-06-13 01:34:21 -04:00
parent 2a4ea2ed9d
commit be0a54a1a6
3 changed files with 212 additions and 0 deletions

View File

@@ -2,7 +2,11 @@ package code
import (
"errors"
"io/ioutil"
"os"
"os/exec"
"regexp"
"time"
)
type Block struct {
@@ -10,6 +14,12 @@ type Block struct {
Language string
}
type Result struct {
Out string
ExitCode int
ExecutionTime time.Duration
}
// ?: means non-capture group
var re = regexp.MustCompile("(?:```|~~~)(.*)\n(.*)\n(?:```|~~~)")
@@ -33,3 +43,58 @@ func Parse(markdown string) (Block, error) {
Code: match[2],
}, nil
}
const (
// ExitCodeInternalError represents the exit code in which the code
// executing the code didn't work.
ExitCodeInternalError = -1
)
// Execute takes a code.Block and returns the output of the executed code
func Execute(code Block) Result {
// Check supported language
language, ok := Languages[code.Language]
if !ok {
return Result{
Out: "Error: unsupported language",
ExitCode: ExitCodeInternalError,
}
}
// Write the code block to a temporary file
f, err := ioutil.TempFile(os.TempDir(), "slides-*."+Languages[code.Language].Extension)
if err != nil {
return Result{
Out: "Error: could not create file",
ExitCode: ExitCodeInternalError,
}
}
defer os.Remove(f.Name())
_, err = f.WriteString(code.Code)
if err != nil {
return Result{
Out: "Error: could not write to file",
ExitCode: ExitCodeInternalError,
}
}
cmd := exec.Command(language.Command[0], append(language.Command[1:], f.Name())...)
// For accuracy of program execution speed, we can't put anything after
// recording the start time or before recording the end time.
start := time.Now()
out, err := cmd.Output()
end := time.Now()
exitCode := 0
if err != nil {
exitCode = 1
}
return Result{
Out: string(out),
ExitCode: exitCode,
ExecutionTime: end.Sub(start),
}
}

View File

@@ -0,0 +1,114 @@
package code_test
import (
"testing"
"github.com/maaslalani/slides/internal/code"
)
func TestExecute(t *testing.T) {
tt := []struct {
block code.Block
expected code.Result
}{
{
block: code.Block{
Code: `puts "Hello, world!"`,
Language: "ruby",
},
expected: code.Result{
Out: "Hello, world!\n",
ExitCode: 0,
},
},
{
block: code.Block{
Code: `puts "Hi, there!"`,
Language: "ruby",
},
expected: code.Result{
Out: "Hi, there!\n",
ExitCode: 0,
},
},
{
block: code.Block{
Code: `print "No new line"`,
Language: "ruby",
},
expected: code.Result{
Out: "No new line",
ExitCode: 0,
},
},
{
block: code.Block{
Code: `
package main
import "fmt"
func main() {
fmt.Print("Hello, go!")
}
`,
Language: "go",
},
expected: code.Result{
Out: "Hello, go!",
ExitCode: 0,
},
},
{
block: code.Block{
Code: `print("Hello, python!")`,
Language: "python",
},
expected: code.Result{
Out: "Hello, python!\n",
ExitCode: 0,
},
},
{
block: code.Block{
Code: `echo "Hello, bash!"`,
Language: "bash",
},
expected: code.Result{
Out: "Hello, bash!\n",
ExitCode: 0,
},
},
{
block: code.Block{
Code: `Invalid Code`,
Language: "bash",
},
expected: code.Result{
Out: "",
ExitCode: 1,
},
},
{
block: code.Block{
Code: `Invalid Code`,
Language: "invalid",
},
expected: code.Result{
Out: "Error: unsupported language",
ExitCode: code.ExitCodeInternalError,
},
},
}
for _, tc := range tt {
r := code.Execute(tc.block)
if r.Out != tc.expected.Out {
t.Fatalf("invalid output, got %s, want %s", r.Out, tc.expected.Out)
}
if r.ExitCode != tc.expected.ExitCode {
t.Fatalf("unexpected exit code, got %d, want %d", r.ExitCode, tc.expected.ExitCode)
}
}
}

View File

@@ -0,0 +1,33 @@
package code
type Language struct {
Extension string
Command []string
}
// Supported Languages
const (
Bash = "bash"
Go = "go"
Ruby = "ruby"
Python = "python"
)
var Languages = map[string]Language{
Bash: {
Extension: "sh",
Command: []string{"bash"},
},
Go: {
Extension: "go",
Command: []string{"go", "run"},
},
Ruby: {
Extension: "rb",
Command: []string{"ruby"},
},
Python: {
Extension: "py",
Command: []string{"python"},
},
}