diff --git a/middleware/markdown/metadata.go b/middleware/markdown/metadata.go index 38660586..3e3e48be 100644 --- a/middleware/markdown/metadata.go +++ b/middleware/markdown/metadata.go @@ -1,7 +1,10 @@ package markdown import ( + "bufio" + "bytes" "encoding/json" + "fmt" "github.com/BurntSushi/toml" "gopkg.in/yaml.v2" @@ -79,12 +82,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 @@ -148,3 +151,65 @@ func (y *YAMLMetadataParser) Opening() []byte { func (y *YAMLMetadataParser) Closing() []byte { return []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) { + b = bytes.TrimSpace(b) + reader := bytes.NewBuffer(b) + scanner := bufio.NewScanner(reader) + var parser MetadataParser + + // Read first line + if scanner.Scan() { + line := scanner.Bytes() + parser = findParser(line) + // if no parser found, + // assume metadata not present + if parser == nil { + return metadata, b, nil + } + } + + // buffer for metadata contents + buf := bytes.Buffer{} + + // Read remaining lines until closing identifier is found + for scanner.Scan() { + line := scanner.Bytes() + + // 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 + }) + // scan the remaining bytes + scanner.Scan() + + return parser.Metadata(), 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())) +} + +// findParser finds the parser using line that contains opening identifier +func findParser(line []byte) MetadataParser { + line = bytes.TrimSpace(line) + for _, parser := range parsers { + if bytes.Equal(parser.Opening(), line) { + return parser + } + } + return nil +} diff --git a/middleware/markdown/metadata_test.go b/middleware/markdown/metadata_test.go new file mode 100644 index 00000000..987c89f8 --- /dev/null +++ b/middleware/markdown/metadata_test.go @@ -0,0 +1,166 @@ +package markdown + +import ( + "bytes" + "fmt" + "reflect" + "testing" +) + +var TOML = [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" + `, + `title = "A title" template = "default" [variables] name = "value"`, +} + +var YAML = [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 +`, + `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" + } +} +`, + ` + ::: +{{ + "title" : "A title", + "template" : "default", + "variables" : { + "name" : "value" + } +} +::: + `, +} + +func check(t *testing.T, err error) { + if err != nil { + t.Fatal(err) + } +} + +func TestParsers(t *testing.T) { + expected := Metadata{ + Title: "A title", + Template: "default", + Variables: map[string]interface{}{"name": "value"}, + } + compare := func(m Metadata) bool { + if m.Title != expected.Title { + return false + } + if m.Template != expected.Template { + return false + } + for k, v := range m.Variables { + if v != expected.Variables[k] { + return false + } + } + return true + } + + data := []struct { + parser MetadataParser + testData [4]string + name string + }{ + {&JSONMetadataParser{}, JSON, "json"}, + {&YAMLMetadataParser{}, YAML, "yaml"}, + {&TOMLMetadataParser{}, TOML, "toml"}, + } + + for _, v := range data { + // metadata without identifiers + err := v.parser.Parse([]byte(v.testData[0])) + 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) + } + + var line []byte + fmt.Fscanln(bytes.NewReader([]byte(v.testData[1])), &line) + if parser := findParser(line); parser == nil { + t.Fatalf("Parser must be found for %v", v.name) + } else { + if reflect.TypeOf(parser) != reflect.TypeOf(v.parser) { + t.Fatalf("parsers not equal. %v != %v", reflect.TypeOf(parser), reflect.TypeOf(v.parser)) + } + } + + // metadata without closing identifier + if _, _, err := extractMetadata([]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 { + t.Fatalf("Expected error for invalid metadata for %v", v.name) + } + } + +} diff --git a/middleware/markdown/process.go b/middleware/markdown/process.go index d775bf8b..5bf9badf 100644 --- a/middleware/markdown/process.go +++ b/middleware/markdown/process.go @@ -1,17 +1,16 @@ package markdown import ( - "bufio" "bytes" "fmt" "io/ioutil" + "log" + "os" "path/filepath" + "strings" "text/template" "github.com/russross/blackfriday" - "log" - "os" - "strings" ) const ( @@ -48,73 +47,11 @@ func (md Markdown) process(c Config, requestPath string, b []byte) ([]byte, erro markdown = blackfriday.Markdown(markdown, c.Renderer, 0) // set it as body for template - metadata.Variables["body"] = string(markdown) + metadata.Variables["markdown"] = string(markdown) return md.processTemplate(c, requestPath, tmpl, metadata) } -// 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) { - b = bytes.TrimSpace(b) - reader := bytes.NewBuffer(b) - scanner := bufio.NewScanner(reader) - var parser MetadataParser - - // Read first line - if scanner.Scan() { - line := scanner.Bytes() - parser = findParser(line) - // if no parser found, - // assume metadata not present - if parser == nil { - return metadata, b, nil - } - } - - // buffer for metadata contents - buf := bytes.Buffer{} - - // Read remaining lines until closing identifier is found - for scanner.Scan() { - line := scanner.Bytes() - - // 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 - }) - // scan the remaining bytes - scanner.Scan() - - return parser.Metadata(), 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())) -} - -// findParser finds the parser using line that contains opening identifier -func findParser(line []byte) MetadataParser { - line = bytes.TrimSpace(line) - for _, parser := range parsers { - if bytes.Equal(parser.Opening(), line) { - return parser - } - } - return nil -} - // processTemplate processes a template given a requestPath, // template (tmpl) and metadata func (md Markdown) processTemplate(c Config, requestPath string, tmpl []byte, metadata Metadata) ([]byte, error) {