diff options
author | Cameron Moore <moorereason@gmail.com> | 2016-12-27 00:23:20 +0300 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2017-03-11 19:52:25 +0300 |
commit | f039e3be9e4a11808508c8cd3043b340deea040f (patch) | |
tree | df3e709fc6321fd2a3e1d23efa2c3512d8ed4a94 /parser/frontmatter.go | |
parent | ddc8cc0082965143a650052a9aa538bac9133481 (diff) |
parser: Refactor frontmatter parser and add tests
Lots of cleanups here:
- Refactor InterfaceToConfig and InterfaceToFrontMatter to use io.Writer.
- Simplify InterfaceToFrontMatter by wrapping InterfaceToConfig.
- Export FrontmatterType since we return it in DetectFrontMatter.
- Refactor removeTOMLIdentifier to avoid blindly replacing "+++".
- Update HandleJSONMetaData to return an empty map on nil input.
- Updates vendored goorgeous package and test for org-mode frontmatter.
- Add tests and godoc comments.
Coverage for parser package increased from 45.2% to 85.2%.
Diffstat (limited to 'parser/frontmatter.go')
-rw-r--r-- | parser/frontmatter.go | 161 |
1 files changed, 98 insertions, 63 deletions
diff --git a/parser/frontmatter.go b/parser/frontmatter.go index e57a593ab..797c6fcf0 100644 --- a/parser/frontmatter.go +++ b/parser/frontmatter.go @@ -17,6 +17,7 @@ import ( "bytes" "encoding/json" "errors" + "io" "strings" "github.com/chaseadamsio/goorgeous" @@ -25,113 +26,116 @@ import ( "gopkg.in/yaml.v2" ) -type frontmatterType struct { - markstart, markend []byte - Parse func([]byte) (interface{}, error) - includeMark bool +// FrontmatterType represents a type of frontmatter. +type FrontmatterType struct { + // Parse decodes content into a Go interface. + Parse func([]byte) (interface{}, error) + + markstart, markend []byte // starting and ending delimiters + includeMark bool // include start and end mark in output } -func InterfaceToConfig(in interface{}, mark rune) ([]byte, error) { +// InterfaceToConfig encodes a given input based upon the mark and writes to w. +func InterfaceToConfig(in interface{}, mark rune, w io.Writer) error { if in == nil { - return []byte{}, errors.New("input was nil") + return errors.New("input was nil") } - b := new(bytes.Buffer) - switch mark { case rune(YAMLLead[0]): - by, err := yaml.Marshal(in) - if err != nil { - return nil, err - } - b.Write(by) - _, err = b.Write([]byte("...")) + b, err := yaml.Marshal(in) if err != nil { - return nil, err + return err } - return b.Bytes(), nil + + _, err = w.Write(b) + return err + case rune(TOMLLead[0]): tree := toml.TreeFromMap(in.(map[string]interface{})) - return []byte(tree.String()), nil + b := []byte(tree.String()) + + _, err := w.Write(b) + return err + case rune(JSONLead[0]): - by, err := json.MarshalIndent(in, "", " ") + b, err := json.MarshalIndent(in, "", " ") if err != nil { - return nil, err + return err } - b.Write(by) - _, err = b.Write([]byte("\n")) + + _, err = w.Write(b) if err != nil { - return nil, err + return err } - return b.Bytes(), nil + + _, err = w.Write([]byte{'\n'}) + return err + default: - return nil, errors.New("Unsupported Format provided") + return errors.New("Unsupported Format provided") } } -func InterfaceToFrontMatter(in interface{}, mark rune) ([]byte, error) { +// InterfaceToFrontMatter encodes a given input into a frontmatter +// representation based upon the mark with the appropriate front matter delimiters +// surrounding the output, which is written to w. +func InterfaceToFrontMatter(in interface{}, mark rune, w io.Writer) error { if in == nil { - return []byte{}, errors.New("input was nil") + return errors.New("input was nil") } - b := new(bytes.Buffer) - switch mark { case rune(YAMLLead[0]): - _, err := b.Write([]byte(YAMLDelimUnix)) + _, err := w.Write([]byte(YAMLDelimUnix)) if err != nil { - return nil, err + return err } - by, err := yaml.Marshal(in) - if err != nil { - return nil, err - } - b.Write(by) - _, err = b.Write([]byte(YAMLDelimUnix)) + + err = InterfaceToConfig(in, mark, w) if err != nil { - return nil, err + return err } - return b.Bytes(), nil + + _, err = w.Write([]byte(YAMLDelimUnix)) + return err + case rune(TOMLLead[0]): - _, err := b.Write([]byte(TOMLDelimUnix)) + _, err := w.Write([]byte(TOMLDelimUnix)) if err != nil { - return nil, err + return err } - tree := toml.TreeFromMap(in.(map[string]interface{})) - b.Write([]byte(tree.String())) - _, err = b.Write([]byte("\n" + TOMLDelimUnix)) - if err != nil { - return nil, err - } - return b.Bytes(), nil - case rune(JSONLead[0]): - by, err := json.MarshalIndent(in, "", " ") - if err != nil { - return nil, err - } - b.Write(by) - _, err = b.Write([]byte("\n")) + err = InterfaceToConfig(in, mark, w) if err != nil { - return nil, err + return err } - return b.Bytes(), nil + + _, err = w.Write([]byte("\n" + TOMLDelimUnix)) + return err + default: - return nil, errors.New("Unsupported Format provided") + return InterfaceToConfig(in, mark, w) } } +// FormatToLeadRune takes a given format kind and return the leading front +// matter delimiter. func FormatToLeadRune(kind string) rune { switch FormatSanitize(kind) { case "yaml": return rune([]byte(YAMLLead)[0]) case "json": return rune([]byte(JSONLead)[0]) + case "org": + return '#' default: return rune([]byte(TOMLLead)[0]) } } +// FormatSanitize returns the canonical format name for a given kind. +// // TODO(bep) move to helpers func FormatSanitize(kind string) string { switch strings.ToLower(kind) { @@ -141,27 +145,31 @@ func FormatSanitize(kind string) string { return "toml" case "json", "js": return "json" + case "org": + return kind default: return "toml" } } // DetectFrontMatter detects the type of frontmatter analysing its first character. -func DetectFrontMatter(mark rune) (f *frontmatterType) { +func DetectFrontMatter(mark rune) (f *FrontmatterType) { switch mark { case '-': - return &frontmatterType{[]byte(YAMLDelim), []byte(YAMLDelim), HandleYAMLMetaData, false} + return &FrontmatterType{HandleYAMLMetaData, []byte(YAMLDelim), []byte(YAMLDelim), false} case '+': - return &frontmatterType{[]byte(TOMLDelim), []byte(TOMLDelim), HandleTOMLMetaData, false} + return &FrontmatterType{HandleTOMLMetaData, []byte(TOMLDelim), []byte(TOMLDelim), false} case '{': - return &frontmatterType{[]byte{'{'}, []byte{'}'}, HandleJSONMetaData, true} + return &FrontmatterType{HandleJSONMetaData, []byte{'{'}, []byte{'}'}, true} case '#': - return &frontmatterType{[]byte("#+"), []byte("\n"), HandleOrgMetaData, false} + return &FrontmatterType{HandleOrgMetaData, []byte("#+"), []byte("\n"), false} default: return nil } } +// HandleTOMLMetaData unmarshals TOML-encoded datum and returns a Go interface +// representing the encoded data structure. func HandleTOMLMetaData(datum []byte) (interface{}, error) { m := map[string]interface{}{} datum = removeTOMLIdentifier(datum) @@ -177,22 +185,49 @@ func HandleTOMLMetaData(datum []byte) (interface{}, error) { return m, nil } +// removeTOMLIdentifier removes, if necessary, beginning and ending TOML +// frontmatter delimiters from a byte slice. func removeTOMLIdentifier(datum []byte) []byte { - return bytes.Replace(datum, []byte(TOMLDelim), []byte(""), -1) + ld := len(datum) + if ld < 8 { + return datum + } + + b := bytes.TrimPrefix(datum, []byte(TOMLDelim)) + if ld-len(b) != 3 { + // No TOML prefix trimmed, so bail out + return datum + } + + b = bytes.Trim(b, "\r\n") + return bytes.TrimSuffix(b, []byte(TOMLDelim)) } +// HandleYAMLMetaData unmarshals YAML-encoded datum and returns a Go interface +// representing the encoded data structure. func HandleYAMLMetaData(datum []byte) (interface{}, error) { m := map[string]interface{}{} err := yaml.Unmarshal(datum, &m) return m, err } +// HandleJSONMetaData unmarshals JSON-encoded datum and returns a Go interface +// representing the encoded data structure. func HandleJSONMetaData(datum []byte) (interface{}, error) { + if datum == nil { + // Package json returns on error on nil input. + // Return an empty map to be consistent with our other supported + // formats. + return make(map[string]interface{}), nil + } + var f interface{} err := json.Unmarshal(datum, &f) return f, err } +// HandleOrgMetaData unmarshals org-mode encoded datum and returns a Go +// interface representing the encoded data structure. func HandleOrgMetaData(datum []byte) (interface{}, error) { return goorgeous.OrgHeaders(datum) } |