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)
}
|