mirror of https://github.com/dirtbags/moth.git
Begin trying to integrate the transpiler
This commit is contained in:
parent
8f85b02935
commit
8aea668af7
|
@ -0,0 +1,124 @@
|
|||
// Provides a Puzzle interface that runs a command for each request
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PuzzleCommand specifies a command to run for the puzzle API
|
||||
type PuzzleCommand struct {
|
||||
Path string
|
||||
Args []string
|
||||
}
|
||||
|
||||
// Inventory runs with "action=inventory", and parses the output into a category list.
|
||||
func (pc PuzzleCommand) Inventory() (inv []Category) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, pc.Path, pc.Args...)
|
||||
cmd.Env = os.Environ()
|
||||
cmd.Env = append(cmd.Env, "action=inventory")
|
||||
|
||||
stdout, err := cmd.Output()
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(string(stdout), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(line, " ")
|
||||
if len(parts) < 2 {
|
||||
log.Println("Skipping misformatted line:", line)
|
||||
continue
|
||||
}
|
||||
name := parts[0]
|
||||
puzzles := make([]int, 0, 10)
|
||||
for _, pointsString := range parts[1:] {
|
||||
points, err := strconv.Atoi(pointsString)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
puzzles = append(puzzles, points)
|
||||
}
|
||||
inv = append(inv, Category{name, puzzles})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type NullReadSeekCloser struct {
|
||||
io.ReadSeeker
|
||||
}
|
||||
|
||||
func (f NullReadSeekCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Open passes its arguments to the command with "action=open".
|
||||
func (pc PuzzleCommand) Open(cat string, points int, path string) (ReadSeekCloser, time.Time, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, pc.Path, pc.Args...)
|
||||
cmd.Env = os.Environ()
|
||||
cmd.Env = append(cmd.Env, "action=open")
|
||||
cmd.Env = append(cmd.Env, "cat="+cat)
|
||||
cmd.Env = append(cmd.Env, "points="+strconv.Itoa(points))
|
||||
cmd.Env = append(cmd.Env, "path="+path)
|
||||
|
||||
stdoutBytes, err := cmd.Output()
|
||||
stdout := NullReadSeekCloser{bytes.NewReader(stdoutBytes)}
|
||||
now := time.Now()
|
||||
return stdout, now, err
|
||||
}
|
||||
|
||||
// CheckAnswer passes its arguments to the command with "action=answer".
|
||||
// If the command exits successfully and sends "correct" to stdout,
|
||||
// nil is returned.
|
||||
func (pc PuzzleCommand) CheckAnswer(cat string, points int, answer string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, pc.Path, pc.Args...)
|
||||
cmd.Env = os.Environ()
|
||||
cmd.Env = append(cmd.Env, "action=answer")
|
||||
cmd.Env = append(cmd.Env, "cat="+cat)
|
||||
cmd.Env = append(cmd.Env, "points="+strconv.Itoa(points))
|
||||
cmd.Env = append(cmd.Env, "answer="+answer)
|
||||
|
||||
stdout, err := cmd.Output()
|
||||
if ee, ok := err.(*exec.ExitError); ok {
|
||||
log.Printf("%s: %s", pc.Path, string(ee.Stderr))
|
||||
return err
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
result := strings.TrimSpace(string(stdout))
|
||||
|
||||
if result != "correct" {
|
||||
if result == "" {
|
||||
result = "Nothing written to stdout"
|
||||
}
|
||||
return fmt.Errorf("Wrong answer: %s", result)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Maintain does nothing: a command puzzle provider has no housekeeping
|
||||
func (pc PuzzleCommand) Maintain(updateInterval time.Duration) {
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPuzzleCommand(t *testing.T) {
|
||||
pc := PuzzleCommand{
|
||||
Path: "testdata/testpiler.sh",
|
||||
}
|
||||
|
||||
inv := pc.Inventory()
|
||||
if len(inv) != 2 {
|
||||
t.Errorf("Wrong length for inventory")
|
||||
}
|
||||
for _, cat := range inv {
|
||||
switch cat.Name {
|
||||
case "pategory":
|
||||
if len(cat.Puzzles) != 8 {
|
||||
t.Errorf("pategory wrong number of puzzles: %d", len(cat.Puzzles))
|
||||
}
|
||||
if cat.Puzzles[5] != 10 {
|
||||
t.Errorf("pategory puzzles[5] wrong value: %d", cat.Puzzles[5])
|
||||
}
|
||||
case "nealegory":
|
||||
if len(cat.Puzzles) != 3 {
|
||||
t.Errorf("nealegoy wrong number of puzzles: %d", len(cat.Puzzles))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := pc.CheckAnswer("pategory", 1, "answer"); err != nil {
|
||||
t.Errorf("Correct answer for pategory: %v", err)
|
||||
}
|
||||
if err := pc.CheckAnswer("pategory", 1, "wrong"); err == nil {
|
||||
t.Errorf("Wrong answer for pategory judged correct")
|
||||
}
|
||||
|
||||
if err := pc.CheckAnswer("pategory", 2, "answer"); err == nil {
|
||||
t.Errorf("Internal error not returned")
|
||||
} else if ee, ok := err.(*exec.ExitError); ok {
|
||||
if string(ee.Stderr) != "Internal error\n" {
|
||||
t.Errorf("Unexpected error returned: %#v", string(ee.Stderr))
|
||||
}
|
||||
} else if err.Error() != "moo" {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if f, _, err := pc.Open("pategory", 1, "moo.txt"); err != nil {
|
||||
t.Error(err)
|
||||
} else if buf, err := ioutil.ReadAll(f); err != nil {
|
||||
f.Close()
|
||||
t.Error(err)
|
||||
} else if string(buf) != "Moo.\n" {
|
||||
f.Close()
|
||||
t.Errorf("Wrong contents: %#v", string(buf))
|
||||
} else {
|
||||
f.Close()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
#! /bin/sh -e
|
||||
|
||||
case "$action:$cat:$points" in
|
||||
inventory::)
|
||||
echo "pategory 1 2 3 4 5 10 20 300"
|
||||
echo "nealegory 1 2 3"
|
||||
;;
|
||||
open:*:*)
|
||||
if [ "$path" = "moo.txt" ]; then
|
||||
echo "Moo."
|
||||
else
|
||||
cat $cat_$points_$path
|
||||
fi
|
||||
;;
|
||||
answer:pategory:1)
|
||||
if [ "$answer" = "answer" ]; then
|
||||
echo "correct"
|
||||
else
|
||||
echo "Sorry, wrong answer."
|
||||
fi
|
||||
;;
|
||||
answer:pategory:2)
|
||||
echo "Internal error" 1>&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: Unknown action: $action" 1>&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
Loading…
Reference in New Issue