mirror of https://github.com/dirtbags/moth.git
Start to support generated categories
This commit is contained in:
parent
7b06171839
commit
f9cabc5255
|
@ -1,27 +1,42 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewCategory returns a new category for the given path in the given fs.
|
type NopReadCloser struct {
|
||||||
func NewCategory(fs afero.Fs, cat string) Category {
|
}
|
||||||
return Category{
|
|
||||||
Fs: NewBasePathFs(fs, cat),
|
func (n NopReadCloser) Read(b []byte) (int, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
func (n NopReadCloser) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFsCategory returns a Category based on which files are present.
|
||||||
|
// If 'mkcategory' is present and executable, an FsCommandCategory is returned.
|
||||||
|
// Otherwise, FsCategory is returned.
|
||||||
|
func NewFsCategory(fs afero.Fs) Category {
|
||||||
|
if info, err := fs.Stat("mkcategory"); (err == nil) && (info.Mode()&0100 != 0) {
|
||||||
|
return FsCommandCategory{fs: fs}
|
||||||
|
} else {
|
||||||
|
return FsCategory{fs: fs}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Category represents an on-disk category.
|
type FsCategory struct {
|
||||||
type Category struct {
|
fs afero.Fs
|
||||||
afero.Fs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Puzzles returns a list of puzzle values.
|
// Category returns a list of puzzle values.
|
||||||
func (c Category) Puzzles() ([]int, error) {
|
func (c FsCategory) Inventory() ([]int, error) {
|
||||||
puzzleEntries, err := afero.ReadDir(c, ".")
|
puzzleEntries, err := afero.ReadDir(c.fs, ".")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -41,7 +56,44 @@ func (c Category) Puzzles() ([]int, error) {
|
||||||
return puzzles, nil
|
return puzzles, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PuzzleDir returns the PuzzleDir associated with points.
|
func (c FsCategory) Puzzle(points int) (Puzzle, error) {
|
||||||
func (c Category) PuzzleDir(points int) *PuzzleDir {
|
return NewFsPuzzle(c.fs, points).Puzzle()
|
||||||
return NewPuzzleDir(c.Fs, points)
|
}
|
||||||
|
|
||||||
|
func (c FsCategory) Open(points int, filename string) (io.ReadCloser, error) {
|
||||||
|
return NewFsPuzzle(c.fs, points).Open(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c FsCategory) Answer(points int, answer string) bool {
|
||||||
|
// BUG(neale): FsCategory.Answer should probably always return false, to prevent you from running uncompiled puzzles with participants.
|
||||||
|
p, err := c.Puzzle(points)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, a := range p.Answers {
|
||||||
|
if a == answer {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type FsCommandCategory struct {
|
||||||
|
fs afero.Fs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c FsCommandCategory) Inventory() ([]int, error) {
|
||||||
|
return nil, fmt.Errorf("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c FsCommandCategory) Puzzle(points int) (Puzzle, error) {
|
||||||
|
return Puzzle{}, fmt.Errorf("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c FsCommandCategory) Open(points int, filename string) (io.ReadCloser, error) {
|
||||||
|
return NopReadCloser{}, fmt.Errorf("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c FsCommandCategory) Answer(points int, answer string) bool {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,54 @@ import (
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Category defines the functionality required to be a puzzle category.
|
||||||
|
type Category interface {
|
||||||
|
// Inventory lists every puzzle in the category.
|
||||||
|
Inventory() ([]int, error)
|
||||||
|
|
||||||
|
// Puzzle provides a Puzzle structure for the given point value.
|
||||||
|
Puzzle(points int) (Puzzle, error)
|
||||||
|
|
||||||
|
// Open returns an io.ReadCloser for the given filename.
|
||||||
|
Open(points int, filename string) (io.ReadCloser, error)
|
||||||
|
|
||||||
|
// Answer returns whether the given answer is correct.
|
||||||
|
Answer(points int, answer string) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// PuzzleDef 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
|
||||||
|
@ -24,11 +72,6 @@ type T struct {
|
||||||
Fs afero.Fs
|
Fs afero.Fs
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCategory returns a new Category as specified by cat.
|
|
||||||
func (t *T) NewCategory(cat string) Category {
|
|
||||||
return NewCategory(t.Fs, cat)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseArgs parses command-line arguments into T, returning the action to take
|
// ParseArgs parses command-line arguments into T, returning the action to take
|
||||||
func (t *T) ParseArgs() string {
|
func (t *T) ParseArgs() string {
|
||||||
action := flag.String("action", "inventory", "Action to take: must be 'inventory', 'open', 'answer', or 'mothball'")
|
action := flag.String("action", "inventory", "Action to take: must be 'inventory', 'open', 'answer', or 'mothball'")
|
||||||
|
@ -66,7 +109,7 @@ func (t *T) PrintInventory() error {
|
||||||
for _, ent := range dirEnts {
|
for _, ent := range dirEnts {
|
||||||
if ent.IsDir() {
|
if ent.IsDir() {
|
||||||
c := t.NewCategory(ent.Name())
|
c := t.NewCategory(ent.Name())
|
||||||
if puzzles, err := c.Puzzles(); err != nil {
|
if puzzles, err := c.Inventory(); err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
|
@ -86,11 +129,10 @@ func (t *T) PrintInventory() error {
|
||||||
// Open writes a file to the writer.
|
// Open writes a file to the writer.
|
||||||
func (t *T) Open() error {
|
func (t *T) Open() error {
|
||||||
c := t.NewCategory(t.Cat)
|
c := t.NewCategory(t.Cat)
|
||||||
pd := c.PuzzleDir(t.Points)
|
|
||||||
|
|
||||||
switch t.Filename {
|
switch t.Filename {
|
||||||
case "puzzle.json", "":
|
case "puzzle.json", "":
|
||||||
p, err := pd.Export()
|
p, err := c.Puzzle(t.Points)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -100,7 +142,7 @@ func (t *T) Open() error {
|
||||||
}
|
}
|
||||||
t.w.Write(jp)
|
t.w.Write(jp)
|
||||||
default:
|
default:
|
||||||
f, err := pd.Open(t.Filename)
|
f, err := c.Open(t.Points, t.Filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -113,6 +155,10 @@ func (t *T) Open() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *T) NewCategory(name string) Category {
|
||||||
|
return NewFsCategory(NewBasePathFs(t.Fs, name))
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// XXX: Convert puzzle.py to standalone thingies
|
// XXX: Convert puzzle.py to standalone thingies
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"os"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -20,56 +19,27 @@ import (
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewPuzzleDir returns a new PuzzleDir for points.
|
// NewFsPuzzle returns a new FsPuzzle for points.
|
||||||
func NewPuzzleDir(fs afero.Fs, points int) *PuzzleDir {
|
func NewFsPuzzle(fs afero.Fs, points int) *FsPuzzle {
|
||||||
pd := &PuzzleDir{
|
fp := &FsPuzzle{
|
||||||
fs: NewBasePathFs(fs, strconv.Itoa(points)),
|
fs: NewBasePathFs(fs, strconv.Itoa(points)),
|
||||||
}
|
}
|
||||||
// BUG(neale): Doesn't yet handle "puzzle.py" or "mkpuzzle"
|
|
||||||
|
|
||||||
return pd
|
return fp
|
||||||
}
|
}
|
||||||
|
|
||||||
// PuzzleDir is a single puzzle's directory.
|
// FsPuzzle is a single puzzle's directory.
|
||||||
type PuzzleDir struct {
|
type FsPuzzle struct {
|
||||||
fs afero.Fs
|
fs afero.Fs
|
||||||
mkpuzzle bool
|
mkpuzzle bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open returns a newly-opened file.
|
// Puzzle returns a Puzzle struct for the current puzzle.
|
||||||
func (pd *PuzzleDir) Open(name string) (io.ReadCloser, error) {
|
func (fp FsPuzzle) Puzzle() (Puzzle, error) {
|
||||||
// BUG(neale): You cannot open generated files in puzzles, only files actually on the disk
|
r, err := fp.fs.Open("puzzle.md")
|
||||||
if _, err := pd.fs.Stat(""
|
|
||||||
return pd.fs.Open(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export returns a Puzzle struct for the current puzzle.
|
|
||||||
func (pd *PuzzleDir) Export() (Puzzle, error) {
|
|
||||||
p, staticErr := pd.exportStatic()
|
|
||||||
if staticErr == nil {
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only fall through if the static files don't exist. Otherwise, report the error.
|
|
||||||
if !os.IsNotExist(staticErr) {
|
|
||||||
return p, staticErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if p, cmdErr := pd.exportCommand(); cmdErr == nil {
|
|
||||||
return p, nil
|
|
||||||
} else if os.IsNotExist(cmdErr) {
|
|
||||||
// If the command doesn't exist either, report the non-existence of the static file instead.
|
|
||||||
return p, staticErr
|
|
||||||
} else {
|
|
||||||
return p, cmdErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pd *PuzzleDir) exportStatic() (Puzzle, error) {
|
|
||||||
r, err := pd.fs.Open("puzzle.md")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var err2 error
|
var err2 error
|
||||||
if r, err2 = pd.fs.Open("puzzle.moth"); err2 != nil {
|
if r, err2 = fp.fs.Open("puzzle.moth"); err2 != nil {
|
||||||
return Puzzle{}, err
|
return Puzzle{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,34 +94,9 @@ func (pd *PuzzleDir) exportStatic() (Puzzle, error) {
|
||||||
return puzzle, nil
|
return puzzle, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pd *PuzzleDir) exportCommand() (Puzzle, error) {
|
// Open returns a newly-opened file.
|
||||||
bfs, ok := pd.fs.(*BasePathFs)
|
func (fp FsPuzzle) Open(name string) (io.ReadCloser, error) {
|
||||||
if !ok {
|
return fp.fs.Open(name)
|
||||||
return Puzzle{}, fmt.Errorf("Fs won't resolve real paths for %v", pd)
|
|
||||||
}
|
|
||||||
mkpuzzlePath, err := bfs.RealPath("mkpuzzle")
|
|
||||||
if err != nil {
|
|
||||||
return Puzzle{}, err
|
|
||||||
}
|
|
||||||
log.Print(mkpuzzlePath)
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, mkpuzzlePath)
|
|
||||||
stdout, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return Puzzle{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
jsdec := json.NewDecoder(bytes.NewReader(stdout))
|
|
||||||
jsdec.DisallowUnknownFields()
|
|
||||||
puzzle := Puzzle{}
|
|
||||||
if err := jsdec.Decode(&puzzle); err != nil {
|
|
||||||
return Puzzle{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return puzzle, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func legacyAttachmentParser(val []string) []Attachment {
|
func legacyAttachmentParser(val []string) []Attachment {
|
||||||
|
@ -175,39 +120,6 @@ func legacyAttachmentParser(val []string) []Attachment {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func yamlHeaderParser(r io.Reader) (Puzzle, error) {
|
func yamlHeaderParser(r io.Reader) (Puzzle, error) {
|
||||||
p := Puzzle{}
|
p := Puzzle{}
|
||||||
decoder := yaml.NewDecoder(r)
|
decoder := yaml.NewDecoder(r)
|
||||||
|
@ -249,3 +161,49 @@ func rfc822HeaderParser(r io.Reader) (Puzzle, error) {
|
||||||
|
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fp FsPuzzle) Answer(answer string) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type FsCommandPuzzle struct {
|
||||||
|
fs afero.Fs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fp FsCommandPuzzle) Puzzle() (Puzzle, error) {
|
||||||
|
bfs, ok := fp.fs.(*BasePathFs)
|
||||||
|
if !ok {
|
||||||
|
return Puzzle{}, fmt.Errorf("Fs won't resolve real paths for %v", fp)
|
||||||
|
}
|
||||||
|
mkpuzzlePath, err := bfs.RealPath("mkpuzzle")
|
||||||
|
if err != nil {
|
||||||
|
return Puzzle{}, err
|
||||||
|
}
|
||||||
|
log.Print(mkpuzzlePath)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, mkpuzzlePath)
|
||||||
|
stdout, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return Puzzle{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jsdec := json.NewDecoder(bytes.NewReader(stdout))
|
||||||
|
jsdec.DisallowUnknownFields()
|
||||||
|
puzzle := Puzzle{}
|
||||||
|
if err := jsdec.Decode(&puzzle); err != nil {
|
||||||
|
return Puzzle{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return puzzle, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fp FsCommandPuzzle) Open(filename string) (io.ReadCloser, error) {
|
||||||
|
return NopReadCloser{}, fmt.Errorf("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fp FsCommandPuzzle) Answer(answer string) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -14,8 +14,8 @@ func TestPuzzle(t *testing.T) {
|
||||||
catFs := afero.NewBasePathFs(puzzleFs, "cat0")
|
catFs := afero.NewBasePathFs(puzzleFs, "cat0")
|
||||||
|
|
||||||
{
|
{
|
||||||
pd := NewPuzzleDir(catFs, 1)
|
pd := NewFsPuzzle(catFs, 1)
|
||||||
p, err := pd.Export()
|
p, err := pd.Puzzle()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ func TestPuzzle(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
p, err := NewPuzzleDir(catFs, 2).Export()
|
p, err := NewFsPuzzle(catFs, 2).Puzzle()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -47,24 +47,24 @@ func TestPuzzle(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := NewPuzzleDir(catFs, 3).Export(); err != nil {
|
if _, err := NewFsPuzzle(catFs, 3).Puzzle(); err != nil {
|
||||||
t.Error("Legacy `puzzle.moth` file:", err)
|
t.Error("Legacy `puzzle.moth` file:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := NewPuzzleDir(catFs, 99).Export(); err == nil {
|
if _, err := NewFsPuzzle(catFs, 99).Puzzle(); err == nil {
|
||||||
t.Error("Non-existent puzzle", err)
|
t.Error("Non-existent puzzle", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := NewPuzzleDir(catFs, 10).Export(); err == nil {
|
if _, err := NewFsPuzzle(catFs, 10).Puzzle(); err == nil {
|
||||||
t.Error("Broken YAML")
|
t.Error("Broken YAML")
|
||||||
}
|
}
|
||||||
if _, err := NewPuzzleDir(catFs, 20).Export(); err == nil {
|
if _, err := NewFsPuzzle(catFs, 20).Puzzle(); err == nil {
|
||||||
t.Error("Bad RFC822 header")
|
t.Error("Bad RFC822 header")
|
||||||
}
|
}
|
||||||
if _, err := NewPuzzleDir(catFs, 21).Export(); err == nil {
|
if _, err := NewFsPuzzle(catFs, 21).Puzzle(); err == nil {
|
||||||
t.Error("Boken RFC822 header")
|
t.Error("Boken RFC822 header")
|
||||||
}
|
}
|
||||||
if p, err := NewPuzzleDir(catFs, 22).Export(); err == nil {
|
if p, err := NewFsPuzzle(catFs, 22).Puzzle(); err == nil {
|
||||||
t.Error("Duplicate bodies")
|
t.Error("Duplicate bodies")
|
||||||
} else if !strings.HasPrefix(err.Error(), "Puzzle body present") {
|
} else if !strings.HasPrefix(err.Error(), "Puzzle body present") {
|
||||||
t.Log(p)
|
t.Log(p)
|
||||||
|
@ -75,16 +75,16 @@ func TestPuzzle(t *testing.T) {
|
||||||
func TestFsPuzzle(t *testing.T) {
|
func TestFsPuzzle(t *testing.T) {
|
||||||
catFs := afero.NewBasePathFs(afero.NewOsFs(), "testdata")
|
catFs := afero.NewBasePathFs(afero.NewOsFs(), "testdata")
|
||||||
|
|
||||||
if _, err := NewPuzzleDir(catFs, 1).Export(); err != nil {
|
if _, err := NewFsPuzzle(catFs, 1).Puzzle(); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := NewPuzzleDir(catFs, 2).Export(); err != nil {
|
if _, err := NewFsPuzzle(catFs, 2).Puzzle(); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mkpuzzleDir := NewPuzzleDir(catFs, 3)
|
mkpuzzleDir := NewFsPuzzle(catFs, 3)
|
||||||
if _, err := mkpuzzleDir.Export(); err != nil {
|
if _, err := mkpuzzleDir.Puzzle(); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
package main
|
Loading…
Reference in New Issue