diff --git a/middleware/markdown/metadata.go b/middleware/markdown/metadata.go index 8734663a..11b7f32a 100644 --- a/middleware/markdown/metadata.go +++ b/middleware/markdown/metadata.go @@ -51,8 +51,10 @@ type MetadataParser interface { // Closing identifier Closing() []byte - // Parse the metadata - Parse([]byte) error + // 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 @@ -65,13 +67,25 @@ type JSONMetadataParser struct { } // Parse the metadata -func (j *JSONMetadataParser) Parse(b []byte) error { +func (j *JSONMetadataParser) Parse(b []byte) ([]byte, error) { m := make(map[string]interface{}) - if err := json.Unmarshal(b, &m); err != nil { - return err + + // Read the preceding JSON object + decoder := json.NewDecoder(bytes.NewReader(b)) + if err := decoder.Decode(&m); err != nil { + return b, err } + j.metadata.load(m) - return nil + + // Retrieve remaining bytes after decoding + buf := make([]byte, len(b)) + n, err := decoder.Buffered().Read(buf) + if err != nil { + return b, err + } + + return buf[:n], nil } // Parsed metadata. @@ -82,12 +96,12 @@ func (j *JSONMetadataParser) Metadata() Metadata { // Opening returns the opening identifier JSON metadata func (j *JSONMetadataParser) Opening() []byte { - return []byte(":::") + return []byte("{") } // Closing returns the closing identifier JSON metadata func (j *JSONMetadataParser) Closing() []byte { - return []byte(":::") + return []byte("}") } // TOMLMetadataParser is the MetadataParser for TOML @@ -96,13 +110,17 @@ type TOMLMetadataParser struct { } // Parse the metadata -func (t *TOMLMetadataParser) Parse(b []byte) error { +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 err + return markdown, err } t.metadata.load(m) - return nil + return markdown, nil } // Parsed metadata. @@ -127,13 +145,17 @@ type YAMLMetadataParser struct { } // Parse the metadata -func (y *YAMLMetadataParser) Parse(b []byte) error { +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 err + return markdown, err } y.metadata.load(m) - return nil + return markdown, nil } // Parsed metadata. @@ -154,26 +176,23 @@ func (y *YAMLMetadataParser) Closing() []byte { // extractMetadata extracts metadata content from a page. // it returns the metadata, the remaining bytes (markdown), -// and an error if any -func extractMetadata(b []byte) (metadata Metadata, markdown []byte, err error) { +// and an error if any. +// Useful for MetadataParser with defined identifiers (YAML, TOML) +func extractMetadata(parser MetadataParser, b []byte) (metadata []byte, markdown []byte, err error) { b = bytes.TrimSpace(b) reader := bytes.NewBuffer(b) scanner := bufio.NewScanner(reader) - var parser MetadataParser // Read first line if !scanner.Scan() { // if no line is read, // assume metadata not present - return metadata, b, nil + return nil, b, nil } - line := scanner.Bytes() - parser = findParser(line) - // if no parser found, - // assume metadata not present - if parser == nil { - return metadata, b, nil + line := bytes.TrimSpace(scanner.Bytes()) + if !bytes.Equal(line, parser.Opening()) { + return nil, b, fmt.Errorf("Wrong identifier") } // buffer for metadata contents @@ -185,11 +204,7 @@ func extractMetadata(b []byte) (metadata Metadata, markdown []byte, err error) { // if closing identifier found if bytes.Equal(bytes.TrimSpace(line), parser.Closing()) { - // parse the metadata - err := parser.Parse(buf.Bytes()) - if err != nil { - return metadata, nil, err - } + // get the scanner to return remaining bytes scanner.Split(func(data []byte, atEOF bool) (int, []byte, error) { return len(data), data, nil @@ -197,18 +212,23 @@ func extractMetadata(b []byte) (metadata Metadata, markdown []byte, err error) { // scan the remaining bytes scanner.Scan() - return parser.Metadata(), scanner.Bytes(), nil + return buf.Bytes(), scanner.Bytes(), nil } buf.Write(line) buf.WriteString("\r\n") } // closing identifier not found - return metadata, nil, fmt.Errorf("Metadata not closed. '%v' not found", string(parser.Closing())) + return buf.Bytes(), nil, fmt.Errorf("Metadata not closed. '%v' not found", string(parser.Closing())) } // findParser finds the parser using line that contains opening identifier -func findParser(line []byte) MetadataParser { +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) { diff --git a/middleware/markdown/metadata_test.go b/middleware/markdown/metadata_test.go index 987c89f8..68659bf4 100644 --- a/middleware/markdown/metadata_test.go +++ b/middleware/markdown/metadata_test.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "reflect" + "strings" "testing" ) @@ -19,6 +20,7 @@ template = "default" [variables] name = "value" +++ +Page content `, `+++ title = "A title" @@ -41,6 +43,7 @@ template : default variables : - name : value --- +Page content `, `--- title : A title @@ -51,34 +54,30 @@ variables : `title : A title template : default variables : name : value`, } var JSON = [4]string{` -{ "title" : "A title", "template" : "default", "variables" : { "name" : "value" } -} `, - `::: -{ - "title" : "A title", - "template" : "default", - "variables" : { - "name" : "value" - } -} -:::`, - `::: -{ + `{ "title" : "A title", "template" : "default", "variables" : { "name" : "value" } } +Page content +`, + ` +{ + "title" : "A title", + "template" : "default", + "variables" : { + "name" : "value" + } `, ` - ::: {{ "title" : "A title", "template" : "default", @@ -86,7 +85,6 @@ var JSON = [4]string{` "name" : "value" } } -::: `, } @@ -129,17 +127,18 @@ func TestParsers(t *testing.T) { for _, v := range data { // metadata without identifiers - err := v.parser.Parse([]byte(v.testData[0])) + if _, err := v.parser.Parse([]byte(v.testData[0])); err == nil { + t.Fatalf("Expected error for invalid metadata for %v", v.name) + } + + // metadata with identifiers + md, err := v.parser.Parse([]byte(v.testData[1])) check(t, err) if !compare(v.parser.Metadata()) { t.Fatalf("Expected %v, found %v for %v", expected, v.parser.Metadata().Variables, v.name) } - - // metadata with identifiers - metadata, _, err := extractMetadata([]byte(v.testData[1])) - check(t, err) - if !compare(metadata) { - t.Fatalf("Expected %v, found %v for %v", expected, metadata.Variables, v.name) + if "Page content" != strings.TrimSpace(string(md)) { + t.Fatalf("Expected %v, found %v for %v", "Page content", string(md), v.name) } var line []byte @@ -153,12 +152,12 @@ func TestParsers(t *testing.T) { } // metadata without closing identifier - if _, _, err := extractMetadata([]byte(v.testData[2])); err == nil { + if _, err := v.parser.Parse([]byte(v.testData[2])); err == nil { t.Fatalf("Expected error for missing closing identifier for %v", v.name) } // invalid metadata - if err := v.parser.Parse([]byte(v.testData[3])); err == nil { + if md, err = v.parser.Parse([]byte(v.testData[3])); err == nil { t.Fatalf("Expected error for invalid metadata for %v", v.name) } } diff --git a/middleware/markdown/process.go b/middleware/markdown/process.go index 9741bd17..ffcd456d 100644 --- a/middleware/markdown/process.go +++ b/middleware/markdown/process.go @@ -21,10 +21,22 @@ const ( // process processes the contents of a page. // It parses the metadata (if any) and uses the template (if found) func (md Markdown) process(c Config, requestPath string, b []byte) ([]byte, error) { - metadata, markdown, err := extractMetadata(b) - if err != nil { - return nil, err + var metadata = Metadata{} + var markdown []byte + var err error + + // find parser compatible with page contents + parser := findParser(b) + + // if found, assume metadata present and parse. + if parser != nil { + markdown, err = parser.Parse(b) + if err != nil { + return nil, err + } + metadata = parser.Metadata() } + // if template is not specified, check if Default template is set if metadata.Template == "" { if _, ok := c.Templates[DefaultTemplate]; ok {