From 027f697fdf781a7b3d0795e9a89c66305f85ab0a Mon Sep 17 00:00:00 2001 From: Tobias Weingartner Date: Sun, 10 Apr 2016 17:20:09 -0700 Subject: [PATCH 01/19] Revamp markdown processing. Nuke pre-generation. This may come back in the form of a more general caching layer at some later stage. Nuke index generation. This should likely be rethought and re-implemented. --- caddy/setup/markdown.go | 63 ++-------- caddy/setup/markdown_test.go | 107 +++-------------- middleware/context.go | 6 +- middleware/markdown/generator.go | 146 ----------------------- middleware/markdown/markdown.go | 125 ++++++-------------- middleware/markdown/markdown_test.go | 128 +++++--------------- middleware/markdown/metadata.go | 136 +++------------------ middleware/markdown/metadata_json.go | 45 +++++++ middleware/markdown/metadata_toml.go | 40 +++++++ middleware/markdown/metadata_yaml.go | 41 +++++++ middleware/markdown/page.go | 169 --------------------------- middleware/markdown/process.go | 120 ++++--------------- middleware/markdown/renderer.go | 139 ---------------------- middleware/markdown/watcher.go | 42 ------- middleware/markdown/watcher_test.go | 50 -------- 15 files changed, 264 insertions(+), 1093 deletions(-) delete mode 100644 middleware/markdown/generator.go create mode 100644 middleware/markdown/metadata_json.go create mode 100644 middleware/markdown/metadata_toml.go create mode 100644 middleware/markdown/metadata_yaml.go delete mode 100644 middleware/markdown/page.go delete mode 100644 middleware/markdown/renderer.go delete mode 100644 middleware/markdown/watcher.go delete mode 100644 middleware/markdown/watcher_test.go diff --git a/caddy/setup/markdown.go b/caddy/setup/markdown.go index 27ac14a5..08a05567 100644 --- a/caddy/setup/markdown.go +++ b/caddy/setup/markdown.go @@ -2,9 +2,7 @@ package setup import ( "net/http" - "path" "path/filepath" - "strings" "github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware/markdown" @@ -25,25 +23,6 @@ func Markdown(c *Controller) (middleware.Middleware, error) { IndexFiles: []string{"index.md"}, } - // Sweep the whole path at startup to at least generate link index, maybe generate static site - c.Startup = append(c.Startup, func() error { - for i := range mdconfigs { - cfg := mdconfigs[i] - - // Generate link index and static files (if enabled) - if err := markdown.GenerateStatic(md, cfg); err != nil { - return err - } - - // Watch file changes for static site generation if not in development mode. - if !cfg.Development { - markdown.Watch(md, cfg, markdown.DefaultInterval) - } - } - - return nil - }) - return func(next middleware.Handler) middleware.Handler { md.Next = next return md @@ -55,9 +34,9 @@ func markdownParse(c *Controller) ([]*markdown.Config, error) { for c.Next() { md := &markdown.Config{ - Renderer: blackfriday.HtmlRenderer(0, "", ""), - Templates: make(map[string]string), - StaticFiles: make(map[string]string), + Renderer: blackfriday.HtmlRenderer(0, "", ""), + Extensions: make(map[string]struct{}), + Templates: make(map[string]string), } // Get the path scope @@ -80,7 +59,9 @@ func markdownParse(c *Controller) ([]*markdown.Config, error) { // If no extensions were specified, assume some defaults if len(md.Extensions) == 0 { - md.Extensions = []string{".md", ".markdown", ".mdown"} + md.Extensions[".md"] = struct{}{} + md.Extensions[".markdown"] = struct{}{} + md.Extensions[".mdown"] = struct{}{} } mdconfigs = append(mdconfigs, md) @@ -92,11 +73,9 @@ func markdownParse(c *Controller) ([]*markdown.Config, error) { func loadParams(c *Controller, mdc *markdown.Config) error { switch c.Val() { case "ext": - exts := c.RemainingArgs() - if len(exts) == 0 { - return c.ArgErr() + for _, ext := range c.RemainingArgs() { + mdc.Extensions[ext] = struct{}{} } - mdc.Extensions = append(mdc.Extensions, exts...) return nil case "css": if !c.NextArg() { @@ -113,7 +92,7 @@ func loadParams(c *Controller, mdc *markdown.Config) error { case "template": tArgs := c.RemainingArgs() switch len(tArgs) { - case 0: + default: return c.ArgErr() case 1: if _, ok := mdc.Templates[markdown.DefaultTemplate]; ok { @@ -126,31 +105,7 @@ func loadParams(c *Controller, mdc *markdown.Config) error { fpath := filepath.ToSlash(filepath.Clean(c.Root + string(filepath.Separator) + tArgs[1])) mdc.Templates[tArgs[0]] = fpath return nil - default: - return c.ArgErr() } - case "sitegen": - if c.NextArg() { - mdc.StaticDir = path.Join(c.Root, c.Val()) - } else { - mdc.StaticDir = path.Join(c.Root, markdown.DefaultStaticDir) - } - if c.NextArg() { - // only 1 argument allowed - return c.ArgErr() - } - return nil - case "dev": - if c.NextArg() { - mdc.Development = strings.ToLower(c.Val()) == "true" - } else { - mdc.Development = true - } - if c.NextArg() { - // only 1 argument allowed - return c.ArgErr() - } - return nil default: return c.Err("Expected valid markdown configuration property") } diff --git a/caddy/setup/markdown_test.go b/caddy/setup/markdown_test.go index e562678e..18af6254 100644 --- a/caddy/setup/markdown_test.go +++ b/caddy/setup/markdown_test.go @@ -1,15 +1,9 @@ package setup import ( - "bytes" "fmt" - "io/ioutil" - "net/http" - "os" - "path/filepath" "testing" - "github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware/markdown" ) @@ -37,84 +31,14 @@ func TestMarkdown(t *testing.T) { if myHandler.Configs[0].PathScope != "/blog" { t.Errorf("Expected /blog as the Path Scope") } - if fmt.Sprint(myHandler.Configs[0].Extensions) != fmt.Sprint([]string{".md", ".markdown", ".mdown"}) { - t.Errorf("Expected .md, .markdown, and .mdown as default extensions") + if len(myHandler.Configs[0].Extensions) != 3 { + t.Error("Expected 3 markdown extensions") } -} - -func TestMarkdownStaticGen(t *testing.T) { - c := NewTestController(`markdown /blog { - ext .md - template tpl_with_include.html - sitegen -}`) - - c.Root = "./testdata" - mid, err := Markdown(c) - - if err != nil { - t.Errorf("Expected no errors, got: %v", err) - } - - if mid == nil { - t.Fatal("Expected middleware, was nil instead") - } - - for _, start := range c.Startup { - err := start() - if err != nil { - t.Errorf("Startup error: %v", err) + for _, key := range []string{".md", ".markdown", ".mdown"} { + if ext, ok := myHandler.Configs[0].Extensions[key]; !ok { + t.Errorf("Expected extensions to contain %v", ext) } } - - next := middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - t.Fatalf("Next shouldn't be called") - return 0, nil - }) - hndlr := mid(next) - mkdwn, ok := hndlr.(markdown.Markdown) - if !ok { - t.Fatalf("Was expecting a markdown.Markdown but got %T", hndlr) - } - - expectedStaticFiles := map[string]string{"/blog/first_post.md": "testdata/generated_site/blog/first_post.md/index.html"} - if fmt.Sprint(expectedStaticFiles) != fmt.Sprint(mkdwn.Configs[0].StaticFiles) { - t.Fatalf("Test expected StaticFiles to be %s, but got %s", - fmt.Sprint(expectedStaticFiles), fmt.Sprint(mkdwn.Configs[0].StaticFiles)) - } - - filePath := "testdata/generated_site/blog/first_post.md/index.html" - if _, err := os.Stat(filePath); err != nil { - t.Fatalf("An error occured when getting the file information: %v", err) - } - - html, err := ioutil.ReadFile(filePath) - if err != nil { - t.Fatalf("An error occured when getting the file content: %v", err) - } - - expectedBody := []byte(` - - -first_post - - -

Header title

- -

Test h1

- - - -`) - - if !bytes.Equal(html, expectedBody) { - t.Fatalf("Expected file content: %s got: %s", string(expectedBody), string(html)) - } - - fp := filepath.Join(c.Root, markdown.DefaultStaticDir) - if err = os.RemoveAll(fp); err != nil { - t.Errorf("Error while removing the generated static files: %v", err) - } } func TestMarkdownParse(t *testing.T) { @@ -129,20 +53,23 @@ func TestMarkdownParse(t *testing.T) { css /resources/css/blog.css js /resources/js/blog.js }`, false, []markdown.Config{{ - PathScope: "/blog", - Extensions: []string{".md", ".txt"}, - Styles: []string{"/resources/css/blog.css"}, - Scripts: []string{"/resources/js/blog.js"}, + PathScope: "/blog", + Extensions: map[string]struct{}{ + ".md": struct{}{}, + ".txt": struct{}{}, + }, + Styles: []string{"/resources/css/blog.css"}, + Scripts: []string{"/resources/js/blog.js"}, }}}, {`markdown /blog { ext .md template tpl_with_include.html - sitegen }`, false, []markdown.Config{{ - PathScope: "/blog", - Extensions: []string{".md"}, - Templates: map[string]string{markdown.DefaultTemplate: "testdata/tpl_with_include.html"}, - StaticDir: markdown.DefaultStaticDir, + PathScope: "/blog", + Extensions: map[string]struct{}{ + ".md": struct{}{}, + }, + Templates: map[string]string{markdown.DefaultTemplate: "testdata/tpl_with_include.html"}, }}}, } for i, test := range tests { diff --git a/middleware/context.go b/middleware/context.go index 3facb953..c43aa293 100644 --- a/middleware/context.go +++ b/middleware/context.go @@ -184,7 +184,11 @@ func (c Context) Markdown(filename string) (string, error) { return "", err } renderer := blackfriday.HtmlRenderer(0, "", "") - extns := blackfriday.EXTENSION_TABLES | blackfriday.EXTENSION_FENCED_CODE | blackfriday.EXTENSION_STRIKETHROUGH | blackfriday.EXTENSION_DEFINITION_LISTS + extns := 0 + extns |= blackfriday.EXTENSION_TABLES + extns |= blackfriday.EXTENSION_FENCED_CODE + extns |= blackfriday.EXTENSION_STRIKETHROUGH + extns |= blackfriday.EXTENSION_DEFINITION_LISTS markdown := blackfriday.Markdown([]byte(body), renderer, extns) return string(markdown), nil diff --git a/middleware/markdown/generator.go b/middleware/markdown/generator.go deleted file mode 100644 index d218f22b..00000000 --- a/middleware/markdown/generator.go +++ /dev/null @@ -1,146 +0,0 @@ -package markdown - -import ( - "crypto/md5" - "encoding/hex" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "os" - "path/filepath" - "strings" - "sync" - - "github.com/mholt/caddy/middleware" -) - -// GenerateStatic generate static files and link index from markdowns. -// It only generates static files if it is enabled (cfg.StaticDir -// must be set). -func GenerateStatic(md Markdown, cfg *Config) error { - // Generate links since they may be needed, even without sitegen. - generated, err := generateLinks(md, cfg) - if err != nil { - return err - } - - // No new file changes, return. - if !generated { - return nil - } - - // If static site generation is enabled, generate the site. - if cfg.StaticDir != "" { - if err := generateStaticHTML(md, cfg); err != nil { - return err - } - } - - return nil -} - -type linkGenerator struct { - gens map[*Config]*linkGen - sync.Mutex -} - -var generator = linkGenerator{gens: make(map[*Config]*linkGen)} - -// generateLinks generates links to all markdown files ordered by newest date. -// This blocks until link generation is done. When called by multiple goroutines, -// the first caller starts the generation and others only wait. -// It returns if generation is done and any error that occurred. -func generateLinks(md Markdown, cfg *Config) (bool, error) { - generator.Lock() - - // if link generator exists for config and running, wait. - if g, ok := generator.gens[cfg]; ok { - if g.started() { - g.addWaiter() - generator.Unlock() - g.Wait() - // another goroutine has done the generation. - return false, g.lastErr - } - } - - g := &linkGen{} - generator.gens[cfg] = g - generator.Unlock() - - generated := g.generateLinks(md, cfg) - g.discardWaiters() - return generated, g.lastErr -} - -// generateStaticHTML generates static HTML files from markdowns. -func generateStaticHTML(md Markdown, cfg *Config) error { - // If generated site already exists, clear it out - _, err := os.Stat(cfg.StaticDir) - if err == nil { - err := os.RemoveAll(cfg.StaticDir) - if err != nil { - return err - } - } - - fp := filepath.Join(md.Root, cfg.PathScope) - - return filepath.Walk(fp, func(path string, info os.FileInfo, err error) error { - for _, ext := range cfg.Extensions { - if !info.IsDir() && strings.HasSuffix(info.Name(), ext) { - // Load the file - body, err := ioutil.ReadFile(path) - if err != nil { - return err - } - - // Get the relative path as if it were a HTTP request, - // then prepend with "/" (like a real HTTP request) - reqPath, err := filepath.Rel(md.Root, path) - if err != nil { - return err - } - reqPath = filepath.ToSlash(reqPath) - reqPath = "/" + reqPath - - // Create empty requests and url to cater for template values. - req, _ := http.NewRequest("", "/", nil) - urlVar, _ := url.Parse("/") - - // Generate the static file - ctx := middleware.Context{Root: md.FileSys, Req: req, URL: urlVar} - _, err = md.Process(cfg, reqPath, body, ctx) - if err != nil { - return err - } - - break // don't try other file extensions - } - } - return nil - }) -} - -// computeDirHash computes an hash on static directory of c. -func computeDirHash(md Markdown, c *Config) (string, error) { - dir := filepath.Join(md.Root, c.PathScope) - if _, err := os.Stat(dir); err != nil { - return "", err - } - - hashString := "" - err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if !info.IsDir() && c.IsValidExt(filepath.Ext(path)) { - hashString += fmt.Sprintf("%v%v%v%v", info.ModTime(), info.Name(), info.Size(), path) - } - return nil - }) - if err != nil { - return "", err - } - - sum := md5.Sum([]byte(hashString)) - return hex.EncodeToString(sum[:]), nil -} diff --git a/middleware/markdown/markdown.go b/middleware/markdown/markdown.go index 9f14ca8d..5e4a2cb5 100644 --- a/middleware/markdown/markdown.go +++ b/middleware/markdown/markdown.go @@ -7,8 +7,7 @@ import ( "log" "net/http" "os" - "strings" - "sync" + "path" "github.com/mholt/caddy/middleware" "github.com/russross/blackfriday" @@ -52,7 +51,7 @@ type Config struct { PathScope string // List of extensions to consider as markdown files - Extensions []string + Extensions map[string]struct{} // List of style sheets to load for each markdown file Styles []string @@ -62,34 +61,6 @@ type Config struct { // 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. @@ -104,69 +75,39 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error fpath = idx } - for _, ext := range cfg.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 + // 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 } - - fs, err := f.Stat() - if err != nil { - return http.StatusNotFound, nil - } - - // if development is set, scan directory for file changes for links. - if cfg.Development { - if err := GenerateStatic(md, cfg); err != nil { - log.Printf("[ERROR] markdown: on-demand site generation error: %v", err) - } - } - - cfg.RLock() - filepath, ok := cfg.StaticFiles[fpath] - cfg.RUnlock() - // if static site is generated, attempt to use it - if 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().Before(fs1.ModTime()) { - if html, err := ioutil.ReadFile(filepath); err == nil { - middleware.SetLastModifiedHeader(w, fs1.ModTime()) - 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(cfg, fpath, body, ctx) - if err != nil { - return http.StatusInternalServerError, err - } - - middleware.SetLastModifiedHeader(w, fs.ModTime()) - w.Write(html) - return http.StatusOK, nil + return http.StatusNotFound, nil } + + fs, err := f.Stat() + if err != nil { + 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(cfg, fpath, body, ctx) + if err != nil { + return http.StatusInternalServerError, err + } + + middleware.SetLastModifiedHeader(w, fs.ModTime()) + w.Write(html) + return http.StatusOK, nil } } diff --git a/middleware/markdown/markdown_test.go b/middleware/markdown/markdown_test.go index e8796d11..fbfb7379 100644 --- a/middleware/markdown/markdown_test.go +++ b/middleware/markdown/markdown_test.go @@ -2,12 +2,10 @@ package markdown import ( "bufio" - "log" "net/http" "net/http/httptest" "os" "strings" - "sync" "testing" "time" @@ -23,54 +21,46 @@ func TestMarkdown(t *testing.T) { FileSys: http.Dir("./testdata"), Configs: []*Config{ { - Renderer: blackfriday.HtmlRenderer(0, "", ""), - PathScope: "/blog", - Extensions: []string{".md"}, - Styles: []string{}, - Scripts: []string{}, - Templates: templates, - StaticDir: DefaultStaticDir, - StaticFiles: make(map[string]string), + Renderer: blackfriday.HtmlRenderer(0, "", ""), + PathScope: "/blog", + Extensions: map[string]struct{}{ + ".md": struct{}{}, + }, + Styles: []string{}, + Scripts: []string{}, + Templates: templates, }, { - Renderer: blackfriday.HtmlRenderer(0, "", ""), - PathScope: "/docflags", - Extensions: []string{".md"}, - Styles: []string{}, - Scripts: []string{}, + Renderer: blackfriday.HtmlRenderer(0, "", ""), + PathScope: "/docflags", + Extensions: map[string]struct{}{ + ".md": struct{}{}, + }, + Styles: []string{}, + Scripts: []string{}, Templates: map[string]string{ DefaultTemplate: "testdata/docflags/template.txt", }, - StaticDir: DefaultStaticDir, - StaticFiles: make(map[string]string), }, { - 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), - StaticDir: DefaultStaticDir, - StaticFiles: make(map[string]string), - }, - { - Renderer: blackfriday.HtmlRenderer(0, "", ""), - PathScope: "/og", - Extensions: []string{".md"}, - Styles: []string{}, - Scripts: []string{}, - Templates: templates, - StaticDir: "testdata/og_static", - StaticFiles: map[string]string{"/og/first.md": "testdata/og_static/og/first.md/index.html"}, - Links: []PageLink{ - { - Title: "first", - Summary: "", - Date: time.Now(), - URL: "/og/first.md", - }, + Renderer: blackfriday.HtmlRenderer(0, "", ""), + PathScope: "/log", + Extensions: map[string]struct{}{ + ".md": struct{}{}, }, + 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), + }, + { + Renderer: blackfriday.HtmlRenderer(0, "", ""), + PathScope: "/og", + Extensions: map[string]struct{}{ + ".md": struct{}{}, + }, + Styles: []string{}, + Scripts: []string{}, + Templates: templates, }, }, IndexFiles: []string{"index.html"}, @@ -80,14 +70,6 @@ func TestMarkdown(t *testing.T) { }), } - for i := range md.Configs { - c := md.Configs[i] - if err := GenerateStatic(md, c); err != nil { - t.Fatalf("Error: %v", err) - } - Watch(md, c, time.Millisecond*100) - } - req, err := http.NewRequest("GET", "/blog/test.md", nil) if err != nil { t.Fatalf("Could not create HTTP request: %v", err) @@ -219,52 +201,6 @@ Welcome to title! if !equalStrings(respBody, expectedBody) { t.Fatalf("Expected body: %v got: %v", expectedBody, respBody) } - - expectedLinks := []string{ - "/blog/test.md", - "/docflags/test.md", - "/log/test.md", - } - - for i, c := range md.Configs[:2] { - log.Printf("Test number: %d, configuration links: %v, config: %v", i, c.Links, c) - if c.Links[0].URL != expectedLinks[i] { - t.Fatalf("Expected %v got %v", expectedLinks[i], c.Links[0].URL) - } - } - - // attempt to trigger race conditions - var w sync.WaitGroup - f := func() { - 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) - w.Done() - } - for i := 0; i < 5; i++ { - w.Add(1) - go f() - } - w.Wait() - - f = func() { - GenerateStatic(md, md.Configs[0]) - w.Done() - } - for i := 0; i < 5; i++ { - w.Add(1) - go f() - } - w.Wait() - - if err = os.RemoveAll(DefaultStaticDir); err != nil { - t.Errorf("Error while removing the generated static files: %v", err) - } - } func equalStrings(s1, s2 string) bool { diff --git a/middleware/markdown/metadata.go b/middleware/markdown/metadata.go index 9b5c416a..20a30e18 100644 --- a/middleware/markdown/metadata.go +++ b/middleware/markdown/metadata.go @@ -2,12 +2,16 @@ package markdown import ( "bytes" - "encoding/json" "fmt" "time" +) - "github.com/BurntSushi/toml" - "gopkg.in/yaml.v2" +var ( + // Date format YYYY-MM-DD HH:MM:SS or YYYY-MM-DD + timeLayout = []string{ + `2006-01-02 15:04:05`, + `2006-01-02`, + } ) // Metadata stores a page's metadata @@ -30,6 +34,8 @@ type Metadata struct { // load loads parsed values in parsedMap into Metadata func (m *Metadata) load(parsedMap map[string]interface{}) { + + // Pull top level things out if title, ok := parsedMap["title"]; ok { m.Title, _ = title.(string) } @@ -37,17 +43,21 @@ func (m *Metadata) load(parsedMap map[string]interface{}) { m.Template, _ = template.(string) } if date, ok := parsedMap["date"].(string); ok { - if t, err := time.Parse(timeLayout, date); err == nil { - m.Date = t + for _, layout := range timeLayout { + if t, err := time.Parse(layout, date); err == nil { + m.Date = t + break + } } } - // store everything as a variable + + // Store everything as a flag or variable for key, val := range parsedMap { switch v := val.(type) { - case string: - m.Variables[key] = v case bool: m.Flags[key] = v + case string: + m.Variables[key] = v } } } @@ -70,116 +80,6 @@ type MetadataParser interface { Metadata() Metadata } -// JSONMetadataParser is the MetadataParser for JSON -type JSONMetadataParser struct { - metadata Metadata -} - -// Parse the metadata -func (j *JSONMetadataParser) Parse(b []byte) ([]byte, error) { - b, markdown, err := extractMetadata(j, b) - if err != nil { - return markdown, err - } - m := make(map[string]interface{}) - - // Read the preceding JSON object - decoder := json.NewDecoder(bytes.NewReader(b)) - if err := decoder.Decode(&m); err != nil { - return markdown, err - } - j.metadata.load(m) - - return markdown, nil -} - -// Metadata returns parsed metadata. It should be called -// only after a call to Parse returns without error. -func (j *JSONMetadataParser) Metadata() Metadata { - return j.metadata -} - -// Opening returns the opening identifier JSON metadata -func (j *JSONMetadataParser) Opening() []byte { - return []byte("{") -} - -// Closing returns the closing identifier JSON metadata -func (j *JSONMetadataParser) Closing() []byte { - return []byte("}") -} - -// TOMLMetadataParser is the MetadataParser for TOML -type TOMLMetadataParser struct { - metadata Metadata -} - -// Parse the metadata -func (t *TOMLMetadataParser) Parse(b []byte) ([]byte, error) { - b, markdown, err := extractMetadata(t, b) - if err != nil { - return markdown, err - } - m := make(map[string]interface{}) - if err := toml.Unmarshal(b, &m); err != nil { - return markdown, err - } - t.metadata.load(m) - return markdown, nil -} - -// Metadata returns parsed metadata. It should be called -// only after a call to Parse returns without error. -func (t *TOMLMetadataParser) Metadata() Metadata { - return t.metadata -} - -// Opening returns the opening identifier TOML metadata -func (t *TOMLMetadataParser) Opening() []byte { - return []byte("+++") -} - -// Closing returns the closing identifier TOML metadata -func (t *TOMLMetadataParser) Closing() []byte { - return []byte("+++") -} - -// YAMLMetadataParser is the MetadataParser for YAML -type YAMLMetadataParser struct { - metadata Metadata -} - -// Parse the metadata -func (y *YAMLMetadataParser) Parse(b []byte) ([]byte, error) { - b, markdown, err := extractMetadata(y, b) - if err != nil { - return markdown, err - } - - m := make(map[string]interface{}) - if err := yaml.Unmarshal(b, &m); err != nil { - return markdown, err - } - y.metadata.load(m) - return markdown, nil -} - -// Metadata returns parsed metadata. It should be called -// only after a call to Parse returns without error. -func (y *YAMLMetadataParser) Metadata() Metadata { - return y.metadata -} - -// Opening returns the opening identifier YAML metadata -func (y *YAMLMetadataParser) Opening() []byte { - return []byte("---") -} - -// Closing returns the closing identifier YAML metadata -func (y *YAMLMetadataParser) Closing() []byte { - return []byte("---") -} - // extractMetadata separates metadata content from from markdown content in b. // It returns the metadata, the remaining bytes (markdown), and an error, if any. func extractMetadata(parser MetadataParser, b []byte) (metadata []byte, markdown []byte, err error) { diff --git a/middleware/markdown/metadata_json.go b/middleware/markdown/metadata_json.go new file mode 100644 index 00000000..53dbea19 --- /dev/null +++ b/middleware/markdown/metadata_json.go @@ -0,0 +1,45 @@ +package markdown + +import ( + "bytes" + "encoding/json" +) + +// JSONMetadataParser is the MetadataParser for JSON +type JSONMetadataParser struct { + metadata Metadata +} + +// Parse the metadata +func (j *JSONMetadataParser) Parse(b []byte) ([]byte, error) { + b, markdown, err := extractMetadata(j, b) + if err != nil { + return markdown, err + } + m := make(map[string]interface{}) + + // Read the preceding JSON object + decoder := json.NewDecoder(bytes.NewReader(b)) + if err := decoder.Decode(&m); err != nil { + return markdown, err + } + j.metadata.load(m) + + return markdown, nil +} + +// Metadata returns parsed metadata. It should be called +// only after a call to Parse returns without error. +func (j *JSONMetadataParser) Metadata() Metadata { + return j.metadata +} + +// Opening returns the opening identifier JSON metadata +func (j *JSONMetadataParser) Opening() []byte { + return []byte("{") +} + +// Closing returns the closing identifier JSON metadata +func (j *JSONMetadataParser) Closing() []byte { + return []byte("}") +} diff --git a/middleware/markdown/metadata_toml.go b/middleware/markdown/metadata_toml.go new file mode 100644 index 00000000..fe4068d7 --- /dev/null +++ b/middleware/markdown/metadata_toml.go @@ -0,0 +1,40 @@ +package markdown + +import ( + "github.com/BurntSushi/toml" +) + +// TOMLMetadataParser is the MetadataParser for TOML +type TOMLMetadataParser struct { + metadata Metadata +} + +// Parse the metadata +func (t *TOMLMetadataParser) Parse(b []byte) ([]byte, error) { + b, markdown, err := extractMetadata(t, b) + if err != nil { + return markdown, err + } + m := make(map[string]interface{}) + if err := toml.Unmarshal(b, &m); err != nil { + return markdown, err + } + t.metadata.load(m) + return markdown, nil +} + +// Metadata returns parsed metadata. It should be called +// only after a call to Parse returns without error. +func (t *TOMLMetadataParser) Metadata() Metadata { + return t.metadata +} + +// Opening returns the opening identifier TOML metadata +func (t *TOMLMetadataParser) Opening() []byte { + return []byte("+++") +} + +// Closing returns the closing identifier TOML metadata +func (t *TOMLMetadataParser) Closing() []byte { + return []byte("+++") +} diff --git a/middleware/markdown/metadata_yaml.go b/middleware/markdown/metadata_yaml.go new file mode 100644 index 00000000..41103e21 --- /dev/null +++ b/middleware/markdown/metadata_yaml.go @@ -0,0 +1,41 @@ +package markdown + +import ( + "gopkg.in/yaml.v2" +) + +// YAMLMetadataParser is the MetadataParser for YAML +type YAMLMetadataParser struct { + metadata Metadata +} + +// Parse the metadata +func (y *YAMLMetadataParser) Parse(b []byte) ([]byte, error) { + b, markdown, err := extractMetadata(y, b) + if err != nil { + return markdown, err + } + + m := make(map[string]interface{}) + if err := yaml.Unmarshal(b, &m); err != nil { + return markdown, err + } + y.metadata.load(m) + return markdown, nil +} + +// Metadata returns parsed metadata. It should be called +// only after a call to Parse returns without error. +func (y *YAMLMetadataParser) Metadata() Metadata { + return y.metadata +} + +// Opening returns the opening identifier YAML metadata +func (y *YAMLMetadataParser) Opening() []byte { + return []byte("---") +} + +// Closing returns the closing identifier YAML metadata +func (y *YAMLMetadataParser) Closing() []byte { + return []byte("---") +} diff --git a/middleware/markdown/page.go b/middleware/markdown/page.go deleted file mode 100644 index 9266d9c4..00000000 --- a/middleware/markdown/page.go +++ /dev/null @@ -1,169 +0,0 @@ -package markdown - -import ( - "bytes" - "io/ioutil" - "log" - "os" - "path/filepath" - "sort" - "strings" - "sync" - "time" - - "github.com/russross/blackfriday" -) - -const ( - // Date format YYYY-MM-DD HH:MM:SS - timeLayout = `2006-01-02 15:04:05` - - // Maximum length of page summary. - summaryLen = 500 -) - -// PageLink represents a statically generated markdown page. -type PageLink struct { - Title string - Summary string - Date time.Time - URL string -} - -// byDate sorts PageLink by newest date to oldest. -type byDate []PageLink - -func (p byDate) Len() int { return len(p) } -func (p byDate) Swap(i, j int) { p[i], p[j] = p[j], p[i] } -func (p byDate) Less(i, j int) bool { return p[i].Date.After(p[j].Date) } - -type linkGen struct { - generating bool - waiters int - lastErr error - sync.RWMutex - sync.WaitGroup -} - -func (l *linkGen) addWaiter() { - l.WaitGroup.Add(1) - l.waiters++ -} - -func (l *linkGen) discardWaiters() { - l.Lock() - defer l.Unlock() - for i := 0; i < l.waiters; i++ { - l.Done() - } -} - -func (l *linkGen) started() bool { - l.RLock() - defer l.RUnlock() - return l.generating -} - -// generateLinks generate links to markdown files if there are file changes. -// It returns true when generation is done and false otherwise. -func (l *linkGen) generateLinks(md Markdown, cfg *Config) bool { - l.Lock() - l.generating = true - l.Unlock() - - fp := filepath.Join(md.Root, cfg.PathScope) // path to scan for .md files - - // If the file path to scan for Markdown files (fp) does - // not exist, there are no markdown files to scan for. - if _, err := os.Stat(fp); os.IsNotExist(err) { - l.Lock() - l.lastErr = err - l.generating = false - l.Unlock() - return false - } - - hash, err := computeDirHash(md, cfg) - - // same hash, return. - if err == nil && hash == cfg.linksHash { - l.Lock() - l.generating = false - l.Unlock() - return false - } else if err != nil { - log.Printf("[ERROR] markdown: Hash error: %v", err) - } - - cfg.Links = []PageLink{} - - cfg.Lock() - l.lastErr = filepath.Walk(fp, func(path string, info os.FileInfo, err error) error { - for _, ext := range cfg.Extensions { - if info.IsDir() || !strings.HasSuffix(info.Name(), ext) { - continue - } - - // Load the file - body, err := ioutil.ReadFile(path) - if err != nil { - return err - } - - // Get the relative path as if it were a HTTP request, - // then prepend with "/" (like a real HTTP request) - reqPath, err := filepath.Rel(md.Root, path) - if err != nil { - return err - } - reqPath = "/" + filepath.ToSlash(reqPath) - - // Make the summary - parser := findParser(body) - if parser == nil { - // no metadata, ignore. - continue - } - summaryRaw, err := parser.Parse(body) - if err != nil { - return err - } - summary := blackfriday.Markdown(summaryRaw, SummaryRenderer{}, 0) - - // truncate summary to maximum length - if len(summary) > summaryLen { - summary = summary[:summaryLen] - - // trim to nearest word - lastSpace := bytes.LastIndex(summary, []byte(" ")) - if lastSpace != -1 { - summary = summary[:lastSpace] - } - } - - metadata := parser.Metadata() - - cfg.Links = append(cfg.Links, PageLink{ - Title: metadata.Title, - URL: reqPath, - Date: metadata.Date, - Summary: string(summary), - }) - - break // don't try other file extensions - } - - return nil - }) - - // sort by newest date - sort.Sort(byDate(cfg.Links)) - - cfg.linksHash = hash - cfg.Unlock() - - l.Lock() - l.generating = false - l.Unlock() - return true -} diff --git a/middleware/markdown/process.go b/middleware/markdown/process.go index 94887d0e..e9768627 100644 --- a/middleware/markdown/process.go +++ b/middleware/markdown/process.go @@ -3,10 +3,7 @@ package markdown import ( "bytes" "io/ioutil" - "log" - "os" "path/filepath" - "strings" "text/template" "github.com/mholt/caddy/middleware" @@ -16,8 +13,6 @@ import ( const ( // DefaultTemplate is the default template. DefaultTemplate = "defaultTemplate" - // DefaultStaticDir is the default static directory. - DefaultStaticDir = "generated_site" ) // Data represents a markdown document. @@ -25,7 +20,8 @@ type Data struct { middleware.Context Doc map[string]string DocFlags map[string]bool - Links []PageLink + Styles []string + Scripts []string } // Include "overrides" the embedded middleware.Context's Include() @@ -75,7 +71,11 @@ func (md Markdown) Process(c *Config, requestPath string, b []byte, ctx middlewa } // process markdown - extns := blackfriday.EXTENSION_TABLES | blackfriday.EXTENSION_FENCED_CODE | blackfriday.EXTENSION_STRIKETHROUGH | blackfriday.EXTENSION_DEFINITION_LISTS + extns := 0 + extns |= blackfriday.EXTENSION_TABLES + extns |= blackfriday.EXTENSION_FENCED_CODE + extns |= blackfriday.EXTENSION_STRIKETHROUGH + extns |= blackfriday.EXTENSION_DEFINITION_LISTS markdown = blackfriday.Markdown(markdown, c.Renderer, extns) // set it as body for template @@ -94,123 +94,51 @@ func (md Markdown) Process(c *Config, requestPath string, b []byte, ctx middlewa // processTemplate processes a template given a requestPath, // template (tmpl) and metadata func (md Markdown) processTemplate(c *Config, requestPath string, tmpl []byte, metadata Metadata, ctx middleware.Context) ([]byte, error) { + var t *template.Template + var err error + // if template is not specified, // use the default template if tmpl == nil { - tmpl = defaultTemplate(c, metadata, requestPath) + t = template.Must(template.New("").Parse(htmlTemplate)) + } else { + t, err = template.New("").Parse(string(tmpl)) + if err != nil { + return nil, err + } } // process the template - b := new(bytes.Buffer) - t, err := template.New("").Parse(string(tmpl)) - if err != nil { - return nil, err - } mdData := Data{ Context: ctx, Doc: metadata.Variables, DocFlags: metadata.Flags, - Links: c.Links, + Styles: c.Styles, + Scripts: c.Scripts, } - c.RLock() + b := new(bytes.Buffer) err = t.Execute(b, mdData) - c.RUnlock() - if err != nil { return nil, err } - // generate static page - if err = md.generatePage(c, requestPath, b.Bytes()); err != nil { - // if static page generation fails, nothing fatal, only log the error. - // TODO: Report (return) this non-fatal error, but don't log it here? - log.Println("[ERROR] markdown: Render:", err) - } - return b.Bytes(), nil - -} - -// generatePage generates a static html page from the markdown in content if c.StaticDir -// is a non-empty value, meaning that the user enabled static site generation. -func (md Markdown) generatePage(c *Config, requestPath string, content []byte) error { - // Only generate the page if static site generation is enabled - if c.StaticDir != "" { - // if static directory is not existing, create it - if _, err := os.Stat(c.StaticDir); err != nil { - err := os.MkdirAll(c.StaticDir, os.FileMode(0755)) - if err != nil { - return err - } - } - - // the URL will always use "/" as a path separator, - // convert that to a native path to support OS that - // use different path separators - filePath := filepath.Join(c.StaticDir, filepath.FromSlash(requestPath)) - - // If it is index file, use the directory instead - if md.IsIndexFile(filepath.Base(requestPath)) { - filePath, _ = filepath.Split(filePath) - } - - // Create the directory in case it is not existing - if err := os.MkdirAll(filePath, os.FileMode(0744)); err != nil { - return err - } - - // generate index.html file in the directory - filePath = filepath.Join(filePath, "index.html") - err := ioutil.WriteFile(filePath, content, os.FileMode(0664)) - if err != nil { - return err - } - - c.Lock() - c.StaticFiles[requestPath] = filepath.ToSlash(filePath) - c.Unlock() - } - - return nil -} - -// defaultTemplate constructs a default template. -func defaultTemplate(c *Config, metadata Metadata, requestPath string) []byte { - var scripts, styles bytes.Buffer - for _, style := range c.Styles { - styles.WriteString(strings.Replace(cssTemplate, "{{url}}", style, 1)) - styles.WriteString("\r\n") - } - for _, script := range c.Scripts { - scripts.WriteString(strings.Replace(jsTemplate, "{{url}}", script, 1)) - scripts.WriteString("\r\n") - } - - // Title is first line (length-limited), otherwise filename - title, _ := metadata.Variables["title"] - - html := []byte(htmlTemplate) - html = bytes.Replace(html, []byte("{{title}}"), []byte(title), 1) - html = bytes.Replace(html, []byte("{{css}}"), styles.Bytes(), 1) - html = bytes.Replace(html, []byte("{{js}}"), scripts.Bytes(), 1) - - return html } const ( htmlTemplate = ` - {{title}} + {{.Doc.title}} - {{css}} - {{js}} + {{range .Styles}} + {{end -}} + {{range .Scripts}} + {{end -}} {{.Doc.body}} ` - cssTemplate = `` - jsTemplate = `` ) diff --git a/middleware/markdown/renderer.go b/middleware/markdown/renderer.go deleted file mode 100644 index 44c0163d..00000000 --- a/middleware/markdown/renderer.go +++ /dev/null @@ -1,139 +0,0 @@ -package markdown - -import ( - "bytes" -) - -// SummaryRenderer represents a summary renderer. -type SummaryRenderer struct{} - -// Block-level callbacks - -// BlockCode is the code tag callback. -func (r SummaryRenderer) BlockCode(out *bytes.Buffer, text []byte, lang string) {} - -// BlockQuote is the quote tag callback. -func (r SummaryRenderer) BlockQuote(out *bytes.Buffer, text []byte) {} - -// BlockHtml is the HTML tag callback. -func (r SummaryRenderer) BlockHtml(out *bytes.Buffer, text []byte) {} - -// Header is the header tag callback. -func (r SummaryRenderer) Header(out *bytes.Buffer, text func() bool, level int, id string) {} - -// HRule is the horizontal rule tag callback. -func (r SummaryRenderer) HRule(out *bytes.Buffer) {} - -// List is the list tag callback. -func (r SummaryRenderer) List(out *bytes.Buffer, text func() bool, flags int) { - // TODO: This is not desired (we'd rather not write lists as part of summary), - // but see this issue: https://github.com/russross/blackfriday/issues/189 - marker := out.Len() - if !text() { - out.Truncate(marker) - } - out.Write([]byte{' '}) -} - -// ListItem is the list item tag callback. -func (r SummaryRenderer) ListItem(out *bytes.Buffer, text []byte, flags int) {} - -// Paragraph is the paragraph tag callback. -func (r SummaryRenderer) Paragraph(out *bytes.Buffer, text func() bool) { - marker := out.Len() - if !text() { - out.Truncate(marker) - } - out.Write([]byte{' '}) -} - -// Table is the table tag callback. -func (r SummaryRenderer) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {} - -// TableRow is the table row tag callback. -func (r SummaryRenderer) TableRow(out *bytes.Buffer, text []byte) {} - -// TableHeaderCell is the table header cell tag callback. -func (r SummaryRenderer) TableHeaderCell(out *bytes.Buffer, text []byte, flags int) {} - -// TableCell is the table cell tag callback. -func (r SummaryRenderer) TableCell(out *bytes.Buffer, text []byte, flags int) {} - -// Footnotes is the foot notes tag callback. -func (r SummaryRenderer) Footnotes(out *bytes.Buffer, text func() bool) {} - -// FootnoteItem is the footnote item tag callback. -func (r SummaryRenderer) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {} - -// TitleBlock is the title tag callback. -func (r SummaryRenderer) TitleBlock(out *bytes.Buffer, text []byte) {} - -// Span-level callbacks - -// AutoLink is the autolink tag callback. -func (r SummaryRenderer) AutoLink(out *bytes.Buffer, link []byte, kind int) {} - -// CodeSpan is the code span tag callback. -func (r SummaryRenderer) CodeSpan(out *bytes.Buffer, text []byte) { - out.Write([]byte("`")) - out.Write(text) - out.Write([]byte("`")) -} - -// DoubleEmphasis is the double emphasis tag callback. -func (r SummaryRenderer) DoubleEmphasis(out *bytes.Buffer, text []byte) { - out.Write(text) -} - -// Emphasis is the emphasis tag callback. -func (r SummaryRenderer) Emphasis(out *bytes.Buffer, text []byte) { - out.Write(text) -} - -// Image is the image tag callback. -func (r SummaryRenderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {} - -// LineBreak is the line break tag callback. -func (r SummaryRenderer) LineBreak(out *bytes.Buffer) {} - -// Link is the link tag callback. -func (r SummaryRenderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) { - out.Write(content) -} - -// RawHtmlTag is the raw HTML tag callback. -func (r SummaryRenderer) RawHtmlTag(out *bytes.Buffer, tag []byte) {} - -// TripleEmphasis is the triple emphasis tag callback. -func (r SummaryRenderer) TripleEmphasis(out *bytes.Buffer, text []byte) { - out.Write(text) -} - -// StrikeThrough is the strikethrough tag callback. -func (r SummaryRenderer) StrikeThrough(out *bytes.Buffer, text []byte) {} - -// FootnoteRef is the footnote ref tag callback. -func (r SummaryRenderer) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {} - -// Low-level callbacks - -// Entity callback. -func (r SummaryRenderer) Entity(out *bytes.Buffer, entity []byte) { - out.Write(entity) -} - -// NormalText callback. -func (r SummaryRenderer) NormalText(out *bytes.Buffer, text []byte) { - out.Write(text) -} - -// Header and footer - -// DocumentHeader callback. -func (r SummaryRenderer) DocumentHeader(out *bytes.Buffer) {} - -// DocumentFooter callback. -func (r SummaryRenderer) DocumentFooter(out *bytes.Buffer) {} - -// GetFlags returns zero. -func (r SummaryRenderer) GetFlags() int { return 0 } diff --git a/middleware/markdown/watcher.go b/middleware/markdown/watcher.go deleted file mode 100644 index fa5fb3ee..00000000 --- a/middleware/markdown/watcher.go +++ /dev/null @@ -1,42 +0,0 @@ -package markdown - -import ( - "log" - "time" -) - -// DefaultInterval is the default interval at which the markdown watcher -// checks for changes. -const DefaultInterval = time.Second * 60 - -// Watch monitors the configured markdown directory for changes. It calls GenerateLinks -// when there are changes. -func Watch(md Markdown, c *Config, interval time.Duration) (stopChan chan struct{}) { - return TickerFunc(interval, func() { - if err := GenerateStatic(md, c); err != nil { - log.Printf("[ERROR] markdown: Re-generating static site: %v", err) - } - }) -} - -// TickerFunc runs f at interval. A message to the returned channel will stop the -// executing goroutine. -func TickerFunc(interval time.Duration, f func()) chan struct{} { - stopChan := make(chan struct{}) - - ticker := time.NewTicker(interval) - go func() { - loop: - for { - select { - case <-ticker.C: - f() - case <-stopChan: - ticker.Stop() - break loop - } - } - }() - - return stopChan -} diff --git a/middleware/markdown/watcher_test.go b/middleware/markdown/watcher_test.go deleted file mode 100644 index 8a89e007..00000000 --- a/middleware/markdown/watcher_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package markdown - -import ( - "fmt" - "strings" - "sync" - "testing" - "time" -) - -func TestWatcher(t *testing.T) { - expected := "12345678" - interval := time.Millisecond * 100 - i := 0 - out := "" - syncChan := make(chan struct{}) - stopChan := TickerFunc(interval, func() { - i++ - out += fmt.Sprint(i) - syncChan <- struct{}{} - }) - sleepInSync(8, syncChan, stopChan) - if out != expected { - t.Fatalf("Expected to have prefix %v, found %v", expected, out) - } - out = "" - i = 0 - var mu sync.Mutex - stopChan = TickerFunc(interval, func() { - i++ - mu.Lock() - out += fmt.Sprint(i) - mu.Unlock() - syncChan <- struct{}{} - }) - sleepInSync(9, syncChan, stopChan) - mu.Lock() - res := out - mu.Unlock() - if !strings.HasPrefix(res, expected) || res == expected { - t.Fatalf("expected (%v) must be a proper prefix of out(%v).", expected, out) - } -} - -func sleepInSync(times int, syncChan chan struct{}, stopChan chan struct{}) { - for i := 0; i < times; i++ { - <-syncChan - } - stopChan <- struct{}{} -} From 42b7d574210e49020cc0f6ef0490e71116c90ed8 Mon Sep 17 00:00:00 2001 From: Tobias Weingartner Date: Sun, 10 Apr 2016 23:17:01 -0700 Subject: [PATCH 02/19] Fix more tests, and fix template parsing. --- caddy/setup/markdown.go | 27 ++++++++--- caddy/setup/markdown_test.go | 59 +++++++++++++++++++++--- middleware/markdown/markdown.go | 6 +-- middleware/markdown/markdown_test.go | 47 +++++++++---------- middleware/markdown/process.go | 68 ++-------------------------- middleware/markdown/template.go | 55 ++++++++++++++++++++++ 6 files changed, 160 insertions(+), 102 deletions(-) create mode 100644 middleware/markdown/template.go diff --git a/caddy/setup/markdown.go b/caddy/setup/markdown.go index 08a05567..fdc91991 100644 --- a/caddy/setup/markdown.go +++ b/caddy/setup/markdown.go @@ -36,7 +36,7 @@ func markdownParse(c *Controller) ([]*markdown.Config, error) { md := &markdown.Config{ Renderer: blackfriday.HtmlRenderer(0, "", ""), Extensions: make(map[string]struct{}), - Templates: make(map[string]string), + Template: markdown.GetDefaultTemplate(), } // Get the path scope @@ -95,17 +95,32 @@ func loadParams(c *Controller, mdc *markdown.Config) error { default: return c.ArgErr() case 1: - if _, ok := mdc.Templates[markdown.DefaultTemplate]; ok { - return c.Err("only one default template is allowed, use alias.") - } fpath := filepath.ToSlash(filepath.Clean(c.Root + string(filepath.Separator) + tArgs[0])) - mdc.Templates[markdown.DefaultTemplate] = fpath + + if err := markdown.SetTemplate(mdc.Template, "", fpath); err != nil { + c.Errf("default template parse error: %v", err) + } return nil case 2: fpath := filepath.ToSlash(filepath.Clean(c.Root + string(filepath.Separator) + tArgs[1])) - mdc.Templates[tArgs[0]] = fpath + + if err := markdown.SetTemplate(mdc.Template, tArgs[0], fpath); err != nil { + c.Errf("template parse error: %v", err) + } return nil } + case "templatedir": + if !c.NextArg() { + return c.ArgErr() + } + _, err := mdc.Template.ParseGlob(c.Val()) + if err != nil { + c.Errf("template load error: %v", err) + } + if c.NextArg() { + return c.ArgErr() + } + return nil default: return c.Err("Expected valid markdown configuration property") } diff --git a/caddy/setup/markdown_test.go b/caddy/setup/markdown_test.go index 18af6254..e1e698b2 100644 --- a/caddy/setup/markdown_test.go +++ b/caddy/setup/markdown_test.go @@ -1,9 +1,13 @@ package setup import ( + "bytes" "fmt" + "net/http" "testing" + "text/template" + "github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware/markdown" ) @@ -58,8 +62,9 @@ func TestMarkdownParse(t *testing.T) { ".md": struct{}{}, ".txt": struct{}{}, }, - Styles: []string{"/resources/css/blog.css"}, - Scripts: []string{"/resources/js/blog.js"}, + Styles: []string{"/resources/css/blog.css"}, + Scripts: []string{"/resources/js/blog.js"}, + Template: markdown.GetDefaultTemplate(), }}}, {`markdown /blog { ext .md @@ -69,9 +74,13 @@ func TestMarkdownParse(t *testing.T) { Extensions: map[string]struct{}{ ".md": struct{}{}, }, - Templates: map[string]string{markdown.DefaultTemplate: "testdata/tpl_with_include.html"}, + Template: markdown.GetDefaultTemplate(), }}}, } + // Setup the extra template + tmpl := tests[1].expectedMarkdownConfig[0].Template + markdown.SetTemplate(tmpl, "", "./testdata/tpl_with_include.html") + for i, test := range tests { c := NewTestController(test.inputMarkdownConfig) c.Root = "./testdata" @@ -101,11 +110,47 @@ func TestMarkdownParse(t *testing.T) { t.Errorf("Test %d expected %dth Markdown Config Scripts to be %s , but got %s", i, j, fmt.Sprint(test.expectedMarkdownConfig[j].Scripts), fmt.Sprint(actualMarkdownConfig.Scripts)) } - if fmt.Sprint(actualMarkdownConfig.Templates) != fmt.Sprint(test.expectedMarkdownConfig[j].Templates) { - t.Errorf("Test %d expected %dth Markdown Config Templates to be %s , but got %s", - i, j, fmt.Sprint(test.expectedMarkdownConfig[j].Templates), fmt.Sprint(actualMarkdownConfig.Templates)) + if ok, tx, ty := equalTemplates(actualMarkdownConfig.Template, test.expectedMarkdownConfig[j].Template); !ok { + t.Errorf("Test %d the %dth Markdown Config Templates did not match, expected %s to be %s", i, j, tx, ty) } } } - +} + +func equalTemplates(i, j *template.Template) (bool, string, string) { + // Just in case :) + if i == j { + return true, "", "" + } + + // We can't do much here, templates can't really be compared. However, + // we can execute the templates and compare their outputs to be reasonably + // sure that they're the same. + + // This is exceedingly ugly. + ctx := middleware.Context{ + Root: http.Dir("./testdata"), + } + + md := markdown.Data{ + Context: ctx, + Doc: make(map[string]string), + DocFlags: make(map[string]bool), + Styles: []string{"style1"}, + Scripts: []string{"js1"}, + } + md.Doc["title"] = "some title" + md.Doc["body"] = "some body" + + bufi := new(bytes.Buffer) + bufj := new(bytes.Buffer) + + if err := i.Execute(bufi, md); err != nil { + return false, fmt.Sprintf("%v", err), "" + } + if err := j.Execute(bufj, md); err != nil { + return false, "", fmt.Sprintf("%v", err) + } + + return bytes.Equal(bufi.Bytes(), bufj.Bytes()), string(bufi.Bytes()), string(bufj.Bytes()) } diff --git a/middleware/markdown/markdown.go b/middleware/markdown/markdown.go index 5e4a2cb5..5c2ac5fc 100644 --- a/middleware/markdown/markdown.go +++ b/middleware/markdown/markdown.go @@ -4,10 +4,10 @@ package markdown import ( "io/ioutil" - "log" "net/http" "os" "path" + "text/template" "github.com/mholt/caddy/middleware" "github.com/russross/blackfriday" @@ -59,8 +59,8 @@ type Config struct { // List of JavaScript files to load for each markdown file Scripts []string - // Map of registered templates - Templates map[string]string + // Template(s) to render with + Template *template.Template } // ServeHTTP implements the http.Handler interface. diff --git a/middleware/markdown/markdown_test.go b/middleware/markdown/markdown_test.go index fbfb7379..b38c34f6 100644 --- a/middleware/markdown/markdown_test.go +++ b/middleware/markdown/markdown_test.go @@ -5,6 +5,7 @@ import ( "net/http" "net/http/httptest" "os" + "path/filepath" "strings" "testing" "time" @@ -14,11 +15,15 @@ import ( ) func TestMarkdown(t *testing.T) { - templates := make(map[string]string) - templates[DefaultTemplate] = "testdata/markdown_tpl.html" + rootDir := "./testdata" + + f := func(filename string) string { + return filepath.ToSlash(rootDir + string(filepath.Separator) + filename) + } + md := Markdown{ - Root: "./testdata", - FileSys: http.Dir("./testdata"), + Root: rootDir, + FileSys: http.Dir(rootDir), Configs: []*Config{ { Renderer: blackfriday.HtmlRenderer(0, "", ""), @@ -26,9 +31,9 @@ func TestMarkdown(t *testing.T) { Extensions: map[string]struct{}{ ".md": struct{}{}, }, - Styles: []string{}, - Scripts: []string{}, - Templates: templates, + Styles: []string{}, + Scripts: []string{}, + Template: setDefaultTemplate(f("markdown_tpl.html")), }, { Renderer: blackfriday.HtmlRenderer(0, "", ""), @@ -36,11 +41,9 @@ func TestMarkdown(t *testing.T) { Extensions: map[string]struct{}{ ".md": struct{}{}, }, - Styles: []string{}, - Scripts: []string{}, - Templates: map[string]string{ - DefaultTemplate: "testdata/docflags/template.txt", - }, + Styles: []string{}, + Scripts: []string{}, + Template: setDefaultTemplate(f("docflags/template.txt")), }, { Renderer: blackfriday.HtmlRenderer(0, "", ""), @@ -48,9 +51,9 @@ func TestMarkdown(t *testing.T) { Extensions: map[string]struct{}{ ".md": struct{}{}, }, - 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), + Styles: []string{"/resources/css/log.css", "/resources/css/default.css"}, + Scripts: []string{"/resources/js/log.js", "/resources/js/default.js"}, + Template: GetDefaultTemplate(), }, { Renderer: blackfriday.HtmlRenderer(0, "", ""), @@ -58,9 +61,9 @@ func TestMarkdown(t *testing.T) { Extensions: map[string]struct{}{ ".md": struct{}{}, }, - Styles: []string{}, - Scripts: []string{}, - Templates: templates, + Styles: []string{}, + Scripts: []string{}, + Template: setDefaultTemplate(f("markdown_tpl.html")), }, }, IndexFiles: []string{"index.html"}, @@ -145,12 +148,10 @@ DocFlags.var_bool true` Markdown test 2 - - + - - - + +

Welcome on the blog

diff --git a/middleware/markdown/process.go b/middleware/markdown/process.go index e9768627..e661e697 100644 --- a/middleware/markdown/process.go +++ b/middleware/markdown/process.go @@ -2,19 +2,12 @@ package markdown import ( "bytes" - "io/ioutil" "path/filepath" - "text/template" "github.com/mholt/caddy/middleware" "github.com/russross/blackfriday" ) -const ( - // DefaultTemplate is the default template. - DefaultTemplate = "defaultTemplate" -) - // Data represents a markdown document. type Data struct { middleware.Context @@ -52,24 +45,6 @@ func (md Markdown) Process(c *Config, requestPath string, b []byte, ctx middlewa metadata = parser.Metadata() } - // if template is not specified, check if Default template is set - if metadata.Template == "" { - if _, ok := c.Templates[DefaultTemplate]; ok { - metadata.Template = DefaultTemplate - } - } - - // if template is set, load it - var tmpl []byte - if metadata.Template != "" { - if t, ok := c.Templates[metadata.Template]; ok { - tmpl, err = ioutil.ReadFile(t) - } - if err != nil { - return nil, err - } - } - // process markdown extns := 0 extns |= blackfriday.EXTENSION_TABLES @@ -88,27 +63,12 @@ func (md Markdown) Process(c *Config, requestPath string, b []byte, ctx middlewa } metadata.Variables["title"] = title - return md.processTemplate(c, requestPath, tmpl, metadata, ctx) + // return md.processTemplate(c, requestPath, metadata, ctx) + return md.doTemplate(c, requestPath, 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, ctx middleware.Context) ([]byte, error) { - var t *template.Template - var err error - - // if template is not specified, - // use the default template - if tmpl == nil { - t = template.Must(template.New("").Parse(htmlTemplate)) - } else { - t, err = template.New("").Parse(string(tmpl)) - if err != nil { - return nil, err - } - } - - // process the template +// doTemplate executes a template given a requestPath, template, and metadata +func (md Markdown) doTemplate(c *Config, reqPath string, metadata Metadata, ctx middleware.Context) ([]byte, error) { mdData := Data{ Context: ctx, Doc: metadata.Variables, @@ -118,27 +78,9 @@ func (md Markdown) processTemplate(c *Config, requestPath string, tmpl []byte, m } b := new(bytes.Buffer) - err = t.Execute(b, mdData) - if err != nil { + if err := c.Template.ExecuteTemplate(b, metadata.Template, mdData); err != nil { return nil, err } return b.Bytes(), nil } - -const ( - htmlTemplate = ` - - - {{.Doc.title}} - - {{range .Styles}} - {{end -}} - {{range .Scripts}} - {{end -}} - - - {{.Doc.body}} - -` -) diff --git a/middleware/markdown/template.go b/middleware/markdown/template.go new file mode 100644 index 00000000..b1118287 --- /dev/null +++ b/middleware/markdown/template.go @@ -0,0 +1,55 @@ +package markdown + +import ( + "io/ioutil" + "text/template" +) + +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 + buf, err := ioutil.ReadFile(filename) + if err != nil { + return err + } + + // Update if exists + if tt := t.Lookup(name); tt != nil { + _, err = tt.Parse(string(buf)) + return err + } + + // Allocate new name if not + _, err = t.New(name).Parse(string(buf)) + return err +} + +func GetDefaultTemplate() *template.Template { + return template.Must(template.New("").Parse(defaultTemplate)) +} + +const ( + defaultTemplate = ` + + + {{.Doc.title}} + + {{range .Styles}} + {{end -}} + {{range .Scripts}} + {{end -}} + + + {{.Doc.body}} + +` +) From 48d294a69568c3ab3433a2c8e066f6192c3fa40a Mon Sep 17 00:00:00 2001 From: Tobias Weingartner Date: Tue, 12 Apr 2016 18:53:15 -0700 Subject: [PATCH 03/19] Better default template. --- middleware/markdown/markdown_test.go | 2 +- middleware/markdown/template.go | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/middleware/markdown/markdown_test.go b/middleware/markdown/markdown_test.go index b38c34f6..59c90a13 100644 --- a/middleware/markdown/markdown_test.go +++ b/middleware/markdown/markdown_test.go @@ -151,7 +151,7 @@ DocFlags.var_bool true` - +

Welcome on the blog

diff --git a/middleware/markdown/template.go b/middleware/markdown/template.go index b1118287..245ad995 100644 --- a/middleware/markdown/template.go +++ b/middleware/markdown/template.go @@ -42,11 +42,9 @@ const ( {{.Doc.title}} - - {{range .Styles}} - {{end -}} - {{range .Scripts}} - {{end -}} + {{range .Styles}} + {{end}}{{range .Scripts}} + {{end}} {{.Doc.body}} From 7c9867917aacf3755c24bfe3bcb8ccbdb04201ec Mon Sep 17 00:00:00 2001 From: Tobias Weingartner Date: Sat, 16 Apr 2016 14:45:32 -0700 Subject: [PATCH 04/19] Fixup tests and move metadata into own package --- middleware/markdown/markdown.go | 24 +++++- .../markdown/{ => metadata}/metadata.go | 84 ++++++++++++++++--- .../markdown/{ => metadata}/metadata_json.go | 39 ++++++++- middleware/markdown/metadata/metadata_none.go | 50 +++++++++++ .../markdown/{ => metadata}/metadata_test.go | 68 ++++++++------- .../markdown/{ => metadata}/metadata_toml.go | 32 ++++++- .../markdown/{ => metadata}/metadata_yaml.go | 30 ++++++- middleware/markdown/process.go | 69 +++------------ middleware/markdown/template.go | 40 +++++++++ 9 files changed, 330 insertions(+), 106 deletions(-) rename middleware/markdown/{ => metadata}/metadata.go (65%) rename middleware/markdown/{ => metadata}/metadata_json.go (57%) create mode 100644 middleware/markdown/metadata/metadata_none.go rename middleware/markdown/{ => metadata}/metadata_test.go (77%) rename middleware/markdown/{ => metadata}/metadata_toml.go (61%) rename middleware/markdown/{ => metadata}/metadata_yaml.go (62%) diff --git a/middleware/markdown/markdown.go b/middleware/markdown/markdown.go index 5c2ac5fc..56c735f9 100644 --- a/middleware/markdown/markdown.go +++ b/middleware/markdown/markdown.go @@ -70,9 +70,26 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error continue } + var dirents []os.FileInfo fpath := r.URL.Path if idx, ok := middleware.IndexFile(md.FileSys, fpath, md.IndexFiles); ok { + // We're serving a directory index file, which may be a markdown + // file with a template. Let's grab a list of files this directory + // 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 { + return http.StatusInternalServerError, err + } + dirents, err = fdp.Readdir(-1) + if err != nil { + return http.StatusInternalServerError, err + } + + // Set path to found index file fpath = idx + _ = dirents } // If supported extension, process it @@ -100,11 +117,16 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error Req: r, URL: r.URL, } - html, err := md.Process(cfg, fpath, body, ctx) + html, err := cfg.Markdown(fpath, body, ctx) if err != nil { return http.StatusInternalServerError, err } + // html, err = md.doTemplate(cfg, html, ctx) + // if err != nil { + // return http.StatusInternalServerError, err + // } + middleware.SetLastModifiedHeader(w, fs.ModTime()) w.Write(html) return http.StatusOK, nil diff --git a/middleware/markdown/metadata.go b/middleware/markdown/metadata/metadata.go similarity index 65% rename from middleware/markdown/metadata.go rename to middleware/markdown/metadata/metadata.go index 20a30e18..c5ccaaaa 100644 --- a/middleware/markdown/metadata.go +++ b/middleware/markdown/metadata/metadata.go @@ -1,6 +1,7 @@ -package markdown +package metadata import ( + "bufio" "bytes" "fmt" "time" @@ -64,20 +65,23 @@ func (m *Metadata) load(parsedMap map[string]interface{}) { // MetadataParser is a an interface that must be satisfied by each parser type MetadataParser interface { + // Initialize a parser + Init(b *bytes.Buffer) bool + + // Type of metadata + Type() string + // Opening identifier Opening() []byte // Closing identifier Closing() []byte - // Parse the metadata. - // Returns the remaining page contents (Markdown) - // after extracting metadata - Parse([]byte) ([]byte, error) - // Parsed metadata. - // Should be called after a call to Parse returns no error Metadata() Metadata + + // Raw markdown. + Markdown() []byte } // extractMetadata separates metadata content from from markdown content in b. @@ -109,8 +113,19 @@ func extractMetadata(parser MetadataParser, b []byte) (metadata []byte, markdown return metadata, markdown, nil } +func GetParser(buf []byte) MetadataParser { + for _, p := range parsers() { + b := bytes.NewBuffer(buf) + if p.Init(b) { + return p + } + } + + return nil +} + // findParser finds the parser using line that contains opening identifier -func findParser(b []byte) MetadataParser { +func FindParser(b []byte) MetadataParser { var line []byte // Read first line if _, err := fmt.Fscanln(bytes.NewReader(b), &line); err != nil { @@ -125,7 +140,7 @@ func findParser(b []byte) MetadataParser { return nil } -func newMetadata() Metadata { +func NewMetadata() Metadata { return Metadata{ Variables: make(map[string]string), Flags: make(map[string]bool), @@ -135,8 +150,53 @@ func newMetadata() Metadata { // parsers returns all available parsers func parsers() []MetadataParser { return []MetadataParser{ - &JSONMetadataParser{metadata: newMetadata()}, - &TOMLMetadataParser{metadata: newMetadata()}, - &YAMLMetadataParser{metadata: newMetadata()}, + &TOMLMetadataParser{}, + &YAMLMetadataParser{metadata: NewMetadata()}, + &JSONMetadataParser{}, + &NoneMetadataParser{}, } } + +// Split out "normal" metadata with given delimiter +func splitBuffer(b *bytes.Buffer, delim string) (*bytes.Buffer, *bytes.Buffer) { + scanner := bufio.NewScanner(b) + + // Read and check first line + if !scanner.Scan() { + return nil, nil + } + if string(bytes.TrimSpace(scanner.Bytes())) != delim { + return nil, nil + } + + // Accumulate metadata, until delimiter + meta := bytes.NewBuffer(nil) + for scanner.Scan() { + if string(bytes.TrimSpace(scanner.Bytes())) == delim { + break + } + if _, err := meta.Write(scanner.Bytes()); err != nil { + return nil, nil + } + if _, err := meta.WriteRune('\n'); err != nil { + return nil, nil + } + } + // Make sure we saw closing delimiter + if string(bytes.TrimSpace(scanner.Bytes())) != delim { + return nil, nil + } + + // The rest is markdown + markdown := new(bytes.Buffer) + for scanner.Scan() { + if _, err := markdown.Write(scanner.Bytes()); err != nil { + return nil, nil + } + if _, err := markdown.WriteRune('\n'); err != nil { + return nil, nil + } + } + + return meta, markdown +} diff --git a/middleware/markdown/metadata_json.go b/middleware/markdown/metadata/metadata_json.go similarity index 57% rename from middleware/markdown/metadata_json.go rename to middleware/markdown/metadata/metadata_json.go index 53dbea19..bfc8c13d 100644 --- a/middleware/markdown/metadata_json.go +++ b/middleware/markdown/metadata/metadata_json.go @@ -1,4 +1,4 @@ -package markdown +package metadata import ( "bytes" @@ -8,6 +8,39 @@ import ( // JSONMetadataParser is the MetadataParser for JSON type JSONMetadataParser struct { metadata Metadata + markdown *bytes.Buffer +} + +func (j *JSONMetadataParser) Type() string { + return "JSON" +} + +// Parse metadata/markdown file +func (j *JSONMetadataParser) Init(b *bytes.Buffer) bool { + m := make(map[string]interface{}) + + err := json.Unmarshal(b.Bytes(), &m) + if err != nil { + var offset int + + if jerr, ok := err.(*json.SyntaxError); !ok { + return false + } else { + offset = int(jerr.Offset) + } + + m = make(map[string]interface{}) + err = json.Unmarshal(b.Next(offset-1), &m) + if err != nil { + return false + } + } + + j.metadata = NewMetadata() + j.metadata.load(m) + j.markdown = bytes.NewBuffer(b.Bytes()) + + return true } // Parse the metadata @@ -34,6 +67,10 @@ func (j *JSONMetadataParser) Metadata() Metadata { return j.metadata } +func (j *JSONMetadataParser) Markdown() []byte { + return j.markdown.Bytes() +} + // Opening returns the opening identifier JSON metadata func (j *JSONMetadataParser) Opening() []byte { return []byte("{") diff --git a/middleware/markdown/metadata/metadata_none.go b/middleware/markdown/metadata/metadata_none.go new file mode 100644 index 00000000..3dc22c07 --- /dev/null +++ b/middleware/markdown/metadata/metadata_none.go @@ -0,0 +1,50 @@ +package metadata + +import ( + "bytes" +) + +// TOMLMetadataParser is the MetadataParser for TOML +type NoneMetadataParser struct { + metadata Metadata + markdown *bytes.Buffer +} + +func (n *NoneMetadataParser) Type() string { + return "None" +} + +// Parse metadata/markdown file +func (n *NoneMetadataParser) Init(b *bytes.Buffer) bool { + m := make(map[string]interface{}) + n.metadata = NewMetadata() + n.metadata.load(m) + n.markdown = bytes.NewBuffer(b.Bytes()) + + return true +} + +// Parse the metadata +func (n *NoneMetadataParser) Parse(b []byte) ([]byte, error) { + return nil, nil +} + +// Metadata returns parsed metadata. It should be called +// only after a call to Parse returns without error. +func (n *NoneMetadataParser) Metadata() Metadata { + return n.metadata +} + +func (n *NoneMetadataParser) Markdown() []byte { + return n.markdown.Bytes() +} + +// Opening returns the opening identifier TOML metadata +func (n *NoneMetadataParser) Opening() []byte { + return []byte("...") +} + +// Closing returns the closing identifier TOML metadata +func (n *NoneMetadataParser) Closing() []byte { + return []byte("...") +} diff --git a/middleware/markdown/metadata_test.go b/middleware/markdown/metadata/metadata_test.go similarity index 77% rename from middleware/markdown/metadata_test.go rename to middleware/markdown/metadata/metadata_test.go index f219d923..0c155d37 100644 --- a/middleware/markdown/metadata_test.go +++ b/middleware/markdown/metadata/metadata_test.go @@ -1,9 +1,7 @@ -package markdown +package metadata import ( "bytes" - "fmt" - "reflect" "strings" "testing" ) @@ -164,56 +162,52 @@ func TestParsers(t *testing.T) { testData [5]string name string }{ - {&JSONMetadataParser{metadata: newMetadata()}, JSON, "json"}, - {&YAMLMetadataParser{metadata: newMetadata()}, YAML, "yaml"}, - {&TOMLMetadataParser{metadata: newMetadata()}, TOML, "toml"}, + {&JSONMetadataParser{}, JSON, "JSON"}, + {&YAMLMetadataParser{}, YAML, "YAML"}, + {&TOMLMetadataParser{}, TOML, "TOML"}, } for _, v := range data { // metadata without identifiers - if _, err := v.parser.Parse([]byte(v.testData[0])); err == nil { + if v.parser.Init(bytes.NewBufferString(v.testData[0])) { t.Fatalf("Expected error for invalid metadata for %v", v.name) } // metadata with identifiers - md, err := v.parser.Parse([]byte(v.testData[1])) - check(t, err) + if !v.parser.Init(bytes.NewBufferString(v.testData[1])) { + t.Fatalf("Metadata failed to initialize, type %v", v.parser.Type()) + } + md := v.parser.Markdown() if !compare(v.parser.Metadata()) { t.Fatalf("Expected %v, found %v for %v", expected, v.parser.Metadata(), v.name) } if "Page content" != strings.TrimSpace(string(md)) { t.Fatalf("Expected %v, found %v for %v", "Page content", string(md), v.name) } - - var line []byte - fmt.Fscanln(bytes.NewReader([]byte(v.testData[1])), &line) - if parser := findParser(line); parser == nil { - t.Fatalf("Parser must be found for %v", v.name) - } else { - if reflect.TypeOf(parser) != reflect.TypeOf(v.parser) { - t.Fatalf("parsers not equal. %v != %v", reflect.TypeOf(parser), reflect.TypeOf(v.parser)) - } + // Check that we find the correct metadata parser type + if p := GetParser([]byte(v.testData[1])); p.Type() != v.name { + t.Fatalf("Wrong parser found, expected %v, found %v", v.name, p.Type()) } // metadata without closing identifier - if _, err := v.parser.Parse([]byte(v.testData[2])); err == nil { - t.Fatalf("Expected error for missing closing identifier for %v", v.name) + if v.parser.Init(bytes.NewBufferString(v.testData[2])) { + t.Fatalf("Expected error for missing closing identifier for %v parser", v.name) } // invalid metadata - if _, err = v.parser.Parse([]byte(v.testData[3])); err == nil { + if v.parser.Init(bytes.NewBufferString(v.testData[3])) { t.Fatalf("Expected error for invalid metadata for %v", v.name) } // front matter but no body - if _, err = v.parser.Parse([]byte(v.testData[4])); err != nil { + if !v.parser.Init(bytes.NewBufferString(v.testData[4])) { t.Fatalf("Unexpected error for valid metadata but no body for %v", v.name) } } - } func TestLargeBody(t *testing.T) { + var JSON = `{ "template": "chapter" } @@ -232,24 +226,36 @@ Mycket olika byggnader har man i de nordiska rikena: pyramidformiga, kilformiga, template : chapter --- +Mycket olika byggnader har man i de nordiska rikena: pyramidformiga, kilformiga, välvda, runda och fyrkantiga. De pyramidformiga består helt enkelt av träribbor, som upptill löper samman och nedtill bildar en vidare krets; de är avsedda att användas av hantverkarna under sommaren, för att de inte ska plågas av solen, på samma gång som de besväras av rök och eld. De kilformiga husen är i regel försedda med höga tak, för att de täta och tunga snömassorna fortare ska kunna blåsa av och inte tynga ned taken. Dessa är täckta av björknäver, tegel eller kluvet spån av furu - för kådans skull -, gran, ek eller bok; taken på de förmögnas hus däremot med plåtar av koppar eller bly, i likhet med kyrktaken. Valvbyggnaderna uppförs ganska konstnärligt till skydd mot våldsamma vindar och snöfall, görs av sten eller trä, och är avsedda för olika alldagliga viktiga ändamål. Liknande byggnader kan finnas i stormännens gårdar där de används som förvaringsrum för husgeråd och jordbruksredskap. De runda byggnaderna - som för övrigt är de högst sällsynta - används av konstnärer, som vid sitt arbete behöver ett jämnt fördelat ljus från taket. Vanligast är de fyrkantiga husen, vars grova bjälkar är synnerligen väl hopfogade i hörnen - ett sant mästerverk av byggnadskonst; även dessa har fönster högt uppe i taken, för att dagsljuset skall kunna strömma in och ge alla därinne full belysning. Stenhusen har dörröppningar i förhållande till byggnadens storlek, men smala fönstergluggar, som skydd mot den stränga kölden, frosten och snön. Vore de större och vidare, såsom fönstren i Italien, skulle husen i följd av den fint yrande snön, som röres upp av den starka blåsten, precis som dammet av virvelvinden, snart nog fyllas med massor av snö och inte kunna stå emot dess tryck, utan störta samman. + + ` + var NONE = ` + Mycket olika byggnader har man i de nordiska rikena: pyramidformiga, kilformiga, välvda, runda och fyrkantiga. De pyramidformiga består helt enkelt av träribbor, som upptill löper samman och nedtill bildar en vidare krets; de är avsedda att användas av hantverkarna under sommaren, för att de inte ska plågas av solen, på samma gång som de besväras av rök och eld. De kilformiga husen är i regel försedda med höga tak, för att de täta och tunga snömassorna fortare ska kunna blåsa av och inte tynga ned taken. Dessa är täckta av björknäver, tegel eller kluvet spån av furu - för kådans skull -, gran, ek eller bok; taken på de förmögnas hus däremot med plåtar av koppar eller bly, i likhet med kyrktaken. Valvbyggnaderna uppförs ganska konstnärligt till skydd mot våldsamma vindar och snöfall, görs av sten eller trä, och är avsedda för olika alldagliga viktiga ändamål. Liknande byggnader kan finnas i stormännens gårdar där de används som förvaringsrum för husgeråd och jordbruksredskap. De runda byggnaderna - som för övrigt är de högst sällsynta - används av konstnärer, som vid sitt arbete behöver ett jämnt fördelat ljus från taket. Vanligast är de fyrkantiga husen, vars grova bjälkar är synnerligen väl hopfogade i hörnen - ett sant mästerverk av byggnadskonst; även dessa har fönster högt uppe i taken, för att dagsljuset skall kunna strömma in och ge alla därinne full belysning. Stenhusen har dörröppningar i förhållande till byggnadens storlek, men smala fönstergluggar, som skydd mot den stränga kölden, frosten och snön. Vore de större och vidare, såsom fönstren i Italien, skulle husen i följd av den fint yrande snön, som röres upp av den starka blåsten, precis som dammet av virvelvinden, snart nog fyllas med massor av snö och inte kunna stå emot dess tryck, utan störta samman. ` var expectedBody = `Mycket olika byggnader har man i de nordiska rikena: pyramidformiga, kilformiga, välvda, runda och fyrkantiga. De pyramidformiga består helt enkelt av träribbor, som upptill löper samman och nedtill bildar en vidare krets; de är avsedda att användas av hantverkarna under sommaren, för att de inte ska plågas av solen, på samma gång som de besväras av rök och eld. De kilformiga husen är i regel försedda med höga tak, för att de täta och tunga snömassorna fortare ska kunna blåsa av och inte tynga ned taken. Dessa är täckta av björknäver, tegel eller kluvet spån av furu - för kådans skull -, gran, ek eller bok; taken på de förmögnas hus däremot med plåtar av koppar eller bly, i likhet med kyrktaken. Valvbyggnaderna uppförs ganska konstnärligt till skydd mot våldsamma vindar och snöfall, görs av sten eller trä, och är avsedda för olika alldagliga viktiga ändamål. Liknande byggnader kan finnas i stormännens gårdar där de används som förvaringsrum för husgeråd och jordbruksredskap. De runda byggnaderna - som för övrigt är de högst sällsynta - används av konstnärer, som vid sitt arbete behöver ett jämnt fördelat ljus från taket. Vanligast är de fyrkantiga husen, vars grova bjälkar är synnerligen väl hopfogade i hörnen - ett sant mästerverk av byggnadskonst; även dessa har fönster högt uppe i taken, för att dagsljuset skall kunna strömma in och ge alla därinne full belysning. Stenhusen har dörröppningar i förhållande till byggnadens storlek, men smala fönstergluggar, som skydd mot den stränga kölden, frosten och snön. Vore de större och vidare, såsom fönstren i Italien, skulle husen i följd av den fint yrande snön, som röres upp av den starka blåsten, precis som dammet av virvelvinden, snart nog fyllas med massor av snö och inte kunna stå emot dess tryck, utan störta samman. ` + data := []struct { - parser MetadataParser + pType string testData string - name string }{ - {&JSONMetadataParser{metadata: newMetadata()}, JSON, "json"}, - {&YAMLMetadataParser{metadata: newMetadata()}, YAML, "yaml"}, - {&TOMLMetadataParser{metadata: newMetadata()}, TOML, "toml"}, + {"JSON", JSON}, + {"TOML", TOML}, + {"YAML", YAML}, + {"None", NONE}, } for _, v := range data { - // metadata without identifiers - if md, err := v.parser.Parse([]byte(v.testData)); err != nil || strings.TrimSpace(string(md)) != strings.TrimSpace(expectedBody) { - t.Fatalf("Error not expected and/or markdown not equal for %v", v.name) + p := GetParser([]byte(v.testData)) + if v.pType != p.Type() { + t.Fatalf("Wrong parser type, expected %v, got %v", v.pType, p.Type()) + } + md := p.Markdown() + if strings.TrimSpace(string(md)) != strings.TrimSpace(expectedBody) { + t.Log("Provided:", v.testData) + t.Log("Returned:", p.Markdown()) + t.Fatalf("Error, mismatched body in expected type %v, matched type %v", v.pType, p.Type()) } } } diff --git a/middleware/markdown/metadata_toml.go b/middleware/markdown/metadata/metadata_toml.go similarity index 61% rename from middleware/markdown/metadata_toml.go rename to middleware/markdown/metadata/metadata_toml.go index fe4068d7..cfd3a9f7 100644 --- a/middleware/markdown/metadata_toml.go +++ b/middleware/markdown/metadata/metadata_toml.go @@ -1,12 +1,37 @@ -package markdown +package metadata import ( + "bytes" + "github.com/BurntSushi/toml" ) // TOMLMetadataParser is the MetadataParser for TOML type TOMLMetadataParser struct { metadata Metadata + markdown *bytes.Buffer +} + +func (t *TOMLMetadataParser) Type() string { + return "TOML" +} + +// Parse metadata/markdown file +func (t *TOMLMetadataParser) Init(b *bytes.Buffer) bool { + meta, data := splitBuffer(b, "+++") + if meta == nil || data == nil { + return false + } + t.markdown = data + + m := make(map[string]interface{}) + if err := toml.Unmarshal(meta.Bytes(), &m); err != nil { + return false + } + t.metadata = NewMetadata() + t.metadata.load(m) + + return true } // Parse the metadata @@ -15,6 +40,7 @@ func (t *TOMLMetadataParser) Parse(b []byte) ([]byte, error) { if err != nil { return markdown, err } + m := make(map[string]interface{}) if err := toml.Unmarshal(b, &m); err != nil { return markdown, err @@ -29,6 +55,10 @@ func (t *TOMLMetadataParser) Metadata() Metadata { return t.metadata } +func (t *TOMLMetadataParser) Markdown() []byte { + return t.markdown.Bytes() +} + // Opening returns the opening identifier TOML metadata func (t *TOMLMetadataParser) Opening() []byte { return []byte("+++") diff --git a/middleware/markdown/metadata_yaml.go b/middleware/markdown/metadata/metadata_yaml.go similarity index 62% rename from middleware/markdown/metadata_yaml.go rename to middleware/markdown/metadata/metadata_yaml.go index 41103e21..e7877445 100644 --- a/middleware/markdown/metadata_yaml.go +++ b/middleware/markdown/metadata/metadata_yaml.go @@ -1,12 +1,36 @@ -package markdown +package metadata import ( + "bytes" + "gopkg.in/yaml.v2" ) // YAMLMetadataParser is the MetadataParser for YAML type YAMLMetadataParser struct { metadata Metadata + markdown *bytes.Buffer +} + +func (y *YAMLMetadataParser) Type() string { + return "YAML" +} + +func (y *YAMLMetadataParser) Init(b *bytes.Buffer) bool { + meta, data := splitBuffer(b, "---") + if meta == nil || data == nil { + return false + } + y.markdown = data + + m := make(map[string]interface{}) + if err := yaml.Unmarshal(meta.Bytes(), &m); err != nil { + return false + } + y.metadata = NewMetadata() + y.metadata.load(m) + + return true } // Parse the metadata @@ -30,6 +54,10 @@ func (y *YAMLMetadataParser) Metadata() Metadata { return y.metadata } +func (y *YAMLMetadataParser) Markdown() []byte { + return y.markdown.Bytes() +} + // Opening returns the opening identifier YAML metadata func (y *YAMLMetadataParser) Opening() []byte { return []byte("---") diff --git a/middleware/markdown/process.go b/middleware/markdown/process.go index e661e697..27f337dd 100644 --- a/middleware/markdown/process.go +++ b/middleware/markdown/process.go @@ -1,49 +1,19 @@ package markdown import ( - "bytes" "path/filepath" "github.com/mholt/caddy/middleware" + "github.com/mholt/caddy/middleware/markdown/metadata" "github.com/russross/blackfriday" ) -// Data represents a markdown document. -type Data struct { - middleware.Context - Doc map[string]string - DocFlags map[string]bool - Styles []string - Scripts []string -} - -// Include "overrides" the embedded middleware.Context's Include() -// method so that included files have access to d's fields. -func (d Data) Include(filename string) (string, error) { - return middleware.ContextInclude(filename, d, d.Root) -} - -// Process processes the contents of a page in b. It parses the metadata +// Markdown 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, ctx middleware.Context) ([]byte, error) { - var metadata = newMetadata() - var markdown []byte - var err error - - // find parser compatible with page contents - parser := findParser(b) - - if parser == nil { - // if not found, assume whole file is markdown (no front matter) - markdown = b - } else { - // if found, assume metadata present and parse. - markdown, err = parser.Parse(b) - if err != nil { - return nil, err - } - metadata = parser.Metadata() - } +func (c *Config) Markdown(requestPath string, b []byte, ctx middleware.Context) ([]byte, error) { + parser := metadata.GetParser(b) + markdown := parser.Markdown() + mdata := parser.Metadata() // process markdown extns := 0 @@ -54,33 +24,14 @@ func (md Markdown) Process(c *Config, requestPath string, b []byte, ctx middlewa markdown = blackfriday.Markdown(markdown, c.Renderer, extns) // set it as body for template - metadata.Variables["body"] = string(markdown) - title := metadata.Title + mdata.Variables["body"] = string(markdown) + title := mdata.Title if title == "" { title = filepath.Base(requestPath) var extension = filepath.Ext(requestPath) title = title[0 : len(title)-len(extension)] } - metadata.Variables["title"] = title + mdata.Variables["title"] = title - // return md.processTemplate(c, requestPath, metadata, ctx) - return md.doTemplate(c, requestPath, metadata, ctx) -} - -// doTemplate executes a template given a requestPath, template, and metadata -func (md Markdown) doTemplate(c *Config, reqPath string, metadata Metadata, ctx middleware.Context) ([]byte, error) { - mdData := Data{ - Context: ctx, - Doc: metadata.Variables, - DocFlags: metadata.Flags, - Styles: c.Styles, - Scripts: c.Scripts, - } - - b := new(bytes.Buffer) - if err := c.Template.ExecuteTemplate(b, metadata.Template, mdData); err != nil { - return nil, err - } - - return b.Bytes(), nil + return execTemplate(c, mdata, ctx) } diff --git a/middleware/markdown/template.go b/middleware/markdown/template.go index 245ad995..18d486a1 100644 --- a/middleware/markdown/template.go +++ b/middleware/markdown/template.go @@ -1,10 +1,50 @@ package markdown import ( + "bytes" "io/ioutil" + "os" "text/template" + + "github.com/mholt/caddy/middleware" + "github.com/mholt/caddy/middleware/markdown/metadata" ) +// Data represents a markdown document. +type Data struct { + middleware.Context + Doc map[string]string + DocFlags map[string]bool + Styles []string + Scripts []string + Files []os.FileInfo +} + +// Include "overrides" the embedded middleware.Context's Include() +// method so that included files have access to d's fields. +// Note: using {{template 'template-name' .}} instead might be better. +func (d Data) Include(filename string) (string, error) { + return middleware.ContextInclude(filename, d, d.Root) +} + +// execTemplate executes a template given a requestPath, template, and metadata +func execTemplate(c *Config, mdata metadata.Metadata, ctx middleware.Context) ([]byte, error) { + mdData := Data{ + Context: ctx, + Doc: mdata.Variables, + DocFlags: mdata.Flags, + Styles: c.Styles, + Scripts: c.Scripts, + } + + b := new(bytes.Buffer) + if err := c.Template.ExecuteTemplate(b, mdata.Template, mdData); err != nil { + return nil, err + } + + return b.Bytes(), nil +} + func setDefaultTemplate(filename string) *template.Template { buf, err := ioutil.ReadFile(filename) if err != nil { From b97a7909d81dd909a5454aa6cb8ad464707af315 Mon Sep 17 00:00:00 2001 From: Tobias Weingartner Date: Sat, 16 Apr 2016 15:18:17 -0700 Subject: [PATCH 05/19] Nuke more redundant things. --- middleware/markdown/metadata/metadata.go | 59 ++----------------- middleware/markdown/metadata/metadata_json.go | 28 --------- middleware/markdown/metadata/metadata_none.go | 10 ---- middleware/markdown/metadata/metadata_toml.go | 25 -------- middleware/markdown/metadata/metadata_yaml.go | 25 -------- 5 files changed, 5 insertions(+), 142 deletions(-) diff --git a/middleware/markdown/metadata/metadata.go b/middleware/markdown/metadata/metadata.go index c5ccaaaa..027a8cdb 100644 --- a/middleware/markdown/metadata/metadata.go +++ b/middleware/markdown/metadata/metadata.go @@ -3,7 +3,6 @@ package metadata import ( "bufio" "bytes" - "fmt" "time" ) @@ -71,12 +70,6 @@ type MetadataParser interface { // Type of metadata Type() string - // Opening identifier - Opening() []byte - - // Closing identifier - Closing() []byte - // Parsed metadata. Metadata() Metadata @@ -84,35 +77,7 @@ type MetadataParser interface { Markdown() []byte } -// extractMetadata separates metadata content from from markdown content in b. -// It returns the metadata, the remaining bytes (markdown), and an error, if any. -func extractMetadata(parser MetadataParser, b []byte) (metadata []byte, markdown []byte, err error) { - b = bytes.TrimSpace(b) - openingLine := parser.Opening() - closingLine := parser.Closing() - if !bytes.HasPrefix(b, openingLine) { - return nil, b, fmt.Errorf("first line missing expected metadata identifier") - } - metaStart := len(openingLine) - if _, ok := parser.(*JSONMetadataParser); ok { - metaStart = 0 - } - metaEnd := bytes.Index(b[metaStart:], closingLine) - if metaEnd == -1 { - return nil, nil, fmt.Errorf("metadata not closed ('%s' not found)", parser.Closing()) - } - metaEnd += metaStart - if _, ok := parser.(*JSONMetadataParser); ok { - metaEnd += len(closingLine) - } - metadata = b[metaStart:metaEnd] - markdown = b[metaEnd:] - if _, ok := parser.(*JSONMetadataParser); !ok { - markdown = b[metaEnd+len(closingLine):] - } - return metadata, markdown, nil -} - +// GetParser returns a parser for the given data func GetParser(buf []byte) MetadataParser { for _, p := range parsers() { b := bytes.NewBuffer(buf) @@ -124,22 +89,6 @@ func GetParser(buf []byte) MetadataParser { return nil } -// findParser finds the parser using line that contains opening identifier -func FindParser(b []byte) MetadataParser { - var line []byte - // Read first line - if _, err := fmt.Fscanln(bytes.NewReader(b), &line); err != nil { - return nil - } - line = bytes.TrimSpace(line) - for _, parser := range parsers() { - if bytes.Equal(parser.Opening(), line) { - return parser - } - } - return nil -} - func NewMetadata() Metadata { return Metadata{ Variables: make(map[string]string), @@ -151,13 +100,15 @@ func NewMetadata() Metadata { func parsers() []MetadataParser { return []MetadataParser{ &TOMLMetadataParser{}, - &YAMLMetadataParser{metadata: NewMetadata()}, + &YAMLMetadataParser{}, &JSONMetadataParser{}, + + // This one must be last &NoneMetadataParser{}, } } -// Split out "normal" metadata with given delimiter +// Split out prefixed/suffixed metadata with given delimiter func splitBuffer(b *bytes.Buffer, delim string) (*bytes.Buffer, *bytes.Buffer) { scanner := bufio.NewScanner(b) diff --git a/middleware/markdown/metadata/metadata_json.go b/middleware/markdown/metadata/metadata_json.go index bfc8c13d..c35148c4 100644 --- a/middleware/markdown/metadata/metadata_json.go +++ b/middleware/markdown/metadata/metadata_json.go @@ -43,24 +43,6 @@ func (j *JSONMetadataParser) Init(b *bytes.Buffer) bool { return true } -// Parse the metadata -func (j *JSONMetadataParser) Parse(b []byte) ([]byte, error) { - b, markdown, err := extractMetadata(j, b) - if err != nil { - return markdown, err - } - m := make(map[string]interface{}) - - // Read the preceding JSON object - decoder := json.NewDecoder(bytes.NewReader(b)) - if err := decoder.Decode(&m); err != nil { - return markdown, err - } - j.metadata.load(m) - - return markdown, nil -} - // Metadata returns parsed metadata. It should be called // only after a call to Parse returns without error. func (j *JSONMetadataParser) Metadata() Metadata { @@ -70,13 +52,3 @@ func (j *JSONMetadataParser) Metadata() Metadata { func (j *JSONMetadataParser) Markdown() []byte { return j.markdown.Bytes() } - -// Opening returns the opening identifier JSON metadata -func (j *JSONMetadataParser) Opening() []byte { - return []byte("{") -} - -// Closing returns the closing identifier JSON metadata -func (j *JSONMetadataParser) Closing() []byte { - return []byte("}") -} diff --git a/middleware/markdown/metadata/metadata_none.go b/middleware/markdown/metadata/metadata_none.go index 3dc22c07..f2ca10f3 100644 --- a/middleware/markdown/metadata/metadata_none.go +++ b/middleware/markdown/metadata/metadata_none.go @@ -38,13 +38,3 @@ func (n *NoneMetadataParser) Metadata() Metadata { func (n *NoneMetadataParser) Markdown() []byte { return n.markdown.Bytes() } - -// Opening returns the opening identifier TOML metadata -func (n *NoneMetadataParser) Opening() []byte { - return []byte("...") -} - -// Closing returns the closing identifier TOML metadata -func (n *NoneMetadataParser) Closing() []byte { - return []byte("...") -} diff --git a/middleware/markdown/metadata/metadata_toml.go b/middleware/markdown/metadata/metadata_toml.go index cfd3a9f7..ed3a8451 100644 --- a/middleware/markdown/metadata/metadata_toml.go +++ b/middleware/markdown/metadata/metadata_toml.go @@ -34,21 +34,6 @@ func (t *TOMLMetadataParser) Init(b *bytes.Buffer) bool { return true } -// Parse the metadata -func (t *TOMLMetadataParser) Parse(b []byte) ([]byte, error) { - b, markdown, err := extractMetadata(t, b) - if err != nil { - return markdown, err - } - - m := make(map[string]interface{}) - if err := toml.Unmarshal(b, &m); err != nil { - return markdown, err - } - t.metadata.load(m) - return markdown, nil -} - // Metadata returns parsed metadata. It should be called // only after a call to Parse returns without error. func (t *TOMLMetadataParser) Metadata() Metadata { @@ -58,13 +43,3 @@ func (t *TOMLMetadataParser) Metadata() Metadata { func (t *TOMLMetadataParser) Markdown() []byte { return t.markdown.Bytes() } - -// Opening returns the opening identifier TOML metadata -func (t *TOMLMetadataParser) Opening() []byte { - return []byte("+++") -} - -// Closing returns the closing identifier TOML metadata -func (t *TOMLMetadataParser) Closing() []byte { - return []byte("+++") -} diff --git a/middleware/markdown/metadata/metadata_yaml.go b/middleware/markdown/metadata/metadata_yaml.go index e7877445..14161f5e 100644 --- a/middleware/markdown/metadata/metadata_yaml.go +++ b/middleware/markdown/metadata/metadata_yaml.go @@ -33,21 +33,6 @@ func (y *YAMLMetadataParser) Init(b *bytes.Buffer) bool { return true } -// Parse the metadata -func (y *YAMLMetadataParser) Parse(b []byte) ([]byte, error) { - b, markdown, err := extractMetadata(y, b) - if err != nil { - return markdown, err - } - - m := make(map[string]interface{}) - if err := yaml.Unmarshal(b, &m); err != nil { - return markdown, err - } - y.metadata.load(m) - return markdown, nil -} - // Metadata returns parsed metadata. It should be called // only after a call to Parse returns without error. func (y *YAMLMetadataParser) Metadata() Metadata { @@ -57,13 +42,3 @@ func (y *YAMLMetadataParser) Metadata() Metadata { func (y *YAMLMetadataParser) Markdown() []byte { return y.markdown.Bytes() } - -// Opening returns the opening identifier YAML metadata -func (y *YAMLMetadataParser) Opening() []byte { - return []byte("---") -} - -// Closing returns the closing identifier YAML metadata -func (y *YAMLMetadataParser) Closing() []byte { - return []byte("---") -} From e652d12cfcc642e5b589fecf633a0794cae594eb Mon Sep 17 00:00:00 2001 From: Tobias Weingartner Date: Sat, 16 Apr 2016 15:36:52 -0700 Subject: [PATCH 06/19] Move Metadata load into NewMetadata function. --- middleware/markdown/markdown.go | 4 +++- middleware/markdown/metadata/metadata.go | 18 +++++++++++------- middleware/markdown/metadata/metadata_json.go | 3 +-- middleware/markdown/metadata/metadata_none.go | 3 +-- middleware/markdown/metadata/metadata_toml.go | 3 +-- middleware/markdown/metadata/metadata_yaml.go | 3 +-- 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/middleware/markdown/markdown.go b/middleware/markdown/markdown.go index 56c735f9..17083668 100644 --- a/middleware/markdown/markdown.go +++ b/middleware/markdown/markdown.go @@ -122,7 +122,9 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error return http.StatusInternalServerError, err } - // html, err = md.doTemplate(cfg, html, ctx) + // TODO(weingart): move template execution here, something like: + // + // html, err = md.execTemplate(cfg, html, ctx) // if err != nil { // return http.StatusInternalServerError, err // } diff --git a/middleware/markdown/metadata/metadata.go b/middleware/markdown/metadata/metadata.go index 027a8cdb..24cdb391 100644 --- a/middleware/markdown/metadata/metadata.go +++ b/middleware/markdown/metadata/metadata.go @@ -32,6 +32,17 @@ type Metadata struct { Flags map[string]bool } +// NewMetadata() returns a new Metadata struct, loaded with the given map +func NewMetadata(parsedMap map[string]interface{}) Metadata { + md := Metadata{ + Variables: make(map[string]string), + Flags: make(map[string]bool), + } + md.load(parsedMap) + + return md +} + // load loads parsed values in parsedMap into Metadata func (m *Metadata) load(parsedMap map[string]interface{}) { @@ -89,13 +100,6 @@ func GetParser(buf []byte) MetadataParser { return nil } -func NewMetadata() Metadata { - return Metadata{ - Variables: make(map[string]string), - Flags: make(map[string]bool), - } -} - // parsers returns all available parsers func parsers() []MetadataParser { return []MetadataParser{ diff --git a/middleware/markdown/metadata/metadata_json.go b/middleware/markdown/metadata/metadata_json.go index c35148c4..d3b9991f 100644 --- a/middleware/markdown/metadata/metadata_json.go +++ b/middleware/markdown/metadata/metadata_json.go @@ -36,8 +36,7 @@ func (j *JSONMetadataParser) Init(b *bytes.Buffer) bool { } } - j.metadata = NewMetadata() - j.metadata.load(m) + j.metadata = NewMetadata(m) j.markdown = bytes.NewBuffer(b.Bytes()) return true diff --git a/middleware/markdown/metadata/metadata_none.go b/middleware/markdown/metadata/metadata_none.go index f2ca10f3..ed034f2f 100644 --- a/middleware/markdown/metadata/metadata_none.go +++ b/middleware/markdown/metadata/metadata_none.go @@ -17,8 +17,7 @@ func (n *NoneMetadataParser) Type() string { // Parse metadata/markdown file func (n *NoneMetadataParser) Init(b *bytes.Buffer) bool { m := make(map[string]interface{}) - n.metadata = NewMetadata() - n.metadata.load(m) + n.metadata = NewMetadata(m) n.markdown = bytes.NewBuffer(b.Bytes()) return true diff --git a/middleware/markdown/metadata/metadata_toml.go b/middleware/markdown/metadata/metadata_toml.go index ed3a8451..75c2067f 100644 --- a/middleware/markdown/metadata/metadata_toml.go +++ b/middleware/markdown/metadata/metadata_toml.go @@ -28,8 +28,7 @@ func (t *TOMLMetadataParser) Init(b *bytes.Buffer) bool { if err := toml.Unmarshal(meta.Bytes(), &m); err != nil { return false } - t.metadata = NewMetadata() - t.metadata.load(m) + t.metadata = NewMetadata(m) return true } diff --git a/middleware/markdown/metadata/metadata_yaml.go b/middleware/markdown/metadata/metadata_yaml.go index 14161f5e..f7ef5bb4 100644 --- a/middleware/markdown/metadata/metadata_yaml.go +++ b/middleware/markdown/metadata/metadata_yaml.go @@ -27,8 +27,7 @@ func (y *YAMLMetadataParser) Init(b *bytes.Buffer) bool { if err := yaml.Unmarshal(meta.Bytes(), &m); err != nil { return false } - y.metadata = NewMetadata() - y.metadata.load(m) + y.metadata = NewMetadata(m) return true } From b541c717ca9a964175658f6864e12adeb2882c87 Mon Sep 17 00:00:00 2001 From: Tobias Weingartner Date: Sat, 16 Apr 2016 16:50:45 -0700 Subject: [PATCH 07/19] Add ability to markdown a directory with a template. --- middleware/markdown/markdown.go | 3 +-- middleware/markdown/metadata/metadata.go | 4 ++++ middleware/markdown/process.go | 10 +++++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/middleware/markdown/markdown.go b/middleware/markdown/markdown.go index 17083668..5d6da36f 100644 --- a/middleware/markdown/markdown.go +++ b/middleware/markdown/markdown.go @@ -89,7 +89,6 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error // Set path to found index file fpath = idx - _ = dirents } // If supported extension, process it @@ -117,7 +116,7 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error Req: r, URL: r.URL, } - html, err := cfg.Markdown(fpath, body, ctx) + html, err := cfg.Markdown(fpath, body, dirents, ctx) if err != nil { return http.StatusInternalServerError, err } diff --git a/middleware/markdown/metadata/metadata.go b/middleware/markdown/metadata/metadata.go index 24cdb391..19b50b44 100644 --- a/middleware/markdown/metadata/metadata.go +++ b/middleware/markdown/metadata/metadata.go @@ -3,6 +3,7 @@ package metadata import ( "bufio" "bytes" + "os" "time" ) @@ -30,6 +31,9 @@ type Metadata struct { // Flags to be used with Template Flags map[string]bool + + // Directory entries present, if a directory + Dirents []os.FileInfo } // NewMetadata() returns a new Metadata struct, loaded with the given map diff --git a/middleware/markdown/process.go b/middleware/markdown/process.go index 27f337dd..bae5cf81 100644 --- a/middleware/markdown/process.go +++ b/middleware/markdown/process.go @@ -1,6 +1,7 @@ package markdown import ( + "os" "path/filepath" "github.com/mholt/caddy/middleware" @@ -10,7 +11,7 @@ import ( // 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, ctx middleware.Context) ([]byte, error) { +func (c *Config) Markdown(requestPath string, b []byte, dirents []os.FileInfo, ctx middleware.Context) ([]byte, error) { parser := metadata.GetParser(b) markdown := parser.Markdown() mdata := parser.Metadata() @@ -33,5 +34,12 @@ func (c *Config) Markdown(requestPath string, b []byte, ctx middleware.Context) } mdata.Variables["title"] = title + if len(dirents) > 0 { + mdata.Flags["dirents"] = true + mdata.Dirents = dirents + } else { + mdata.Flags["dirents"] = false + } + return execTemplate(c, mdata, ctx) } From ecf91f525fe5a720235042b4f775d7b31dc43c17 Mon Sep 17 00:00:00 2001 From: Tobias Weingartner Date: Sat, 16 Apr 2016 17:07:06 -0700 Subject: [PATCH 08/19] Fix gofmt -s and ineffassign. --- middleware/markdown/markdown_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/middleware/markdown/markdown_test.go b/middleware/markdown/markdown_test.go index 59c90a13..1e86e248 100644 --- a/middleware/markdown/markdown_test.go +++ b/middleware/markdown/markdown_test.go @@ -29,7 +29,7 @@ func TestMarkdown(t *testing.T) { Renderer: blackfriday.HtmlRenderer(0, "", ""), PathScope: "/blog", Extensions: map[string]struct{}{ - ".md": struct{}{}, + ".md": {}, }, Styles: []string{}, Scripts: []string{}, @@ -39,7 +39,7 @@ func TestMarkdown(t *testing.T) { Renderer: blackfriday.HtmlRenderer(0, "", ""), PathScope: "/docflags", Extensions: map[string]struct{}{ - ".md": struct{}{}, + ".md": {}, }, Styles: []string{}, Scripts: []string{}, @@ -49,7 +49,7 @@ func TestMarkdown(t *testing.T) { Renderer: blackfriday.HtmlRenderer(0, "", ""), PathScope: "/log", Extensions: map[string]struct{}{ - ".md": struct{}{}, + ".md": {}, }, Styles: []string{"/resources/css/log.css", "/resources/css/default.css"}, Scripts: []string{"/resources/js/log.js", "/resources/js/default.js"}, @@ -59,7 +59,7 @@ func TestMarkdown(t *testing.T) { Renderer: blackfriday.HtmlRenderer(0, "", ""), PathScope: "/og", Extensions: map[string]struct{}{ - ".md": struct{}{}, + ".md": {}, }, Styles: []string{}, Scripts: []string{}, @@ -175,9 +175,9 @@ DocFlags.var_bool true` } rec = httptest.NewRecorder() currenttime := time.Now().Local().Add(-time.Second) - err = os.Chtimes("testdata/og/first.md", currenttime, currenttime) + _ = os.Chtimes("testdata/og/first.md", currenttime, currenttime) currenttime = time.Now().Local() - err = os.Chtimes("testdata/og_static/og/first.md/index.html", currenttime, currenttime) + _ = os.Chtimes("testdata/og_static/og/first.md/index.html", currenttime, currenttime) time.Sleep(time.Millisecond * 200) md.ServeHTTP(rec, req) From 5a0d373fcd81aa60afbe672b296cc60711a5194c Mon Sep 17 00:00:00 2001 From: Tobias Weingartner Date: Sat, 16 Apr 2016 17:13:13 -0700 Subject: [PATCH 09/19] Missed fixing setup. --- caddy/setup/markdown_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/caddy/setup/markdown_test.go b/caddy/setup/markdown_test.go index e1e698b2..fee9a332 100644 --- a/caddy/setup/markdown_test.go +++ b/caddy/setup/markdown_test.go @@ -59,8 +59,8 @@ func TestMarkdownParse(t *testing.T) { }`, false, []markdown.Config{{ PathScope: "/blog", Extensions: map[string]struct{}{ - ".md": struct{}{}, - ".txt": struct{}{}, + ".md": {}, + ".txt": {}, }, Styles: []string{"/resources/css/blog.css"}, Scripts: []string{"/resources/js/blog.js"}, @@ -72,7 +72,7 @@ func TestMarkdownParse(t *testing.T) { }`, false, []markdown.Config{{ PathScope: "/blog", Extensions: map[string]struct{}{ - ".md": struct{}{}, + ".md": {}, }, Template: markdown.GetDefaultTemplate(), }}}, From dd4de698cf993407797f535cffc266cad39483ea Mon Sep 17 00:00:00 2001 From: Tobias Weingartner Date: Sun, 17 Apr 2016 10:58:51 -0700 Subject: [PATCH 10/19] Better search for config. Handle LastModifiedHeader better. Handle HEAD/GET. --- middleware/markdown/markdown.go | 147 ++++++++++++++++++++------------ 1 file changed, 92 insertions(+), 55 deletions(-) diff --git a/middleware/markdown/markdown.go b/middleware/markdown/markdown.go index 5d6da36f..00e0f72d 100644 --- a/middleware/markdown/markdown.go +++ b/middleware/markdown/markdown.go @@ -8,6 +8,7 @@ import ( "os" "path" "text/template" + "time" "github.com/mholt/caddy/middleware" "github.com/russross/blackfriday" @@ -65,75 +66,111 @@ type Config struct { // ServeHTTP implements the http.Handler interface. func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - for _, cfg := range md.Configs { - if !middleware.Path(r.URL.Path).Matches(cfg.PathScope) { - continue + var cfg *Config + for _, c := range md.Configs { + if middleware.Path(r.URL.Path).Matches(c.PathScope) { // not negated + cfg = c + break // or goto + } + } + if cfg == nil { + return md.Next.ServeHTTP(w, r) // exit early + } + + // We only deal with HEAD/GET + switch r.Method { + case http.MethodGet, http.MethodHead: + default: + return http.StatusMethodNotAllowed, nil + } + + var dirents []os.FileInfo + var lastModTime time.Time + fpath := r.URL.Path + if idx, ok := middleware.IndexFile(md.FileSys, fpath, md.IndexFiles); ok { + // We're serving a directory index file, which may be a markdown + // file with a template. Let's grab a list of files this directory + // 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 + } + return http.StatusInternalServerError, err } - var dirents []os.FileInfo - fpath := r.URL.Path - if idx, ok := middleware.IndexFile(md.FileSys, fpath, md.IndexFiles); ok { - // We're serving a directory index file, which may be a markdown - // file with a template. Let's grab a list of files this directory - // 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 { - return http.StatusInternalServerError, err - } - dirents, err = fdp.Readdir(-1) - if err != nil { - return http.StatusInternalServerError, err - } - - // Set path to found index file - fpath = idx + // 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. + dirents, _ = fdp.Readdir(-1) + for _, d := range dirents { + lastModTime = latest(lastModTime, d.ModTime()) } - // 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 - } + // Set path to found index file + fpath = idx + } - fs, err := f.Stat() - if err != nil { - return http.StatusNotFound, nil + // 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 + } - body, err := ioutil.ReadAll(f) - if err != nil { - return http.StatusInternalServerError, err - } + fs, err := f.Stat() + if err != nil { + return http.StatusNotFound, nil + } + lastModTime = latest(lastModTime, fs.ModTime()) - 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 - } + body, err := ioutil.ReadAll(f) + 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 - // } + 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 + } - middleware.SetLastModifiedHeader(w, fs.ModTime()) + // 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 == "GET" { w.Write(html) - return http.StatusOK, nil } + return http.StatusOK, nil } // Didn't qualify to serve as markdown; pass-thru return md.Next.ServeHTTP(w, r) } + +// latest returns the latest time.Time +func latest(t ...time.Time) time.Time { + var last time.Time + + for _, tt := range t { + if tt.After(last) { + last = tt + } + } + + return last +} From 249c9a17f58cd4d58a92a158a01508347bedfc8e Mon Sep 17 00:00:00 2001 From: Tobias Weingartner Date: Sat, 30 Apr 2016 14:07:42 -0700 Subject: [PATCH 11/19] Make default template more readable/clean. --- middleware/markdown/template.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/middleware/markdown/template.go b/middleware/markdown/template.go index 18d486a1..f9f1f661 100644 --- a/middleware/markdown/template.go +++ b/middleware/markdown/template.go @@ -82,9 +82,13 @@ const ( {{.Doc.title}} - {{range .Styles}} - {{end}}{{range .Scripts}} - {{end}} + + {{- range .Styles}} + + {{- end}} + {{- range .Scripts}} + + {{- end}} {{.Doc.body}} From 20dfaab703d44745c58a04d67b6f2893246e0969 Mon Sep 17 00:00:00 2001 From: Tobias Weingartner Date: Sat, 30 Apr 2016 14:16:19 -0700 Subject: [PATCH 12/19] Nuke unused function. --- middleware/markdown/markdown.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/middleware/markdown/markdown.go b/middleware/markdown/markdown.go index 00e0f72d..11078e7b 100644 --- a/middleware/markdown/markdown.go +++ b/middleware/markdown/markdown.go @@ -33,16 +33,6 @@ type Markdown struct { 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 From bd2a33dd1403b952cd04be09b5c711870d45cdf7 Mon Sep 17 00:00:00 2001 From: Tobias Weingartner Date: Sat, 30 Apr 2016 14:21:30 -0700 Subject: [PATCH 13/19] Europeans know time. :) --- middleware/markdown/metadata/metadata.go | 1 + 1 file changed, 1 insertion(+) diff --git a/middleware/markdown/metadata/metadata.go b/middleware/markdown/metadata/metadata.go index 19b50b44..a861ee4e 100644 --- a/middleware/markdown/metadata/metadata.go +++ b/middleware/markdown/metadata/metadata.go @@ -10,6 +10,7 @@ import ( var ( // Date format YYYY-MM-DD HH:MM:SS or YYYY-MM-DD timeLayout = []string{ + `2006-01-02 15:04:05-0700`, `2006-01-02 15:04:05`, `2006-01-02`, } From e0bc4260506f18895f7cc4916cd8ee0e0f32f482 Mon Sep 17 00:00:00 2001 From: Tobias Weingartner Date: Sat, 30 Apr 2016 17:01:31 -0700 Subject: [PATCH 14/19] Initial re-add of markdown summary functionality. --- middleware/markdown/metadata/metadata.go | 4 - middleware/markdown/process.go | 39 ++++++- middleware/markdown/summary/render.go | 143 +++++++++++++++++++++++ middleware/markdown/summary/summary.go | 16 +++ middleware/markdown/template.go | 7 +- 5 files changed, 196 insertions(+), 13 deletions(-) create mode 100644 middleware/markdown/summary/render.go create mode 100644 middleware/markdown/summary/summary.go diff --git a/middleware/markdown/metadata/metadata.go b/middleware/markdown/metadata/metadata.go index a861ee4e..ade7fcc9 100644 --- a/middleware/markdown/metadata/metadata.go +++ b/middleware/markdown/metadata/metadata.go @@ -3,7 +3,6 @@ package metadata import ( "bufio" "bytes" - "os" "time" ) @@ -32,9 +31,6 @@ type Metadata struct { // Flags to be used with Template Flags map[string]bool - - // Directory entries present, if a directory - Dirents []os.FileInfo } // NewMetadata() returns a new Metadata struct, loaded with the given map diff --git a/middleware/markdown/process.go b/middleware/markdown/process.go index bae5cf81..15e5e7ca 100644 --- a/middleware/markdown/process.go +++ b/middleware/markdown/process.go @@ -1,14 +1,36 @@ package markdown import ( + "io/ioutil" "os" "path/filepath" "github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware/markdown/metadata" + "github.com/mholt/caddy/middleware/markdown/summary" "github.com/russross/blackfriday" ) +type FileInfo struct { + os.FileInfo + ctx middleware.Context +} + +func (f FileInfo) Summarize(wordcount int) string { + fp, err := f.ctx.Root.Open(f.Name()) + if err != nil { + return "" + } + defer fp.Close() + + buf, err := ioutil.ReadAll(fp) + if err != nil { + return "" + } + + return string(summary.Markdown(buf, wordcount)) +} + // 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) { @@ -26,6 +48,8 @@ func (c *Config) Markdown(requestPath string, b []byte, dirents []os.FileInfo, c // set it as body for template mdata.Variables["body"] = string(markdown) + + // fixup title title := mdata.Title if title == "" { title = filepath.Base(requestPath) @@ -34,12 +58,15 @@ func (c *Config) Markdown(requestPath string, b []byte, dirents []os.FileInfo, c } mdata.Variables["title"] = title - if len(dirents) > 0 { - mdata.Flags["dirents"] = true - mdata.Dirents = dirents - } else { - mdata.Flags["dirents"] = false + // massage possible files + files := []FileInfo{} + for _, ent := range dirents { + file := FileInfo{ + FileInfo: ent, + ctx: ctx, + } + files = append(files, file) } - return execTemplate(c, mdata, ctx) + return execTemplate(c, mdata, files, ctx) } diff --git a/middleware/markdown/summary/render.go b/middleware/markdown/summary/render.go new file mode 100644 index 00000000..fb11841c --- /dev/null +++ b/middleware/markdown/summary/render.go @@ -0,0 +1,143 @@ +package summary + +import ( + "bytes" + + "github.com/russross/blackfriday" +) + +// Ensure we implement the Blackfriday Markdown Renderer interface +var _ blackfriday.Renderer = (*Renderer)(nil) + +type Renderer struct{} + +// Blocklevel callbacks + +// BlockCode is the code tag callback. +func (r Renderer) BlockCode(out *bytes.Buffer, text []byte, land string) {} + +// BlockQuote is teh quote tag callback. +func (r Renderer) BlockQuote(out *bytes.Buffer, text []byte) {} + +// BlockHtml is the HTML tag callback. +func (r Renderer) BlockHtml(out *bytes.Buffer, text []byte) {} + +// 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. +func (r Renderer) HRule(out *bytes.Buffer) {} + +// List is the list tag callback. +func (r Renderer) List(out *bytes.Buffer, text func() bool, flags int) { + // TODO: This is not desired (we'd rather not write lists as part of summary), + // but see this issue: https://github.com/russross/blackfriday/issues/189 + marker := out.Len() + if !text() { + out.Truncate(marker) + } + out.Write([]byte{' '}) +} + +// ListItem is the list item tag callback. +func (r Renderer) ListItem(out *bytes.Buffer, text []byte, flags int) {} + +// Paragraph is the paragraph tag callback. +func (r Renderer) Paragraph(out *bytes.Buffer, text func() bool) { + marker := out.Len() + if !text() { + out.Truncate(marker) + } + out.Write([]byte{' '}) +} + +// 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. +func (r Renderer) TableRow(out *bytes.Buffer, text []byte) {} + +// 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. +func (r Renderer) TableCell(out *bytes.Buffer, text []byte, flags int) {} + +// Footnotes is the foot notes tag callback. +func (r Renderer) Footnotes(out *bytes.Buffer, text func() bool) {} + +// 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. +func (r Renderer) TitleBlock(out *bytes.Buffer, text []byte) {} + +// Spanlevel callbacks + +// AutoLink is the autolink tag callback. +func (r Renderer) AutoLink(out *bytes.Buffer, link []byte, kind int) {} + +// CodeSpan is the code span tag callback. +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. +func (r Renderer) DoubleEmphasis(out *bytes.Buffer, text []byte) { + out.Write(text) +} + +// Emphasis is the emphasis tag callback. +func (r Renderer) Emphasis(out *bytes.Buffer, text []byte) { + out.Write(text) +} + +// 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. +func (r Renderer) LineBreak(out *bytes.Buffer) {} + +// Link is the link tag callback. +func (r Renderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) { + out.Write(content) +} + +// RawHtmlTag is the raw HTML tag callback. +func (r Renderer) RawHtmlTag(out *bytes.Buffer, tag []byte) {} + +// TripleEmphasis is the triple emphasis tag callback. +func (r Renderer) TripleEmphasis(out *bytes.Buffer, text []byte) { + out.Write(text) +} + +// StrikeThrough is the strikethrough tag callback. +func (r Renderer) StrikeThrough(out *bytes.Buffer, text []byte) {} + +// FootnoteRef is the footnote ref tag callback. +func (r Renderer) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {} + +// Lowlevel callbacks + +// Entity callback. +func (r Renderer) Entity(out *bytes.Buffer, entity []byte) { + out.Write(entity) +} + +// NormalText callback. +func (r Renderer) NormalText(out *bytes.Buffer, text []byte) { + out.Write(text) +} + +// Header and footer + +// DocumentHeader callback. +func (r Renderer) DocumentHeader(out *bytes.Buffer) {} + +// DocumentFooter callback. +func (r Renderer) DocumentFooter(out *bytes.Buffer) {} + +// GetFlags returns zero. +func (r Renderer) GetFlags() int { return 0 } diff --git a/middleware/markdown/summary/summary.go b/middleware/markdown/summary/summary.go new file mode 100644 index 00000000..2527de03 --- /dev/null +++ b/middleware/markdown/summary/summary.go @@ -0,0 +1,16 @@ +package summary + +import ( + "bytes" + + "github.com/russross/blackfriday" +) + +func Markdown(input []byte, wordcount int) []byte { + words := bytes.Fields(blackfriday.Markdown(input, Renderer{}, 0)) + if wordcount > len(words) { + wordcount = len(words) + } + + return bytes.Join(words[0:wordcount], []byte{' '}) +} diff --git a/middleware/markdown/template.go b/middleware/markdown/template.go index f9f1f661..76d3a99d 100644 --- a/middleware/markdown/template.go +++ b/middleware/markdown/template.go @@ -3,7 +3,7 @@ package markdown import ( "bytes" "io/ioutil" - "os" + // "os" "text/template" "github.com/mholt/caddy/middleware" @@ -17,7 +17,7 @@ type Data struct { DocFlags map[string]bool Styles []string Scripts []string - Files []os.FileInfo + Files []FileInfo } // Include "overrides" the embedded middleware.Context's Include() @@ -28,13 +28,14 @@ func (d Data) Include(filename string) (string, error) { } // execTemplate executes a template given a requestPath, template, and metadata -func execTemplate(c *Config, mdata metadata.Metadata, ctx middleware.Context) ([]byte, error) { +func execTemplate(c *Config, mdata metadata.Metadata, files []FileInfo, ctx middleware.Context) ([]byte, error) { mdData := Data{ Context: ctx, Doc: mdata.Variables, DocFlags: mdata.Flags, Styles: c.Styles, Scripts: c.Scripts, + Files: files, } b := new(bytes.Buffer) From 04089c533bd2dac7970cae2e6202a83c3e13b802 Mon Sep 17 00:00:00 2001 From: Tobias Weingartner Date: Sat, 30 Apr 2016 17:04:44 -0700 Subject: [PATCH 15/19] Return errors. --- middleware/markdown/process.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/middleware/markdown/process.go b/middleware/markdown/process.go index 15e5e7ca..1dd468d3 100644 --- a/middleware/markdown/process.go +++ b/middleware/markdown/process.go @@ -16,19 +16,19 @@ type FileInfo struct { ctx middleware.Context } -func (f FileInfo) Summarize(wordcount int) string { +func (f FileInfo) Summarize(wordcount int) (string, error) { fp, err := f.ctx.Root.Open(f.Name()) if err != nil { - return "" + return "", err } defer fp.Close() buf, err := ioutil.ReadAll(fp) if err != nil { - return "" + return "", err } - return string(summary.Markdown(buf, wordcount)) + return string(summary.Markdown(buf, wordcount)), nil } // Markdown processes the contents of a page in b. It parses the metadata From a3af232dc5b33dddeb94525484ee5a0029ffd5d4 Mon Sep 17 00:00:00 2001 From: Tobias Weingartner Date: Sat, 30 Apr 2016 17:06:46 -0700 Subject: [PATCH 16/19] Use http.MethodGet instead of "GET". --- middleware/markdown/markdown.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/middleware/markdown/markdown.go b/middleware/markdown/markdown.go index 11078e7b..6b09db46 100644 --- a/middleware/markdown/markdown.go +++ b/middleware/markdown/markdown.go @@ -142,7 +142,7 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error // } middleware.SetLastModifiedHeader(w, lastModTime) - if r.Method == "GET" { + if r.Method == http.MethodGet { w.Write(html) } return http.StatusOK, nil From 2bccc1466e45f95c01e85640a6e247372d2c978a Mon Sep 17 00:00:00 2001 From: Tobias Weingartner Date: Sat, 30 Apr 2016 20:09:25 -0700 Subject: [PATCH 17/19] Fixup and address most of the feedback. --- middleware/markdown/markdown.go | 104 +++++++++++++------------ middleware/markdown/markdown_test.go | 11 +++ middleware/markdown/process.go | 25 +++--- middleware/markdown/summary/render.go | 69 +++++++++------- middleware/markdown/summary/summary.go | 2 + middleware/markdown/template.go | 10 --- 6 files changed, 119 insertions(+), 102 deletions(-) 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 From 45e783c3f9601dcdb15aa13069022566b19d1850 Mon Sep 17 00:00:00 2001 From: Tobias Weingartner Date: Sun, 1 May 2016 16:45:24 -0700 Subject: [PATCH 18/19] Fix headers and unexport plaintext renderer. --- middleware/markdown/markdown.go | 3 ++ middleware/markdown/summary/render.go | 73 +++++++++++++------------- middleware/markdown/summary/summary.go | 2 +- 3 files changed, 41 insertions(+), 37 deletions(-) diff --git a/middleware/markdown/markdown.go b/middleware/markdown/markdown.go index 3a33dc39..ab53710e 100644 --- a/middleware/markdown/markdown.go +++ b/middleware/markdown/markdown.go @@ -6,6 +6,7 @@ import ( "net/http" "os" "path" + "strconv" "strings" "text/template" "time" @@ -142,6 +143,8 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error return http.StatusInternalServerError, err } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Header().Set("Content-Length", strconv.FormatInt(int64(len(html)), 10)) middleware.SetLastModifiedHeader(w, lastModTime) if r.Method == http.MethodGet { w.Write(html) diff --git a/middleware/markdown/summary/render.go b/middleware/markdown/summary/render.go index 5e3bfe19..0de9800e 100644 --- a/middleware/markdown/summary/render.go +++ b/middleware/markdown/summary/render.go @@ -7,32 +7,33 @@ import ( ) // Ensure we implement the Blackfriday Markdown Renderer interface -var _ blackfriday.Renderer = (*Renderer)(nil) +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{} +// renderer renders Markdown to plain-text meant for listings and excerpts, +// and implements the blackfriday.Renderer interface. +// +// Many of the methods are stubs with no output to prevent output of HTML markup. +type renderer struct{} // Blocklevel callbacks // Stub BlockCode is the code tag callback. -func (r Renderer) BlockCode(out *bytes.Buffer, text []byte, land string) {} +func (r renderer) BlockCode(out *bytes.Buffer, text []byte, land string) {} // Stub BlockQuote is teh quote tag callback. -func (r Renderer) BlockQuote(out *bytes.Buffer, text []byte) {} +func (r renderer) BlockQuote(out *bytes.Buffer, text []byte) {} // Stub BlockHtml is the HTML tag callback. -func (r Renderer) BlockHtml(out *bytes.Buffer, text []byte) {} +func (r renderer) BlockHtml(out *bytes.Buffer, text []byte) {} // Stub Header is the header tag callback. -func (r Renderer) Header(out *bytes.Buffer, text func() bool, level int, id string) {} +func (r renderer) Header(out *bytes.Buffer, text func() bool, level int, id string) {} // Stub HRule is the horizontal rule tag callback. -func (r Renderer) HRule(out *bytes.Buffer) {} +func (r renderer) HRule(out *bytes.Buffer) {} // List is the list tag callback. -func (r Renderer) List(out *bytes.Buffer, text func() bool, flags int) { +func (r renderer) List(out *bytes.Buffer, text func() bool, flags int) { // TODO: This is not desired (we'd rather not write lists as part of summary), // but see this issue: https://github.com/russross/blackfriday/issues/189 marker := out.Len() @@ -43,11 +44,11 @@ func (r Renderer) List(out *bytes.Buffer, text func() bool, flags int) { } // Stub ListItem is the list item tag callback. -func (r Renderer) ListItem(out *bytes.Buffer, text []byte, flags int) {} +func (r renderer) ListItem(out *bytes.Buffer, text []byte, flags int) {} // 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) { +func (r renderer) Paragraph(out *bytes.Buffer, text func() bool) { marker := out.Len() if !text() { out.Truncate(marker) @@ -56,34 +57,34 @@ func (r Renderer) Paragraph(out *bytes.Buffer, text func() bool) { } // Stub Table is the table tag callback. -func (r Renderer) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {} +func (r renderer) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {} // Stub TableRow is the table row tag callback. -func (r Renderer) TableRow(out *bytes.Buffer, text []byte) {} +func (r renderer) TableRow(out *bytes.Buffer, text []byte) {} // Stub TableHeaderCell is the table header cell tag callback. -func (r Renderer) TableHeaderCell(out *bytes.Buffer, text []byte, flags int) {} +func (r renderer) TableHeaderCell(out *bytes.Buffer, text []byte, flags int) {} // Stub TableCell is the table cell tag callback. -func (r Renderer) TableCell(out *bytes.Buffer, text []byte, flags int) {} +func (r renderer) TableCell(out *bytes.Buffer, text []byte, flags int) {} // Stub Footnotes is the foot notes tag callback. -func (r Renderer) Footnotes(out *bytes.Buffer, text func() bool) {} +func (r renderer) Footnotes(out *bytes.Buffer, text func() bool) {} // Stub FootnoteItem is the footnote item tag callback. -func (r Renderer) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {} +func (r renderer) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {} // Stub TitleBlock is the title tag callback. -func (r Renderer) TitleBlock(out *bytes.Buffer, text []byte) {} +func (r renderer) TitleBlock(out *bytes.Buffer, text []byte) {} // Spanlevel callbacks // Stub AutoLink is the autolink tag callback. -func (r Renderer) AutoLink(out *bytes.Buffer, link []byte, kind int) {} +func (r renderer) AutoLink(out *bytes.Buffer, link []byte, kind int) {} // 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) { +func (r renderer) CodeSpan(out *bytes.Buffer, text []byte) { out.Write([]byte("`")) out.Write(text) out.Write([]byte("`")) @@ -91,62 +92,62 @@ func (r Renderer) CodeSpan(out *bytes.Buffer, text []byte) { // 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) { +func (r renderer) DoubleEmphasis(out *bytes.Buffer, text []byte) { out.Write(text) } // Emphasis is the emphasis tag callback. Outputs a simple plain-text // version of the input. -func (r Renderer) Emphasis(out *bytes.Buffer, text []byte) { +func (r renderer) Emphasis(out *bytes.Buffer, text []byte) { out.Write(text) } // Stub Image is the image tag callback. -func (r Renderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {} +func (r renderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {} // Stub LineBreak is the line break tag callback. -func (r Renderer) LineBreak(out *bytes.Buffer) {} +func (r renderer) LineBreak(out *bytes.Buffer) {} // 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) { +func (r renderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) { out.Write(content) } // Stub RawHtmlTag is the raw HTML tag callback. -func (r Renderer) RawHtmlTag(out *bytes.Buffer, tag []byte) {} +func (r renderer) RawHtmlTag(out *bytes.Buffer, tag []byte) {} // 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) { +func (r renderer) TripleEmphasis(out *bytes.Buffer, text []byte) { out.Write(text) } // Stub StrikeThrough is the strikethrough tag callback. -func (r Renderer) StrikeThrough(out *bytes.Buffer, text []byte) {} +func (r renderer) StrikeThrough(out *bytes.Buffer, text []byte) {} // Stub FootnoteRef is the footnote ref tag callback. -func (r Renderer) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {} +func (r renderer) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {} // Lowlevel callbacks // Entity callback. Outputs a simple plain-text version of the input. -func (r Renderer) Entity(out *bytes.Buffer, entity []byte) { +func (r renderer) Entity(out *bytes.Buffer, entity []byte) { out.Write(entity) } // NormalText callback. Outputs a simple plain-text version of the input. -func (r Renderer) NormalText(out *bytes.Buffer, text []byte) { +func (r renderer) NormalText(out *bytes.Buffer, text []byte) { out.Write(text) } // Header and footer // Stub DocumentHeader callback. -func (r Renderer) DocumentHeader(out *bytes.Buffer) {} +func (r renderer) DocumentHeader(out *bytes.Buffer) {} // Stub DocumentFooter callback. -func (r Renderer) DocumentFooter(out *bytes.Buffer) {} +func (r renderer) DocumentFooter(out *bytes.Buffer) {} // Stub GetFlags returns zero. -func (r Renderer) GetFlags() int { return 0 } +func (r renderer) GetFlags() int { return 0 } diff --git a/middleware/markdown/summary/summary.go b/middleware/markdown/summary/summary.go index a4d14540..e43a1718 100644 --- a/middleware/markdown/summary/summary.go +++ b/middleware/markdown/summary/summary.go @@ -9,7 +9,7 @@ import ( // 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)) + words := bytes.Fields(blackfriday.Markdown(input, renderer{}, 0)) if wordcount > len(words) { wordcount = len(words) } From 61c7a51bfaec9d6c16afc4ff5f82063478ab4bdb Mon Sep 17 00:00:00 2001 From: Tobias Weingartner Date: Sun, 1 May 2016 16:50:25 -0700 Subject: [PATCH 19/19] Errant commented import. --- middleware/markdown/process.go | 1 - 1 file changed, 1 deletion(-) diff --git a/middleware/markdown/process.go b/middleware/markdown/process.go index f72b5390..dc1dc6d0 100644 --- a/middleware/markdown/process.go +++ b/middleware/markdown/process.go @@ -4,7 +4,6 @@ import ( "io" "io/ioutil" "os" - // "path/filepath" "github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware/markdown/metadata"