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 package main
import ( import (
"bytes"
"log" "log"
"net/http" "net/http"
"strconv" "strconv"
@ -171,11 +172,12 @@ func (h *HTTPServer) MothballerHandler(mh MothRequestHandler, w http.ResponseWri
// parts[0] == "mothballer" // parts[0] == "mothballer"
filename := parts[1] filename := parts[1]
cat := strings.TrimSuffix(filename, ".mb") cat := strings.TrimSuffix(filename, ".mb")
mothball, err := mh.Mothball(cat) mb := new(bytes.Buffer)
if err != nil { if err := mh.Mothball(cat, mb); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return 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 ( import (
"archive/zip" "archive/zip"
"bufio" "bufio"
"bytes"
"fmt" "fmt"
"io" "io"
"log" "log"
@ -174,8 +173,8 @@ func (m *Mothballs) refresh() {
} }
// Mothball just returns an error // Mothball just returns an error
func (m *Mothballs) Mothball(cat string) (*bytes.Reader, error) { func (m *Mothballs) Mothball(cat string, w io.Writer) error {
return nil, fmt.Errorf("Can't repackage a compiled mothball") return fmt.Errorf("Refusing to repackage a compiled mothball")
} }
// Maintain performs housekeeping for Mothballs. // Maintain performs housekeeping for Mothballs.

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
"strconv" "strconv"
@ -42,7 +41,7 @@ type PuzzleProvider interface {
Open(cat string, points int, path string) (ReadSeekCloser, time.Time, error) Open(cat string, points int, path string) (ReadSeekCloser, time.Time, error)
Inventory() []Category Inventory() []Category
CheckAnswer(cat string, points int, answer string) (bool, error) CheckAnswer(cat string, points int, answer string) (bool, error)
Mothball(cat string) (*bytes.Reader, error) Mothball(cat string, w io.Writer) error
Maintainer Maintainer
} }
@ -233,14 +232,16 @@ func (mh *MothRequestHandler) ExportState() *StateExport {
} }
// Mothball generates a mothball for the given category. // 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 { 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 { for _, provider := range mh.PuzzleProviders {
if r, err = provider.Mothball(cat); err == nil { if err = provider.Mothball(cat, w); err == nil {
return r, 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. // 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) c := transpile.NewFsCategory(p.fs, cat)
return transpile.Mothball(c) return transpile.Mothball(c, w)
} }
// Maintain performs housekeeping. // Maintain performs housekeeping.

View File

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

View File

@ -1,8 +1,10 @@
package main package main
import ( import (
"archive/zip"
"bytes" "bytes"
"encoding/json" "encoding/json"
"io/ioutil"
"testing" "testing"
"github.com/dirtbags/moth/pkg/transpile" "github.com/dirtbags/moth/pkg/transpile"
@ -83,16 +85,72 @@ func TestEverything(t *testing.T) {
if stdout.String() != "Moo." { if stdout.String() != "Moo." {
t.Error("Wrong file pulled", stdout.String()) 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() stdout.Reset()
if err := tp.Run("mothball", "-dir=unbroken"); err != nil { if err := tp.Run("mothball", "-dir=unbroken", "-out=unbroken.mb"); err != nil {
t.Log(tp.fs) 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) t.Error(err)
} }
if stdout.Len() < 200 { for _, fi := range fis {
t.Error("That's way too short to be a mothball") t.Log(fi.Name())
}
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")
}
} }
if stdout.String()[:2] != "PK" {
t.Error("This mothball isn't a zip file!")
} }
} }

View File

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

View File

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