-
Notifications
You must be signed in to change notification settings - Fork 1
/
frontmatter.go
127 lines (111 loc) · 2.81 KB
/
frontmatter.go
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package xtemplate
import (
"encoding/json"
"fmt"
"strings"
"unicode"
"github.com/BurntSushi/toml"
"gopkg.in/yaml.v3"
)
func extractFrontMatter(input string) (map[string]any, 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]
// ensure residue windows carriage return byte is removed
firstLine = strings.TrimSpace(firstLine)
// see what kind of front matter there is, if any
var closingFence []string
var fmParser func([]byte) (map[string]any, error)
for _, fmType := range supportedFrontMatterTypes {
if firstLine == fmType.FenceOpen {
closingFence = fmType.FenceClose
fmParser = fmType.ParseFunc
}
}
if fmParser == nil {
// no recognized front matter; whole document is body
return nil, input, nil
}
// find end of front matter
var fmEndFence string
fmEndFenceStart := -1
for _, fence := range closingFence {
index := strings.Index(input[firstLineEnd:], "\n"+fence)
if index >= 0 {
fmEndFenceStart = index
fmEndFence = fence
break
}
}
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(fmEndFence):]
return fm, body, nil
}
func yamlFrontMatter(input []byte) (map[string]any, error) {
m := make(map[string]any)
err := yaml.Unmarshal(input, &m)
return m, err
}
func tomlFrontMatter(input []byte) (map[string]any, error) {
m := make(map[string]any)
err := toml.Unmarshal(input, &m)
return m, err
}
func jsonFrontMatter(input []byte) (map[string]any, error) {
input = append([]byte{'{'}, input...)
input = append(input, '}')
m := make(map[string]any)
err := json.Unmarshal(input, &m)
return m, err
}
type parsedMarkdownDoc struct {
Meta map[string]any `json:"meta,omitempty"`
Body string `json:"body,omitempty"`
}
type frontMatterType struct {
FenceOpen string
FenceClose []string
ParseFunc func(input []byte) (map[string]any, error)
}
var supportedFrontMatterTypes = []frontMatterType{
{
FenceOpen: "---",
FenceClose: []string{"---", "..."},
ParseFunc: yamlFrontMatter,
},
{
FenceOpen: "+++",
FenceClose: []string{"+++"},
ParseFunc: tomlFrontMatter,
},
{
FenceOpen: "{",
FenceClose: []string{"}"},
ParseFunc: jsonFrontMatter,
},
}