2015-03-17 01:45:51 +08:00
|
|
|
// Package markdown is middleware to render markdown files as HTML
|
|
|
|
// on-the-fly.
|
|
|
|
package markdown
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io/ioutil"
|
2015-08-05 07:41:04 +08:00
|
|
|
"log"
|
2015-03-17 01:45:51 +08:00
|
|
|
"net/http"
|
2015-03-29 06:55:40 +08:00
|
|
|
"os"
|
2015-03-17 01:45:51 +08:00
|
|
|
"strings"
|
2015-07-29 19:15:02 +08:00
|
|
|
"sync"
|
2015-03-17 01:45:51 +08:00
|
|
|
|
|
|
|
"github.com/mholt/caddy/middleware"
|
|
|
|
"github.com/russross/blackfriday"
|
|
|
|
)
|
|
|
|
|
2015-03-22 00:59:29 +08:00
|
|
|
// Markdown implements a layer of middleware that serves
|
|
|
|
// markdown as HTML.
|
2015-03-17 01:45:51 +08:00
|
|
|
type Markdown struct {
|
|
|
|
// Server root
|
|
|
|
Root string
|
|
|
|
|
2015-05-06 05:50:42 +08:00
|
|
|
// Jail the requests to site root with a mock file system
|
|
|
|
FileSys http.FileSystem
|
|
|
|
|
2015-03-17 01:45:51 +08:00
|
|
|
// Next HTTP handler in the chain
|
2015-04-03 13:30:54 +08:00
|
|
|
Next middleware.Handler
|
2015-03-17 01:45:51 +08:00
|
|
|
|
2015-03-22 00:59:29 +08:00
|
|
|
// The list of markdown configurations
|
2015-09-11 06:12:46 +08:00
|
|
|
Configs []*Config
|
2015-05-06 05:50:42 +08:00
|
|
|
|
|
|
|
// The list of index files to try
|
|
|
|
IndexFiles []string
|
2015-03-22 00:59:29 +08:00
|
|
|
}
|
|
|
|
|
2015-05-25 10:52:34 +08:00
|
|
|
// IsIndexFile checks to see if a file is an index file
|
|
|
|
func (md Markdown) IsIndexFile(file string) bool {
|
|
|
|
for _, f := range md.IndexFiles {
|
2015-05-07 20:45:27 +08:00
|
|
|
if f == file {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2015-05-05 01:49:49 +08:00
|
|
|
// Config stores markdown middleware configurations.
|
|
|
|
type Config struct {
|
2015-03-17 01:45:51 +08:00
|
|
|
// 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
|
2015-05-06 10:37:29 +08:00
|
|
|
|
|
|
|
// Map of registered templates
|
2015-05-07 07:19:02 +08:00
|
|
|
Templates map[string]string
|
|
|
|
|
2015-05-08 23:20:07 +08:00
|
|
|
// Map of request URL to static files generated
|
2015-05-07 07:19:02 +08:00
|
|
|
StaticFiles map[string]string
|
2015-05-07 20:45:27 +08:00
|
|
|
|
2015-07-28 12:21:09 +08:00
|
|
|
// Links to all markdown pages ordered by date.
|
|
|
|
Links []PageLink
|
|
|
|
|
2015-08-05 06:35:09 +08:00
|
|
|
// Stores a directory hash to check for changes.
|
|
|
|
linksHash string
|
|
|
|
|
2015-05-08 23:20:07 +08:00
|
|
|
// Directory to store static files
|
2015-05-07 20:45:27 +08:00
|
|
|
StaticDir string
|
2015-07-29 19:15:02 +08:00
|
|
|
|
2015-08-05 06:35:09 +08:00
|
|
|
// If in development mode. i.e. Actively editing markdown files.
|
|
|
|
Development bool
|
|
|
|
|
2015-07-29 19:15:02 +08:00
|
|
|
sync.RWMutex
|
2015-03-17 01:45:51 +08:00
|
|
|
}
|
|
|
|
|
2015-08-05 06:35:09 +08:00
|
|
|
// IsValidExt checks to see if an extension is a valid markdown extension
|
|
|
|
// for config.
|
2015-09-11 06:12:46 +08:00
|
|
|
func (c *Config) IsValidExt(ext string) bool {
|
2015-08-05 06:35:09 +08:00
|
|
|
for _, e := range c.Extensions {
|
|
|
|
if e == ext {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2015-03-17 01:45:51 +08:00
|
|
|
// ServeHTTP implements the http.Handler interface.
|
2015-03-29 06:55:40 +08:00
|
|
|
func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
2015-08-05 22:15:52 +08:00
|
|
|
for _, cfg := range md.Configs {
|
|
|
|
if !middleware.Path(r.URL.Path).Matches(cfg.PathScope) {
|
2015-03-22 00:59:29 +08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2015-05-06 05:50:42 +08:00
|
|
|
fpath := r.URL.Path
|
|
|
|
if idx, ok := middleware.IndexFile(md.FileSys, fpath, md.IndexFiles); ok {
|
|
|
|
fpath = idx
|
|
|
|
}
|
2015-03-17 01:45:51 +08:00
|
|
|
|
2015-08-05 22:15:52 +08:00
|
|
|
for _, ext := range cfg.Extensions {
|
2015-05-06 05:50:42 +08:00
|
|
|
if strings.HasSuffix(fpath, ext) {
|
|
|
|
f, err := md.FileSys.Open(fpath)
|
2015-03-17 01:45:51 +08:00
|
|
|
if err != nil {
|
2015-03-29 06:55:40 +08:00
|
|
|
if os.IsPermission(err) {
|
|
|
|
return http.StatusForbidden, err
|
|
|
|
}
|
|
|
|
return http.StatusNotFound, nil
|
2015-03-17 01:45:51 +08:00
|
|
|
}
|
|
|
|
|
2015-05-07 20:45:27 +08:00
|
|
|
fs, err := f.Stat()
|
|
|
|
if err != nil {
|
|
|
|
return http.StatusNotFound, nil
|
|
|
|
}
|
|
|
|
|
2015-08-05 07:41:04 +08:00
|
|
|
// if development is set, scan directory for file changes for links.
|
2015-08-05 22:15:52 +08:00
|
|
|
if cfg.Development {
|
2015-09-11 06:12:46 +08:00
|
|
|
if err := GenerateStatic(md, cfg); err != nil {
|
2015-11-06 08:01:08 +08:00
|
|
|
log.Printf("[ERROR] markdown: on-demand site generation error: %v", err)
|
2015-08-05 07:41:04 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-11 23:34:52 +08:00
|
|
|
cfg.RLock()
|
|
|
|
filepath, ok := cfg.StaticFiles[fpath]
|
|
|
|
cfg.RUnlock()
|
2015-05-07 20:45:27 +08:00
|
|
|
// if static site is generated, attempt to use it
|
2015-09-11 23:34:52 +08:00
|
|
|
if ok {
|
2015-05-07 20:45:27 +08:00
|
|
|
if fs1, err := os.Stat(filepath); err == nil {
|
2015-08-05 22:15:52 +08:00
|
|
|
// if markdown has not been modified since static page
|
|
|
|
// generation, serve the static page
|
|
|
|
if fs.ModTime().Before(fs1.ModTime()) {
|
2015-05-07 20:45:27 +08:00
|
|
|
if html, err := ioutil.ReadFile(filepath); err == nil {
|
2015-10-28 20:38:58 +08:00
|
|
|
middleware.SetLastModifiedHeader(w, fs1.ModTime())
|
2015-05-07 20:45:27 +08:00
|
|
|
w.Write(html)
|
|
|
|
return http.StatusOK, nil
|
|
|
|
}
|
2015-05-25 10:52:34 +08:00
|
|
|
if os.IsPermission(err) {
|
|
|
|
return http.StatusForbidden, err
|
|
|
|
}
|
|
|
|
return http.StatusNotFound, nil
|
2015-05-07 20:45:27 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-06 05:50:42 +08:00
|
|
|
body, err := ioutil.ReadAll(f)
|
|
|
|
if err != nil {
|
|
|
|
return http.StatusInternalServerError, err
|
|
|
|
}
|
|
|
|
|
2015-07-21 13:58:34 +08:00
|
|
|
ctx := middleware.Context{
|
|
|
|
Root: md.FileSys,
|
|
|
|
Req: r,
|
|
|
|
URL: r.URL,
|
|
|
|
}
|
2015-08-05 22:15:52 +08:00
|
|
|
html, err := md.Process(cfg, fpath, body, ctx)
|
2015-05-07 07:19:02 +08:00
|
|
|
if err != nil {
|
|
|
|
return http.StatusInternalServerError, err
|
2015-03-20 14:01:39 +08:00
|
|
|
}
|
|
|
|
|
2015-10-28 20:38:58 +08:00
|
|
|
middleware.SetLastModifiedHeader(w, fs.ModTime())
|
2015-05-07 20:45:27 +08:00
|
|
|
w.Write(html)
|
2015-03-29 06:55:40 +08:00
|
|
|
return http.StatusOK, nil
|
2015-03-17 01:45:51 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-20 14:01:39 +08:00
|
|
|
// Didn't qualify to serve as markdown; pass-thru
|
2015-04-03 13:30:54 +08:00
|
|
|
return md.Next.ServeHTTP(w, r)
|
2015-03-17 01:45:51 +08:00
|
|
|
}
|