mirror of
https://github.com/maaslalani/slides.git
synced 2026-01-10 14:58:13 -05:00
There was a bug where the command would reference an old file name which would be deleted which meant that the code block would not execute on future executions and only work the first time.
141 lines
2.9 KiB
Go
141 lines
2.9 KiB
Go
package code
|
|
|
|
import (
|
|
"errors"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"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("(?s)(?:```|~~~)(\\w+)\n(.*?)\n(?:```|~~~)\\s?")
|
|
|
|
var (
|
|
ErrParse = errors.New("Error: could not parse code block")
|
|
)
|
|
|
|
// Parse takes a block of markdown and returns an array of Block's with code
|
|
// and associated languages
|
|
func Parse(markdown string) ([]Block, error) {
|
|
matches := re.FindAllStringSubmatch(markdown, -1)
|
|
|
|
var rv []Block
|
|
for _, match := range matches {
|
|
// There was either no language specified or no code block
|
|
// Either way, we cannot execute the expression
|
|
if len(match) < 3 {
|
|
continue
|
|
}
|
|
rv = append(rv, Block{
|
|
Language: match[1],
|
|
Code: match[2],
|
|
})
|
|
|
|
}
|
|
|
|
if len(rv) == 0 {
|
|
return nil, ErrParse
|
|
}
|
|
|
|
return rv, 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 f.Close()
|
|
defer os.Remove(f.Name())
|
|
|
|
_, err = f.WriteString(code.Code)
|
|
if err != nil {
|
|
return Result{
|
|
Out: "Error: could not write to file",
|
|
ExitCode: ExitCodeInternalError,
|
|
}
|
|
}
|
|
|
|
var (
|
|
output strings.Builder
|
|
exitCode int
|
|
)
|
|
|
|
// replacer for commands
|
|
repl := strings.NewReplacer(
|
|
"<file>", f.Name(),
|
|
// <name>: file name without extension and without path
|
|
"<name>", filepath.Base(strings.TrimSuffix(f.Name(), filepath.Ext(f.Name()))),
|
|
"<path>", filepath.Dir(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()
|
|
|
|
for _, c := range language.Commands {
|
|
var command []string
|
|
// replace <file>, <name> and <path> in commands
|
|
for _, v := range c {
|
|
command = append(command, repl.Replace(v))
|
|
}
|
|
// execute and write output
|
|
cmd := exec.Command(command[0], command[1:]...)
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
output.Write([]byte(err.Error()))
|
|
} else {
|
|
output.Write(out)
|
|
}
|
|
|
|
// update status code
|
|
if err != nil {
|
|
exitCode = 1
|
|
}
|
|
}
|
|
|
|
end := time.Now()
|
|
|
|
return Result{
|
|
Out: output.String(),
|
|
ExitCode: exitCode,
|
|
ExecutionTime: end.Sub(start),
|
|
}
|
|
}
|