diff --git a/config/setup/browse.go b/config/setup/browse.go index 7db8b40ac..a98ec313d 100644 --- a/config/setup/browse.go +++ b/config/setup/browse.go @@ -2,8 +2,8 @@ package setup import ( "fmt" - "html/template" "io/ioutil" + "text/template" "github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware/browse" diff --git a/config/setup/markdown.go b/config/setup/markdown.go index fd92f20ad..bbccf9d5f 100644 --- a/config/setup/markdown.go +++ b/config/setup/markdown.go @@ -62,7 +62,8 @@ func Markdown(c *Controller) (middleware.Middleware, error) { reqPath = "/" + reqPath // 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 { return err } diff --git a/middleware/browse/browse.go b/middleware/browse/browse.go index d11fe6f88..756ca8c83 100644 --- a/middleware/browse/browse.go +++ b/middleware/browse/browse.go @@ -5,13 +5,13 @@ package browse import ( "bytes" "errors" - "html/template" "net/http" "net/url" "os" "path" "sort" "strings" + "text/template" "time" "github.com/dustin/go-humanize" @@ -51,6 +51,8 @@ type Listing struct { // And which order Order string + + middleware.Context } // FileInfo is the info about a particular file or directory @@ -137,8 +139,9 @@ var IndexPages = []string{ "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 urlPath = r.URL.Path for _, f := range files { name := f.Name() @@ -170,6 +173,11 @@ func directoryListing(files []os.FileInfo, urlPath string, canGoUp bool) (Listin Path: urlPath, CanGoUp: canGoUp, Items: fileinfos, + Context: middleware.Context{ + Root: http.Dir(root), + Req: r, + URL: r.URL, + }, }, nil } @@ -224,7 +232,7 @@ func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { } } // 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 continue } diff --git a/middleware/browse/browse_test.go b/middleware/browse/browse_test.go index 33218a195..b0ff28db6 100644 --- a/middleware/browse/browse_test.go +++ b/middleware/browse/browse_test.go @@ -1,9 +1,14 @@ package browse import ( + "net/http" + "net/http/httptest" "sort" "testing" + "text/template" "time" + + "github.com/mholt/caddy/middleware" ) // "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) } } + +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 := ` + + +Template + + +

Header

+ +

/photos/

+ +test.html
+ +test2.html
+ + + +` + + if respBody != expectedBody { + t.Fatalf("Expected body: %v got: %v", expectedBody, respBody) + } +} diff --git a/middleware/browse/testdata/header.html b/middleware/browse/testdata/header.html new file mode 100644 index 000000000..78e5a6a48 --- /dev/null +++ b/middleware/browse/testdata/header.html @@ -0,0 +1 @@ +

Header

diff --git a/middleware/browse/testdata/photos.tpl b/middleware/browse/testdata/photos.tpl new file mode 100644 index 000000000..5163ca008 --- /dev/null +++ b/middleware/browse/testdata/photos.tpl @@ -0,0 +1,13 @@ + + + +Template + + +{{.Include "header.html"}} +

{{.Path}}

+{{range .Items}} +{{.Name}}
+{{end}} + + diff --git a/middleware/browse/testdata/photos/test.html b/middleware/browse/testdata/photos/test.html new file mode 100644 index 000000000..40535a223 --- /dev/null +++ b/middleware/browse/testdata/photos/test.html @@ -0,0 +1,8 @@ + + + +Test + + + + diff --git a/middleware/browse/testdata/photos/test2.html b/middleware/browse/testdata/photos/test2.html new file mode 100644 index 000000000..8e10c5780 --- /dev/null +++ b/middleware/browse/testdata/photos/test2.html @@ -0,0 +1,8 @@ + + + +Test 2 + + + + diff --git a/middleware/templates/context.go b/middleware/context.go similarity index 63% rename from middleware/templates/context.go rename to middleware/context.go index 4f125c991..508ec6042 100644 --- a/middleware/templates/context.go +++ b/middleware/context.go @@ -1,4 +1,4 @@ -package templates +package middleware import ( "bytes" @@ -8,23 +8,22 @@ import ( "net/url" "text/template" "time" - - "github.com/mholt/caddy/middleware" ) // This file contains the context and functions available for // use in the templates. // context is the context with which templates are executed. -type context struct { - root http.FileSystem - req *http.Request - URL *url.URL +type Context struct { + Root http.FileSystem + Req *http.Request + // This is used to access information about the URL. + URL *url.URL } // Include returns the contents of filename relative to the site root -func (c context) Include(filename string) (string, error) { - file, err := c.root.Open(filename) +func (c Context) Include(filename string) (string, error) { + file, err := c.Root.Open(filename) if err != nil { return "", err } @@ -50,13 +49,13 @@ func (c context) Include(filename string) (string, error) { } // 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) } // Cookie gets the value of a cookie with name name. -func (c context) Cookie(name string) string { - cookies := c.req.Cookies() +func (c Context) Cookie(name string) string { + cookies := c.Req.Cookies() for _, cookie := range cookies { if cookie.Name == name { 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. -func (c context) Header(name string) string { - return c.req.Header.Get(name) +func (c Context) Header(name string) string { + return c.Req.Header.Get(name) } // IP gets the (remote) IP address of the client making the request. -func (c context) IP() string { - ip, _, err := net.SplitHostPort(c.req.RemoteAddr) +func (c Context) IP() string { + ip, _, err := net.SplitHostPort(c.Req.RemoteAddr) if err != nil { - return c.req.RemoteAddr + return c.Req.RemoteAddr } return ip } @@ -82,14 +81,14 @@ func (c context) IP() string { // URI returns the raw, unprocessed request URI (including query // string and hash) obtained directly from the Request-Line of // the HTTP request. -func (c context) URI() string { - return c.req.RequestURI +func (c Context) URI() string { + return c.Req.RequestURI } // Host returns the hostname portion of the Host header // from the HTTP request. -func (c context) Host() (string, error) { - host, _, err := net.SplitHostPort(c.req.Host) +func (c Context) Host() (string, error) { + host, _, err := net.SplitHostPort(c.Req.Host) if err != nil { return "", err } @@ -97,8 +96,8 @@ func (c context) Host() (string, error) { } // Port returns the port portion of the Host header if specified. -func (c context) Port() (string, error) { - _, port, err := net.SplitHostPort(c.req.Host) +func (c Context) Port() (string, error) { + _, port, err := net.SplitHostPort(c.Req.Host) if err != nil { return "", err } @@ -106,12 +105,12 @@ func (c context) Port() (string, error) { } // Method returns the method (GET, POST, etc.) of the request. -func (c context) Method() string { - return c.req.Method +func (c Context) Method() string { + return c.Req.Method } // PathMatches returns true if the path portion of the request // URL matches pattern. -func (c context) PathMatches(pattern string) bool { - return middleware.Path(c.req.URL.Path).Matches(pattern) +func (c Context) PathMatches(pattern string) bool { + return Path(c.Req.URL.Path).Matches(pattern) } diff --git a/middleware/markdown/markdown.go b/middleware/markdown/markdown.go index 6b7f35563..71b189068 100644 --- a/middleware/markdown/markdown.go +++ b/middleware/markdown/markdown.go @@ -119,7 +119,12 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error 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 { return http.StatusInternalServerError, err } diff --git a/middleware/markdown/markdown_test.go b/middleware/markdown/markdown_test.go new file mode 100644 index 000000000..e01d88e8e --- /dev/null +++ b/middleware/markdown/markdown_test.go @@ -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 := ` + + +Markdown test + + +

Header

+ +Welcome to A Caddy website! +

Welcome on the blog

+ +

Body

+ +

go +func getTrue() bool { + return true +} +

+ + + +` + 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 = ` + + + Markdown test + + + + + + + + + +

Welcome on the blog

+ +

Body

+ +

go +func getTrue() bool { + return true +} +

+ + +` + + 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) + } +} diff --git a/middleware/markdown/process.go b/middleware/markdown/process.go index 95f7e51b3..81e2f4ec8 100644 --- a/middleware/markdown/process.go +++ b/middleware/markdown/process.go @@ -9,6 +9,7 @@ import ( "strings" "text/template" + "github.com/mholt/caddy/middleware" "github.com/russross/blackfriday" ) @@ -17,9 +18,14 @@ const ( 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 // (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 markdown []byte 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) // 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, // 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, // use the default template if tmpl == nil { @@ -81,7 +94,12 @@ func (md Markdown) processTemplate(c Config, requestPath string, tmpl []byte, me if err != nil { 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 } @@ -148,15 +166,7 @@ func defaultTemplate(c Config, metadata Metadata, requestPath string) []byte { } // Title is first line (length-limited), otherwise filename - title := metadata.Title - 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) - } - } + title, _ := metadata.Variables["title"].(string) html := []byte(htmlTemplate) html = bytes.Replace(html, []byte("{{title}}"), []byte(title), 1) @@ -176,7 +186,7 @@ const ( {{js}} - {{.markdown}} + {{.Doc.body}} ` cssTemplate = `` diff --git a/middleware/markdown/testdata/blog/test.md b/middleware/markdown/testdata/blog/test.md new file mode 100644 index 000000000..7ec766160 --- /dev/null +++ b/middleware/markdown/testdata/blog/test.md @@ -0,0 +1,15 @@ +--- +title: Markdown test +variables: + sitename: A Caddy website +--- + +## Welcome on the blog + +Body + +``` go +func getTrue() bool { + return true +} +``` diff --git a/middleware/markdown/testdata/header.html b/middleware/markdown/testdata/header.html new file mode 100644 index 000000000..78e5a6a48 --- /dev/null +++ b/middleware/markdown/testdata/header.html @@ -0,0 +1 @@ +

Header

diff --git a/middleware/markdown/testdata/log/test.md b/middleware/markdown/testdata/log/test.md new file mode 100644 index 000000000..7ec766160 --- /dev/null +++ b/middleware/markdown/testdata/log/test.md @@ -0,0 +1,15 @@ +--- +title: Markdown test +variables: + sitename: A Caddy website +--- + +## Welcome on the blog + +Body + +``` go +func getTrue() bool { + return true +} +``` diff --git a/middleware/markdown/testdata/markdown_tpl.html b/middleware/markdown/testdata/markdown_tpl.html new file mode 100644 index 000000000..7c6978500 --- /dev/null +++ b/middleware/markdown/testdata/markdown_tpl.html @@ -0,0 +1,11 @@ + + + +{{.Doc.title}} + + +{{.Include "header.html"}} +Welcome to {{.Doc.sitename}}! +{{.Doc.body}} + + diff --git a/middleware/templates/templates.go b/middleware/templates/templates.go index fc4f8242e..a699d0026 100644 --- a/middleware/templates/templates.go +++ b/middleware/templates/templates.go @@ -31,7 +31,7 @@ func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error for _, ext := range rule.Extensions { if reqExt == ext { // 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 tpl, err := template.ParseFiles(filepath.Join(t.Root, fpath)) diff --git a/middleware/templates/templates_test.go b/middleware/templates/templates_test.go new file mode 100644 index 000000000..5c283390f --- /dev/null +++ b/middleware/templates/templates_test.go @@ -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 := `test page

Header title

+ +` + + 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 = `img

Header title

+ +` + + 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 = `root

Header title

+ +` + + if respBody != expectedBody { + t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody) + } +} diff --git a/middleware/templates/testdata/header.html b/middleware/templates/testdata/header.html new file mode 100644 index 000000000..9c96e0e37 --- /dev/null +++ b/middleware/templates/testdata/header.html @@ -0,0 +1 @@ +

Header title

diff --git a/middleware/templates/testdata/images/header.html b/middleware/templates/testdata/images/header.html new file mode 100644 index 000000000..9c96e0e37 --- /dev/null +++ b/middleware/templates/testdata/images/header.html @@ -0,0 +1 @@ +

Header title

diff --git a/middleware/templates/testdata/images/img.htm b/middleware/templates/testdata/images/img.htm new file mode 100644 index 000000000..865a73809 --- /dev/null +++ b/middleware/templates/testdata/images/img.htm @@ -0,0 +1 @@ +img{{.Include "header.html"}} diff --git a/middleware/templates/testdata/photos/test.html b/middleware/templates/testdata/photos/test.html new file mode 100644 index 000000000..e2e95e133 --- /dev/null +++ b/middleware/templates/testdata/photos/test.html @@ -0,0 +1 @@ +test page{{.Include "../header.html"}} diff --git a/middleware/templates/testdata/root.html b/middleware/templates/testdata/root.html new file mode 100644 index 000000000..e1720e726 --- /dev/null +++ b/middleware/templates/testdata/root.html @@ -0,0 +1 @@ +root{{.Include "header.html"}}