mirror of https://github.com/dirtbags/moth.git
transpiler can now create mothballs
This commit is contained in:
parent
4eb0f0a141
commit
854ef771b4
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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!")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue