mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-08 11:58:49 +03:00
markdown: Added template support.
This commit is contained in:
parent
0fccd3707d
commit
48a12c605a
3 changed files with 237 additions and 69 deletions
|
@ -1,7 +1,10 @@
|
||||||
package markdown
|
package markdown
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
@ -79,12 +82,12 @@ func (j *JSONMetadataParser) Metadata() Metadata {
|
||||||
|
|
||||||
// Opening returns the opening identifier JSON metadata
|
// Opening returns the opening identifier JSON metadata
|
||||||
func (j *JSONMetadataParser) Opening() []byte {
|
func (j *JSONMetadataParser) Opening() []byte {
|
||||||
return []byte("{")
|
return []byte(":::")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Closing returns the closing identifier JSON metadata
|
// Closing returns the closing identifier JSON metadata
|
||||||
func (j *JSONMetadataParser) Closing() []byte {
|
func (j *JSONMetadataParser) Closing() []byte {
|
||||||
return []byte("}")
|
return []byte(":::")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TOMLMetadataParser is the MetadataParser for TOML
|
// TOMLMetadataParser is the MetadataParser for TOML
|
||||||
|
@ -148,3 +151,65 @@ func (y *YAMLMetadataParser) Opening() []byte {
|
||||||
func (y *YAMLMetadataParser) Closing() []byte {
|
func (y *YAMLMetadataParser) Closing() []byte {
|
||||||
return []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
|
||||||
|
}
|
||||||
|
|
166
middleware/markdown/metadata_test.go
Normal file
166
middleware/markdown/metadata_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,17 +1,16 @@
|
||||||
package markdown
|
package markdown
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/russross/blackfriday"
|
"github.com/russross/blackfriday"
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -48,73 +47,11 @@ func (md Markdown) process(c Config, requestPath string, b []byte) ([]byte, erro
|
||||||
markdown = blackfriday.Markdown(markdown, c.Renderer, 0)
|
markdown = blackfriday.Markdown(markdown, c.Renderer, 0)
|
||||||
|
|
||||||
// set it as body for template
|
// set it as body for template
|
||||||
metadata.Variables["body"] = string(markdown)
|
metadata.Variables["markdown"] = string(markdown)
|
||||||
|
|
||||||
return md.processTemplate(c, requestPath, tmpl, metadata)
|
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,
|
// processTemplate processes a template given a requestPath,
|
||||||
// template (tmpl) and metadata
|
// template (tmpl) and metadata
|
||||||
func (md Markdown) processTemplate(c Config, requestPath string, tmpl []byte, metadata Metadata) ([]byte, error) {
|
func (md Markdown) processTemplate(c Config, requestPath string, tmpl []byte, metadata Metadata) ([]byte, error) {
|
||||||
|
|
Loading…
Reference in a new issue