Skip to content

Commit

Permalink
Execute code block for supported languages (bash,go,ruby,python)
Browse files Browse the repository at this point in the history
  • Loading branch information
maaslalani committed Jun 13, 2021
1 parent 2a4ea2e commit be0a54a
Show file tree
Hide file tree
Showing 3 changed files with 212 additions and 0 deletions.
65 changes: 65 additions & 0 deletions internal/code/code.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,24 @@ package code

import (
"errors"
"io/ioutil"
"os"
"os/exec"
"regexp"
"time"
)

type Block struct {
Code string
Language string
}

type Result struct {
Out string
ExitCode int
ExecutionTime time.Duration
}

// ?: means non-capture group
var re = regexp.MustCompile("(?:```|~~~)(.*)\n(.*)\n(?:```|~~~)")

Expand All @@ -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),
}
}
114 changes: 114 additions & 0 deletions internal/code/execute_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
33 changes: 33 additions & 0 deletions internal/code/languages.go
Original file line number Diff line number Diff line change
@@ -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"},
},
}

0 comments on commit be0a54a

Please sign in to comment.