diff --git a/deps/deps.go b/deps/deps.go index aaed900e5..d7b381ce9 100644 --- a/deps/deps.go +++ b/deps/deps.go @@ -223,7 +223,7 @@ func New(cfg DepsCfg) (*Deps, error) { return nil, err } - contentSpec, err := helpers.NewContentSpec(cfg.Language) + contentSpec, err := helpers.NewContentSpec(cfg.Language, logger, ps.BaseFs.Content.Fs) if err != nil { return nil, err } @@ -277,7 +277,7 @@ func (d Deps) ForLanguage(cfg DepsCfg, onCreated func(d *Deps) error) (*Deps, er return nil, err } - d.ContentSpec, err = helpers.NewContentSpec(l) + d.ContentSpec, err = helpers.NewContentSpec(l, d.Log, d.BaseFs.Content.Fs) if err != nil { return nil, err } diff --git a/helpers/content.go b/helpers/content.go index fe96ce7d2..357bd48e7 100644 --- a/helpers/content.go +++ b/helpers/content.go @@ -19,22 +19,18 @@ package helpers import ( "bytes" - "fmt" "html/template" - "os/exec" - "runtime" "unicode" "unicode/utf8" - "github.com/gohugoio/hugo/common/maps" - "github.com/gohugoio/hugo/hugolib/filesystems" - "github.com/niklasfasching/go-org/org" + "github.com/gohugoio/hugo/common/loggers" + + "github.com/gohugoio/hugo/markup/converter" + + "github.com/gohugoio/hugo/markup" bp "github.com/gohugoio/hugo/bufferpool" "github.com/gohugoio/hugo/config" - "github.com/miekg/mmark" - "github.com/mitchellh/mapstructure" - "github.com/russross/blackfriday" "github.com/spf13/afero" jww "github.com/spf13/jwalterweatherman" @@ -52,9 +48,9 @@ var ( // ContentSpec provides functionality to render markdown content. type ContentSpec struct { - BlackFriday *BlackFriday - footnoteAnchorPrefix string - footnoteReturnLinkContents string + Converters markup.ConverterProvider + MardownConverter converter.Converter // Markdown converter with no document context + // SummaryLength is the length of the summary that Hugo extracts from a content. summaryLength int @@ -70,16 +66,13 @@ type ContentSpec struct { // NewContentSpec returns a ContentSpec initialized // with the appropriate fields from the given config.Provider. -func NewContentSpec(cfg config.Provider) (*ContentSpec, error) { - bf := newBlackfriday(cfg.GetStringMap("blackfriday")) +func NewContentSpec(cfg config.Provider, logger *loggers.Logger, contentFs afero.Fs) (*ContentSpec, error) { + spec := &ContentSpec{ - BlackFriday: bf, - footnoteAnchorPrefix: cfg.GetString("footnoteAnchorPrefix"), - footnoteReturnLinkContents: cfg.GetString("footnoteReturnLinkContents"), - summaryLength: cfg.GetInt("summaryLength"), - BuildFuture: cfg.GetBool("buildFuture"), - BuildExpired: cfg.GetBool("buildExpired"), - BuildDrafts: cfg.GetBool("buildDrafts"), + summaryLength: cfg.GetInt("summaryLength"), + BuildFuture: cfg.GetBool("buildFuture"), + BuildExpired: cfg.GetBool("buildExpired"), + BuildDrafts: cfg.GetBool("buildDrafts"), Cfg: cfg, } @@ -109,99 +102,29 @@ func NewContentSpec(cfg config.Provider) (*ContentSpec, error) { spec.Highlight = h.chromaHighlight } + converterProvider, err := markup.NewConverterProvider(converter.ProviderConfig{ + Cfg: cfg, + ContentFs: contentFs, + Logger: logger, + Highlight: spec.Highlight, + }) + if err != nil { + return nil, err + } + + spec.Converters = converterProvider + p := converterProvider.Get("markdown") + conv, err := p.New(converter.DocumentContext{}) + if err != nil { + return nil, err + } + spec.MardownConverter = conv + return spec, nil } -// BlackFriday holds configuration values for BlackFriday rendering. -type BlackFriday struct { - Smartypants bool - SmartypantsQuotesNBSP bool - AngledQuotes bool - Fractions bool - HrefTargetBlank bool - NofollowLinks bool - NoreferrerLinks bool - SmartDashes bool - LatexDashes bool - TaskLists bool - PlainIDAnchors bool - Extensions []string - ExtensionsMask []string - SkipHTML bool -} - -// NewBlackfriday creates a new Blackfriday filled with site config or some sane defaults. -func newBlackfriday(config map[string]interface{}) *BlackFriday { - defaultParam := map[string]interface{}{ - "smartypants": true, - "angledQuotes": false, - "smartypantsQuotesNBSP": false, - "fractions": true, - "hrefTargetBlank": false, - "nofollowLinks": false, - "noreferrerLinks": false, - "smartDashes": true, - "latexDashes": true, - "plainIDAnchors": true, - "taskLists": true, - "skipHTML": false, - } - - maps.ToLower(defaultParam) - - siteConfig := make(map[string]interface{}) - - for k, v := range defaultParam { - siteConfig[k] = v - } - - for k, v := range config { - siteConfig[k] = v - } - - combinedConfig := &BlackFriday{} - if err := mapstructure.Decode(siteConfig, combinedConfig); err != nil { - jww.FATAL.Printf("Failed to get site rendering config\n%s", err.Error()) - } - - return combinedConfig -} - -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, - "backslashLineBreak": blackfriday.EXTENSION_BACKSLASH_LINE_BREAK, - "definitionLists": blackfriday.EXTENSION_DEFINITION_LISTS, - "joinLines": blackfriday.EXTENSION_JOIN_LINES, -} - var stripHTMLReplacer = strings.NewReplacer("\n", " ", "

", "\n", "
", "\n", "
", "\n") -var mmarkExtensionMap = map[string]int{ - "tables": mmark.EXTENSION_TABLES, - "fencedCode": mmark.EXTENSION_FENCED_CODE, - "autolink": mmark.EXTENSION_AUTOLINK, - "laxHtmlBlocks": mmark.EXTENSION_LAX_HTML_BLOCKS, - "spaceHeaders": mmark.EXTENSION_SPACE_HEADERS, - "hardLineBreak": mmark.EXTENSION_HARD_LINE_BREAK, - "footnotes": mmark.EXTENSION_FOOTNOTES, - "noEmptyLineBeforeBlock": mmark.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK, - "headerIds": mmark.EXTENSION_HEADER_IDS, - "autoHeaderIds": mmark.EXTENSION_AUTO_HEADER_IDS, -} - // StripHTML accepts a string, strips out all HTML tags and returns it. func StripHTML(s string) string { @@ -250,181 +173,6 @@ func BytesToHTML(b []byte) template.HTML { return template.HTML(string(b)) } -// getHTMLRenderer creates a new Blackfriday HTML Renderer with the given configuration. -func (c *ContentSpec) getHTMLRenderer(defaultFlags int, ctx *RenderingContext) blackfriday.Renderer { - renderParameters := blackfriday.HtmlRendererParameters{ - FootnoteAnchorPrefix: c.footnoteAnchorPrefix, - FootnoteReturnLinkContents: c.footnoteReturnLinkContents, - } - - b := len(ctx.DocumentID) != 0 - - if ctx.Config == nil { - panic(fmt.Sprintf("RenderingContext of %q doesn't have a config", ctx.DocumentID)) - } - - if b && !ctx.Config.PlainIDAnchors { - renderParameters.FootnoteAnchorPrefix = ctx.DocumentID + ":" + renderParameters.FootnoteAnchorPrefix - renderParameters.HeaderIDSuffix = ":" + ctx.DocumentID - } - - htmlFlags := defaultFlags - htmlFlags |= blackfriday.HTML_USE_XHTML - htmlFlags |= blackfriday.HTML_FOOTNOTE_RETURN_LINKS - - if ctx.Config.Smartypants { - htmlFlags |= blackfriday.HTML_USE_SMARTYPANTS - } - - if ctx.Config.SmartypantsQuotesNBSP { - htmlFlags |= blackfriday.HTML_SMARTYPANTS_QUOTES_NBSP - } - - if ctx.Config.AngledQuotes { - htmlFlags |= blackfriday.HTML_SMARTYPANTS_ANGLED_QUOTES - } - - if ctx.Config.Fractions { - htmlFlags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS - } - - if ctx.Config.HrefTargetBlank { - htmlFlags |= blackfriday.HTML_HREF_TARGET_BLANK - } - - if ctx.Config.NofollowLinks { - htmlFlags |= blackfriday.HTML_NOFOLLOW_LINKS - } - - if ctx.Config.NoreferrerLinks { - htmlFlags |= blackfriday.HTML_NOREFERRER_LINKS - } - - if ctx.Config.SmartDashes { - htmlFlags |= blackfriday.HTML_SMARTYPANTS_DASHES - } - - if ctx.Config.LatexDashes { - htmlFlags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES - } - - if ctx.Config.SkipHTML { - htmlFlags |= blackfriday.HTML_SKIP_HTML - } - - return &HugoHTMLRenderer{ - cs: c, - RenderingContext: ctx, - Renderer: blackfriday.HtmlRendererWithParameters(htmlFlags, "", "", renderParameters), - } -} - -func getMarkdownExtensions(ctx *RenderingContext) int { - // Default Blackfriday common extensions - commonExtensions := 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_HEADER_IDS | - blackfriday.EXTENSION_BACKSLASH_LINE_BREAK | - blackfriday.EXTENSION_DEFINITION_LISTS - - // Extra Blackfriday extensions that Hugo enables by default - flags := commonExtensions | - blackfriday.EXTENSION_AUTO_HEADER_IDS | - blackfriday.EXTENSION_FOOTNOTES - - if ctx.Config == nil { - panic(fmt.Sprintf("RenderingContext of %q doesn't have a config", ctx.DocumentID)) - } - - for _, extension := range ctx.Config.Extensions { - if flag, ok := blackfridayExtensionMap[extension]; ok { - flags |= flag - } - } - for _, extension := range ctx.Config.ExtensionsMask { - if flag, ok := blackfridayExtensionMap[extension]; ok { - flags &= ^flag - } - } - return flags -} - -func (c *ContentSpec) markdownRender(ctx *RenderingContext) []byte { - if ctx.RenderTOC { - return blackfriday.Markdown(ctx.Content, - c.getHTMLRenderer(blackfriday.HTML_TOC, ctx), - getMarkdownExtensions(ctx)) - } - return blackfriday.Markdown(ctx.Content, c.getHTMLRenderer(0, ctx), - getMarkdownExtensions(ctx)) -} - -// getMmarkHTMLRenderer creates a new mmark HTML Renderer with the given configuration. -func (c *ContentSpec) getMmarkHTMLRenderer(defaultFlags int, ctx *RenderingContext) mmark.Renderer { - renderParameters := mmark.HtmlRendererParameters{ - FootnoteAnchorPrefix: c.footnoteAnchorPrefix, - FootnoteReturnLinkContents: c.footnoteReturnLinkContents, - } - - b := len(ctx.DocumentID) != 0 - - if ctx.Config == nil { - panic(fmt.Sprintf("RenderingContext of %q doesn't have a config", ctx.DocumentID)) - } - - if b && !ctx.Config.PlainIDAnchors { - renderParameters.FootnoteAnchorPrefix = ctx.DocumentID + ":" + renderParameters.FootnoteAnchorPrefix - // renderParameters.HeaderIDSuffix = ":" + ctx.DocumentId - } - - htmlFlags := defaultFlags - htmlFlags |= mmark.HTML_FOOTNOTE_RETURN_LINKS - - return &HugoMmarkHTMLRenderer{ - cs: c, - Renderer: mmark.HtmlRendererWithParameters(htmlFlags, "", "", renderParameters), - Cfg: c.Cfg, - } -} - -func getMmarkExtensions(ctx *RenderingContext) int { - flags := 0 - flags |= mmark.EXTENSION_TABLES - flags |= mmark.EXTENSION_FENCED_CODE - flags |= mmark.EXTENSION_AUTOLINK - flags |= mmark.EXTENSION_SPACE_HEADERS - flags |= mmark.EXTENSION_CITATION - flags |= mmark.EXTENSION_TITLEBLOCK_TOML - flags |= mmark.EXTENSION_HEADER_IDS - flags |= mmark.EXTENSION_AUTO_HEADER_IDS - flags |= mmark.EXTENSION_UNIQUE_HEADER_IDS - flags |= mmark.EXTENSION_FOOTNOTES - flags |= mmark.EXTENSION_SHORT_REF - flags |= mmark.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK - flags |= mmark.EXTENSION_INCLUDE - - if ctx.Config == nil { - panic(fmt.Sprintf("RenderingContext of %q doesn't have a config", ctx.DocumentID)) - } - - for _, extension := range ctx.Config.Extensions { - if flag, ok := mmarkExtensionMap[extension]; ok { - flags |= flag - } - } - return flags -} - -func (c *ContentSpec) mmarkRender(ctx *RenderingContext) []byte { - return mmark.Parse(ctx.Content, c.getMmarkHTMLRenderer(0, ctx), - getMmarkExtensions(ctx)).Bytes() -} - // ExtractTOC extracts Table of Contents from content. func ExtractTOC(content []byte) (newcontent []byte, toc []byte) { if !bytes.Contains(content, []byte("