mirror of https://github.com/dirtbags/moth.git
Transpiler seems complete
This commit is contained in:
parent
31a50cbf2c
commit
6696d27ee0
|
@ -143,6 +143,8 @@ func (c FsCommandCategory) Puzzle(points int) (Puzzle, error) {
|
||||||
return p, err
|
return p, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.computeAnswerHashes()
|
||||||
|
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ func TestFsCategory(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if r, err := c.Open(1, "moo.txt"); err != nil {
|
if r, err := c.Open(1, "moo.txt"); err != nil {
|
||||||
|
t.Log(c.Puzzle(1))
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
} else {
|
} else {
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
|
|
|
@ -28,39 +28,6 @@ type Category interface {
|
||||||
Answer(points int, answer string) bool
|
Answer(points int, answer string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Puzzle contains everything about a puzzle.
|
|
||||||
type Puzzle struct {
|
|
||||||
Pre struct {
|
|
||||||
Authors []string
|
|
||||||
Attachments []Attachment
|
|
||||||
Scripts []Attachment
|
|
||||||
AnswerPattern string
|
|
||||||
Body string
|
|
||||||
}
|
|
||||||
Post struct {
|
|
||||||
Objective string
|
|
||||||
Success struct {
|
|
||||||
Acceptable string
|
|
||||||
Mastery string
|
|
||||||
}
|
|
||||||
KSAs []string
|
|
||||||
}
|
|
||||||
Debug struct {
|
|
||||||
Log []string
|
|
||||||
Errors []string
|
|
||||||
Hints []string
|
|
||||||
Summary string
|
|
||||||
}
|
|
||||||
Answers []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attachment carries information about an attached file.
|
|
||||||
type Attachment struct {
|
|
||||||
Filename string // Filename presented as part of puzzle
|
|
||||||
FilesystemPath string // Filename in backing FS (URL, mothball, or local FS)
|
|
||||||
Listed bool // Whether this file is listed as an attachment
|
|
||||||
}
|
|
||||||
|
|
||||||
// T contains everything required for a transpilation invocation (across the nation).
|
// T contains everything required for a transpilation invocation (across the nation).
|
||||||
type T struct {
|
type T struct {
|
||||||
// What action to take
|
// What action to take
|
||||||
|
@ -95,6 +62,8 @@ func (t *T) Handle(action string) error {
|
||||||
return t.PrintInventory()
|
return t.PrintInventory()
|
||||||
case "open":
|
case "open":
|
||||||
return t.Open()
|
return t.Open()
|
||||||
|
case "mothball":
|
||||||
|
return t.Mothball()
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("Unimplemented action: %s", action)
|
return fmt.Errorf("Unimplemented action: %s", action)
|
||||||
}
|
}
|
||||||
|
@ -134,6 +103,7 @@ func (t *T) Open() error {
|
||||||
|
|
||||||
switch t.Filename {
|
switch t.Filename {
|
||||||
case "puzzle.json", "":
|
case "puzzle.json", "":
|
||||||
|
// BUG(neale): we need a way to tell the transpiler to strip answers
|
||||||
p, err := c.Puzzle(t.Points)
|
p, err := c.Puzzle(t.Points)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -157,6 +127,19 @@ func (t *T) Open() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mothball writes a mothball to the writer.
|
||||||
|
func (t *T) Mothball() error {
|
||||||
|
c := t.NewCategory(t.Cat)
|
||||||
|
mb, err := Mothball(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(t.w, mb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewCategory returns a new Fs-backed category.
|
// NewCategory returns a new Fs-backed category.
|
||||||
func (t *T) NewCategory(name string) Category {
|
func (t *T) NewCategory(name string) Category {
|
||||||
return NewFsCategory(t.Fs, name)
|
return NewFsCategory(t.Fs, name)
|
||||||
|
|
|
@ -17,6 +17,8 @@ pre:
|
||||||
- Arthur
|
- Arthur
|
||||||
- Buster
|
- Buster
|
||||||
- DW
|
- DW
|
||||||
|
attachments:
|
||||||
|
- filename: moo.txt
|
||||||
---
|
---
|
||||||
YAML body
|
YAML body
|
||||||
`)
|
`)
|
||||||
|
@ -46,9 +48,12 @@ body
|
||||||
`), 0644)
|
`), 0644)
|
||||||
afero.WriteFile(fs, "cat0/20/puzzle.md", []byte("Answer: no\nBadField: yes\n\nbody\n"), 0644)
|
afero.WriteFile(fs, "cat0/20/puzzle.md", []byte("Answer: no\nBadField: yes\n\nbody\n"), 0644)
|
||||||
afero.WriteFile(fs, "cat0/21/puzzle.md", []byte("Answer: broken\nSpooon\n"), 0644)
|
afero.WriteFile(fs, "cat0/21/puzzle.md", []byte("Answer: broken\nSpooon\n"), 0644)
|
||||||
afero.WriteFile(fs, "cat0/22/puzzle.md", []byte("---\nanswers:\n - pencil\npre:\n body: Spooon\n---\nSpoon?\n"), 0644)
|
afero.WriteFile(fs, "cat0/22/puzzle.md", []byte("---\nanswers:\n - pencil\npre:\n unused-field: Spooon\n---\nSpoon?\n"), 0644)
|
||||||
afero.WriteFile(fs, "cat1/93/puzzle.md", []byte("Answer: no\n\nbody"), 0644)
|
afero.WriteFile(fs, "cat1/93/puzzle.md", []byte("Answer: no\n\nbody"), 0644)
|
||||||
afero.WriteFile(fs, "cat1/barney/puzzle.md", testMothYaml, 0644)
|
afero.WriteFile(fs, "cat1/barney/puzzle.md", testMothYaml, 0644)
|
||||||
|
afero.WriteFile(fs, "unbroken/1/puzzle.md", testMothYaml, 0644)
|
||||||
|
afero.WriteFile(fs, "unbroken/1/moo.txt", []byte("Moo."), 0644)
|
||||||
|
afero.WriteFile(fs, "unbroken/2/puzzle.md", testMothRfc822, 0644)
|
||||||
return fs
|
return fs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +67,7 @@ func TestEverything(t *testing.T) {
|
||||||
if err := tp.Handle("inventory"); err != nil {
|
if err := tp.Handle("inventory"); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(stdout.String()) != `{"cat0":[1,2,3,4,5,10,20,21,22],"cat1":[93]}` {
|
if strings.TrimSpace(stdout.String()) != `{"cat0":[1,2,3,4,5,10,20,21,22],"cat1":[93],"unbroken":[1,2]}` {
|
||||||
t.Errorf("Bad inventory: %#v", stdout.String())
|
t.Errorf("Bad inventory: %#v", stdout.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,4 +94,16 @@ func TestEverything(t *testing.T) {
|
||||||
if stdout.String() != "Moo." {
|
if stdout.String() != "Moo." {
|
||||||
t.Error("Wrong file pulled")
|
t.Error("Wrong file pulled")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stdout.Reset()
|
||||||
|
tp.Cat = "unbroken"
|
||||||
|
if err := tp.Handle("mothball"); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if stdout.Len() < 200 {
|
||||||
|
t.Error("That's way too short to be a mothball")
|
||||||
|
}
|
||||||
|
if stdout.String()[:2] != "PK" {
|
||||||
|
t.Error("This mothball isn't a zip file!")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,77 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
inv, err := c.Inventory()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, points := range inv {
|
||||||
|
fmt.Fprintln(puzzlesTxt, points)
|
||||||
|
|
||||||
|
puzzlePath := fmt.Sprintf("%d/puzzle.json", points)
|
||||||
|
pw, err := zf.Create(puzzlePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
puzzle, err := c.Puzzle(points)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record answers in answers.txt
|
||||||
|
for _, answer := range puzzle.Answers {
|
||||||
|
fmt.Fprintln(answersTxt, points, answer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all answers from puzzle object
|
||||||
|
puzzle.Answers = []string{}
|
||||||
|
|
||||||
|
// Write out Puzzle object
|
||||||
|
penc := json.NewEncoder(pw)
|
||||||
|
if err := penc.Encode(puzzle); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write out all attachments and scripts
|
||||||
|
attachments := append(puzzle.Pre.Attachments, puzzle.Pre.Scripts...)
|
||||||
|
for _, att := range attachments {
|
||||||
|
attPath := fmt.Sprintf("%d/%s", points, att)
|
||||||
|
aw, err := zf.Create(attPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ar, err := c.Open(points, att)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(aw, ar); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zf.Close()
|
||||||
|
|
||||||
|
return bytes.NewReader(buf.Bytes()), nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"github.com/spf13/afero/zipfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMothballs(t *testing.T) {
|
||||||
|
fs := NewRecursiveBasePathFs(afero.NewOsFs(), "testdata")
|
||||||
|
static := NewFsCategory(fs, "static")
|
||||||
|
mb, err := Mothball(static)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mbr, err := zip.NewReader(mb, int64(mb.Len()))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
zfs := zipfs.New(mbr)
|
||||||
|
|
||||||
|
if f, err := zfs.Open("puzzles.txt"); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else {
|
||||||
|
defer f.Close()
|
||||||
|
if buf, err := ioutil.ReadAll(f); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else if string(buf) != "" {
|
||||||
|
t.Error("Bad puzzles.txt", string(buf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -20,6 +21,77 @@ import (
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Puzzle contains everything about a puzzle that a client would see.
|
||||||
|
type Puzzle struct {
|
||||||
|
Pre struct {
|
||||||
|
Authors []string
|
||||||
|
Attachments []string
|
||||||
|
Scripts []string
|
||||||
|
AnswerHashes []string
|
||||||
|
AnswerPattern string
|
||||||
|
Body string
|
||||||
|
}
|
||||||
|
Post struct {
|
||||||
|
Objective string
|
||||||
|
Success struct {
|
||||||
|
Acceptable string
|
||||||
|
Mastery string
|
||||||
|
}
|
||||||
|
KSAs []string
|
||||||
|
}
|
||||||
|
Debug struct {
|
||||||
|
Log []string
|
||||||
|
Errors []string
|
||||||
|
Hints []string
|
||||||
|
Summary string
|
||||||
|
}
|
||||||
|
Answers []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (puzzle *Puzzle) computeAnswerHashes() {
|
||||||
|
if len(puzzle.Answers) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
puzzle.Pre.AnswerHashes = make([]string, len(puzzle.Answers))
|
||||||
|
for i, answer := range puzzle.Answers {
|
||||||
|
sum := sha256.Sum256([]byte(answer))
|
||||||
|
hexsum := fmt.Sprintf("%x", sum)
|
||||||
|
puzzle.Pre.AnswerHashes[i] = hexsum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StaticPuzzle contains everything a static puzzle might tell us.
|
||||||
|
type StaticPuzzle struct {
|
||||||
|
Pre struct {
|
||||||
|
Authors []string
|
||||||
|
Attachments []StaticAttachment
|
||||||
|
Scripts []StaticAttachment
|
||||||
|
AnswerPattern string
|
||||||
|
}
|
||||||
|
Post struct {
|
||||||
|
Objective string
|
||||||
|
Success struct {
|
||||||
|
Acceptable string
|
||||||
|
Mastery string
|
||||||
|
}
|
||||||
|
KSAs []string
|
||||||
|
}
|
||||||
|
Debug struct {
|
||||||
|
Log []string
|
||||||
|
Errors []string
|
||||||
|
Hints []string
|
||||||
|
Summary string
|
||||||
|
}
|
||||||
|
Answers []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// StaticAttachment carries information about an attached file.
|
||||||
|
type StaticAttachment struct {
|
||||||
|
Filename string // Filename presented as part of puzzle
|
||||||
|
FilesystemPath string // Filename in backing FS (URL, mothball, or local FS)
|
||||||
|
Listed bool // Whether this file is listed as an attachment
|
||||||
|
}
|
||||||
|
|
||||||
// PuzzleProvider establishes the functionality required to provide one puzzle.
|
// PuzzleProvider establishes the functionality required to provide one puzzle.
|
||||||
type PuzzleProvider interface {
|
type PuzzleProvider interface {
|
||||||
// Puzzle returns a Puzzle struct for the current puzzle.
|
// Puzzle returns a Puzzle struct for the current puzzle.
|
||||||
|
@ -60,11 +132,64 @@ type FsPuzzle struct {
|
||||||
|
|
||||||
// Puzzle returns a Puzzle struct for the current puzzle.
|
// Puzzle returns a Puzzle struct for the current puzzle.
|
||||||
func (fp FsPuzzle) Puzzle() (Puzzle, error) {
|
func (fp FsPuzzle) Puzzle() (Puzzle, error) {
|
||||||
|
var puzzle Puzzle
|
||||||
|
|
||||||
|
static, body, err := fp.staticPuzzle()
|
||||||
|
if err != nil {
|
||||||
|
return puzzle, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to an exportable Puzzle
|
||||||
|
puzzle.Post = static.Post
|
||||||
|
puzzle.Debug = static.Debug
|
||||||
|
puzzle.Answers = static.Answers
|
||||||
|
puzzle.Pre.Authors = static.Pre.Authors
|
||||||
|
puzzle.Pre.Body = string(body)
|
||||||
|
puzzle.Pre.AnswerPattern = static.Pre.AnswerPattern
|
||||||
|
puzzle.Pre.Attachments = make([]string, len(static.Pre.Attachments))
|
||||||
|
for i, attachment := range static.Pre.Attachments {
|
||||||
|
puzzle.Pre.Attachments[i] = attachment.Filename
|
||||||
|
}
|
||||||
|
puzzle.Pre.Scripts = make([]string, len(static.Pre.Scripts))
|
||||||
|
for i, script := range static.Pre.Scripts {
|
||||||
|
puzzle.Pre.Scripts[i] = script.Filename
|
||||||
|
}
|
||||||
|
puzzle.computeAnswerHashes()
|
||||||
|
|
||||||
|
return puzzle, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open returns a newly-opened file.
|
||||||
|
func (fp FsPuzzle) Open(name string) (io.ReadCloser, error) {
|
||||||
|
empty := ioutil.NopCloser(new(bytes.Buffer))
|
||||||
|
static, _, err := fp.staticPuzzle()
|
||||||
|
if err != nil {
|
||||||
|
return empty, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var fsPath string
|
||||||
|
for _, attachment := range append(static.Pre.Attachments, static.Pre.Scripts...) {
|
||||||
|
if attachment.Filename == name {
|
||||||
|
if attachment.FilesystemPath == "" {
|
||||||
|
fsPath = attachment.Filename
|
||||||
|
} else {
|
||||||
|
fsPath = attachment.FilesystemPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fsPath == "" {
|
||||||
|
return empty, fmt.Errorf("Not listed in attachments or scripts: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fp.fs.Open(fsPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fp FsPuzzle) staticPuzzle() (StaticPuzzle, []byte, error) {
|
||||||
r, err := fp.fs.Open("puzzle.md")
|
r, err := fp.fs.Open("puzzle.md")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var err2 error
|
var err2 error
|
||||||
if r, err2 = fp.fs.Open("puzzle.moth"); err2 != nil {
|
if r, err2 = fp.fs.Open("puzzle.moth"); err2 != nil {
|
||||||
return Puzzle{}, err
|
return StaticPuzzle{}, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
|
@ -101,33 +226,21 @@ func (fp FsPuzzle) Puzzle() (Puzzle, error) {
|
||||||
bodyBuf.WriteRune('\n')
|
bodyBuf.WriteRune('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
puzzle, err := headerParser(headerBuf)
|
static, err := headerParser(headerBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return puzzle, err
|
return static, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Markdownify the body
|
body := blackfriday.Run(bodyBuf.Bytes())
|
||||||
if puzzle.Pre.Body != "" {
|
|
||||||
if bodyBuf.Len() > 0 {
|
|
||||||
return puzzle, fmt.Errorf("Puzzle body present in header and in moth body")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
puzzle.Pre.Body = string(blackfriday.Run(bodyBuf.Bytes()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return puzzle, nil
|
return static, body, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open returns a newly-opened file.
|
func legacyAttachmentParser(val []string) []StaticAttachment {
|
||||||
func (fp FsPuzzle) Open(name string) (io.ReadCloser, error) {
|
ret := make([]StaticAttachment, len(val))
|
||||||
return fp.fs.Open(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func legacyAttachmentParser(val []string) []Attachment {
|
|
||||||
ret := make([]Attachment, len(val))
|
|
||||||
for idx, txt := range val {
|
for idx, txt := range val {
|
||||||
parts := strings.SplitN(txt, " ", 3)
|
parts := strings.SplitN(txt, " ", 3)
|
||||||
cur := Attachment{}
|
cur := StaticAttachment{}
|
||||||
cur.FilesystemPath = parts[0]
|
cur.FilesystemPath = parts[0]
|
||||||
if len(parts) > 1 {
|
if len(parts) > 1 {
|
||||||
cur.Filename = parts[1]
|
cur.Filename = parts[1]
|
||||||
|
@ -144,16 +257,16 @@ func legacyAttachmentParser(val []string) []Attachment {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func yamlHeaderParser(r io.Reader) (Puzzle, error) {
|
func yamlHeaderParser(r io.Reader) (StaticPuzzle, error) {
|
||||||
p := Puzzle{}
|
p := StaticPuzzle{}
|
||||||
decoder := yaml.NewDecoder(r)
|
decoder := yaml.NewDecoder(r)
|
||||||
decoder.SetStrict(true)
|
decoder.SetStrict(true)
|
||||||
err := decoder.Decode(&p)
|
err := decoder.Decode(&p)
|
||||||
return p, err
|
return p, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func rfc822HeaderParser(r io.Reader) (Puzzle, error) {
|
func rfc822HeaderParser(r io.Reader) (StaticPuzzle, error) {
|
||||||
p := Puzzle{}
|
p := StaticPuzzle{}
|
||||||
m, err := mail.ReadMessage(r)
|
m, err := mail.ReadMessage(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return p, fmt.Errorf("Parsing RFC822 headers: %v", err)
|
return p, fmt.Errorf("Parsing RFC822 headers: %v", err)
|
||||||
|
@ -188,6 +301,15 @@ func rfc822HeaderParser(r io.Reader) (Puzzle, error) {
|
||||||
|
|
||||||
// Answer checks whether the given answer is correct.
|
// Answer checks whether the given answer is correct.
|
||||||
func (fp FsPuzzle) Answer(answer string) bool {
|
func (fp FsPuzzle) Answer(answer string) bool {
|
||||||
|
p, _, err := fp.staticPuzzle()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, ans := range p.Answers {
|
||||||
|
if ans == answer {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,6 +338,8 @@ func (fp FsCommandPuzzle) Puzzle() (Puzzle, error) {
|
||||||
return Puzzle{}, err
|
return Puzzle{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
puzzle.computeAnswerHashes()
|
||||||
|
|
||||||
return puzzle, nil
|
return puzzle, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
|
@ -64,12 +63,6 @@ func TestPuzzle(t *testing.T) {
|
||||||
if _, err := NewFsPuzzle(catFs, 21).Puzzle(); err == nil {
|
if _, err := NewFsPuzzle(catFs, 21).Puzzle(); err == nil {
|
||||||
t.Error("Boken RFC822 header")
|
t.Error("Boken RFC822 header")
|
||||||
}
|
}
|
||||||
if p, err := NewFsPuzzle(catFs, 22).Puzzle(); err == nil {
|
|
||||||
t.Error("Duplicate bodies")
|
|
||||||
} else if !strings.HasPrefix(err.Error(), "Puzzle body present") {
|
|
||||||
t.Log(p)
|
|
||||||
t.Error("Wrong error for duplicate body:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
fs := afero.NewMemMapFs()
|
fs := afero.NewMemMapFs()
|
||||||
|
|
Loading…
Reference in New Issue