diff --git a/commands/hugo.go b/commands/hugo.go index f57a7c910..94c955925 100644 --- a/commands/hugo.go +++ b/commands/hugo.go @@ -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) diff --git a/docs/content/overview/configuration.md b/docs/content/overview/configuration.md index a07ef5672..c492296fc 100644 --- a/docs/content/overview/configuration.md +++ b/docs/content/overview/configuration.md @@ -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)): @@ -115,6 +115,16 @@ but only these three. + + + + + + + + + +
Purpose: If true, then header and footnote IDs are generated without the document ID (e.g. #my-header instead of #my-header:bec3ed8ba720b9073ab75abcf3ba5d97)
extensions[]EXTENSION_*
Purpose:Use non-default additional extensions (e.g. Add "hardLineBreak" to use EXTENSION_HARD_LINE_BREAK)
@@ -130,11 +140,14 @@ but only these three. angledQuotes = true fractions = false plainIdAnchors = true + extensions = ["hardLineBreak"]
blackfriday:
   angledQuotes: true
   fractions: false
   plainIdAnchors: true
+  extensions:
+    - hardLineBreak
 
diff --git a/helpers/content.go b/helpers/content.go index 3f9cc55d5..59207964e 100644 --- a/helpers/content.go +++ b/helpers/content.go @@ -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 let's user define where summarization ends. var SummaryDivider = []byte("") +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)) } } diff --git a/hugolib/page.go b/hugolib/page.go index eadf6eb3d..ffbe7772b 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -17,16 +17,12 @@ import ( "bytes" "errors" "fmt" - "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" + "github.com/mitchellh/mapstructure" + "github.com/spf13/hugo/helpers" + "github.com/spf13/hugo/parser" + "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 { diff --git a/hugolib/page_test.go b/hugolib/page_test.go index d4ce7a0a0..dc8ebf64b 100644 --- a/hugolib/page_test.go +++ b/hugolib/page_test.go @@ -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, "\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, "

first line.
\nsecond line.

\n\n

fourth line.

\n") +} + func TestTableOfContents(t *testing.T) { p, _ := NewPage("tocpage.md") err := p.ReadFrom(strings.NewReader(PAGE_WITH_TOC)) diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go index ff3eaeb89..03cd7d4a7 100644 --- a/hugolib/shortcode.go +++ b/hugolib/shortcode.go @@ -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 diff --git a/hugolib/site.go b/hugolib/site.go index 39e900cfd..a6a18a2fe 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -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() } }