mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-25 17:56:34 +08:00
fbc18c5b85
We can't use json meta parser's remaining buffered data as the markdown body because it may not contain the entire original content. Now we adopt the way like toml and yaml parser's way to extract the meta content at first. Also when spilting the meta data and content body, additional io.Copy is unnecessary. Fix issue #355 Signed-off-by: Tw <tw19881113@gmail.com>
230 lines
5.7 KiB
Go
230 lines
5.7 KiB
Go
package markdown
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
"gopkg.in/yaml.v2"
|
|
"time"
|
|
)
|
|
|
|
// Metadata stores a page's metadata
|
|
type Metadata struct {
|
|
// Page title
|
|
Title string
|
|
|
|
// Page template
|
|
Template string
|
|
|
|
// Publish date
|
|
Date time.Time
|
|
|
|
// Variables to be used with Template
|
|
Variables map[string]string
|
|
}
|
|
|
|
// load loads parsed values in parsedMap into Metadata
|
|
func (m *Metadata) load(parsedMap map[string]interface{}) {
|
|
if title, ok := parsedMap["title"]; ok {
|
|
m.Title, _ = title.(string)
|
|
}
|
|
if template, ok := parsedMap["template"]; ok {
|
|
m.Template, _ = template.(string)
|
|
}
|
|
if date, ok := parsedMap["date"].(string); ok {
|
|
if t, err := time.Parse(timeLayout, date); err == nil {
|
|
m.Date = t
|
|
}
|
|
}
|
|
// store everything as a variable
|
|
for key, val := range parsedMap {
|
|
if v, ok := val.(string); ok {
|
|
m.Variables[key] = v
|
|
}
|
|
}
|
|
}
|
|
|
|
// MetadataParser is a an interface that must be satisfied by each parser
|
|
type MetadataParser interface {
|
|
// Opening identifier
|
|
Opening() []byte
|
|
|
|
// Closing identifier
|
|
Closing() []byte
|
|
|
|
// Parse the metadata.
|
|
// Returns the remaining page contents (Markdown)
|
|
// after extracting metadata
|
|
Parse([]byte) ([]byte, error)
|
|
|
|
// Parsed metadata.
|
|
// Should be called after a call to Parse returns no error
|
|
Metadata() Metadata
|
|
}
|
|
|
|
// JSONMetadataParser is the MetadataParser for JSON
|
|
type JSONMetadataParser struct {
|
|
metadata Metadata
|
|
}
|
|
|
|
// Parse the metadata
|
|
func (j *JSONMetadataParser) Parse(b []byte) ([]byte, error) {
|
|
b, markdown, err := extractMetadata(j, b)
|
|
if err != nil {
|
|
return markdown, err
|
|
}
|
|
m := make(map[string]interface{})
|
|
|
|
// Read the preceding JSON object
|
|
decoder := json.NewDecoder(bytes.NewReader(b))
|
|
if err := decoder.Decode(&m); err != nil {
|
|
return markdown, err
|
|
}
|
|
j.metadata.load(m)
|
|
|
|
return markdown, nil
|
|
}
|
|
|
|
// Metadata returns parsed metadata. It should be called
|
|
// only after a call to Parse returns without error.
|
|
func (j *JSONMetadataParser) Metadata() Metadata {
|
|
return j.metadata
|
|
}
|
|
|
|
// Opening returns the opening identifier JSON metadata
|
|
func (j *JSONMetadataParser) Opening() []byte {
|
|
return []byte("{")
|
|
}
|
|
|
|
// Closing returns the closing identifier JSON metadata
|
|
func (j *JSONMetadataParser) Closing() []byte {
|
|
return []byte("}")
|
|
}
|
|
|
|
// TOMLMetadataParser is the MetadataParser for TOML
|
|
type TOMLMetadataParser struct {
|
|
metadata Metadata
|
|
}
|
|
|
|
// Parse the metadata
|
|
func (t *TOMLMetadataParser) Parse(b []byte) ([]byte, error) {
|
|
b, markdown, err := extractMetadata(t, b)
|
|
if err != nil {
|
|
return markdown, err
|
|
}
|
|
m := make(map[string]interface{})
|
|
if err := toml.Unmarshal(b, &m); err != nil {
|
|
return markdown, err
|
|
}
|
|
t.metadata.load(m)
|
|
return markdown, nil
|
|
}
|
|
|
|
// Metadata returns parsed metadata. It should be called
|
|
// only after a call to Parse returns without error.
|
|
func (t *TOMLMetadataParser) Metadata() Metadata {
|
|
return t.metadata
|
|
}
|
|
|
|
// Opening returns the opening identifier TOML metadata
|
|
func (t *TOMLMetadataParser) Opening() []byte {
|
|
return []byte("+++")
|
|
}
|
|
|
|
// Closing returns the closing identifier TOML metadata
|
|
func (t *TOMLMetadataParser) Closing() []byte {
|
|
return []byte("+++")
|
|
}
|
|
|
|
// YAMLMetadataParser is the MetadataParser for YAML
|
|
type YAMLMetadataParser struct {
|
|
metadata Metadata
|
|
}
|
|
|
|
// Parse the metadata
|
|
func (y *YAMLMetadataParser) Parse(b []byte) ([]byte, error) {
|
|
b, markdown, err := extractMetadata(y, b)
|
|
if err != nil {
|
|
return markdown, err
|
|
}
|
|
|
|
m := make(map[string]interface{})
|
|
if err := yaml.Unmarshal(b, &m); err != nil {
|
|
return markdown, err
|
|
}
|
|
y.metadata.load(m)
|
|
return markdown, nil
|
|
}
|
|
|
|
// Metadata returns parsed metadata. It should be called
|
|
// only after a call to Parse returns without error.
|
|
func (y *YAMLMetadataParser) Metadata() Metadata {
|
|
return y.metadata
|
|
}
|
|
|
|
// Opening returns the opening identifier YAML metadata
|
|
func (y *YAMLMetadataParser) Opening() []byte {
|
|
return []byte("---")
|
|
}
|
|
|
|
// Closing returns the closing identifier YAML metadata
|
|
func (y *YAMLMetadataParser) Closing() []byte {
|
|
return []byte("---")
|
|
}
|
|
|
|
// extractMetadata separates metadata content from from markdown content in b.
|
|
// It returns the metadata, the remaining bytes (markdown), and an error, if any.
|
|
func extractMetadata(parser MetadataParser, b []byte) (metadata []byte, markdown []byte, err error) {
|
|
b = bytes.TrimSpace(b)
|
|
openingLine := append(parser.Opening(), '\n')
|
|
closingLine := append(parser.Closing(), '\n')
|
|
if !bytes.HasPrefix(b, openingLine) {
|
|
return nil, b, fmt.Errorf("first line missing expected metadata identifier")
|
|
}
|
|
metaStart := len(openingLine)
|
|
if _, ok := parser.(*JSONMetadataParser); ok {
|
|
metaStart = 0
|
|
}
|
|
metaEnd := bytes.Index(b[metaStart:], closingLine)
|
|
if metaEnd == -1 {
|
|
return nil, nil, fmt.Errorf("metadata not closed ('%s' not found)", parser.Closing())
|
|
}
|
|
metaEnd += metaStart
|
|
if _, ok := parser.(*JSONMetadataParser); ok {
|
|
metaEnd += len(closingLine)
|
|
}
|
|
metadata = b[metaStart:metaEnd]
|
|
markdown = b[metaEnd:]
|
|
if _, ok := parser.(*JSONMetadataParser); !ok {
|
|
markdown = b[metaEnd+len(closingLine):]
|
|
}
|
|
return metadata, markdown, nil
|
|
}
|
|
|
|
// findParser finds the parser using line that contains opening identifier
|
|
func findParser(b []byte) MetadataParser {
|
|
var line []byte
|
|
// Read first line
|
|
if _, err := fmt.Fscanln(bytes.NewReader(b), &line); err != nil {
|
|
return nil
|
|
}
|
|
line = bytes.TrimSpace(line)
|
|
for _, parser := range parsers() {
|
|
if bytes.Equal(parser.Opening(), line) {
|
|
return parser
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// parsers returns all available parsers
|
|
func parsers() []MetadataParser {
|
|
return []MetadataParser{
|
|
&JSONMetadataParser{metadata: Metadata{Variables: make(map[string]string)}},
|
|
&TOMLMetadataParser{metadata: Metadata{Variables: make(map[string]string)}},
|
|
&YAMLMetadataParser{metadata: Metadata{Variables: make(map[string]string)}},
|
|
}
|
|
}
|