transpiler can now create mothballs

This commit is contained in:
Neale Pickett 2020-10-12 17:44:44 -06:00
parent 4eb0f0a141
commit 854ef771b4
8 changed files with 141 additions and 60 deletions

View File

@ -1,6 +1,7 @@
package main
import (
"bytes"
"log"
"net/http"
"strconv"
@ -171,11 +172,12 @@ func (h *HTTPServer) MothballerHandler(mh MothRequestHandler, w http.ResponseWri
// parts[0] == "mothballer"
filename := parts[1]
cat := strings.TrimSuffix(filename, ".mb")
mothball, err := mh.Mothball(cat)
if err != nil {
mb := new(bytes.Buffer)
if err := mh.Mothball(cat, mb); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.ServeContent(w, req, filename, time.Now(), mothball)
mbReader := bytes.NewReader(mb.Bytes())
http.ServeContent(w, req, filename, time.Now(), mbReader)
}

View File

@ -3,7 +3,6 @@ package main
import (
"archive/zip"
"bufio"
"bytes"
"fmt"
"io"
"log"
@ -174,8 +173,8 @@ func (m *Mothballs) refresh() {
}
// Mothball just returns an error
func (m *Mothballs) Mothball(cat string) (*bytes.Reader, error) {
return nil, fmt.Errorf("Can't repackage a compiled mothball")
func (m *Mothballs) Mothball(cat string, w io.Writer) error {
return fmt.Errorf("Refusing to repackage a compiled mothball")
}
// Maintain performs housekeeping for Mothballs.

View File

@ -1,7 +1,6 @@
package main
import (
"bytes"
"fmt"
"io"
"strconv"
@ -42,7 +41,7 @@ type PuzzleProvider interface {
Open(cat string, points int, path string) (ReadSeekCloser, time.Time, error)
Inventory() []Category
CheckAnswer(cat string, points int, answer string) (bool, error)
Mothball(cat string) (*bytes.Reader, error)
Mothball(cat string, w io.Writer) error
Maintainer
}
@ -233,14 +232,16 @@ func (mh *MothRequestHandler) ExportState() *StateExport {
}
// Mothball generates a mothball for the given category.
func (mh *MothRequestHandler) Mothball(cat string) (r *bytes.Reader, err error) {
func (mh *MothRequestHandler) Mothball(cat string, w io.Writer) error {
var err error
if !mh.Config.Devel {
return nil, fmt.Errorf("Cannot mothball in production mode")
return fmt.Errorf("Cannot mothball in production mode")
}
for _, provider := range mh.PuzzleProviders {
if r, err = provider.Mothball(cat); err == nil {
return r, nil
if err = provider.Mothball(cat, w); err == nil {
return nil
}
}
return nil, err
return err
}

View File

@ -70,9 +70,9 @@ func (p TranspilerProvider) CheckAnswer(cat string, points int, answer string) (
}
// Mothball packages up a category into a mothball.
func (p TranspilerProvider) Mothball(cat string) (*bytes.Reader, error) {
func (p TranspilerProvider) Mothball(cat string, w io.Writer) error {
c := transpile.NewFsCategory(p.fs, cat)
return transpile.Mothball(c)
return transpile.Mothball(c, w)
}
// Maintain performs housekeeping.

View File

@ -16,11 +16,13 @@ import (
// T represents the state of things
type T struct {
Stdout io.Writer
Stderr io.Writer
Args []string
BaseFs afero.Fs
fs afero.Fs
Stdout io.Writer
Stderr io.Writer
Args []string
BaseFs afero.Fs
fs afero.Fs
// Arguments
filename string
answer string
}
@ -51,19 +53,21 @@ func (t *T) ParseArgs() (Command, error) {
}
flags := flag.NewFlagSet(t.Args[1], flag.ContinueOnError)
flags.SetOutput(t.Stderr)
directory := flags.String("dir", "", "Work directory")
switch t.Args[1] {
case "mothball":
cmd = t.DumpMothball
flags.StringVar(&t.filename, "out", "", "Path to create mothball (empty for stdout)")
case "inventory":
cmd = t.PrintInventory
case "open":
flags.StringVar(&t.filename, "file", "puzzle.json", "Filename to open")
cmd = t.DumpFile
flags.StringVar(&t.filename, "file", "puzzle.json", "Filename to open")
case "answer":
flags.StringVar(&t.answer, "answer", "", "Answer to check")
cmd = t.CheckAnswer
flags.StringVar(&t.answer, "answer", "", "Answer to check")
case "help":
usage(t.Stderr)
return nothing, nil
@ -73,7 +77,6 @@ func (t *T) ParseArgs() (Command, error) {
return nothing, fmt.Errorf("Invalid command")
}
flags.SetOutput(t.Stderr)
if err := flags.Parse(t.Args[2:]); err != nil {
return nothing, err
}
@ -140,14 +143,24 @@ func (t *T) DumpFile() error {
return nil
}
// DumpMothball writes a mothball to the writer.
// DumpMothball writes a mothball to the writer, or an output file if specified.
func (t *T) DumpMothball() error {
var w io.Writer
c := transpile.NewFsCategory(t.fs, "")
mb, err := transpile.Mothball(c)
if err != nil {
return err
if t.filename == "" {
w = t.Stdout
} else {
log.Println("Writing to", t.filename, t.fs)
outf, err := t.BaseFs.Create(t.filename)
if err != nil {
return err
}
defer outf.Close()
w = outf
}
if _, err := io.Copy(t.Stdout, mb); err != nil {
log.Println(t.fs)
if err := transpile.Mothball(c, w); err != nil {
return err
}
return nil
@ -165,8 +178,6 @@ func (t *T) CheckAnswer() error {
}
func main() {
// XXX: Convert puzzle.py to standalone thingies
t := &T{
Stdout: os.Stdout,
Stderr: os.Stderr,

View File

@ -1,8 +1,10 @@
package main
import (
"archive/zip"
"bytes"
"encoding/json"
"io/ioutil"
"testing"
"github.com/dirtbags/moth/pkg/transpile"
@ -83,16 +85,72 @@ func TestEverything(t *testing.T) {
if stdout.String() != "Moo." {
t.Error("Wrong file pulled", stdout.String())
}
}
func TestMothballs(t *testing.T) {
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
tp := T{
Stdout: stdout,
Stderr: stderr,
BaseFs: newTestFs(),
}
stdout.Reset()
if err := tp.Run("mothball", "-dir=unbroken"); err != nil {
t.Log(tp.fs)
if err := tp.Run("mothball", "-dir=unbroken", "-out=unbroken.mb"); err != nil {
t.Error(err)
return
}
// afero.WriteFile(tp.BaseFs, "unbroken.mb", []byte("moo"), 0644)
fis, err := afero.ReadDir(tp.BaseFs, "/")
if err != nil {
t.Error(err)
}
if stdout.Len() < 200 {
t.Error("That's way too short to be a mothball")
for _, fi := range fis {
t.Log(fi.Name())
}
if stdout.String()[:2] != "PK" {
t.Error("This mothball isn't a zip file!")
mb, err := tp.BaseFs.Open("unbroken.mb")
if err != nil {
t.Error(err)
return
}
defer mb.Close()
info, err := mb.Stat()
if err != nil {
t.Error(err)
return
}
zmb, err := zip.NewReader(mb, info.Size())
if err != nil {
t.Error(err)
return
}
for _, zf := range zmb.File {
f, err := zf.Open()
if err != nil {
t.Error(err)
continue
}
defer f.Close()
buf, err := ioutil.ReadAll(f)
if err != nil {
t.Error(err)
continue
}
switch zf.Name {
case "answers.txt":
if len(buf) == 0 {
t.Error("answers.txt empty")
}
case "puzzles.txt":
if len(buf) == 0 {
t.Error("puzzles.txt empty")
}
}
}
}

View File

@ -10,23 +10,16 @@ import (
)
// Mothball packages a Category up for a production server run.
func Mothball(c Category) (*bytes.Reader, error) {
buf := new(bytes.Buffer)
zf := zip.NewWriter(buf)
func Mothball(c Category, w io.Writer) error {
zf := zip.NewWriter(w)
inv, err := c.Inventory()
if err != nil {
return nil, err
return err
}
puzzlesTxt, err := zf.Create("puzzles.txt")
if err != nil {
return nil, err
}
answersTxt, err := zf.Create("answers.txt")
if err != nil {
return nil, err
}
puzzlesTxt := new(bytes.Buffer)
answersTxt := new(bytes.Buffer)
for _, points := range inv {
fmt.Fprintln(puzzlesTxt, points)
@ -34,11 +27,11 @@ func Mothball(c Category) (*bytes.Reader, error) {
puzzlePath := fmt.Sprintf("%d/puzzle.json", points)
pw, err := zf.Create(puzzlePath)
if err != nil {
return nil, err
return err
}
puzzle, err := c.Puzzle(points)
if err != nil {
return nil, fmt.Errorf("Puzzle %d: %s", points, err)
return fmt.Errorf("Puzzle %d: %s", points, err)
}
// Record answers in answers.txt
@ -55,7 +48,7 @@ func Mothball(c Category) (*bytes.Reader, error) {
// Write out Puzzle object
penc := json.NewEncoder(pw)
if err := penc.Encode(puzzle); err != nil {
return nil, fmt.Errorf("Puzzle %d: %s", points, err)
return fmt.Errorf("Puzzle %d: %s", points, err)
}
// Write out all attachments and scripts
@ -64,20 +57,33 @@ func Mothball(c Category) (*bytes.Reader, error) {
attPath := fmt.Sprintf("%d/%s", points, att)
aw, err := zf.Create(attPath)
if err != nil {
return nil, err
return err
}
ar, err := c.Open(points, att)
if exerr, ok := err.(*exec.ExitError); ok {
return nil, fmt.Errorf("Puzzle %d: %s: %s: %s", points, att, err, string(exerr.Stderr))
return fmt.Errorf("Puzzle %d: %s: %s: %s", points, att, err, string(exerr.Stderr))
} else if err != nil {
return nil, fmt.Errorf("Puzzle %d: %s: %s", points, att, err)
return fmt.Errorf("Puzzle %d: %s: %s", points, att, err)
}
if _, err := io.Copy(aw, ar); err != nil {
return nil, fmt.Errorf("Puzzle %d: %s: %s", points, att, err)
return fmt.Errorf("Puzzle %d: %s: %s", points, att, err)
}
}
}
pf, err := zf.Create("puzzles.txt")
if err != nil {
return err
}
puzzlesTxt.WriteTo(pf)
af, err := zf.Create("answers.txt")
if err != nil {
return err
}
answersTxt.WriteTo(af)
zf.Close()
return bytes.NewReader(buf.Bytes()), nil
return nil
}

View File

@ -2,6 +2,7 @@ package transpile
import (
"archive/zip"
"bytes"
"io/ioutil"
"os"
"path"
@ -14,7 +15,8 @@ import (
func TestMothballsMemFs(t *testing.T) {
static := NewFsCategory(newTestFs(), "cat1")
if _, err := Mothball(static); err != nil {
mb := new(bytes.Buffer)
if err := Mothball(static, mb); err != nil {
t.Error(err)
}
}
@ -25,13 +27,15 @@ func TestMothballsOsFs(t *testing.T) {
fs := NewRecursiveBasePathFs(afero.NewOsFs(), "testdata")
static := NewFsCategory(fs, "static")
mb, err := Mothball(static)
mb := new(bytes.Buffer)
err := Mothball(static, mb)
if err != nil {
t.Error(err)
return
}
mbr, err := zip.NewReader(mb, int64(mb.Len()))
mbReader := bytes.NewReader(mb.Bytes())
mbr, err := zip.NewReader(mbReader, int64(mb.Len()))
if err != nil {
t.Error(err)
}
@ -43,7 +47,7 @@ func TestMothballsOsFs(t *testing.T) {
defer f.Close()
if buf, err := ioutil.ReadAll(f); err != nil {
t.Error(err)
} else if string(buf) != "" {
} else if string(buf) != "1\n2\n3\n" {
t.Error("Bad puzzles.txt", string(buf))
}
}