mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-24 01:26:47 +01:00
Merge pull request #196 from evermax/master
markdown, browse: Integrated Context struct for templating
This commit is contained in:
commit
bf47951f3a
23 changed files with 457 additions and 49 deletions
|
@ -2,8 +2,8 @@ package setup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/mholt/caddy/middleware"
|
"github.com/mholt/caddy/middleware"
|
||||||
"github.com/mholt/caddy/middleware/browse"
|
"github.com/mholt/caddy/middleware/browse"
|
||||||
|
|
|
@ -62,7 +62,8 @@ func Markdown(c *Controller) (middleware.Middleware, error) {
|
||||||
reqPath = "/" + reqPath
|
reqPath = "/" + reqPath
|
||||||
|
|
||||||
// Generate the static file
|
// Generate the static file
|
||||||
_, err = md.Process(cfg, reqPath, body)
|
ctx := middleware.Context{Root: md.FileSys}
|
||||||
|
_, err = md.Process(cfg, reqPath, body, ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,13 @@ package browse
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"html/template"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
|
@ -51,6 +51,8 @@ type Listing struct {
|
||||||
|
|
||||||
// And which order
|
// And which order
|
||||||
Order string
|
Order string
|
||||||
|
|
||||||
|
middleware.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileInfo is the info about a particular file or directory
|
// FileInfo is the info about a particular file or directory
|
||||||
|
@ -137,8 +139,9 @@ var IndexPages = []string{
|
||||||
"default.txt",
|
"default.txt",
|
||||||
}
|
}
|
||||||
|
|
||||||
func directoryListing(files []os.FileInfo, urlPath string, canGoUp bool) (Listing, error) {
|
func directoryListing(files []os.FileInfo, r *http.Request, canGoUp bool, root string) (Listing, error) {
|
||||||
var fileinfos []FileInfo
|
var fileinfos []FileInfo
|
||||||
|
var urlPath = r.URL.Path
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
name := f.Name()
|
name := f.Name()
|
||||||
|
|
||||||
|
@ -170,6 +173,11 @@ func directoryListing(files []os.FileInfo, urlPath string, canGoUp bool) (Listin
|
||||||
Path: urlPath,
|
Path: urlPath,
|
||||||
CanGoUp: canGoUp,
|
CanGoUp: canGoUp,
|
||||||
Items: fileinfos,
|
Items: fileinfos,
|
||||||
|
Context: middleware.Context{
|
||||||
|
Root: http.Dir(root),
|
||||||
|
Req: r,
|
||||||
|
URL: r.URL,
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,7 +232,7 @@ func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Assemble listing of directory contents
|
// Assemble listing of directory contents
|
||||||
listing, err := directoryListing(files, r.URL.Path, canGoUp)
|
listing, err := directoryListing(files, r, canGoUp, b.Root)
|
||||||
if err != nil { // directory isn't browsable
|
if err != nil { // directory isn't browsable
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
package browse
|
package browse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
// "sort" package has "IsSorted" function, but no "IsReversed";
|
// "sort" package has "IsSorted" function, but no "IsReversed";
|
||||||
|
@ -94,3 +99,59 @@ func TestSort(t *testing.T) {
|
||||||
t.Errorf("The listing isn't reversed by time: %v", listing.Items)
|
t.Errorf("The listing isn't reversed by time: %v", listing.Items)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBrowseTemplate(t *testing.T) {
|
||||||
|
tmpl, err := template.ParseFiles("testdata/photos.tpl")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("An error occured while parsing the template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b := Browse{
|
||||||
|
Next: middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
t.Fatalf("Next shouldn't be called")
|
||||||
|
return 0, nil
|
||||||
|
}),
|
||||||
|
Root: "./testdata",
|
||||||
|
Configs: []Config{
|
||||||
|
Config{
|
||||||
|
PathScope: "/photos",
|
||||||
|
Template: tmpl,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "/photos/", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test: Could not create HTTP request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
|
b.ServeHTTP(rec, req)
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("Wrong status, expected %d, got %d", http.StatusOK, rec.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
respBody := rec.Body.String()
|
||||||
|
expectedBody := `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Template</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Header</h1>
|
||||||
|
|
||||||
|
<h1>/photos/</h1>
|
||||||
|
|
||||||
|
<a href="test.html">test.html</a><br>
|
||||||
|
|
||||||
|
<a href="test2.html">test2.html</a><br>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
if respBody != expectedBody {
|
||||||
|
t.Fatalf("Expected body: %v got: %v", expectedBody, respBody)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
1
middleware/browse/testdata/header.html
vendored
Normal file
1
middleware/browse/testdata/header.html
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<h1>Header</h1>
|
13
middleware/browse/testdata/photos.tpl
vendored
Normal file
13
middleware/browse/testdata/photos.tpl
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Template</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{.Include "header.html"}}
|
||||||
|
<h1>{{.Path}}</h1>
|
||||||
|
{{range .Items}}
|
||||||
|
<a href="{{.URL}}">{{.Name}}</a><br>
|
||||||
|
{{end}}
|
||||||
|
</body>
|
||||||
|
</html>
|
8
middleware/browse/testdata/photos/test.html
vendored
Normal file
8
middleware/browse/testdata/photos/test.html
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
8
middleware/browse/testdata/photos/test2.html
vendored
Normal file
8
middleware/browse/testdata/photos/test2.html
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Test 2</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,4 +1,4 @@
|
||||||
package templates
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -8,23 +8,22 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mholt/caddy/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// This file contains the context and functions available for
|
// This file contains the context and functions available for
|
||||||
// use in the templates.
|
// use in the templates.
|
||||||
|
|
||||||
// context is the context with which templates are executed.
|
// context is the context with which templates are executed.
|
||||||
type context struct {
|
type Context struct {
|
||||||
root http.FileSystem
|
Root http.FileSystem
|
||||||
req *http.Request
|
Req *http.Request
|
||||||
|
// This is used to access information about the URL.
|
||||||
URL *url.URL
|
URL *url.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include returns the contents of filename relative to the site root
|
// Include returns the contents of filename relative to the site root
|
||||||
func (c context) Include(filename string) (string, error) {
|
func (c Context) Include(filename string) (string, error) {
|
||||||
file, err := c.root.Open(filename)
|
file, err := c.Root.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -50,13 +49,13 @@ func (c context) Include(filename string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Date returns the current timestamp in the specified format
|
// Date returns the current timestamp in the specified format
|
||||||
func (c context) Date(format string) string {
|
func (c Context) Date(format string) string {
|
||||||
return time.Now().Format(format)
|
return time.Now().Format(format)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cookie gets the value of a cookie with name name.
|
// Cookie gets the value of a cookie with name name.
|
||||||
func (c context) Cookie(name string) string {
|
func (c Context) Cookie(name string) string {
|
||||||
cookies := c.req.Cookies()
|
cookies := c.Req.Cookies()
|
||||||
for _, cookie := range cookies {
|
for _, cookie := range cookies {
|
||||||
if cookie.Name == name {
|
if cookie.Name == name {
|
||||||
return cookie.Value
|
return cookie.Value
|
||||||
|
@ -66,15 +65,15 @@ func (c context) Cookie(name string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header gets the value of a request header with field name.
|
// Header gets the value of a request header with field name.
|
||||||
func (c context) Header(name string) string {
|
func (c Context) Header(name string) string {
|
||||||
return c.req.Header.Get(name)
|
return c.Req.Header.Get(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IP gets the (remote) IP address of the client making the request.
|
// IP gets the (remote) IP address of the client making the request.
|
||||||
func (c context) IP() string {
|
func (c Context) IP() string {
|
||||||
ip, _, err := net.SplitHostPort(c.req.RemoteAddr)
|
ip, _, err := net.SplitHostPort(c.Req.RemoteAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.req.RemoteAddr
|
return c.Req.RemoteAddr
|
||||||
}
|
}
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
|
@ -82,14 +81,14 @@ func (c context) IP() string {
|
||||||
// URI returns the raw, unprocessed request URI (including query
|
// URI returns the raw, unprocessed request URI (including query
|
||||||
// string and hash) obtained directly from the Request-Line of
|
// string and hash) obtained directly from the Request-Line of
|
||||||
// the HTTP request.
|
// the HTTP request.
|
||||||
func (c context) URI() string {
|
func (c Context) URI() string {
|
||||||
return c.req.RequestURI
|
return c.Req.RequestURI
|
||||||
}
|
}
|
||||||
|
|
||||||
// Host returns the hostname portion of the Host header
|
// Host returns the hostname portion of the Host header
|
||||||
// from the HTTP request.
|
// from the HTTP request.
|
||||||
func (c context) Host() (string, error) {
|
func (c Context) Host() (string, error) {
|
||||||
host, _, err := net.SplitHostPort(c.req.Host)
|
host, _, err := net.SplitHostPort(c.Req.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -97,8 +96,8 @@ func (c context) Host() (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Port returns the port portion of the Host header if specified.
|
// Port returns the port portion of the Host header if specified.
|
||||||
func (c context) Port() (string, error) {
|
func (c Context) Port() (string, error) {
|
||||||
_, port, err := net.SplitHostPort(c.req.Host)
|
_, port, err := net.SplitHostPort(c.Req.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -106,12 +105,12 @@ func (c context) Port() (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method returns the method (GET, POST, etc.) of the request.
|
// Method returns the method (GET, POST, etc.) of the request.
|
||||||
func (c context) Method() string {
|
func (c Context) Method() string {
|
||||||
return c.req.Method
|
return c.Req.Method
|
||||||
}
|
}
|
||||||
|
|
||||||
// PathMatches returns true if the path portion of the request
|
// PathMatches returns true if the path portion of the request
|
||||||
// URL matches pattern.
|
// URL matches pattern.
|
||||||
func (c context) PathMatches(pattern string) bool {
|
func (c Context) PathMatches(pattern string) bool {
|
||||||
return middleware.Path(c.req.URL.Path).Matches(pattern)
|
return Path(c.Req.URL.Path).Matches(pattern)
|
||||||
}
|
}
|
|
@ -119,7 +119,12 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
html, err := md.Process(m, fpath, body)
|
ctx := middleware.Context{
|
||||||
|
Root: md.FileSys,
|
||||||
|
Req: r,
|
||||||
|
URL: r.URL,
|
||||||
|
}
|
||||||
|
html, err := md.Process(m, fpath, body, ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
126
middleware/markdown/markdown_test.go
Normal file
126
middleware/markdown/markdown_test.go
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
package markdown
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
"github.com/russross/blackfriday"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMarkdown(t *testing.T) {
|
||||||
|
templates := make(map[string]string)
|
||||||
|
templates[DefaultTemplate] = "testdata/markdown_tpl.html"
|
||||||
|
md := Markdown{
|
||||||
|
Root: "./testdata",
|
||||||
|
FileSys: http.Dir("./testdata"),
|
||||||
|
Configs: []Config{
|
||||||
|
Config{
|
||||||
|
Renderer: blackfriday.HtmlRenderer(0, "", ""),
|
||||||
|
PathScope: "/blog",
|
||||||
|
Extensions: []string{".md"},
|
||||||
|
Styles: []string{},
|
||||||
|
Scripts: []string{},
|
||||||
|
Templates: templates,
|
||||||
|
},
|
||||||
|
Config{
|
||||||
|
Renderer: blackfriday.HtmlRenderer(0, "", ""),
|
||||||
|
PathScope: "/log",
|
||||||
|
Extensions: []string{".md"},
|
||||||
|
Styles: []string{"/resources/css/log.css", "/resources/css/default.css"},
|
||||||
|
Scripts: []string{"/resources/js/log.js", "/resources/js/default.js"},
|
||||||
|
Templates: make(map[string]string),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
IndexFiles: []string{"index.html"},
|
||||||
|
Next: middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
t.Fatalf("Next shouldn't be called")
|
||||||
|
return 0, nil
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "/blog/test.md", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create HTTP request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
|
md.ServeHTTP(rec, req)
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("Wrong status, expected: %d and got %d", http.StatusOK, rec.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
respBody := rec.Body.String()
|
||||||
|
expectedBody := `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Markdown test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Header</h1>
|
||||||
|
|
||||||
|
Welcome to A Caddy website!
|
||||||
|
<h2>Welcome on the blog</h2>
|
||||||
|
|
||||||
|
<p>Body</p>
|
||||||
|
|
||||||
|
<p><code>go
|
||||||
|
func getTrue() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
</code></p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
if respBody != expectedBody {
|
||||||
|
t.Fatalf("Expected body: %v got: %v", expectedBody, respBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err = http.NewRequest("GET", "/log/test.md", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create HTTP request: %v", err)
|
||||||
|
}
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
|
||||||
|
md.ServeHTTP(rec, req)
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("Wrong status, expected: %d and got %d", http.StatusOK, rec.Code)
|
||||||
|
}
|
||||||
|
respBody = rec.Body.String()
|
||||||
|
expectedBody = `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Markdown test</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="stylesheet" href="/resources/css/log.css">
|
||||||
|
<link rel="stylesheet" href="/resources/css/default.css">
|
||||||
|
|
||||||
|
<script src="/resources/js/log.js"></script>
|
||||||
|
<script src="/resources/js/default.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>Welcome on the blog</h2>
|
||||||
|
|
||||||
|
<p>Body</p>
|
||||||
|
|
||||||
|
<p><code>go
|
||||||
|
func getTrue() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
</code></p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
|
||||||
|
replacer := strings.NewReplacer("\r", "", "\n", "")
|
||||||
|
respBody = replacer.Replace(respBody)
|
||||||
|
expectedBody = replacer.Replace(expectedBody)
|
||||||
|
if respBody != expectedBody {
|
||||||
|
t.Fatalf("Expected body: %v got: %v", expectedBody, respBody)
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
"github.com/russross/blackfriday"
|
"github.com/russross/blackfriday"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,9 +18,14 @@ const (
|
||||||
DefaultStaticDir = "generated_site"
|
DefaultStaticDir = "generated_site"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type MarkdownData struct {
|
||||||
|
middleware.Context
|
||||||
|
Doc map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
// Process processes the contents of a page in b. It parses the metadata
|
// Process processes the contents of a page in b. It parses the metadata
|
||||||
// (if any) and uses the template (if found).
|
// (if any) and uses the template (if found).
|
||||||
func (md Markdown) Process(c Config, requestPath string, b []byte) ([]byte, error) {
|
func (md Markdown) Process(c Config, requestPath string, b []byte, ctx middleware.Context) ([]byte, error) {
|
||||||
var metadata = Metadata{Variables: make(map[string]interface{})}
|
var metadata = Metadata{Variables: make(map[string]interface{})}
|
||||||
var markdown []byte
|
var markdown []byte
|
||||||
var err error
|
var err error
|
||||||
|
@ -61,14 +67,21 @@ func (md Markdown) Process(c Config, requestPath string, b []byte) ([]byte, erro
|
||||||
markdown = blackfriday.Markdown(markdown, c.Renderer, 0)
|
markdown = blackfriday.Markdown(markdown, c.Renderer, 0)
|
||||||
|
|
||||||
// set it as body for template
|
// set it as body for template
|
||||||
metadata.Variables["markdown"] = string(markdown)
|
metadata.Variables["body"] = string(markdown)
|
||||||
|
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
|
||||||
|
|
||||||
return md.processTemplate(c, requestPath, tmpl, metadata)
|
return md.processTemplate(c, requestPath, tmpl, metadata, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// processTemplate processes a template given a requestPath,
|
// processTemplate processes a template given a requestPath,
|
||||||
// template (tmpl) and metadata
|
// template (tmpl) and metadata
|
||||||
func (md Markdown) processTemplate(c Config, requestPath string, tmpl []byte, metadata Metadata) ([]byte, error) {
|
func (md Markdown) processTemplate(c Config, requestPath string, tmpl []byte, metadata Metadata, ctx middleware.Context) ([]byte, error) {
|
||||||
// if template is not specified,
|
// if template is not specified,
|
||||||
// use the default template
|
// use the default template
|
||||||
if tmpl == nil {
|
if tmpl == nil {
|
||||||
|
@ -81,7 +94,12 @@ func (md Markdown) processTemplate(c Config, requestPath string, tmpl []byte, me
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err = t.Execute(b, metadata.Variables); err != nil {
|
mdData := MarkdownData{
|
||||||
|
Context: ctx,
|
||||||
|
Doc: metadata.Variables,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = t.Execute(b, mdData); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,15 +166,7 @@ func defaultTemplate(c Config, metadata Metadata, requestPath string) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Title is first line (length-limited), otherwise filename
|
// Title is first line (length-limited), otherwise filename
|
||||||
title := metadata.Title
|
title, _ := metadata.Variables["title"].(string)
|
||||||
if title == "" {
|
|
||||||
title = filepath.Base(requestPath)
|
|
||||||
if body, _ := metadata.Variables["markdown"].([]byte); len(body) > 128 {
|
|
||||||
title = string(body[:128])
|
|
||||||
} else if len(body) > 0 {
|
|
||||||
title = string(body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
html := []byte(htmlTemplate)
|
html := []byte(htmlTemplate)
|
||||||
html = bytes.Replace(html, []byte("{{title}}"), []byte(title), 1)
|
html = bytes.Replace(html, []byte("{{title}}"), []byte(title), 1)
|
||||||
|
@ -176,7 +186,7 @@ const (
|
||||||
{{js}}
|
{{js}}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{{.markdown}}
|
{{.Doc.body}}
|
||||||
</body>
|
</body>
|
||||||
</html>`
|
</html>`
|
||||||
cssTemplate = `<link rel="stylesheet" href="{{url}}">`
|
cssTemplate = `<link rel="stylesheet" href="{{url}}">`
|
||||||
|
|
15
middleware/markdown/testdata/blog/test.md
vendored
Normal file
15
middleware/markdown/testdata/blog/test.md
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
title: Markdown test
|
||||||
|
variables:
|
||||||
|
sitename: A Caddy website
|
||||||
|
---
|
||||||
|
|
||||||
|
## Welcome on the blog
|
||||||
|
|
||||||
|
Body
|
||||||
|
|
||||||
|
``` go
|
||||||
|
func getTrue() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
```
|
1
middleware/markdown/testdata/header.html
vendored
Normal file
1
middleware/markdown/testdata/header.html
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<h1>Header</h1>
|
15
middleware/markdown/testdata/log/test.md
vendored
Normal file
15
middleware/markdown/testdata/log/test.md
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
title: Markdown test
|
||||||
|
variables:
|
||||||
|
sitename: A Caddy website
|
||||||
|
---
|
||||||
|
|
||||||
|
## Welcome on the blog
|
||||||
|
|
||||||
|
Body
|
||||||
|
|
||||||
|
``` go
|
||||||
|
func getTrue() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
```
|
11
middleware/markdown/testdata/markdown_tpl.html
vendored
Normal file
11
middleware/markdown/testdata/markdown_tpl.html
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{{.Doc.title}}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{.Include "header.html"}}
|
||||||
|
Welcome to {{.Doc.sitename}}!
|
||||||
|
{{.Doc.body}}
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -31,7 +31,7 @@ func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
|
||||||
for _, ext := range rule.Extensions {
|
for _, ext := range rule.Extensions {
|
||||||
if reqExt == ext {
|
if reqExt == ext {
|
||||||
// Create execution context
|
// Create execution context
|
||||||
ctx := context{root: t.FileSys, req: r, URL: r.URL}
|
ctx := middleware.Context{Root: t.FileSys, Req: r, URL: r.URL}
|
||||||
|
|
||||||
// Build the template
|
// Build the template
|
||||||
tpl, err := template.ParseFiles(filepath.Join(t.Root, fpath))
|
tpl, err := template.ParseFiles(filepath.Join(t.Root, fpath))
|
||||||
|
|
121
middleware/templates/templates_test.go
Normal file
121
middleware/templates/templates_test.go
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
package templates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test(t *testing.T) {
|
||||||
|
tmpl := Templates{
|
||||||
|
Next: middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
return 0, nil
|
||||||
|
}),
|
||||||
|
Rules: []Rule{
|
||||||
|
Rule{
|
||||||
|
Extensions: []string{".html"},
|
||||||
|
IndexFiles: []string{"index.html"},
|
||||||
|
Path: "/photos",
|
||||||
|
},
|
||||||
|
Rule{
|
||||||
|
Extensions: []string{".html", ".htm"},
|
||||||
|
IndexFiles: []string{"index.html", "index.htm"},
|
||||||
|
Path: "/images",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Root: "./testdata",
|
||||||
|
FileSys: http.Dir("./testdata"),
|
||||||
|
}
|
||||||
|
|
||||||
|
tmplroot := Templates{
|
||||||
|
Next: middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
return 0, nil
|
||||||
|
}),
|
||||||
|
Rules: []Rule{
|
||||||
|
Rule{
|
||||||
|
Extensions: []string{".html"},
|
||||||
|
IndexFiles: []string{"index.html"},
|
||||||
|
Path: "/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Root: "./testdata",
|
||||||
|
FileSys: http.Dir("./testdata"),
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test tmpl on /photos/test.html
|
||||||
|
*/
|
||||||
|
req, err := http.NewRequest("GET", "/photos/test.html", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test: Could not create HTTP request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
|
tmpl.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
respBody := rec.Body.String()
|
||||||
|
expectedBody := `<!DOCTYPE html><html><head><title>test page</title></head><body><h1>Header title</h1>
|
||||||
|
</body></html>
|
||||||
|
`
|
||||||
|
|
||||||
|
if respBody != expectedBody {
|
||||||
|
t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test tmpl on /images/img.htm
|
||||||
|
*/
|
||||||
|
req, err = http.NewRequest("GET", "/images/img.htm", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create HTTP request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
|
||||||
|
tmpl.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
respBody = rec.Body.String()
|
||||||
|
expectedBody = `<!DOCTYPE html><html><head><title>img</title></head><body><h1>Header title</h1>
|
||||||
|
</body></html>
|
||||||
|
`
|
||||||
|
|
||||||
|
if respBody != expectedBody {
|
||||||
|
t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test tmplroot on /root.html
|
||||||
|
*/
|
||||||
|
req, err = http.NewRequest("GET", "/root.html", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create HTTP request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
|
||||||
|
tmplroot.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
respBody = rec.Body.String()
|
||||||
|
expectedBody = `<!DOCTYPE html><html><head><title>root</title></head><body><h1>Header title</h1>
|
||||||
|
</body></html>
|
||||||
|
`
|
||||||
|
|
||||||
|
if respBody != expectedBody {
|
||||||
|
t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody)
|
||||||
|
}
|
||||||
|
}
|
1
middleware/templates/testdata/header.html
vendored
Normal file
1
middleware/templates/testdata/header.html
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<h1>Header title</h1>
|
1
middleware/templates/testdata/images/header.html
vendored
Normal file
1
middleware/templates/testdata/images/header.html
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<h1>Header title</h1>
|
1
middleware/templates/testdata/images/img.htm
vendored
Normal file
1
middleware/templates/testdata/images/img.htm
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<!DOCTYPE html><html><head><title>img</title></head><body>{{.Include "header.html"}}</body></html>
|
1
middleware/templates/testdata/photos/test.html
vendored
Normal file
1
middleware/templates/testdata/photos/test.html
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<!DOCTYPE html><html><head><title>test page</title></head><body>{{.Include "../header.html"}}</body></html>
|
1
middleware/templates/testdata/root.html
vendored
Normal file
1
middleware/templates/testdata/root.html
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<!DOCTYPE html><html><head><title>root</title></head><body>{{.Include "header.html"}}</body></html>
|
Loading…
Reference in a new issue