diff --git a/config/setup/markdown.go b/config/setup/markdown.go
index 3f9b967a..8e296ef4 100644
--- a/config/setup/markdown.go
+++ b/config/setup/markdown.go
@@ -6,6 +6,7 @@ import (
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/markdown"
"github.com/russross/blackfriday"
+ "path/filepath"
)
// Markdown configures a new Markdown middleware instance.
@@ -33,7 +34,9 @@ func markdownParse(c *Controller) ([]markdown.Config, error) {
for c.Next() {
md := markdown.Config{
- Renderer: blackfriday.HtmlRenderer(0, "", ""),
+ Renderer: blackfriday.HtmlRenderer(0, "", ""),
+ Templates: make(map[string]string),
+ StaticFiles: make(map[string]string),
}
// Get the path scope
@@ -61,6 +64,23 @@ func markdownParse(c *Controller) ([]markdown.Config, error) {
return mdconfigs, c.ArgErr()
}
md.Scripts = append(md.Scripts, c.Val())
+ case "template":
+ tArgs := c.RemainingArgs()
+ switch len(tArgs) {
+ case 0:
+ return mdconfigs, c.ArgErr()
+ case 1:
+ if _, ok := md.Templates[markdown.DefaultTemplate]; ok {
+ return mdconfigs, c.Err("only one default template is allowed, use alias.")
+ }
+ fpath := filepath.Clean(c.Root + string(filepath.Separator) + tArgs[0])
+ md.Templates[markdown.DefaultTemplate] = fpath
+ case 2:
+ fpath := filepath.Clean(c.Root + string(filepath.Separator) + tArgs[1])
+ md.Templates[tArgs[0]] = fpath
+ default:
+ return mdconfigs, c.ArgErr()
+ }
default:
return mdconfigs, c.Err("Expected valid markdown configuration property")
}
diff --git a/middleware/markdown/markdown.go b/middleware/markdown/markdown.go
index 5c34cfc2..f4eab69e 100644
--- a/middleware/markdown/markdown.go
+++ b/middleware/markdown/markdown.go
@@ -3,11 +3,9 @@
package markdown
import (
- "bytes"
"io/ioutil"
"net/http"
"os"
- "path"
"strings"
"github.com/mholt/caddy/middleware"
@@ -51,7 +49,10 @@ type Config struct {
Scripts []string
// Map of registered templates
- Templates map[string] string
+ Templates map[string]string
+
+ // Static files
+ StaticFiles map[string]string
}
// ServeHTTP implements the http.Handler interface.
@@ -81,36 +82,10 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
return http.StatusInternalServerError, err
}
- content := blackfriday.Markdown(body, m.Renderer, 0)
-
- var scripts, styles string
- for _, style := range m.Styles {
- styles += strings.Replace(cssTemplate, "{{url}}", style, 1) + "\r\n"
+ html, err := Process(md, fpath, body)
+ if err != nil {
+ return http.StatusInternalServerError, err
}
- for _, script := range m.Scripts {
- scripts += strings.Replace(jsTemplate, "{{url}}", script, 1) + "\r\n"
- }
-
- // Title is first line (length-limited), otherwise filename
- title := path.Base(fpath)
- newline := bytes.Index(body, []byte("\n"))
- if newline > -1 {
- firstline := body[:newline]
- newTitle := strings.TrimSpace(string(firstline))
- if len(newTitle) > 1 {
- if len(newTitle) > 128 {
- title = newTitle[:128]
- } else {
- title = newTitle
- }
- }
- }
-
- html := htmlTemplate
- html = strings.Replace(html, "{{title}}", title, 1)
- html = strings.Replace(html, "{{css}}", styles, 1)
- html = strings.Replace(html, "{{js}}", scripts, 1)
- html = strings.Replace(html, "{{body}}", string(content), 1)
w.Write([]byte(html))
@@ -122,20 +97,3 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
// Didn't qualify to serve as markdown; pass-thru
return md.Next.ServeHTTP(w, r)
}
-
-const (
- htmlTemplate = `
-
-
- {{title}}
-
- {{css}}
- {{js}}
-
-
- {{body}}
-
-`
- cssTemplate = ``
- jsTemplate = ``
-)
diff --git a/middleware/markdown/metadata.go b/middleware/markdown/metadata.go
index 83919fb4..bdaf6c06 100644
--- a/middleware/markdown/metadata.go
+++ b/middleware/markdown/metadata.go
@@ -17,12 +17,16 @@ var (
// Metadata stores a page's metadata
type Metadata struct {
+ Title string
Template string
Variables map[string]interface{}
}
// Load loads parsed metadata into m
func (m *Metadata) load(parsedMap map[string]interface{}) {
+ if template, ok := parsedMap["title"]; ok {
+ m.Title, _ = template.(string)
+ }
if template, ok := parsedMap["template"]; ok {
m.Template, _ = template.(string)
}
@@ -37,6 +41,7 @@ type MetadataParser interface {
// Identifiers
Opening() []byte
Closing() []byte
+
Parse([]byte) error
Metadata() Metadata
}
@@ -121,12 +126,12 @@ func (y *YAMLMetadataParser) Metadata() Metadata {
return y.metadata
}
-// Opening returns the opening identifier TOML metadata
+// Opening returns the opening identifier YAML metadata
func (y *YAMLMetadataParser) Opening() []byte {
return []byte("---")
}
-// Closing returns the closing identifier TOML metadata
+// Closing returns the closing identifier YAML metadata
func (y *YAMLMetadataParser) Closing() []byte {
return []byte("---")
}
diff --git a/middleware/markdown/process.go b/middleware/markdown/process.go
index 68db4188..207b8733 100644
--- a/middleware/markdown/process.go
+++ b/middleware/markdown/process.go
@@ -5,41 +5,44 @@ import (
"bytes"
"fmt"
"io/ioutil"
+ "path/filepath"
"text/template"
+
+ "github.com/russross/blackfriday"
+ "strings"
)
// Process the contents of a page.
// It parses the metadata if any and uses the template if found
-func Process(c Config, b []byte) ([]byte, error) {
+func Process(c Config, fpath string, b []byte) ([]byte, error) {
metadata, markdown, err := extractMetadata(b)
if err != nil {
return nil, err
}
- // if metadata template is included
+ // if template is not specified, check if Default template is set
+ if metadata.Template == "" {
+ if _, ok := c.Templates[DefaultTemplate]; ok {
+ metadata.Template = DefaultTemplate
+ }
+ }
+
+ // if template is set, load it
var tmpl []byte
if metadata.Template != "" {
if t, ok := c.Templates[metadata.Template]; ok {
- tmpl, err = loadTemplate(t)
+ tmpl, err = ioutil.ReadFile(t)
}
if err != nil {
return nil, err
}
}
- // if no template loaded
- // use default template
- if tmpl == nil {
- tmpl = []byte(htmlTemplate)
- }
-
// process markdown
- if markdown, err = processMarkdown(markdown, metadata.Variables); err != nil {
- return nil, err
- }
+ markdown = blackfriday.Markdown(markdown, c.Renderer, 0)
+ // set it as body for template
+ metadata.Variables["body"] = string(markdown)
- tmpl = bytes.Replace(tmpl, []byte("{{body}}"), markdown, 1)
-
- return tmpl, nil
+ return processTemplate(c, fpath, tmpl, metadata)
}
// extractMetadata extracts metadata content from a page.
@@ -70,12 +73,22 @@ func extractMetadata(b []byte) (metadata Metadata, markdown []byte, err error) {
line := scanner.Bytes()
// closing identifier found
if bytes.Equal(bytes.TrimSpace(line), parser.Closing()) {
- if err := parser.Parse(buf.Bytes()); err != nil {
+ // parse the metadata
+ err := parser.Parse(buf.Bytes())
+ if err != nil {
return metadata, nil, err
}
- return parser.Metadata(), reader.Bytes(), nil
+ // 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")
}
return metadata, nil, fmt.Errorf("Metadata not closed. '%v' not found", string(parser.Closing()))
}
@@ -91,25 +104,71 @@ func findParser(line []byte) MetadataParser {
return nil
}
-func loadTemplate(tmpl string) ([]byte, error) {
- b, err := ioutil.ReadFile(tmpl)
+func processTemplate(c Config, fpath string, tmpl []byte, metadata Metadata) ([]byte, error) {
+ // if template is specified
+ // replace parse the template
+ if tmpl != nil {
+ tmpl = defaultTemplate(c, metadata, fpath)
+ }
+
+ b := &bytes.Buffer{}
+ t, err := template.New("").Parse(string(tmpl))
if err != nil {
return nil, err
}
- if !bytes.Contains(b, []byte("{{body}}")) {
- return nil, fmt.Errorf("template missing {{body}}")
+ if err = t.Execute(b, metadata.Variables); err != nil {
+ return nil, err
}
- return b, nil
+ return b.Bytes(), nil
+
}
-func processMarkdown(b []byte, vars map[string]interface{}) ([]byte, error) {
- buf := &bytes.Buffer{}
- t, err := template.New("markdown").Parse(string(b))
- if err != nil {
- return nil, err
+func defaultTemplate(c Config, metadata Metadata, fpath string) []byte {
+ // else, use default template
+ var scripts, styles bytes.Buffer
+ for _, style := range c.Styles {
+ styles.WriteString(strings.Replace(cssTemplate, "{{url}}", style, 1))
+ styles.WriteString("\r\n")
}
- if err := t.Execute(buf, vars); err != nil {
- return nil, err
+ for _, script := range c.Scripts {
+ scripts.WriteString(strings.Replace(jsTemplate, "{{url}}", script, 1))
+ scripts.WriteString("\r\n")
}
- return buf.Bytes(), nil
+
+ // Title is first line (length-limited), otherwise filename
+ title := metadata.Title
+ if title == "" {
+ title = filepath.Base(fpath)
+ if body, _ := metadata.Variables["body"].([]byte); len(body) > 128 {
+ title = string(body[:128])
+ } else if len(body) > 0 {
+ title = string(body)
+ }
+ }
+
+ html := []byte(htmlTemplate)
+ html = bytes.Replace(html, []byte("{{title}}"), []byte(title), 1)
+ html = bytes.Replace(html, []byte("{{css}}"), styles.Bytes(), 1)
+ html = bytes.Replace(html, []byte("{{js}}"), scripts.Bytes(), 1)
+
+ return html
}
+
+const (
+ htmlTemplate = `
+
+
+ {{title}}
+
+ {{css}}
+ {{js}}
+
+
+ {{.body}}
+
+`
+ cssTemplate = ``
+ jsTemplate = ``
+
+ DefaultTemplate = "defaultTemplate"
+)