diff --git a/core/blog.go b/core/blog.go index d3d8d17..b1b73f6 100644 --- a/core/blog.go +++ b/core/blog.go @@ -13,6 +13,7 @@ import ( "github.com/gorilla/feeds" "github.com/gorilla/mux" + "github.com/kirsle/blog/core/internal/markdown" "github.com/kirsle/blog/core/internal/models/comments" "github.com/kirsle/blog/core/internal/models/posts" "github.com/kirsle/blog/core/internal/models/settings" @@ -439,7 +440,7 @@ func (b *Blog) RenderPost(p *posts.Post, indexView bool, numComments int) templa // Render the post to HTML. var rendered template.HTML if p.ContentType == string(MARKDOWN) { - rendered = template.HTML(b.RenderTrustedMarkdown(p.Body)) + rendered = template.HTML(markdown.RenderTrustedMarkdown(p.Body)) } else { rendered = template.HTML(p.Body) } @@ -490,7 +491,7 @@ func (b *Blog) EditBlog(w http.ResponseWriter, r *http.Request) { switch r.FormValue("submit") { case "preview": if post.ContentType == string(MARKDOWN) { - v.Data["preview"] = template.HTML(b.RenderTrustedMarkdown(post.Body)) + v.Data["preview"] = template.HTML(markdown.RenderTrustedMarkdown(post.Body)) } else { v.Data["preview"] = template.HTML(post.Body) } diff --git a/core/comments.go b/core/comments.go index bfbae2e..521521d 100644 --- a/core/comments.go +++ b/core/comments.go @@ -11,6 +11,7 @@ import ( "github.com/google/uuid" "github.com/gorilla/mux" "github.com/gorilla/sessions" + "github.com/kirsle/blog/core/internal/markdown" "github.com/kirsle/blog/core/internal/models/comments" "github.com/kirsle/blog/core/internal/models/users" ) @@ -62,7 +63,7 @@ func (b *Blog) RenderComments(session *sessions.Session, csrfToken, url, subject // Render all the comments in the thread. userMap := map[int]*users.User{} for _, c := range thread.Comments { - c.HTML = template.HTML(b.RenderMarkdown(c.Body)) + c.HTML = template.HTML(markdown.RenderMarkdown(c.Body)) c.ThreadID = thread.ID c.OriginURL = url c.CSRF = csrfToken @@ -203,7 +204,7 @@ func (b *Blog) CommentHandler(w http.ResponseWriter, r *http.Request) { c.Email = currentUser.Email c.LoadAvatar() } - c.HTML = template.HTML(b.RenderMarkdown(c.Body)) + c.HTML = template.HTML(markdown.RenderMarkdown(c.Body)) case "post": if err := c.Validate(); err != nil { v.Error = err diff --git a/core/contact.go b/core/contact.go index f1716f3..f4cb61e 100644 --- a/core/contact.go +++ b/core/contact.go @@ -10,6 +10,7 @@ import ( "github.com/gorilla/mux" "github.com/kirsle/blog/core/internal/forms" + "github.com/kirsle/blog/core/internal/markdown" "github.com/kirsle/blog/core/internal/models/settings" ) @@ -47,7 +48,7 @@ func (b *Blog) ContactRoutes(r *mux.Router) { Template: ".email/contact.gohtml", Data: map[string]interface{}{ "Name": form.Name, - "Message": template.HTML(b.RenderMarkdown(form.Message)), + "Message": template.HTML(markdown.RenderMarkdown(form.Message)), "Email": form.Email, }, }) diff --git a/core/app.go b/core/core.go similarity index 95% rename from core/app.go rename to core/core.go index 68182e2..5a48ecb 100644 --- a/core/app.go +++ b/core/core.go @@ -1,3 +1,4 @@ +// Package core implements the core source code of kirsle/blog. package core import ( @@ -7,14 +8,15 @@ import ( "github.com/gorilla/mux" "github.com/gorilla/sessions" - "github.com/kirsle/blog/jsondb/caches" - "github.com/kirsle/blog/jsondb/caches/null" - "github.com/kirsle/blog/jsondb/caches/redis" - "github.com/kirsle/blog/jsondb" + "github.com/kirsle/blog/core/internal/markdown" "github.com/kirsle/blog/core/internal/models/comments" "github.com/kirsle/blog/core/internal/models/posts" "github.com/kirsle/blog/core/internal/models/settings" "github.com/kirsle/blog/core/internal/models/users" + "github.com/kirsle/blog/jsondb" + "github.com/kirsle/blog/jsondb/caches" + "github.com/kirsle/blog/jsondb/caches/null" + "github.com/kirsle/blog/jsondb/caches/redis" "github.com/shurcooL/github_flavored_markdown/gfmstyle" "github.com/urfave/negroni" ) @@ -76,6 +78,7 @@ func New(documentRoot, userRoot string) *Blog { } else { blog.Cache = cache blog.DB.Cache = cache + markdown.Cache = cache } } diff --git a/core/markdown.go b/core/internal/markdown/markdown.go similarity index 78% rename from core/markdown.go rename to core/internal/markdown/markdown.go index 28953ba..f48b748 100644 --- a/core/markdown.go +++ b/core/internal/markdown/markdown.go @@ -1,4 +1,5 @@ -package core +// Package markdown implements a GitHub Flavored Markdown renderer. +package markdown import ( "bytes" @@ -10,17 +11,23 @@ import ( "regexp" "strings" + "github.com/kirsle/blog/jsondb/caches" + "github.com/kirsle/golog" "github.com/microcosm-cc/bluemonday" "github.com/shurcooL/github_flavored_markdown" ) // Regexps for Markdown use cases. var ( + // Plug your own Redis cacher in. + Cache caches.Cacher + // Match title from the first `# h1` heading. reMarkdownTitle = regexp.MustCompile(`(?m:^#([^#\r\n]+)$)`) // Match fenced code blocks with languages defined. - reFencedCode = regexp.MustCompile("```" + `([a-z]*)[\r\n]([\s\S]*?)[\r\n]\s*` + "```") + reFencedCode = regexp.MustCompile("```" + `([a-z]*)[\r\n]([\s\S]*?)[\r\n]\s*` + "```") + reFencedCodeClass = regexp.MustCompile("^highlight highlight-[a-zA-Z0-9]+$") // Regexp to match fenced code blocks in rendered Markdown HTML. // Tweak this if you change Markdown engines later. @@ -28,6 +35,12 @@ var ( reDecodeBlock = regexp.MustCompile(`\[?FENCED_CODE_%d_BLOCK?\]`) ) +var log *golog.Logger + +func init() { + log = golog.GetLogger("blog") +} + // A container for parsed code blocks. type codeBlock struct { placeholder int @@ -51,8 +64,8 @@ func TitleFromMarkdown(body string) (string, error) { // RenderMarkdown renders markdown to HTML, safely. It uses blackfriday to // render Markdown to HTML and then Bluemonday to sanitize the resulting HTML. -func (b *Blog) RenderMarkdown(input string) string { - unsafe := []byte(b.RenderTrustedMarkdown(input)) +func RenderMarkdown(input string) string { + unsafe := []byte(RenderTrustedMarkdown(input)) // Sanitize HTML, but allow fenced code blocks to not get mangled in user // submitted comments. @@ -65,7 +78,7 @@ func (b *Blog) RenderMarkdown(input string) string { // RenderTrustedMarkdown renders markdown to HTML, but without applying // bluemonday filtering afterward. This is for blog posts and website // Markdown pages, not for user-submitted comments or things. -func (b *Blog) RenderTrustedMarkdown(input string) string { +func RenderTrustedMarkdown(input string) string { // Find and hang on to fenced code blocks. codeBlocks := []codeBlock{} matches := reFencedCode.FindAllStringSubmatch(input, -1) @@ -87,7 +100,7 @@ func (b *Blog) RenderTrustedMarkdown(input string) string { // Substitute fenced codes back in. for _, block := range codeBlocks { - highlighted, _ := b.Pygmentize(block.language, block.source) + highlighted, _ := Pygmentize(block.language, block.source) html = strings.Replace(html, fmt.Sprintf("[?FENCED_CODE_%d_BLOCK?]", block.placeholder), highlighted, @@ -105,7 +118,7 @@ func (b *Blog) RenderTrustedMarkdown(input string) string { // // The rendered result is cached in Redis if available, because the CLI // call takes ~0.6s which is slow if you're rendering a lot of code blocks. -func (b *Blog) Pygmentize(language, source string) (string, error) { +func Pygmentize(language, source string) (string, error) { var result string // Hash the source for the cache key. @@ -114,10 +127,11 @@ func (b *Blog) Pygmentize(language, source string) (string, error) { hash := fmt.Sprintf("%x", h.Sum(nil)) cacheKey := "pygmentize:" + hash + _ = cacheKey // Do we have it cached? - if cached, err := b.Cache.Get(cacheKey); err == nil && len(cached) > 0 { - return string(cached), nil - } + // if cached, err := b.Cache.Get(cacheKey); err == nil && len(cached) > 0 { + // return string(cached), nil + // } // Defer to the `pygmentize` command bin := "pygmentize" @@ -140,10 +154,10 @@ func (b *Blog) Pygmentize(language, source string) (string, error) { } result = out.String() - err := b.Cache.Set(cacheKey, []byte(result), 60*60*24) // cool md5's don't change - if err != nil { - log.Error("Couldn't cache Pygmentize output: %s", err) - } + // err := b.Cache.Set(cacheKey, []byte(result), 60*60*24) // cool md5's don't change + // if err != nil { + // log.Error("Couldn't cache Pygmentize output: %s", err) + // } return result, nil } diff --git a/core/mail.go b/core/mail.go index 4ff5c2b..6c0f750 100644 --- a/core/mail.go +++ b/core/mail.go @@ -8,6 +8,7 @@ import ( "net/url" "strings" + "github.com/kirsle/blog/core/internal/markdown" "github.com/kirsle/blog/core/internal/models/comments" "github.com/kirsle/blog/core/internal/models/settings" "github.com/microcosm-cc/bluemonday" @@ -107,7 +108,7 @@ func (b *Blog) NotifyComment(c *comments.Comment) { Data: map[string]interface{}{ "Name": c.Name, "Subject": c.Subject, - "Body": template.HTML(b.RenderMarkdown(c.Body)), + "Body": template.HTML(markdown.RenderMarkdown(c.Body)), "URL": strings.Trim(s.Site.URL, "/") + c.OriginURL, "QuickDelete": fmt.Sprintf("%s/comments/quick-delete?t=%s&d=%s", strings.Trim(s.Site.URL, "/"), diff --git a/core/pages.go b/core/pages.go index 6712a57..6024ca0 100644 --- a/core/pages.go +++ b/core/pages.go @@ -8,6 +8,8 @@ import ( "os" "path/filepath" "strings" + + "github.com/kirsle/blog/core/internal/markdown" ) // PageHandler is the catch-all route handler, for serving static web pages. @@ -54,8 +56,8 @@ func (b *Blog) PageHandler(w http.ResponseWriter, r *http.Request) { // Render it to HTML and find out its title. body := string(source) - html := b.RenderTrustedMarkdown(body) - title, _ := TitleFromMarkdown(body) + html := markdown.RenderTrustedMarkdown(body) + title, _ := markdown.TitleFromMarkdown(body) b.RenderTemplate(w, r, ".markdown", NewVars(map[interface{}]interface{}{ "Title": title, diff --git a/core/regexp.go b/core/regexp.go deleted file mode 100644 index 3a3ffbe..0000000 --- a/core/regexp.go +++ /dev/null @@ -1,8 +0,0 @@ -package core - -import "regexp" - -var ( - // CSS classes for Markdown fenced code blocks - reFencedCodeClass = regexp.MustCompile("^highlight highlight-[a-zA-Z0-9]+$") -)