summaryrefslogtreecommitdiff
path: root/modules/caddyhttp/templates/frontmatter.go
blob: 730cfd1e0a269bac10c62ffdd0b2900b33ab0afa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package templates

import (
	"encoding/json"
	"fmt"
	"strings"
	"unicode"

	"github.com/naoina/toml"
	"gopkg.in/yaml.v2"
)

func extractFrontMatter(input string) (map[string]interface{}, string, error) {
	// get the bounds of the first non-empty line
	var firstLineStart, firstLineEnd int
	lineEmpty := true
	for i, b := range input {
		if b == '\n' {
			firstLineStart = firstLineEnd
			if firstLineStart > 0 {
				firstLineStart++ // skip newline character
			}
			firstLineEnd = i
			if !lineEmpty {
				break
			}
			continue
		}
		lineEmpty = lineEmpty && unicode.IsSpace(b)
	}
	firstLine := input[firstLineStart:firstLineEnd]

	// see what kind of front matter there is, if any
	var closingFence string
	var fmParser func([]byte) (map[string]interface{}, error)
	switch string(firstLine) {
	case yamlFrontMatterFenceOpen:
		fmParser = yamlFrontMatter
		closingFence = yamlFrontMatterFenceClose
	case tomlFrontMatterFenceOpen:
		fmParser = tomlFrontMatter
		closingFence = tomlFrontMatterFenceClose
	case jsonFrontMatterFenceOpen:
		fmParser = jsonFrontMatter
		closingFence = jsonFrontMatterFenceClose
	default:
		// no recognized front matter; whole document is body
		return nil, input, nil
	}

	// find end of front matter
	fmEndFenceStart := strings.Index(input[firstLineEnd:], "\n"+closingFence)
	if fmEndFenceStart < 0 {
		return nil, "", fmt.Errorf("unterminated front matter")
	}
	fmEndFenceStart += firstLineEnd + 1 // add 1 to account for newline

	// extract and parse front matter
	frontMatter := input[firstLineEnd:fmEndFenceStart]
	fm, err := fmParser([]byte(frontMatter))
	if err != nil {
		return nil, "", err
	}

	// the rest is the body
	body := input[fmEndFenceStart+len(closingFence):]

	return fm, body, nil
}

func yamlFrontMatter(input []byte) (map[string]interface{}, error) {
	m := make(map[string]interface{})
	err := yaml.Unmarshal(input, &m)
	return m, err
}

func tomlFrontMatter(input []byte) (map[string]interface{}, error) {
	m := make(map[string]interface{})
	err := toml.Unmarshal(input, &m)
	return m, err
}

func jsonFrontMatter(input []byte) (map[string]interface{}, error) {
	input = append([]byte{'{'}, input...)
	input = append(input, '}')
	m := make(map[string]interface{})
	err := json.Unmarshal(input, &m)
	return m, err
}

type parsedMarkdownDoc struct {
	Meta map[string]interface{} `json:"meta,omitempty"`
	Body string                 `json:"body,omitempty"`
}

const (
	yamlFrontMatterFenceOpen, yamlFrontMatterFenceClose = "---", "---"
	tomlFrontMatterFenceOpen, tomlFrontMatterFenceClose = "+++", "+++"
	jsonFrontMatterFenceOpen, jsonFrontMatterFenceClose = "{", "}"
)