Add site-wide/per-page [blackfriday] `extensions` option
authorNaoya Inada <naoina@kuune.org>
Sun, 25 Jan 2015 11:08:02 +0000 (20:08 +0900)
committerbep <bjorn.erik.pedersen@gmail.com>
Mon, 26 Jan 2015 08:55:37 +0000 (09:55 +0100)
commands/hugo.go
docs/content/overview/configuration.md
helpers/content.go
hugolib/page.go
hugolib/page_test.go
hugolib/shortcode.go
hugolib/site.go

index f57a7c910e1343129d84e127137ac75c6e0fc2a2..94c9559253d268c9ec42ea99c3d900f15ba016cb 100644 (file)
@@ -136,7 +136,7 @@ func InitializeConfig() {
        viper.SetDefault("FootnoteAnchorPrefix", "")
        viper.SetDefault("FootnoteReturnLinkContents", "")
        viper.SetDefault("NewContentEditor", "")
-       viper.SetDefault("Blackfriday", map[string]bool{"angledQuotes": false, "fractions": true, "plainIdAnchors": false})
+       viper.SetDefault("Blackfriday", new(helpers.Blackfriday))
 
        if hugoCmdV.PersistentFlags().Lookup("buildDrafts").Changed {
                viper.Set("BuildDrafts", Draft)
index a07ef5672a99b542a66ced7429bb4d3447e8305c..c492296fcbd4a48089dd6f7633f7e194ce241953 100644 (file)
@@ -71,7 +71,7 @@ Here is a yaml configuration file which sets a few more options
 
 [Blackfriday](https://github.com/russross/blackfriday) is the [Markdown](http://daringfireball.net/projects/markdown/) rendering engine used in Hugo. The Blackfriday configuration in Hugo is mostly a set of sane defaults that should fit most use cases.
 
-But Hugo does expose some options---as listed in the table below, matched with the corresponding flag in the [Blackfriday source](https://github.com/russross/blackfriday/blob/master/html.go):
+But Hugo does expose some options---as listed in the table below, matched with the corresponding flag in the Blackfriday source ([html.go](https://github.com/russross/blackfriday/blob/master/html.go) and [markdown.go](https://github.com/russross/blackfriday/blob/master/markdown.go)):
 
 <table class="table table-bordered">
 <thead>
@@ -115,6 +115,16 @@ but only these three.</small></td>
 <td class="purpose-title">Purpose:</td>
 <td class="purpose-description" colspan="2">If <code>true</code>, then header and footnote IDs are generated without the document ID <small>(e.g.&nbsp;<code>#my-header</code> instead of <code>#my-header:bec3ed8ba720b9073ab75abcf3ba5d97</code>)</small></td>
 </tr>
+
+<tr>
+<td><code>extensions</code></td>
+<td><code>[]</code></td>
+<td><code>EXTENSION_*</code></td>
+</tr>
+<tr>
+<td class="purpose-title">Purpose:</td>
+<td class="purpose-description" colspan="2">Use non-default additional extensions <small>(e.g.&nbsp;Add <code>"hardLineBreak"</code> to use <code>EXTENSION_HARD_LINE_BREAK</code>)</small></td>
+</tr>
 </tbody>
 </table>
 
@@ -130,11 +140,14 @@ but only these three.</small></td>
   angledQuotes = true
   fractions = false
   plainIdAnchors = true
+  extensions = ["hardLineBreak"]
 </code></pre></td>
 <td><pre><code>blackfriday:
   angledQuotes: true
   fractions: false
   plainIdAnchors: true
+  extensions:
+    - hardLineBreak
 </code></pre></td>
 </tr>
 </table>
index 3f9cc55d51dd7abf28f657aeb64ec212033b8412..59207964e4c8d8536743343acb97afd1708836e7 100644 (file)
@@ -28,6 +28,7 @@ import (
        jww "github.com/spf13/jwalterweatherman"
 
        "strings"
+       "sync"
 )
 
 // Length of the summary that Hugo extracts from a content.
@@ -36,6 +37,30 @@ var SummaryLength = 70
 // Custom divider <!--more--> let's user define where summarization ends.
 var SummaryDivider = []byte("<!--more-->")
 
+type Blackfriday struct {
+       AngledQuotes   bool
+       Fractions      bool
+       PlainIdAnchors bool
+       Extensions     []string
+}
+
+var blackfridayExtensionMap = map[string]int{
+       "noIntraEmphasis":        blackfriday.EXTENSION_NO_INTRA_EMPHASIS,
+       "tables":                 blackfriday.EXTENSION_TABLES,
+       "fencedCode":             blackfriday.EXTENSION_FENCED_CODE,
+       "autolink":               blackfriday.EXTENSION_AUTOLINK,
+       "strikethrough":          blackfriday.EXTENSION_STRIKETHROUGH,
+       "laxHtmlBlocks":          blackfriday.EXTENSION_LAX_HTML_BLOCKS,
+       "spaceHeaders":           blackfriday.EXTENSION_SPACE_HEADERS,
+       "hardLineBreak":          blackfriday.EXTENSION_HARD_LINE_BREAK,
+       "tabSizeEight":           blackfriday.EXTENSION_TAB_SIZE_EIGHT,
+       "footnotes":              blackfriday.EXTENSION_FOOTNOTES,
+       "noEmptyLineBeforeBlock": blackfriday.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK,
+       "headerIds":              blackfriday.EXTENSION_HEADER_IDS,
+       "titleblock":             blackfriday.EXTENSION_TITLEBLOCK,
+       "autoHeaderIds":          blackfriday.EXTENSION_AUTO_HEADER_IDS,
+}
+
 // StripHTML accepts a string, strips out all HTML tags and returns it.
 func StripHTML(s string) string {
        output := ""
@@ -87,7 +112,7 @@ func GetHtmlRenderer(defaultFlags int, ctx RenderingContext) blackfriday.Rendere
 
        b := len(ctx.DocumentId) != 0
 
-       if m, ok := ctx.ConfigFlags["plainIdAnchors"]; b && ((ok && !m) || !ok) {
+       if b && !ctx.getConfig().PlainIdAnchors {
                renderParameters.FootnoteAnchorPrefix = ctx.DocumentId + ":" + renderParameters.FootnoteAnchorPrefix
                renderParameters.HeaderIDSuffix = ":" + ctx.DocumentId
        }
@@ -99,40 +124,40 @@ func GetHtmlRenderer(defaultFlags int, ctx RenderingContext) blackfriday.Rendere
        htmlFlags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES
        htmlFlags |= blackfriday.HTML_FOOTNOTE_RETURN_LINKS
 
-       var angledQuotes bool
-
-       if m, ok := ctx.ConfigFlags["angledQuotes"]; ok {
-               angledQuotes = m
-       }
-
-       if angledQuotes {
+       if ctx.getConfig().AngledQuotes {
                htmlFlags |= blackfriday.HTML_SMARTYPANTS_ANGLED_QUOTES
        }
 
-       if m, ok := ctx.ConfigFlags["fractions"]; ok && !m {
+       if !ctx.getConfig().Fractions {
                htmlFlags &^= blackfriday.HTML_SMARTYPANTS_FRACTIONS
        }
 
        return blackfriday.HtmlRendererWithParameters(htmlFlags, "", "", renderParameters)
 }
 
-func GetMarkdownExtensions() int {
-       return 0 | blackfriday.EXTENSION_NO_INTRA_EMPHASIS |
+func GetMarkdownExtensions(ctx RenderingContext) int {
+       flags := 0 | blackfriday.EXTENSION_NO_INTRA_EMPHASIS |
                blackfriday.EXTENSION_TABLES | blackfriday.EXTENSION_FENCED_CODE |
                blackfriday.EXTENSION_AUTOLINK | blackfriday.EXTENSION_STRIKETHROUGH |
                blackfriday.EXTENSION_SPACE_HEADERS | blackfriday.EXTENSION_FOOTNOTES |
                blackfriday.EXTENSION_HEADER_IDS | blackfriday.EXTENSION_AUTO_HEADER_IDS
+       for _, extension := range ctx.getConfig().Extensions {
+               if flag, ok := blackfridayExtensionMap[extension]; ok {
+                       flags |= flag
+               }
+       }
+       return flags
 }
 
 func MarkdownRender(ctx RenderingContext) []byte {
        return blackfriday.Markdown(ctx.Content, GetHtmlRenderer(0, ctx),
-               GetMarkdownExtensions())
+               GetMarkdownExtensions(ctx))
 }
 
 func MarkdownRenderWithTOC(ctx RenderingContext) []byte {
        return blackfriday.Markdown(ctx.Content,
                GetHtmlRenderer(blackfriday.HTML_TOC, ctx),
-               GetMarkdownExtensions())
+               GetMarkdownExtensions(ctx))
 }
 
 // ExtractTOC extracts Table of Contents from content.
@@ -172,10 +197,20 @@ func ExtractTOC(content []byte) (newcontent []byte, toc []byte) {
 }
 
 type RenderingContext struct {
-       Content     []byte
-       PageFmt     string
-       DocumentId  string
-       ConfigFlags map[string]bool
+       Content    []byte
+       PageFmt    string
+       DocumentId string
+       Config     *Blackfriday
+       configInit sync.Once
+}
+
+func (c *RenderingContext) getConfig() *Blackfriday {
+       c.configInit.Do(func() {
+               if c.Config == nil {
+                       c.Config = new(Blackfriday)
+               }
+       })
+       return c.Config
 }
 
 func RenderBytesWithTOC(ctx RenderingContext) []byte {
@@ -261,8 +296,8 @@ func GetRstContent(content []byte) string {
                path, err = exec.LookPath("rst2html.py")
                if err != nil {
                        jww.ERROR.Println("rst2html / rst2html.py not found in $PATH: Please install.\n",
-                                         "                 Leaving reStructuredText content unrendered.")
-                       return(string(content))
+                               "                 Leaving reStructuredText content unrendered.")
+                       return (string(content))
                }
        }
 
index eadf6eb3d0266ed6216ac99e1db1e4d8a55c652a..ffbe7772b23e9b64db6ab47cc5d01986c11e6ab9 100644 (file)
@@ -17,16 +17,12 @@ import (
        "bytes"
        "errors"
        "fmt"
+       "reflect"
+
+       "github.com/mitchellh/mapstructure"
        "github.com/spf13/hugo/helpers"
        "github.com/spf13/hugo/parser"
-       "reflect"
 
-       "github.com/spf13/cast"
-       "github.com/spf13/hugo/hugofs"
-       "github.com/spf13/hugo/source"
-       "github.com/spf13/hugo/tpl"
-       jww "github.com/spf13/jwalterweatherman"
-       "github.com/spf13/viper"
        "html/template"
        "io"
        "net/url"
@@ -35,6 +31,13 @@ import (
        "strings"
        "sync"
        "time"
+
+       "github.com/spf13/cast"
+       "github.com/spf13/hugo/hugofs"
+       "github.com/spf13/hugo/source"
+       "github.com/spf13/hugo/tpl"
+       jww "github.com/spf13/jwalterweatherman"
+       "github.com/spf13/viper"
 )
 
 type Page struct {
@@ -52,17 +55,17 @@ type Page struct {
        Tmpl            tpl.Template
        Markup          string
 
-       extension                string
-       contentType              string
-       renderable               bool
-       layout                   string
-       linkTitle                string
-       frontmatter              []byte
-       rawContent               []byte
-       contentShortCodes        map[string]string
-       plain                    string // TODO should be []byte
-       renderingConfigFlags     map[string]bool
-       renderingConfigFlagsInit sync.Once
+       extension           string
+       contentType         string
+       renderable          bool
+       layout              string
+       linkTitle           string
+       frontmatter         []byte
+       rawContent          []byte
+       contentShortCodes   map[string]string
+       plain               string // TODO should be []byte
+       renderingConfig     *helpers.Blackfriday
+       renderingConfigInit sync.Once
        PageMeta
        Source
        Position
@@ -182,37 +185,33 @@ func (p *Page) setSummary() {
 func (p *Page) renderBytes(content []byte) []byte {
        return helpers.RenderBytes(
                helpers.RenderingContext{Content: content, PageFmt: p.guessMarkupType(),
-                       DocumentId: p.UniqueId(), ConfigFlags: p.getRenderingConfigFlags()})
+                       DocumentId: p.UniqueId(), Config: p.getRenderingConfig()})
 }
 
 func (p *Page) renderContent(content []byte) []byte {
        return helpers.RenderBytesWithTOC(helpers.RenderingContext{Content: content, PageFmt: p.guessMarkupType(),
-               DocumentId: p.UniqueId(), ConfigFlags: p.getRenderingConfigFlags()})
+               DocumentId: p.UniqueId(), Config: p.getRenderingConfig()})
 }
 
-func (p *Page) getRenderingConfigFlags() map[string]bool {
-
-       p.renderingConfigFlagsInit.Do(func() {
-               p.renderingConfigFlags = make(map[string]bool)
+func (p *Page) getRenderingConfig() *helpers.Blackfriday {
 
+       p.renderingConfigInit.Do(func() {
                pageParam := p.GetParam("blackfriday")
                siteParam := viper.GetStringMap("blackfriday")
 
-               p.renderingConfigFlags = cast.ToStringMapBool(siteParam)
-
                if pageParam != nil {
-                       pageFlags := cast.ToStringMapBool(pageParam)
-                       for key, value := range pageFlags {
-                               p.renderingConfigFlags[key] = value
+                       pageConfig := cast.ToStringMap(pageParam)
+                       for key, value := range pageConfig {
+                               siteParam[key] = value
                        }
                }
+               p.renderingConfig = new(helpers.Blackfriday)
+               if err := mapstructure.Decode(siteParam, p.renderingConfig); err != nil {
+                       jww.FATAL.Printf("Failed to get rendering config for %s:\n%s", p.BaseFileName(), err.Error())
+               }
        })
 
-       return p.renderingConfigFlags
-}
-
-func (p *Page) isRenderingFlagEnabled(flag string) bool {
-       return p.getRenderingConfigFlags()[flag]
+       return p.renderingConfig
 }
 
 func newPage(filename string) *Page {
index d4ce7a0a0f7a8b15e02988901f2f42f4a072b854..dc8ebf64b14e175d7918b37732add6b2f8230e6d 100644 (file)
@@ -212,6 +212,16 @@ the cylinder and strike me down. ## BB
 ### BBB
 
 "You're a great Granser," he cried delightedly, "always making believe them little marks mean something."
+`
+
+       SIMPLE_PAGE_WITH_ADDITIONAL_EXTENSION = `+++
+[blackfriday]
+  extensions = ["hardLineBreak"]
++++
+first line.
+second line.
+
+fourth line.
 `
 )
 
@@ -366,6 +376,16 @@ func TestPageWithEmbeddedScriptTag(t *testing.T) {
        checkPageContent(t, p, "<script type='text/javascript'>alert('the script tags are still there, right?');</script>\n")
 }
 
+func TestPageWithAdditionalExtension(t *testing.T) {
+       p, _ := NewPage("simple.md")
+       err := p.ReadFrom(strings.NewReader(SIMPLE_PAGE_WITH_ADDITIONAL_EXTENSION))
+       p.Convert()
+       if err != nil {
+               t.Fatalf("Unable to create a page with frontmatter and body content: %s", err)
+       }
+       checkPageContent(t, p, "<p>first line.<br />\nsecond line.</p>\n\n<p>fourth line.</p>\n")
+}
+
 func TestTableOfContents(t *testing.T) {
        p, _ := NewPage("tocpage.md")
        err := p.ReadFrom(strings.NewReader(PAGE_WITH_TOC))
index ff3eaeb896a260b8db41b01dea430f6082fac486..03cd7d4a7dc1aa0493ed4d693866ee246f159a34 100644 (file)
@@ -205,7 +205,7 @@ func renderShortcode(sc shortcode, p *Page, t tpl.Template) string {
                if sc.doMarkup {
                        newInner := helpers.RenderBytes(helpers.RenderingContext{
                                Content: []byte(inner), PageFmt: p.guessMarkupType(),
-                               DocumentId: p.UniqueId(), ConfigFlags: p.getRenderingConfigFlags()})
+                               DocumentId: p.UniqueId(), Config: p.getRenderingConfig()})
 
                        // If the type is “unknown” or “markdown”, we assume the markdown
                        // generation has been performed. Given the input: `a line`, markdown
index 39e900cfded964dda83d79bad2be61832955162e..a6a18a2fecfff31e22c1e26523fa05b595fdea1f 100644 (file)
@@ -187,9 +187,9 @@ func (s *SiteInfo) refLink(ref string, page *Page, relative bool) (string, error
        if refUrl.Fragment != "" {
                link = link + "#" + refUrl.Fragment
 
-               if refUrl.Path != "" && target != nil && !target.isRenderingFlagEnabled("plainIdAnchors") {
+               if refUrl.Path != "" && target != nil && !target.getRenderingConfig().PlainIdAnchors {
                        link = link + ":" + target.UniqueId()
-               } else if page != nil && !page.isRenderingFlagEnabled("plainIdAnchors") {
+               } else if page != nil && !page.getRenderingConfig().PlainIdAnchors {
                        link = link + ":" + page.UniqueId()
                }
        }