summaryrefslogtreecommitdiffstats
path: root/hugolib/page__per_output.go
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2022-02-17 13:04:00 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2022-02-24 18:59:50 +0100
commit08fdca9d9365eaf1e496a12e2af5e18617bd0e66 (patch)
tree6c6942d1b74a4160d93a997860bafd52b92025f5 /hugolib/page__per_output.go
parent2c20f5bc00b604e72b3b7e401fbdbf9447fe3470 (diff)
Add Markdown diagrams and render hooks for code blocks
You can now create custom hook templates for code blocks, either one for all (`render-codeblock.html`) or for a given code language (e.g. `render-codeblock-go.html`). We also used this new hook to add support for diagrams in Hugo: * Goat (Go ASCII Tool) is built-in and enabled by default; just create a fenced code block with the language `goat` and start draw your Ascii diagrams. * Another popular alternative for diagrams in Markdown, Mermaid (supported by GitHub), can also be implemented with a simple template. See the Hugo documentation for more information. Updates #7765 Closes #9538 Fixes #9553 Fixes #8520 Fixes #6702 Fixes #9558
Diffstat (limited to 'hugolib/page__per_output.go')
-rw-r--r--hugolib/page__per_output.go173
1 files changed, 99 insertions, 74 deletions
diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go
index bd4e35a5b..29beb672e 100644
--- a/hugolib/page__per_output.go
+++ b/hugolib/page__per_output.go
@@ -32,6 +32,7 @@ import (
"github.com/gohugoio/hugo/markup/converter"
+ "github.com/alecthomas/chroma/lexers"
"github.com/gohugoio/hugo/lazy"
bp "github.com/gohugoio/hugo/bufferpool"
@@ -109,16 +110,8 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err
return err
}
- enableReuse := !(hasShortcodeVariants || cp.renderHooksHaveVariants)
-
- if enableReuse {
- // Reuse this for the other output formats.
- // We may improve on this, but we really want to avoid re-rendering the content
- // to all output formats.
- // The current rule is that if you need output format-aware shortcodes or
- // content rendering hooks, create a output format-specific template, e.g.
- // myshortcode.amp.html.
- cp.enableReuse()
+ if hasShortcodeVariants {
+ p.pageOutputTemplateVariationsState.Store(2)
}
cp.workContent = p.contentToRender(cp.contentPlaceholders)
@@ -199,19 +192,10 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err
return nil
}
- // Recursive loops can only happen in content files with template code (shortcodes etc.)
- // Avoid creating new goroutines if we don't have to.
- needTimeout := p.shortcodeState.hasShortcodes() || cp.renderHooks != nil
-
- if needTimeout {
- cp.initMain = parent.BranchWithTimeout(p.s.siteCfg.timeout, func(ctx context.Context) (interface{}, error) {
- return nil, initContent()
- })
- } else {
- cp.initMain = parent.Branch(func() (interface{}, error) {
- return nil, initContent()
- })
- }
+ // There may be recursive loops in shortcodes and render hooks.
+ cp.initMain = parent.BranchWithTimeout(p.s.siteCfg.timeout, func(ctx context.Context) (interface{}, error) {
+ return nil, initContent()
+ })
cp.initPlain = cp.initMain.Branch(func() (interface{}, error) {
cp.plain = helpers.StripHTML(string(cp.content))
@@ -229,18 +213,14 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err
}
type renderHooks struct {
- hooks hooks.Renderers
- init sync.Once
+ getRenderer hooks.GetRendererFunc
+ init sync.Once
}
// pageContentOutput represents the Page content for a given output format.
type pageContentOutput struct {
f output.Format
- // If we can reuse this for other output formats.
- reuse bool
- reuseInit sync.Once
-
p *pageState
// Lazy load dependencies
@@ -250,13 +230,9 @@ type pageContentOutput struct {
placeholdersEnabled bool
placeholdersEnabledInit sync.Once
+ // Renders Markdown hooks.
renderHooks *renderHooks
- // Set if there are more than one output format variant
- renderHooksHaveVariants bool // TODO(bep) reimplement this in another way, consolidate with shortcodes
-
- // Content state
-
workContent []byte
dependencyTracker identity.Manager // Set in server mode.
@@ -440,55 +416,107 @@ func (p *pageContentOutput) initRenderHooks() error {
return nil
}
- var initErr error
-
p.renderHooks.init.Do(func() {
- ps := p.p
-
- c := ps.getContentConverter()
- if c == nil || !c.Supports(converter.FeatureRenderHooks) {
- return
+ if p.p.pageOutputTemplateVariationsState.Load() == 0 {
+ p.p.pageOutputTemplateVariationsState.Store(1)
}
- h, err := ps.createRenderHooks(p.f)
- if err != nil {
- initErr = err
- return
+ type cacheKey struct {
+ tp hooks.RendererType
+ id interface{}
+ f output.Format
}
- p.renderHooks.hooks = h
-
- if !p.renderHooksHaveVariants || h.IsZero() {
- // Check if there is a different render hooks template
- // for any of the other page output formats.
- // If not, we can reuse this.
- for _, po := range ps.pageOutputs {
- if po.f.Name != p.f.Name {
- h2, err := ps.createRenderHooks(po.f)
- if err != nil {
- initErr = err
- return
- }
- if h2.IsZero() {
- continue
- }
+ renderCache := make(map[cacheKey]interface{})
+ var renderCacheMu sync.Mutex
+
+ p.renderHooks.getRenderer = func(tp hooks.RendererType, id interface{}) interface{} {
+ renderCacheMu.Lock()
+ defer renderCacheMu.Unlock()
+
+ key := cacheKey{tp: tp, id: id, f: p.f}
+ if r, ok := renderCache[key]; ok {
+ return r
+ }
- if p.renderHooks.hooks.IsZero() {
- p.renderHooks.hooks = h2
+ layoutDescriptor := p.p.getLayoutDescriptor()
+ layoutDescriptor.RenderingHook = true
+ layoutDescriptor.LayoutOverride = false
+ layoutDescriptor.Layout = ""
+
+ switch tp {
+ case hooks.LinkRendererType:
+ layoutDescriptor.Kind = "render-link"
+ case hooks.ImageRendererType:
+ layoutDescriptor.Kind = "render-image"
+ case hooks.HeadingRendererType:
+ layoutDescriptor.Kind = "render-heading"
+ case hooks.CodeBlockRendererType:
+ layoutDescriptor.Kind = "render-codeblock"
+ if id != nil {
+ lang := id.(string)
+ lexer := lexers.Get(lang)
+ if lexer != nil {
+ layoutDescriptor.KindVariants = strings.Join(lexer.Config().Aliases, ",")
+ } else {
+ layoutDescriptor.KindVariants = lang
}
+ }
+ }
- p.renderHooksHaveVariants = !h2.Eq(p.renderHooks.hooks)
+ getHookTemplate := func(f output.Format) (tpl.Template, bool) {
+ templ, found, err := p.p.s.Tmpl().LookupLayout(layoutDescriptor, f)
+ if err != nil {
+ panic(err)
+ }
+ return templ, found
+ }
+
+ templ, found1 := getHookTemplate(p.f)
- if p.renderHooksHaveVariants {
- break
+ if p.p.reusePageOutputContent() {
+ // Check if some of the other output formats would give a different template.
+ for _, f := range p.p.s.renderFormats {
+ if f.Name == p.f.Name {
+ continue
+ }
+ templ2, found2 := getHookTemplate(f)
+ if found2 {
+ if !found1 {
+ templ = templ2
+ found1 = true
+ break
+ }
+
+ if templ != templ2 {
+ p.p.pageOutputTemplateVariationsState.Store(2)
+ break
+ }
}
+ }
+ }
+ if !found1 {
+ if tp == hooks.CodeBlockRendererType {
+ // No user provided tempplate for code blocks, so we use the native Go code version -- which is also faster.
+ r := p.p.s.ContentSpec.Converters.GetHighlighter()
+ renderCache[key] = r
+ return r
}
+ return nil
}
+
+ r := hookRendererTemplate{
+ templateHandler: p.p.s.Tmpl(),
+ SearchProvider: templ.(identity.SearchProvider),
+ templ: templ,
+ }
+ renderCache[key] = r
+ return r
}
})
- return initErr
+ return nil
}
func (p *pageContentOutput) setAutoSummary() error {
@@ -512,6 +540,9 @@ func (p *pageContentOutput) setAutoSummary() error {
}
func (cp *pageContentOutput) renderContent(content []byte, renderTOC bool) (converter.Result, error) {
+ if err := cp.initRenderHooks(); err != nil {
+ return nil, err
+ }
c := cp.p.getContentConverter()
return cp.renderContentWithConverter(c, content, renderTOC)
}
@@ -521,7 +552,7 @@ func (cp *pageContentOutput) renderContentWithConverter(c converter.Converter, c
converter.RenderContext{
Src: content,
RenderTOC: renderTOC,
- RenderHooks: cp.renderHooks.hooks,
+ GetRenderer: cp.renderHooks.getRenderer,
})
if err == nil {
@@ -570,12 +601,6 @@ func (p *pageContentOutput) enablePlaceholders() {
})
}
-func (p *pageContentOutput) enableReuse() {
- p.reuseInit.Do(func() {
- p.reuse = true
- })
-}
-
// these will be shifted out when rendering a given output format.
type pagePerOutputProviders interface {
targetPather