145 lines
3.5 KiB
Go
145 lines
3.5 KiB
Go
|
// Package front is a frontmatter extraction library.
|
||
|
package front
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"bytes"
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"io"
|
||
|
"strings"
|
||
|
|
||
|
"gopkg.in/yaml.v2"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
//ErrIsEmpty is an error indicating no front matter was found
|
||
|
ErrIsEmpty = errors.New("front: an empty file")
|
||
|
|
||
|
//ErrUnknownDelim is returned when the delimiters are not known by the
|
||
|
//FrontMatter implementation.
|
||
|
ErrUnknownDelim = errors.New("front: unknown delim")
|
||
|
)
|
||
|
|
||
|
type (
|
||
|
//HandlerFunc is an interface for a function that process front matter text.
|
||
|
HandlerFunc func(string) (map[string]interface{}, error)
|
||
|
)
|
||
|
|
||
|
//Matter is all what matters here.
|
||
|
type Matter struct {
|
||
|
handlers map[string]HandlerFunc
|
||
|
}
|
||
|
|
||
|
//NewMatter creates a new Matter instance
|
||
|
func NewMatter() *Matter {
|
||
|
return &Matter{handlers: make(map[string]HandlerFunc)}
|
||
|
}
|
||
|
|
||
|
//Handle registers a handler for the given frontmatter delimiter
|
||
|
func (m *Matter) Handle(delim string, fn HandlerFunc) {
|
||
|
m.handlers[delim] = fn
|
||
|
}
|
||
|
|
||
|
// Parse parses the input and extract the frontmatter
|
||
|
func (m *Matter) Parse(input io.Reader) (front map[string]interface{}, body string, err error) {
|
||
|
return m.parse(input)
|
||
|
}
|
||
|
func (m *Matter) parse(input io.Reader) (front map[string]interface{}, body string, err error) {
|
||
|
var getFront = func(f string) string {
|
||
|
return strings.TrimSpace(f[3:])
|
||
|
}
|
||
|
f, body, err := m.splitFront(input)
|
||
|
if err != nil {
|
||
|
return nil, "", err
|
||
|
}
|
||
|
h := m.handlers[f[:3]]
|
||
|
front, err = h(getFront(f))
|
||
|
if err != nil {
|
||
|
return nil, "", err
|
||
|
}
|
||
|
return front, body, nil
|
||
|
|
||
|
}
|
||
|
func sniffDelim(input []byte) (string, error) {
|
||
|
if len(input) < 4 {
|
||
|
return "", ErrIsEmpty
|
||
|
}
|
||
|
return string(input[:3]), nil
|
||
|
}
|
||
|
|
||
|
func (m *Matter) splitFront(input io.Reader) (front, body string, err error) {
|
||
|
bufsize := 1024 * 1024
|
||
|
buf := make([]byte, bufsize)
|
||
|
|
||
|
s := bufio.NewScanner(input)
|
||
|
// Necessary so we can handle larger than default 4096b buffer
|
||
|
s.Buffer(buf, bufsize)
|
||
|
|
||
|
rst := make([]string, 2)
|
||
|
s.Split(m.split)
|
||
|
n := 0
|
||
|
for s.Scan() {
|
||
|
if n == 0 {
|
||
|
rst[0] = s.Text()
|
||
|
} else if n == 1 {
|
||
|
rst[1] = s.Text()
|
||
|
}
|
||
|
n++
|
||
|
}
|
||
|
if err = s.Err(); err != nil {
|
||
|
return
|
||
|
}
|
||
|
return rst[0], rst[1], nil
|
||
|
}
|
||
|
|
||
|
//split implements bufio.SplitFunc for spliting fron matter from the body text.
|
||
|
func (m *Matter) split(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||
|
if atEOF && len(data) == 0 {
|
||
|
return 0, nil, nil
|
||
|
}
|
||
|
delim, err := sniffDelim(data)
|
||
|
if err != nil {
|
||
|
return 0, nil, err
|
||
|
}
|
||
|
if _, ok := m.handlers[delim]; !ok {
|
||
|
return 0, nil, ErrUnknownDelim
|
||
|
}
|
||
|
if x := bytes.Index(data, []byte(delim)); x >= 0 {
|
||
|
// check the next delim index
|
||
|
if next := bytes.Index(data[x+len(delim):], []byte(delim)); next > 0 {
|
||
|
return next + len(delim), dropSpace(data[:next+len(delim)]), nil
|
||
|
}
|
||
|
return len(data), dropSpace(data[x+len(delim):]), nil
|
||
|
}
|
||
|
if atEOF {
|
||
|
return len(data), data, nil
|
||
|
}
|
||
|
return 0, nil, nil
|
||
|
}
|
||
|
|
||
|
func dropSpace(d []byte) []byte {
|
||
|
return bytes.TrimSpace(d)
|
||
|
}
|
||
|
|
||
|
//JSONHandler implements HandlerFunc interface. It extracts front matter data from the given
|
||
|
// string argument by interpreting it as a json string.
|
||
|
func JSONHandler(front string) (map[string]interface{}, error) {
|
||
|
var rst interface{}
|
||
|
err := json.Unmarshal([]byte(front), &rst)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return rst.(map[string]interface{}), nil
|
||
|
}
|
||
|
|
||
|
//YAMLHandler decodes ymal string into a go map[string]interface{}
|
||
|
func YAMLHandler(front string) (map[string]interface{}, error) {
|
||
|
out := make(map[string]interface{})
|
||
|
err := yaml.Unmarshal([]byte(front), out)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return out, nil
|
||
|
}
|