Commit 0bfdb50a authored by Abiola Ibrahim's avatar Abiola Ibrahim

markdown: working version of template integration. Awaiting static site generation and tests.

parent 434ec7b6
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/markdown" "github.com/mholt/caddy/middleware/markdown"
"github.com/russross/blackfriday" "github.com/russross/blackfriday"
"path/filepath"
) )
// Markdown configures a new Markdown middleware instance. // Markdown configures a new Markdown middleware instance.
...@@ -33,7 +34,9 @@ func markdownParse(c *Controller) ([]markdown.Config, error) { ...@@ -33,7 +34,9 @@ func markdownParse(c *Controller) ([]markdown.Config, error) {
for c.Next() { for c.Next() {
md := markdown.Config{ 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 // Get the path scope
...@@ -61,6 +64,23 @@ func markdownParse(c *Controller) ([]markdown.Config, error) { ...@@ -61,6 +64,23 @@ func markdownParse(c *Controller) ([]markdown.Config, error) {
return mdconfigs, c.ArgErr() return mdconfigs, c.ArgErr()
} }
md.Scripts = append(md.Scripts, c.Val()) 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: default:
return mdconfigs, c.Err("Expected valid markdown configuration property") return mdconfigs, c.Err("Expected valid markdown configuration property")
} }
......
...@@ -3,11 +3,9 @@ ...@@ -3,11 +3,9 @@
package markdown package markdown
import ( import (
"bytes"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"path"
"strings" "strings"
"github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware"
...@@ -51,7 +49,10 @@ type Config struct { ...@@ -51,7 +49,10 @@ type Config struct {
Scripts []string Scripts []string
// Map of registered templates // Map of registered templates
Templates map[string] string Templates map[string]string
// Static files
StaticFiles map[string]string
} }
// ServeHTTP implements the http.Handler interface. // ServeHTTP implements the http.Handler interface.
...@@ -81,37 +82,11 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error ...@@ -81,37 +82,11 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
content := blackfriday.Markdown(body, m.Renderer, 0) html, err := Process(md, fpath, body)
if err != nil {
var scripts, styles string return http.StatusInternalServerError, err
for _, style := range m.Styles {
styles += strings.Replace(cssTemplate, "{{url}}", style, 1) + "\r\n"
}
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)) w.Write([]byte(html))
return http.StatusOK, nil return http.StatusOK, nil
...@@ -122,20 +97,3 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error ...@@ -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 // Didn't qualify to serve as markdown; pass-thru
return md.Next.ServeHTTP(w, r) return md.Next.ServeHTTP(w, r)
} }
const (
htmlTemplate = `<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
<meta charset="utf-8">
{{css}}
{{js}}
</head>
<body>
{{body}}
</body>
</html>`
cssTemplate = `<link rel="stylesheet" href="{{url}}">`
jsTemplate = `<script src="{{url}}"></script>`
)
...@@ -17,12 +17,16 @@ var ( ...@@ -17,12 +17,16 @@ var (
// Metadata stores a page's metadata // Metadata stores a page's metadata
type Metadata struct { type Metadata struct {
Title string
Template string Template string
Variables map[string]interface{} Variables map[string]interface{}
} }
// Load loads parsed metadata into m // Load loads parsed metadata into m
func (m *Metadata) load(parsedMap map[string]interface{}) { func (m *Metadata) load(parsedMap map[string]interface{}) {
if template, ok := parsedMap["title"]; ok {
m.Title, _ = template.(string)
}
if template, ok := parsedMap["template"]; ok { if template, ok := parsedMap["template"]; ok {
m.Template, _ = template.(string) m.Template, _ = template.(string)
} }
...@@ -37,6 +41,7 @@ type MetadataParser interface { ...@@ -37,6 +41,7 @@ type MetadataParser interface {
// Identifiers // Identifiers
Opening() []byte Opening() []byte
Closing() []byte Closing() []byte
Parse([]byte) error Parse([]byte) error
Metadata() Metadata Metadata() Metadata
} }
...@@ -121,12 +126,12 @@ func (y *YAMLMetadataParser) Metadata() Metadata { ...@@ -121,12 +126,12 @@ func (y *YAMLMetadataParser) Metadata() Metadata {
return y.metadata return y.metadata
} }
// Opening returns the opening identifier TOML metadata // Opening returns the opening identifier YAML metadata
func (y *YAMLMetadataParser) Opening() []byte { func (y *YAMLMetadataParser) Opening() []byte {
return []byte("---") return []byte("---")
} }
// Closing returns the closing identifier TOML metadata // Closing returns the closing identifier YAML metadata
func (y *YAMLMetadataParser) Closing() []byte { func (y *YAMLMetadataParser) Closing() []byte {
return []byte("---") return []byte("---")
} }
...@@ -5,41 +5,44 @@ import ( ...@@ -5,41 +5,44 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"path/filepath"
"text/template" "text/template"
"github.com/russross/blackfriday"
"strings"
) )
// Process the contents of a page. // Process the contents of a page.
// It parses the metadata if any and uses the template if found // 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) metadata, markdown, err := extractMetadata(b)
if err != nil { if err != nil {
return nil, err 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 var tmpl []byte
if metadata.Template != "" { if metadata.Template != "" {
if t, ok := c.Templates[metadata.Template]; ok { if t, ok := c.Templates[metadata.Template]; ok {
tmpl, err = loadTemplate(t) tmpl, err = ioutil.ReadFile(t)
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
// if no template loaded
// use default template
if tmpl == nil {
tmpl = []byte(htmlTemplate)
}
// process markdown // process markdown
if markdown, err = processMarkdown(markdown, metadata.Variables); err != nil { markdown = blackfriday.Markdown(markdown, c.Renderer, 0)
return nil, err // 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. // extractMetadata extracts metadata content from a page.
...@@ -70,12 +73,22 @@ func extractMetadata(b []byte) (metadata Metadata, markdown []byte, err error) { ...@@ -70,12 +73,22 @@ func extractMetadata(b []byte) (metadata Metadata, markdown []byte, err error) {
line := scanner.Bytes() line := scanner.Bytes()
// closing identifier found // closing identifier found
if bytes.Equal(bytes.TrimSpace(line), parser.Closing()) { 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 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.Write(line)
buf.WriteString("\r\n")
} }
return metadata, nil, fmt.Errorf("Metadata not closed. '%v' not found", string(parser.Closing())) return metadata, nil, fmt.Errorf("Metadata not closed. '%v' not found", string(parser.Closing()))
} }
...@@ -91,25 +104,71 @@ func findParser(line []byte) MetadataParser { ...@@ -91,25 +104,71 @@ func findParser(line []byte) MetadataParser {
return nil return nil
} }
func loadTemplate(tmpl string) ([]byte, error) { func processTemplate(c Config, fpath string, tmpl []byte, metadata Metadata) ([]byte, error) {
b, err := ioutil.ReadFile(tmpl) // 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 { if err != nil {
return nil, err return nil, err
} }
if !bytes.Contains(b, []byte("{{body}}")) { if err = t.Execute(b, metadata.Variables); err != nil {
return nil, fmt.Errorf("template missing {{body}}") return nil, err
} }
return b, nil return b.Bytes(), nil
} }
func processMarkdown(b []byte, vars map[string]interface{}) ([]byte, error) { func defaultTemplate(c Config, metadata Metadata, fpath string) []byte {
buf := &bytes.Buffer{} // else, use default template
t, err := template.New("markdown").Parse(string(b)) var scripts, styles bytes.Buffer
if err != nil { for _, style := range c.Styles {
return nil, err styles.WriteString(strings.Replace(cssTemplate, "{{url}}", style, 1))
styles.WriteString("\r\n")
} }
if err := t.Execute(buf, vars); err != nil { for _, script := range c.Scripts {
return nil, err scripts.WriteString(strings.Replace(jsTemplate, "{{url}}", script, 1))
scripts.WriteString("\r\n")
}
// 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)
}
} }
return buf.Bytes(), nil
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 = `<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
<meta charset="utf-8">
{{css}}
{{js}}
</head>
<body>
{{.body}}
</body>
</html>`
cssTemplate = `<link rel="stylesheet" href="{{url}}">`
jsTemplate = `<script src="{{url}}"></script>`
DefaultTemplate = "defaultTemplate"
)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment