diff --git a/middleware/markdown/markdown.go b/middleware/markdown/markdown.go index 6b09db46..3a33dc39 100644 --- a/middleware/markdown/markdown.go +++ b/middleware/markdown/markdown.go @@ -3,10 +3,10 @@ package markdown import ( - "io/ioutil" "net/http" "os" "path" + "strings" "text/template" "time" @@ -83,17 +83,22 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error // URL points to, and pass that in to any possible template invocations, // so that templates can customize the look and feel of a directory. fdp, err := md.FileSys.Open(fpath) - if err != nil { - if os.IsPermission(err) { - return http.StatusForbidden, err - } + switch { + case err == nil: // nop + case os.IsPermission(err): + return http.StatusForbidden, err + case os.IsExist(err): + return http.StatusNotFound, nil + default: // did we run out of FD? return http.StatusInternalServerError, err } + defer fdp.Close() // Grab a possible set of directory entries. Note, we do not check // for errors here (unreadable directory, for example). It may // still be useful to have a directory template file, without the - // directory contents being present. + // directory contents being present. Note, the directory's last + // modification is also present here (entry "."). dirents, _ = fdp.Readdir(-1) for _, d := range dirents { lastModTime = latest(lastModTime, d.ModTime()) @@ -103,53 +108,45 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error fpath = idx } - // If supported extension, process it - if _, ok := cfg.Extensions[path.Ext(fpath)]; ok { - 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 - } - lastModTime = latest(lastModTime, fs.ModTime()) - - 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 := cfg.Markdown(fpath, body, dirents, ctx) - if err != nil { - return http.StatusInternalServerError, err - } - - // TODO(weingart): move template execution here, something like: - // - // html, err = md.execTemplate(cfg, html, ctx) - // if err != nil { - // return http.StatusInternalServerError, err - // } - - middleware.SetLastModifiedHeader(w, lastModTime) - if r.Method == http.MethodGet { - w.Write(html) - } - return http.StatusOK, nil + // If not supported extension, pass on it + if _, ok := cfg.Extensions[path.Ext(fpath)]; !ok { + return md.Next.ServeHTTP(w, r) } - // Didn't qualify to serve as markdown; pass-thru - return md.Next.ServeHTTP(w, r) + // At this point we have a supported extension/markdown + f, err := md.FileSys.Open(fpath) + switch { + case err == nil: // nop + case os.IsPermission(err): + return http.StatusForbidden, err + case os.IsExist(err): + return http.StatusNotFound, nil + default: // did we run out of FD? + return http.StatusInternalServerError, err + } + defer f.Close() + + if fs, err := f.Stat(); err != nil { + return http.StatusGone, nil + } else { + lastModTime = latest(lastModTime, fs.ModTime()) + } + + ctx := middleware.Context{ + Root: md.FileSys, + Req: r, + URL: r.URL, + } + html, err := cfg.Markdown(title(fpath), f, dirents, ctx) + if err != nil { + return http.StatusInternalServerError, err + } + + middleware.SetLastModifiedHeader(w, lastModTime) + if r.Method == http.MethodGet { + w.Write(html) + } + return http.StatusOK, nil } // latest returns the latest time.Time @@ -164,3 +161,8 @@ func latest(t ...time.Time) time.Time { return last } + +// title gives a backup generated title for a page +func title(p string) string { + return strings.TrimRight(path.Base(p), path.Ext(p)) +} diff --git a/middleware/markdown/markdown_test.go b/middleware/markdown/markdown_test.go index 1e86e248..382c8e12 100644 --- a/middleware/markdown/markdown_test.go +++ b/middleware/markdown/markdown_test.go @@ -2,12 +2,14 @@ package markdown import ( "bufio" + "io/ioutil" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" + "text/template" "time" "github.com/mholt/caddy/middleware" @@ -217,3 +219,12 @@ func equalStrings(s1, s2 string) bool { } return true } + +func setDefaultTemplate(filename string) *template.Template { + buf, err := ioutil.ReadFile(filename) + if err != nil { + return nil + } + + return template.Must(GetDefaultTemplate().Parse(string(buf))) +} diff --git a/middleware/markdown/process.go b/middleware/markdown/process.go index 1dd468d3..f72b5390 100644 --- a/middleware/markdown/process.go +++ b/middleware/markdown/process.go @@ -1,9 +1,10 @@ package markdown import ( + "io" "io/ioutil" "os" - "path/filepath" + // "path/filepath" "github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware/markdown/metadata" @@ -33,8 +34,13 @@ func (f FileInfo) Summarize(wordcount int) (string, error) { // Markdown processes the contents of a page in b. It parses the metadata // (if any) and uses the template (if found). -func (c *Config) Markdown(requestPath string, b []byte, dirents []os.FileInfo, ctx middleware.Context) ([]byte, error) { - parser := metadata.GetParser(b) +func (c *Config) Markdown(title string, r io.Reader, dirents []os.FileInfo, ctx middleware.Context) ([]byte, error) { + body, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + + parser := metadata.GetParser(body) markdown := parser.Markdown() mdata := parser.Metadata() @@ -44,19 +50,16 @@ func (c *Config) Markdown(requestPath string, b []byte, dirents []os.FileInfo, c extns |= blackfriday.EXTENSION_FENCED_CODE extns |= blackfriday.EXTENSION_STRIKETHROUGH extns |= blackfriday.EXTENSION_DEFINITION_LISTS - markdown = blackfriday.Markdown(markdown, c.Renderer, extns) + html := blackfriday.Markdown(markdown, c.Renderer, extns) // set it as body for template - mdata.Variables["body"] = string(markdown) + mdata.Variables["body"] = string(html) // fixup title - title := mdata.Title - if title == "" { - title = filepath.Base(requestPath) - var extension = filepath.Ext(requestPath) - title = title[0 : len(title)-len(extension)] + mdata.Variables["title"] = mdata.Title + if mdata.Variables["title"] == "" { + mdata.Variables["title"] = title } - mdata.Variables["title"] = title // massage possible files files := []FileInfo{} diff --git a/middleware/markdown/summary/render.go b/middleware/markdown/summary/render.go index fb11841c..5e3bfe19 100644 --- a/middleware/markdown/summary/render.go +++ b/middleware/markdown/summary/render.go @@ -9,23 +9,26 @@ import ( // Ensure we implement the Blackfriday Markdown Renderer interface var _ blackfriday.Renderer = (*Renderer)(nil) +// Renderer is a plain-text Markdown renderer that implements the +// blackfriday.Renderer interface. Many of the required methods are +// stubs with no output. type Renderer struct{} // Blocklevel callbacks -// BlockCode is the code tag callback. +// Stub BlockCode is the code tag callback. func (r Renderer) BlockCode(out *bytes.Buffer, text []byte, land string) {} -// BlockQuote is teh quote tag callback. +// Stub BlockQuote is teh quote tag callback. func (r Renderer) BlockQuote(out *bytes.Buffer, text []byte) {} -// BlockHtml is the HTML tag callback. +// Stub BlockHtml is the HTML tag callback. func (r Renderer) BlockHtml(out *bytes.Buffer, text []byte) {} -// Header is the header tag callback. +// Stub Header is the header tag callback. func (r Renderer) Header(out *bytes.Buffer, text func() bool, level int, id string) {} -// HRule is the horizontal rule tag callback. +// Stub HRule is the horizontal rule tag callback. func (r Renderer) HRule(out *bytes.Buffer) {} // List is the list tag callback. @@ -39,10 +42,11 @@ func (r Renderer) List(out *bytes.Buffer, text func() bool, flags int) { out.Write([]byte{' '}) } -// ListItem is the list item tag callback. +// Stub ListItem is the list item tag callback. func (r Renderer) ListItem(out *bytes.Buffer, text []byte, flags int) {} -// Paragraph is the paragraph tag callback. +// Paragraph is the paragraph tag callback. This renders simple paragraph text +// into plain text, such that summaries can be easily generated. func (r Renderer) Paragraph(out *bytes.Buffer, text func() bool) { marker := out.Len() if !text() { @@ -51,93 +55,98 @@ func (r Renderer) Paragraph(out *bytes.Buffer, text func() bool) { out.Write([]byte{' '}) } -// Table is the table tag callback. +// Stub Table is the table tag callback. func (r Renderer) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {} -// TableRow is the table row tag callback. +// Stub TableRow is the table row tag callback. func (r Renderer) TableRow(out *bytes.Buffer, text []byte) {} -// TableHeaderCell is the table header cell tag callback. +// Stub TableHeaderCell is the table header cell tag callback. func (r Renderer) TableHeaderCell(out *bytes.Buffer, text []byte, flags int) {} -// TableCell is the table cell tag callback. +// Stub TableCell is the table cell tag callback. func (r Renderer) TableCell(out *bytes.Buffer, text []byte, flags int) {} -// Footnotes is the foot notes tag callback. +// Stub Footnotes is the foot notes tag callback. func (r Renderer) Footnotes(out *bytes.Buffer, text func() bool) {} -// FootnoteItem is the footnote item tag callback. +// Stub FootnoteItem is the footnote item tag callback. func (r Renderer) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {} -// TitleBlock is the title tag callback. +// Stub TitleBlock is the title tag callback. func (r Renderer) TitleBlock(out *bytes.Buffer, text []byte) {} // Spanlevel callbacks -// AutoLink is the autolink tag callback. +// Stub AutoLink is the autolink tag callback. func (r Renderer) AutoLink(out *bytes.Buffer, link []byte, kind int) {} -// CodeSpan is the code span tag callback. +// CodeSpan is the code span tag callback. Outputs a simple Markdown version +// of the code span. func (r Renderer) CodeSpan(out *bytes.Buffer, text []byte) { out.Write([]byte("`")) out.Write(text) out.Write([]byte("`")) } -// DoubleEmphasis is the double emphasis tag callback. +// DoubleEmphasis is the double emphasis tag callback. Outputs a simple +// plain-text version of the input. func (r Renderer) DoubleEmphasis(out *bytes.Buffer, text []byte) { out.Write(text) } -// Emphasis is the emphasis tag callback. +// Emphasis is the emphasis tag callback. Outputs a simple plain-text +// version of the input. func (r Renderer) Emphasis(out *bytes.Buffer, text []byte) { out.Write(text) } -// Image is the image tag callback. +// Stub Image is the image tag callback. func (r Renderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {} -// LineBreak is the line break tag callback. +// Stub LineBreak is the line break tag callback. func (r Renderer) LineBreak(out *bytes.Buffer) {} -// Link is the link tag callback. +// Link is the link tag callback. Outputs a sipmle plain-text version +// of the input. func (r Renderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) { out.Write(content) } -// RawHtmlTag is the raw HTML tag callback. +// Stub RawHtmlTag is the raw HTML tag callback. func (r Renderer) RawHtmlTag(out *bytes.Buffer, tag []byte) {} -// TripleEmphasis is the triple emphasis tag callback. +// TripleEmphasis is the triple emphasis tag callback. Outputs a simple plain-text +// version of the input. func (r Renderer) TripleEmphasis(out *bytes.Buffer, text []byte) { out.Write(text) } -// StrikeThrough is the strikethrough tag callback. +// Stub StrikeThrough is the strikethrough tag callback. func (r Renderer) StrikeThrough(out *bytes.Buffer, text []byte) {} -// FootnoteRef is the footnote ref tag callback. +// Stub FootnoteRef is the footnote ref tag callback. func (r Renderer) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {} // Lowlevel callbacks -// Entity callback. +// Entity callback. Outputs a simple plain-text version of the input. func (r Renderer) Entity(out *bytes.Buffer, entity []byte) { out.Write(entity) } -// NormalText callback. +// NormalText callback. Outputs a simple plain-text version of the input. func (r Renderer) NormalText(out *bytes.Buffer, text []byte) { out.Write(text) } // Header and footer -// DocumentHeader callback. +// Stub DocumentHeader callback. func (r Renderer) DocumentHeader(out *bytes.Buffer) {} -// DocumentFooter callback. +// Stub DocumentFooter callback. func (r Renderer) DocumentFooter(out *bytes.Buffer) {} -// GetFlags returns zero. +// Stub GetFlags returns zero. func (r Renderer) GetFlags() int { return 0 } diff --git a/middleware/markdown/summary/summary.go b/middleware/markdown/summary/summary.go index 2527de03..a4d14540 100644 --- a/middleware/markdown/summary/summary.go +++ b/middleware/markdown/summary/summary.go @@ -6,6 +6,8 @@ import ( "github.com/russross/blackfriday" ) +// Markdown formats input using a plain-text renderer, and +// then returns up to the first `wordcount` words as a summary. func Markdown(input []byte, wordcount int) []byte { words := bytes.Fields(blackfriday.Markdown(input, Renderer{}, 0)) if wordcount > len(words) { diff --git a/middleware/markdown/template.go b/middleware/markdown/template.go index 76d3a99d..10ea31c5 100644 --- a/middleware/markdown/template.go +++ b/middleware/markdown/template.go @@ -3,7 +3,6 @@ package markdown import ( "bytes" "io/ioutil" - // "os" "text/template" "github.com/mholt/caddy/middleware" @@ -46,15 +45,6 @@ func execTemplate(c *Config, mdata metadata.Metadata, files []FileInfo, ctx midd return b.Bytes(), nil } -func setDefaultTemplate(filename string) *template.Template { - buf, err := ioutil.ReadFile(filename) - if err != nil { - return nil - } - - return template.Must(GetDefaultTemplate().Parse(string(buf))) -} - func SetTemplate(t *template.Template, name, filename string) error { // Read template