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

This commit is contained in:
Naoya Inada 2015-01-25 20:08:02 +09:00 committed by bep
parent 39b2cdece0
commit 407e80a9ab
7 changed files with 126 additions and 59 deletions

View file

@ -136,7 +136,7 @@ func InitializeConfig() {
viper.SetDefault("FootnoteAnchorPrefix", "") viper.SetDefault("FootnoteAnchorPrefix", "")
viper.SetDefault("FootnoteReturnLinkContents", "") viper.SetDefault("FootnoteReturnLinkContents", "")
viper.SetDefault("NewContentEditor", "") 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 { if hugoCmdV.PersistentFlags().Lookup("buildDrafts").Changed {
viper.Set("BuildDrafts", Draft) viper.Set("BuildDrafts", Draft)

View 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. [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"> <table class="table table-bordered">
<thead> <thead>
@ -115,6 +115,16 @@ but only these three.</small></td>
<td class="purpose-title">Purpose:</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> <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>
<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> </tbody>
</table> </table>
@ -130,11 +140,14 @@ but only these three.</small></td>
angledQuotes = true angledQuotes = true
fractions = false fractions = false
plainIdAnchors = true plainIdAnchors = true
extensions = ["hardLineBreak"]
</code></pre></td> </code></pre></td>
<td><pre><code>blackfriday: <td><pre><code>blackfriday:
angledQuotes: true angledQuotes: true
fractions: false fractions: false
plainIdAnchors: true plainIdAnchors: true
extensions:
- hardLineBreak
</code></pre></td> </code></pre></td>
</tr> </tr>
</table> </table>

View file

@ -28,6 +28,7 @@ import (
jww "github.com/spf13/jwalterweatherman" jww "github.com/spf13/jwalterweatherman"
"strings" "strings"
"sync"
) )
// Length of the summary that Hugo extracts from a content. // 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. // Custom divider <!--more--> let's user define where summarization ends.
var SummaryDivider = []byte("<!--more-->") 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. // StripHTML accepts a string, strips out all HTML tags and returns it.
func StripHTML(s string) string { func StripHTML(s string) string {
output := "" output := ""
@ -87,7 +112,7 @@ func GetHtmlRenderer(defaultFlags int, ctx RenderingContext) blackfriday.Rendere
b := len(ctx.DocumentId) != 0 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.FootnoteAnchorPrefix = ctx.DocumentId + ":" + renderParameters.FootnoteAnchorPrefix
renderParameters.HeaderIDSuffix = ":" + ctx.DocumentId 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_SMARTYPANTS_LATEX_DASHES
htmlFlags |= blackfriday.HTML_FOOTNOTE_RETURN_LINKS htmlFlags |= blackfriday.HTML_FOOTNOTE_RETURN_LINKS
var angledQuotes bool if ctx.getConfig().AngledQuotes {
if m, ok := ctx.ConfigFlags["angledQuotes"]; ok {
angledQuotes = m
}
if angledQuotes {
htmlFlags |= blackfriday.HTML_SMARTYPANTS_ANGLED_QUOTES htmlFlags |= blackfriday.HTML_SMARTYPANTS_ANGLED_QUOTES
} }
if m, ok := ctx.ConfigFlags["fractions"]; ok && !m { if !ctx.getConfig().Fractions {
htmlFlags &^= blackfriday.HTML_SMARTYPANTS_FRACTIONS htmlFlags &^= blackfriday.HTML_SMARTYPANTS_FRACTIONS
} }
return blackfriday.HtmlRendererWithParameters(htmlFlags, "", "", renderParameters) return blackfriday.HtmlRendererWithParameters(htmlFlags, "", "", renderParameters)
} }
func GetMarkdownExtensions() int { func GetMarkdownExtensions(ctx RenderingContext) int {
return 0 | blackfriday.EXTENSION_NO_INTRA_EMPHASIS | flags := 0 | blackfriday.EXTENSION_NO_INTRA_EMPHASIS |
blackfriday.EXTENSION_TABLES | blackfriday.EXTENSION_FENCED_CODE | blackfriday.EXTENSION_TABLES | blackfriday.EXTENSION_FENCED_CODE |
blackfriday.EXTENSION_AUTOLINK | blackfriday.EXTENSION_STRIKETHROUGH | blackfriday.EXTENSION_AUTOLINK | blackfriday.EXTENSION_STRIKETHROUGH |
blackfriday.EXTENSION_SPACE_HEADERS | blackfriday.EXTENSION_FOOTNOTES | blackfriday.EXTENSION_SPACE_HEADERS | blackfriday.EXTENSION_FOOTNOTES |
blackfriday.EXTENSION_HEADER_IDS | blackfriday.EXTENSION_AUTO_HEADER_IDS 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 { func MarkdownRender(ctx RenderingContext) []byte {
return blackfriday.Markdown(ctx.Content, GetHtmlRenderer(0, ctx), return blackfriday.Markdown(ctx.Content, GetHtmlRenderer(0, ctx),
GetMarkdownExtensions()) GetMarkdownExtensions(ctx))
} }
func MarkdownRenderWithTOC(ctx RenderingContext) []byte { func MarkdownRenderWithTOC(ctx RenderingContext) []byte {
return blackfriday.Markdown(ctx.Content, return blackfriday.Markdown(ctx.Content,
GetHtmlRenderer(blackfriday.HTML_TOC, ctx), GetHtmlRenderer(blackfriday.HTML_TOC, ctx),
GetMarkdownExtensions()) GetMarkdownExtensions(ctx))
} }
// ExtractTOC extracts Table of Contents from content. // ExtractTOC extracts Table of Contents from content.
@ -172,10 +197,20 @@ func ExtractTOC(content []byte) (newcontent []byte, toc []byte) {
} }
type RenderingContext struct { type RenderingContext struct {
Content []byte Content []byte
PageFmt string PageFmt string
DocumentId string DocumentId string
ConfigFlags map[string]bool 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 { func RenderBytesWithTOC(ctx RenderingContext) []byte {
@ -261,8 +296,8 @@ func GetRstContent(content []byte) string {
path, err = exec.LookPath("rst2html.py") path, err = exec.LookPath("rst2html.py")
if err != nil { if err != nil {
jww.ERROR.Println("rst2html / rst2html.py not found in $PATH: Please install.\n", jww.ERROR.Println("rst2html / rst2html.py not found in $PATH: Please install.\n",
" Leaving reStructuredText content unrendered.") " Leaving reStructuredText content unrendered.")
return(string(content)) return (string(content))
} }
} }

View file

@ -17,16 +17,12 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/parser"
"reflect" "reflect"
"github.com/spf13/cast" "github.com/mitchellh/mapstructure"
"github.com/spf13/hugo/hugofs" "github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/source" "github.com/spf13/hugo/parser"
"github.com/spf13/hugo/tpl"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
"html/template" "html/template"
"io" "io"
"net/url" "net/url"
@ -35,6 +31,13 @@ import (
"strings" "strings"
"sync" "sync"
"time" "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 { type Page struct {
@ -52,17 +55,17 @@ type Page struct {
Tmpl tpl.Template Tmpl tpl.Template
Markup string Markup string
extension string extension string
contentType string contentType string
renderable bool renderable bool
layout string layout string
linkTitle string linkTitle string
frontmatter []byte frontmatter []byte
rawContent []byte rawContent []byte
contentShortCodes map[string]string contentShortCodes map[string]string
plain string // TODO should be []byte plain string // TODO should be []byte
renderingConfigFlags map[string]bool renderingConfig *helpers.Blackfriday
renderingConfigFlagsInit sync.Once renderingConfigInit sync.Once
PageMeta PageMeta
Source Source
Position Position
@ -182,37 +185,33 @@ func (p *Page) setSummary() {
func (p *Page) renderBytes(content []byte) []byte { func (p *Page) renderBytes(content []byte) []byte {
return helpers.RenderBytes( return helpers.RenderBytes(
helpers.RenderingContext{Content: content, PageFmt: p.guessMarkupType(), 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 { func (p *Page) renderContent(content []byte) []byte {
return helpers.RenderBytesWithTOC(helpers.RenderingContext{Content: content, PageFmt: p.guessMarkupType(), 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") pageParam := p.GetParam("blackfriday")
siteParam := viper.GetStringMap("blackfriday") siteParam := viper.GetStringMap("blackfriday")
p.renderingConfigFlags = cast.ToStringMapBool(siteParam)
if pageParam != nil { if pageParam != nil {
pageFlags := cast.ToStringMapBool(pageParam) pageConfig := cast.ToStringMap(pageParam)
for key, value := range pageFlags { for key, value := range pageConfig {
p.renderingConfigFlags[key] = value 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 return p.renderingConfig
}
func (p *Page) isRenderingFlagEnabled(flag string) bool {
return p.getRenderingConfigFlags()[flag]
} }
func newPage(filename string) *Page { func newPage(filename string) *Page {

View file

@ -212,6 +212,16 @@ the cylinder and strike me down. ## BB
### BBB ### BBB
"You're a great Granser," he cried delightedly, "always making believe them little marks mean something." "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") 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) { func TestTableOfContents(t *testing.T) {
p, _ := NewPage("tocpage.md") p, _ := NewPage("tocpage.md")
err := p.ReadFrom(strings.NewReader(PAGE_WITH_TOC)) err := p.ReadFrom(strings.NewReader(PAGE_WITH_TOC))

View file

@ -205,7 +205,7 @@ func renderShortcode(sc shortcode, p *Page, t tpl.Template) string {
if sc.doMarkup { if sc.doMarkup {
newInner := helpers.RenderBytes(helpers.RenderingContext{ newInner := helpers.RenderBytes(helpers.RenderingContext{
Content: []byte(inner), PageFmt: p.guessMarkupType(), 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 // If the type is “unknown” or “markdown”, we assume the markdown
// generation has been performed. Given the input: `a line`, markdown // generation has been performed. Given the input: `a line`, markdown

View file

@ -187,9 +187,9 @@ func (s *SiteInfo) refLink(ref string, page *Page, relative bool) (string, error
if refUrl.Fragment != "" { if refUrl.Fragment != "" {
link = link + "#" + 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() link = link + ":" + target.UniqueId()
} else if page != nil && !page.isRenderingFlagEnabled("plainIdAnchors") { } else if page != nil && !page.getRenderingConfig().PlainIdAnchors {
link = link + ":" + page.UniqueId() link = link + ":" + page.UniqueId()
} }
} }