markdown/frontmatter.go (view raw)
1package markdown
2
3import (
4 "bufio"
5 "bytes"
6 "errors"
7 "fmt"
8
9 "gopkg.in/yaml.v3"
10)
11
12type Matter map[string]string
13
14type MarkdownDoc struct {
15 Frontmatter Matter
16 Body []byte
17}
18
19const (
20 yamlDelim = "---"
21)
22
23func (md *MarkdownDoc) Extract(source []byte) error {
24 bufsize := 1024 * 1024
25 buf := make([]byte, bufsize)
26
27 input := bytes.NewReader(source)
28 s := bufio.NewScanner(input)
29 s.Buffer(buf, bufsize)
30
31 matter := []byte{}
32 body := []byte{}
33
34 s.Split(splitFunc)
35 n := 0
36 for s.Scan() {
37 if n == 0 {
38 matter = s.Bytes()
39 } else if n == 1 {
40 body = s.Bytes()
41 }
42 n++
43 }
44 if err := s.Err(); err != nil {
45 return fmt.Errorf("error: failed to scan text")
46 }
47 if err := yaml.Unmarshal(matter, &md.Frontmatter); err != nil {
48 return fmt.Errorf("error: failed to parse yaml")
49 }
50 md.Body = body
51 return nil
52}
53
54func splitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) {
55 if atEOF && len(data) == 0 {
56 return 0, nil, nil
57 }
58 delim, err := sniffDelim(data)
59 if err != nil {
60 return 0, nil, err
61 }
62 if delim != yamlDelim {
63 return 0, nil, fmt.Errorf("error: %s is not a supported delimiter", delim)
64 }
65 if x := bytes.Index(data, []byte(delim)); x >= 0 {
66 if next := bytes.Index(data[x+len(delim):], []byte(delim)); next > 0 {
67 return next + len(delim), bytes.TrimSpace(data[:next+len(delim)]), nil
68 }
69 return len(data), bytes.TrimSpace(data[x+len(delim):]), nil
70 }
71 if atEOF {
72 return len(data), data, nil
73 }
74 return 0, nil, nil
75}
76
77func sniffDelim(input []byte) (string, error) {
78 if len(input) < 4 {
79 return "", errors.New("error: input is empty")
80 }
81 return string(input[:3]), nil
82}