Branch markdown into its own subpackage

pull/4/head
Noah 2018-02-09 19:01:56 -08:00
parent 3d5147ff4c
commit aabcf59181
8 changed files with 49 additions and 34 deletions

View File

@ -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)
}

View File

@ -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

View File

@ -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,
},
})

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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, "/"),

View File

@ -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,

View File

@ -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]+$")
)