diff --git a/cmd/transpile/category.go b/cmd/transpile/category.go index d48289c..80aaaa6 100644 --- a/cmd/transpile/category.go +++ b/cmd/transpile/category.go @@ -12,6 +12,10 @@ import ( "encoding/hex" "strconv" "math/rand" + "context" + "time" + "os/exec" + "bytes" ) @@ -32,18 +36,28 @@ func PrngOfStrings(input ...string) (*rand.Rand) { } -func ParsePuzzle(puzzlePath string, seed string) (*Puzzle, error) { - puzzleFd, err := os.Open(puzzlePath) - if err != nil { - return nil, err - } - defer puzzleFd.Close() +func runPuzzleGen(puzzlePath string, seed string) (*Puzzle, error) { + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() - puzzle, err := ParseMoth(puzzleFd) + cmd := exec.CommandContext(ctx, puzzlePath) + cmd.Env = append( + os.Environ(), + fmt.Sprintf("MOTH_PUZZLE_SEED=%s", seed), + ) + stdout, err := cmd.Output() if err != nil { return nil, err } - + + jsdec := json.NewDecoder(bytes.NewReader(stdout)) + jsdec.DisallowUnknownFields() + puzzle := new(Puzzle) + err = jsdec.Decode(puzzle) + if err != nil { + return nil, err + } + return puzzle, nil } @@ -62,22 +76,43 @@ func ParseCategory(categoryPath string, seed string) ([]PuzzleEntry, error) { puzzleEntries := make([]PuzzleEntry, 0, len(puzzleDirs)) for _, puzzleDir := range puzzleDirs { - puzzlePath := filepath.Join(categoryPath, puzzleDir, "puzzle.moth") - puzzleSeed := fmt.Sprintf("%s/%s", seed, puzzleDir) + var puzzle *Puzzle + puzzlePath := filepath.Join(categoryPath, puzzleDir) + + // Determine point value from directory name points, err := strconv.Atoi(puzzleDir) if err != nil { log.Printf("Skipping %s: %v", puzzlePath, err) continue } - - puzzle, err := ParsePuzzle(puzzlePath, puzzleSeed) - if err != nil { - log.Printf("Skipping %s: %v", puzzlePath, err) + + // Try the .moth file first + puzzleMothPath := filepath.Join(puzzlePath, "puzzle.moth") + puzzleFd, err := os.Open(puzzleMothPath) + if err == nil { + defer puzzleFd.Close() + puzzle, err = ParseMoth(puzzleFd) + if err != nil { + log.Printf("Skipping %s: %v", puzzleMothPath, err) + continue + } + } else if os.IsNotExist(err) { + var genErr error + puzzleGenPath := filepath.Join(puzzlePath, "mkpuzzle") + puzzle, genErr = runPuzzleGen(puzzleGenPath, puzzlePath) + if genErr != nil { + log.Printf("Skipping %20s: %v", puzzleMothPath, err) + log.Printf("Skipping %20s: %v", puzzleGenPath, genErr) + continue + } + } else { + log.Printf("Skipping %s: %v", puzzleMothPath, err) continue } - prng := PrngOfStrings(puzzleSeed) + // Create a category entry for this + prng := PrngOfStrings(puzzlePath) idBytes := make([]byte, 16) prng.Read(idBytes) id := hex.EncodeToString(idBytes) @@ -99,6 +134,10 @@ func main() { flag.Parse() baseSeedString := os.Getenv("SEED") + jsenc := json.NewEncoder(os.Stdout) + jsenc.SetEscapeHTML(false) + jsenc.SetIndent("", " ") + for _, dirname := range flag.Args() { categoryName := filepath.Base(dirname) categorySeed := fmt.Sprintf("%s/%s", baseSeedString, categoryName) @@ -108,12 +147,9 @@ func main() { continue } - jpuzzles, err := json.MarshalIndent(puzzles, "", " ") - if err != nil { + if err := jsenc.Encode(puzzles); err != nil { log.Print(err) continue } - - fmt.Println(string(jpuzzles)) } } diff --git a/example-puzzles/example/6/puzzle.moth b/example-puzzles/example/6/puzzle.moth new file mode 100644 index 0000000..f5425d0 --- /dev/null +++ b/example-puzzles/example/6/puzzle.moth @@ -0,0 +1,17 @@ +--- +Summary: YAML Metadata +Author: neale +Answer: YAML +Answer: yaml +Objective: | + Understand how YAML metadata can be used in a `.moth` file +Success: + Acceptable: | + Enter the answer and move on + Mastery: | + Create a `.moth` file using YAML metadata and serve it + through the devel server. +--- + +You can also provide metadata in YAML format. +This puzzle's `.moth` file serves as an example. diff --git a/example-puzzles/example/7/mkpuzzle b/example-puzzles/example/7/mkpuzzle new file mode 100755 index 0000000..6919d6b --- /dev/null +++ b/example-puzzles/example/7/mkpuzzle @@ -0,0 +1,24 @@ +#! /bin/sh + +cat <Writing Your Own Generator

\ +

\ + You can output the JSON puzzle format if you want.\ + See the source of this puzzle for an example!\ +

\ + " + }, + "debug": { + "log": [ + "There are many fields you can specify, aside from those in this file.", + "See puzzle.moth for the full spec.", + "MOTH_PUZZLE_SEED=$MOTH_PUZZLE_SEED" + ] + }, + "answers": ["JSON"] +} +EOD