moth/cmd/transpile/category.go

112 lines
2.9 KiB
Go

package main
import (
"fmt"
"io"
"log"
"strconv"
"github.com/spf13/afero"
)
// NopReadCloser provides an io.ReadCloser which does nothing.
type NopReadCloser struct {
}
// Read satisfies io.Reader.
func (n NopReadCloser) Read(b []byte) (int, error) {
return 0, nil
}
// Close satisfies io.Closer.
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}
}
return FsCategory{fs: fs}
}
// FsCategory provides a category backed by a .md file.
type FsCategory struct {
fs afero.Fs
}
// Inventory returns a list of point values for this category.
func (c FsCategory) Inventory() ([]int, error) {
puzzleEntries, err := afero.ReadDir(c.fs, ".")
if err != nil {
return nil, err
}
puzzles := make([]int, 0, len(puzzleEntries))
for _, ent := range puzzleEntries {
if !ent.IsDir() {
continue
}
if points, err := strconv.Atoi(ent.Name()); err != nil {
log.Println("Skipping non-numeric directory", ent.Name())
continue
} else {
puzzles = append(puzzles, points)
}
}
return puzzles, nil
}
// Puzzle returns a Puzzle structure for the given point value.
func (c FsCategory) Puzzle(points int) (Puzzle, error) {
return NewFsPuzzle(c.fs, points).Puzzle()
}
// Open returns an io.ReadCloser for the given filename.
func (c FsCategory) Open(points int, filename string) (io.ReadCloser, error) {
return NewFsPuzzle(c.fs, points).Open(filename)
}
// Answer check whether an answer is correct.
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
}
// FsCommandCategory provides a category backed by running an external command.
type FsCommandCategory struct {
fs afero.Fs
}
// Inventory returns a list of point values for this category.
func (c FsCommandCategory) Inventory() ([]int, error) {
return nil, fmt.Errorf("Not implemented")
}
// Puzzle returns a Puzzle structure for the given point value.
func (c FsCommandCategory) Puzzle(points int) (Puzzle, error) {
return Puzzle{}, fmt.Errorf("Not implemented")
}
// Open returns an io.ReadCloser for the given filename.
func (c FsCommandCategory) Open(points int, filename string) (io.ReadCloser, error) {
return NopReadCloser{}, fmt.Errorf("Not implemented")
}
// Answer check whether an answer is correct.
func (c FsCommandCategory) Answer(points int, answer string) bool {
return false
}