// 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 }