2019-08-17 11:01:26 -06:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
2019-08-17 13:09:09 -06:00
|
|
|
"gopkg.in/russross/blackfriday.v2"
|
|
|
|
"gopkg.in/yaml.v2"
|
2019-08-17 11:01:26 -06:00
|
|
|
"io"
|
2019-08-17 13:09:09 -06:00
|
|
|
"log"
|
2019-08-17 11:01:26 -06:00
|
|
|
"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 {
|
2019-08-17 11:01:26 -06:00
|
|
|
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
|
2019-08-17 11:01:26 -06:00
|
|
|
}
|
|
|
|
Post struct {
|
|
|
|
Objective string
|
2019-08-17 13:09:09 -06:00
|
|
|
Success struct {
|
|
|
|
Acceptable string
|
|
|
|
Mastery string
|
|
|
|
}
|
|
|
|
KSAs []string
|
2019-08-17 11:01:26 -06:00
|
|
|
}
|
|
|
|
Debug struct {
|
2019-08-17 13:09:09 -06:00
|
|
|
Log []string
|
|
|
|
Errors []string
|
|
|
|
Hints []string
|
|
|
|
Summary string
|
2019-08-17 11:01:26 -06:00
|
|
|
}
|
2019-08-17 13:09:09 -06:00
|
|
|
Answers []string
|
2019-08-17 11:01:26 -06:00
|
|
|
}
|
|
|
|
|
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 11:01:26 -06:00
|
|
|
|
2019-08-17 13:09:09 -06:00
|
|
|
err := yaml.Unmarshal(input, puzzle)
|
2019-08-17 11:01:26 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-08-17 13:09:09 -06:00
|
|
|
return puzzle, nil
|
2019-08-17 11:01:26 -06:00
|
|
|
}
|
|
|
|
|
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) {
|
2019-08-17 11:01:26 -06:00
|
|
|
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)
|
2019-08-17 11:01:26 -06:00
|
|
|
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 11:01:26 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-17 13:09:09 -06:00
|
|
|
return puzzle, nil
|
2019-08-17 11:01:26 -06:00
|
|
|
}
|
|
|
|
|
2019-08-17 16:00:15 -06:00
|
|
|
func ParseMoth(r io.Reader) (*Puzzle, error) {
|
2019-08-17 11:01:26 -06:00
|
|
|
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
|
|
|
|
2019-08-17 11:01:26 -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())
|
2019-08-17 11:01:26 -06:00
|
|
|
if err != nil {
|
2019-08-17 16:00:15 -06:00
|
|
|
return nil, err
|
2019-08-17 11:01:26 -06:00
|
|
|
}
|
2019-08-17 16:00:15 -06:00
|
|
|
|
|
|
|
// Markdownify the body
|
2019-08-17 11:01:26 -06:00
|
|
|
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
|
2019-08-17 11:01:26 -06:00
|
|
|
}
|