process.go 5.01 KB
Newer Older
1 2 3 4 5
package markdown

import (
	"bytes"
	"io/ioutil"
6 7
	"log"
	"os"
8
	"path/filepath"
9
	"strings"
10
	"text/template"
11

Maxime's avatar
Maxime committed
12
	"github.com/mholt/caddy/middleware"
13
	"github.com/russross/blackfriday"
14 15
)

16
const (
17 18
	DefaultTemplate  = "defaultTemplate"
	DefaultStaticDir = "generated_site"
19 20
)

Maxime's avatar
Maxime committed
21 22
type MarkdownData struct {
	middleware.Context
23 24
	Doc   map[string]string
	Links []PageLink
Maxime's avatar
Maxime committed
25 26
}

27 28
// Process processes the contents of a page in b. It parses the metadata
// (if any) and uses the template (if found).
29
func (md Markdown) Process(c *Config, requestPath string, b []byte, ctx middleware.Context) ([]byte, error) {
Maxime's avatar
Maxime committed
30
	var metadata = Metadata{Variables: make(map[string]string)}
31 32 33 34 35 36
	var markdown []byte
	var err error

	// find parser compatible with page contents
	parser := findParser(b)

37 38 39 40 41
	if parser == nil {
		// if not found, assume whole file is markdown (no front matter)
		markdown = b
	} else {
		// if found, assume metadata present and parse.
42 43 44 45 46
		markdown, err = parser.Parse(b)
		if err != nil {
			return nil, err
		}
		metadata = parser.Metadata()
47
	}
48

49 50 51 52 53 54 55 56
	// 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
57 58 59
	var tmpl []byte
	if metadata.Template != "" {
		if t, ok := c.Templates[metadata.Template]; ok {
60
			tmpl, err = ioutil.ReadFile(t)
61 62 63 64 65 66 67
		}
		if err != nil {
			return nil, err
		}
	}

	// process markdown
68
	markdown = blackfriday.Markdown(markdown, c.Renderer, 0)
69

70
	// set it as body for template
71
	metadata.Variables["body"] = string(markdown)
72 73 74 75 76 77 78
	title := metadata.Title
	if title == "" {
		title = filepath.Base(requestPath)
		var extension = filepath.Ext(requestPath)
		title = title[0 : len(title)-len(extension)]
	}
	metadata.Variables["title"] = title
79

Maxime's avatar
Maxime committed
80
	return md.processTemplate(c, requestPath, tmpl, metadata, ctx)
81 82
}

83 84
// processTemplate processes a template given a requestPath,
// template (tmpl) and metadata
85
func (md Markdown) processTemplate(c *Config, requestPath string, tmpl []byte, metadata Metadata, ctx middleware.Context) ([]byte, error) {
86 87 88 89
	// if template is not specified,
	// use the default template
	if tmpl == nil {
		tmpl = defaultTemplate(c, metadata, requestPath)
90 91
	}

92
	// process the template
93
	b := new(bytes.Buffer)
94
	t, err := template.New("").Parse(string(tmpl))
95 96 97
	if err != nil {
		return nil, err
	}
Maxime's avatar
Maxime committed
98
	mdData := MarkdownData{
99 100
		Context: ctx,
		Doc:     metadata.Variables,
101
		Links:   c.Links,
Maxime's avatar
Maxime committed
102 103
	}

Abiola Ibrahim's avatar
Abiola Ibrahim committed
104
	c.RLock()
105
	err = t.Execute(b, mdData)
Abiola Ibrahim's avatar
Abiola Ibrahim committed
106
	c.RUnlock()
107 108

	if err != nil {
109
		return nil, err
110
	}
111 112

	// generate static page
113
	if err = md.generatePage(c, requestPath, b.Bytes()); err != nil {
114 115
		// if static page generation fails,
		// nothing fatal, only log the error.
116
		// TODO: Report this non-fatal error, but don't log it here
117
		log.Println("Rendering error (markdown):", err)
118 119
	}

120 121
	return b.Bytes(), nil

122 123
}

124 125
// generatePage generates a static html page from the markdown in content if c.StaticDir
// is a non-empty value, meaning that the user enabled static site generation.
126
func (md Markdown) generatePage(c *Config, requestPath string, content []byte) error {
127 128 129 130 131 132 133 134
	// Only generate the page if static site generation is enabled
	if c.StaticDir != "" {
		// if static directory is not existing, create it
		if _, err := os.Stat(c.StaticDir); err != nil {
			err := os.MkdirAll(c.StaticDir, os.FileMode(0755))
			if err != nil {
				return err
			}
135 136
		}

137
		filePath := filepath.Join(c.StaticDir, requestPath)
138

139 140 141 142
		// If it is index file, use the directory instead
		if md.IsIndexFile(filepath.Base(requestPath)) {
			filePath, _ = filepath.Split(filePath)
		}
143

144 145 146 147
		// Create the directory in case it is not existing
		if err := os.MkdirAll(filePath, os.FileMode(0744)); err != nil {
			return err
		}
148

149 150 151 152 153 154 155
		// generate index.html file in the directory
		filePath = filepath.Join(filePath, "index.html")
		err := ioutil.WriteFile(filePath, content, os.FileMode(0664))
		if err != nil {
			return err
		}

156
		c.Lock()
157
		c.StaticFiles[requestPath] = filePath
158
		c.Unlock()
159 160 161 162 163
	}

	return nil
}

164
// defaultTemplate constructs a default template.
165
func defaultTemplate(c *Config, metadata Metadata, requestPath string) []byte {
166 167 168 169 170 171 172 173 174 175 176
	var scripts, styles bytes.Buffer
	for _, style := range c.Styles {
		styles.WriteString(strings.Replace(cssTemplate, "{{url}}", style, 1))
		styles.WriteString("\r\n")
	}
	for _, script := range c.Scripts {
		scripts.WriteString(strings.Replace(jsTemplate, "{{url}}", script, 1))
		scripts.WriteString("\r\n")
	}

	// Title is first line (length-limited), otherwise filename
Maxime's avatar
Maxime committed
177
	title, _ := metadata.Variables["title"]
178 179 180 181 182 183 184 185 186

	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
}

187 188 189 190 191 192 193 194 195 196
const (
	htmlTemplate = `<!DOCTYPE html>
<html>
	<head>
		<title>{{title}}</title>
		<meta charset="utf-8">
		{{css}}
		{{js}}
	</head>
	<body>
197
		{{.Doc.body}}
198 199 200 201 202
	</body>
</html>`
	cssTemplate = `<link rel="stylesheet" href="{{url}}">`
	jsTemplate  = `<script src="{{url}}"></script>`
)