summaryrefslogtreecommitdiffhomepage
path: root/middleware/markdown/markdown.go
blob: 80ca45b124bacbcabe1f8eb8544ebede1148153a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// Package markdown is middleware to render markdown files as HTML
// on-the-fly.
package markdown

import (
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
	"sync"

	"github.com/mholt/caddy/middleware"
	"github.com/russross/blackfriday"
)

// Markdown implements a layer of middleware that serves
// markdown as HTML.
type Markdown struct {
	// Server root
	Root string

	// Jail the requests to site root with a mock file system
	FileSys http.FileSystem

	// Next HTTP handler in the chain
	Next middleware.Handler

	// The list of markdown configurations
	Configs []Config

	// The list of index files to try
	IndexFiles []string
}

// IsIndexFile checks to see if a file is an index file
func (md Markdown) IsIndexFile(file string) bool {
	for _, f := range md.IndexFiles {
		if f == file {
			return true
		}
	}
	return false
}

// Config stores markdown middleware configurations.
type Config struct {
	// Markdown renderer
	Renderer blackfriday.Renderer

	// Base path to match
	PathScope string

	// List of extensions to consider as markdown files
	Extensions []string

	// List of style sheets to load for each markdown file
	Styles []string

	// List of JavaScript files to load for each markdown file
	Scripts []string

	// Map of registered templates
	Templates map[string]string

	// Map of request URL to static files generated
	StaticFiles map[string]string

	// Links to all markdown pages ordered by date.
	Links []PageLink

	// Stores a directory hash to check for changes.
	linksHash string

	// Directory to store static files
	StaticDir string

	// If in development mode. i.e. Actively editing markdown files.
	Development bool

	sync.RWMutex
}

// IsValidExt checks to see if an extension is a valid markdown extension
// for config.
func (c Config) IsValidExt(ext string) bool {
	for _, e := range c.Extensions {
		if e == ext {
			return true
		}
	}
	return false
}

// ServeHTTP implements the http.Handler interface.
func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	for i := range md.Configs {
		m := &md.Configs[i]
		if !middleware.Path(r.URL.Path).Matches(m.PathScope) {
			continue
		}

		fpath := r.URL.Path
		if idx, ok := middleware.IndexFile(md.FileSys, fpath, md.IndexFiles); ok {
			fpath = idx
		}

		for _, ext := range m.Extensions {
			if strings.HasSuffix(fpath, ext) {
				f, err := md.FileSys.Open(fpath)
				if err != nil {
					if os.IsPermission(err) {
						return http.StatusForbidden, err
					}
					return http.StatusNotFound, nil
				}

				fs, err := f.Stat()
				if err != nil {
					return http.StatusNotFound, nil
				}

				// if development is set, scan directory for file changes for links.
				if m.Development {
					if err := GenerateStatic(md, m); err != nil {
						log.Println(err)
					}
				}

				// if static site is generated, attempt to use it
				if filepath, ok := m.StaticFiles[fpath]; ok {
					if fs1, err := os.Stat(filepath); err == nil {
						// if markdown has not been modified
						// since static page generation,
						// serve the static page
						if fs.ModTime().UnixNano() < fs1.ModTime().UnixNano() {
							if html, err := ioutil.ReadFile(filepath); err == nil {
								w.Write(html)
								return http.StatusOK, nil
							}
							if os.IsPermission(err) {
								return http.StatusForbidden, err
							}
							return http.StatusNotFound, nil
						}
					}
				}

				body, err := ioutil.ReadAll(f)
				if err != nil {
					return http.StatusInternalServerError, err
				}

				ctx := middleware.Context{
					Root: md.FileSys,
					Req:  r,
					URL:  r.URL,
				}
				html, err := md.Process(*m, fpath, body, ctx)
				if err != nil {
					return http.StatusInternalServerError, err
				}

				w.Write(html)
				return http.StatusOK, nil
			}
		}
	}

	// Didn't qualify to serve as markdown; pass-thru
	return md.Next.ServeHTTP(w, r)
}