shithub: hugo

Download patch

ref: 407e80a9abbb3b22397d1ed6c62ce7cefcdd312a
parent: 39b2cdece0b4be6a0d48c6b1c6a6b0c220c9bb29
author: Naoya Inada <[email protected]>
date: Sun Jan 25 15:08:02 EST 2015

Add site-wide/per-page [blackfriday] `extensions` option

--- a/commands/hugo.go
+++ b/commands/hugo.go
@@ -136,7 +136,7 @@
 	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)
--- a/docs/content/overview/configuration.md
+++ b/docs/content/overview/configuration.md
@@ -71,7 +71,7 @@
 
 [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 @@
 <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 @@
   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>
--- a/helpers/content.go
+++ b/helpers/content.go
@@ -28,6 +28,7 @@
 	jww "github.com/spf13/jwalterweatherman"
 
 	"strings"
+	"sync"
 )
 
 // Length of the summary that Hugo extracts from a content.
@@ -36,6 +37,30 @@
 // 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 @@
 
 	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,17 +124,11 @@
 	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
 	}
 
@@ -116,23 +135,29 @@
 	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,12 +197,22 @@
 }
 
 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 {
 	switch ctx.PageFmt {
 	default:
@@ -261,8 +296,8 @@
 		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))
 		}
 	}
 
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -17,16 +17,12 @@
 	"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 @@
 	"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 @@
 	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) 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 {
+func (p *Page) getRenderingConfig() *helpers.Blackfriday {
 
-	p.renderingConfigFlagsInit.Do(func() {
-		p.renderingConfigFlags = make(map[string]bool)
-
+	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 {
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -213,6 +213,16 @@
 
 "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.
+`
 )
 
 var PAGE_WITH_VARIOUS_FRONTMATTER_TYPES = `+++
@@ -364,6 +374,16 @@
 		t.Fatalf("Unable to create a page with frontmatter and body content: %s", err)
 	}
 	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) {
--- a/hugolib/shortcode.go
+++ b/hugolib/shortcode.go
@@ -205,7 +205,7 @@
 		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
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -187,9 +187,9 @@
 	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()
 		}
 	}