mirror of
https://github.com/maaslalani/slides.git
synced 2026-01-09 14:28:05 -05:00
Execute code block for supported languages (bash,go,ruby,python)
This commit is contained in:
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
114
internal/code/execute_test.go
Normal file
114
internal/code/execute_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
33
internal/code/languages.go
Normal file
33
internal/code/languages.go
Normal 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"},
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user