From 854ef771b436388644d466f1e09e119f111da5dc Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Mon, 12 Oct 2020 17:44:44 -0600 Subject: [PATCH] transpiler can now create mothballs --- cmd/mothd/httpd.go | 8 ++-- cmd/mothd/mothballs.go | 5 +-- cmd/mothd/server.go | 15 ++++---- cmd/mothd/transpiler.go | 4 +- cmd/transpile/main.go | 41 ++++++++++++-------- cmd/transpile/main_test.go | 70 +++++++++++++++++++++++++++++++--- pkg/transpile/mothball.go | 46 ++++++++++++---------- pkg/transpile/mothball_test.go | 12 ++++-- 8 files changed, 141 insertions(+), 60 deletions(-) diff --git a/cmd/mothd/httpd.go b/cmd/mothd/httpd.go index 66eb6f4..7962fbe 100644 --- a/cmd/mothd/httpd.go +++ b/cmd/mothd/httpd.go @@ -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) } diff --git a/cmd/mothd/mothballs.go b/cmd/mothd/mothballs.go index deab589..6c3ada4 100644 --- a/cmd/mothd/mothballs.go +++ b/cmd/mothd/mothballs.go @@ -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. diff --git a/cmd/mothd/server.go b/cmd/mothd/server.go index a355cf7..35619bc 100644 --- a/cmd/mothd/server.go +++ b/cmd/mothd/server.go @@ -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 } diff --git a/cmd/mothd/transpiler.go b/cmd/mothd/transpiler.go index ddda86b..7103b20 100644 --- a/cmd/mothd/transpiler.go +++ b/cmd/mothd/transpiler.go @@ -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. diff --git a/cmd/transpile/main.go b/cmd/transpile/main.go index 4c95343..34cec5b 100644 --- a/cmd/transpile/main.go +++ b/cmd/transpile/main.go @@ -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, diff --git a/cmd/transpile/main_test.go b/cmd/transpile/main_test.go index ad79cef..52e1a1e 100644 --- a/cmd/transpile/main_test.go +++ b/cmd/transpile/main_test.go @@ -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") + } + } } } diff --git a/pkg/transpile/mothball.go b/pkg/transpile/mothball.go index b393802..6647d5d 100644 --- a/pkg/transpile/mothball.go +++ b/pkg/transpile/mothball.go @@ -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 } diff --git a/pkg/transpile/mothball_test.go b/pkg/transpile/mothball_test.go index 753d536..8865b6f 100644 --- a/pkg/transpile/mothball_test.go +++ b/pkg/transpile/mothball_test.go @@ -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)) } }