xesite/vendor/github.com/gernest/front/front.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
}