moth/cmd/transpile/puzzle.go

163 lines
3.1 KiB
Go
Raw Normal View History

package main
import (
"bufio"
"bytes"
"fmt"
2019-08-17 13:09:09 -06:00
"gopkg.in/russross/blackfriday.v2"
"gopkg.in/yaml.v2"
"io"
2019-08-17 13:09:09 -06:00
"log"
"net/mail"
"strings"
)
2019-08-17 13:09:09 -06:00
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
}
type Puzzle struct {
Pre struct {
2019-08-17 13:09:09 -06:00
Authors []string
Attachments []Attachment
2019-08-17 16:00:15 -06:00
Scripts []Attachment
2019-08-17 13:09:09 -06:00
AnswerPattern string
Body string
}
Post struct {
Objective string
2019-08-17 13:09:09 -06:00
Success struct {
Acceptable string
Mastery string
}
KSAs []string
}
Debug struct {
2019-08-17 13:09:09 -06:00
Log []string
Errors []string
Hints []string
Summary string
}
2019-08-17 13:09:09 -06:00
Answers []string
}
2019-08-17 13:09:09 -06:00
type HeaderParser func([]byte) (*Puzzle, error)
func YamlParser(input []byte) (*Puzzle, error) {
puzzle := new(Puzzle)
2019-08-17 13:09:09 -06:00
err := yaml.Unmarshal(input, puzzle)
if err != nil {
return nil, err
}
2019-08-17 13:09:09 -06:00
return puzzle, nil
}
2019-08-17 16:00:15 -06:00
func AttachmentParser(val []string) ([]Attachment) {
ret := make([]Attachment, len(val))
for idx, txt := range val {
parts := strings.SplitN(txt, " ", 3)
cur := Attachment{}
cur.FilesystemPath = parts[0]
if len(parts) > 1 {
cur.Filename = parts[1]
} else {
cur.Filename = cur.FilesystemPath
}
if (len(parts) > 2) && (parts[2] == "hidden") {
cur.Listed = false
} else {
cur.Listed = true
}
ret[idx] = cur
}
return ret
}
2019-08-17 13:09:09 -06:00
func Rfc822Parser(input []byte) (*Puzzle, error) {
msgBytes := append(input, '\n')
r := bytes.NewReader(msgBytes)
m, err := mail.ReadMessage(r)
if err != nil {
return nil, err
}
2019-08-17 13:09:09 -06:00
puzzle := new(Puzzle)
for key, val := range m.Header {
key = strings.ToLower(key)
switch key {
2019-08-17 13:09:09 -06:00
case "author":
puzzle.Pre.Authors = val
case "pattern":
puzzle.Pre.AnswerPattern = val[0]
2019-08-17 16:00:15 -06:00
case "script":
puzzle.Pre.Scripts = AttachmentParser(val)
case "file":
puzzle.Pre.Attachments = AttachmentParser(val)
2019-08-17 13:09:09 -06:00
case "answer":
puzzle.Answers = val
case "summary":
puzzle.Debug.Summary = val[0]
case "hint":
puzzle.Debug.Hints = val
case "ksa":
puzzle.Post.KSAs = val
default:
return nil, fmt.Errorf("Unknown header field: %s", key)
}
}
2019-08-17 13:09:09 -06:00
return puzzle, nil
}
2019-08-17 16:00:15 -06:00
func ParseMoth(r io.Reader) (*Puzzle, error) {
headerEnd := ""
headerBuf := new(bytes.Buffer)
headerParser := Rfc822Parser
scanner := bufio.NewScanner(r)
lineNo := 0
for scanner.Scan() {
line := scanner.Text()
lineNo += 1
if lineNo == 1 {
if line == "---" {
headerParser = YamlParser
headerEnd = "---"
continue
} else {
headerParser = Rfc822Parser
}
}
if line == headerEnd {
break
}
headerBuf.WriteString(line)
headerBuf.WriteRune('\n')
}
2019-08-17 13:09:09 -06:00
bodyBuf := new(bytes.Buffer)
for scanner.Scan() {
line := scanner.Text()
lineNo += 1
bodyBuf.WriteString(line)
bodyBuf.WriteRune('\n')
}
2019-08-17 13:09:09 -06:00
puzzle, err := headerParser(headerBuf.Bytes())
if err != nil {
2019-08-17 16:00:15 -06:00
return nil, err
}
2019-08-17 16:00:15 -06:00
// Markdownify the body
bodyB := blackfriday.Run(bodyBuf.Bytes())
2019-08-17 13:09:09 -06:00
if (puzzle.Pre.Body != "") && (len(bodyB) > 0) {
log.Print("Body specified in header; overwriting...")
}
puzzle.Pre.Body = string(bodyB)
2019-08-17 16:00:15 -06:00
return puzzle, nil
}