summaryrefslogtreecommitdiffstats
path: root/markup/goldmark/render_hooks.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 /markup/goldmark/render_hooks.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 'markup/goldmark/render_hooks.go')
-rw-r--r--markup/goldmark/render_hooks.go143
1 files changed, 47 insertions, 96 deletions
diff --git a/markup/goldmark/render_hooks.go b/markup/goldmark/render_hooks.go
index 1862c2125..d5e35380a 100644
--- a/markup/goldmark/render_hooks.go
+++ b/markup/goldmark/render_hooks.go
@@ -16,11 +16,10 @@ package goldmark
import (
"bytes"
"strings"
- "sync"
-
- "github.com/spf13/cast"
"github.com/gohugoio/hugo/markup/converter/hooks"
+ "github.com/gohugoio/hugo/markup/goldmark/internal/render"
+ "github.com/gohugoio/hugo/markup/internal/attributes"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
@@ -44,28 +43,6 @@ func newLinks() goldmark.Extender {
return &links{}
}
-type attributesHolder struct {
- // What we get from Goldmark.
- astAttributes []ast.Attribute
-
- // What we send to the the render hooks.
- attributesInit sync.Once
- attributes map[string]string
-}
-
-func (a *attributesHolder) Attributes() map[string]string {
- a.attributesInit.Do(func() {
- a.attributes = make(map[string]string)
- for _, attr := range a.astAttributes {
- if strings.HasPrefix(string(attr.Name), "on") {
- continue
- }
- a.attributes[string(attr.Name)] = string(util.EscapeHTML(attr.Value.([]byte)))
- }
- })
- return a.attributes
-}
-
type linkContext struct {
page interface{}
destination string
@@ -104,7 +81,7 @@ type headingContext struct {
anchor string
text string
plainText string
- *attributesHolder
+ *attributes.AttributesHolder
}
func (ctx headingContext) Page() interface{} {
@@ -143,52 +120,17 @@ func (r *hookedRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer)
reg.Register(ast.KindHeading, r.renderHeading)
}
-func (r *hookedRenderer) renderAttributesForNode(w util.BufWriter, node ast.Node) {
- renderAttributes(w, false, node.Attributes()...)
-}
-
-// Attributes with special meaning that does not make sense to render in HTML.
-var attributeExcludes = map[string]bool{
- "hl_lines": true,
- "hl_style": true,
- "linenos": true,
- "linenostart": true,
-}
-
-func renderAttributes(w util.BufWriter, skipClass bool, attributes ...ast.Attribute) {
- for _, attr := range attributes {
- if skipClass && bytes.Equal(attr.Name, []byte("class")) {
- continue
- }
-
- a := strings.ToLower(string(attr.Name))
- if attributeExcludes[a] || strings.HasPrefix(a, "on") {
- continue
- }
-
- _, _ = w.WriteString(" ")
- _, _ = w.Write(attr.Name)
- _, _ = w.WriteString(`="`)
-
- switch v := attr.Value.(type) {
- case []byte:
- _, _ = w.Write(util.EscapeHTML(v))
- default:
- w.WriteString(cast.ToString(v))
- }
-
- _ = w.WriteByte('"')
- }
-}
-
func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
n := node.(*ast.Image)
- var h hooks.Renderers
+ var lr hooks.LinkRenderer
- ctx, ok := w.(*renderContext)
+ ctx, ok := w.(*render.Context)
if ok {
- h = ctx.RenderContext().RenderHooks
- ok = h.ImageRenderer != nil
+ h := ctx.RenderContext().GetRenderer(hooks.ImageRendererType, nil)
+ ok = h != nil
+ if ok {
+ lr = h.(hooks.LinkRenderer)
+ }
}
if !ok {
@@ -197,15 +139,15 @@ func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.N
if entering {
// Store the current pos so we can capture the rendered text.
- ctx.pushPos(ctx.Buffer.Len())
+ ctx.PushPos(ctx.Buffer.Len())
return ast.WalkContinue, nil
}
- pos := ctx.popPos()
+ pos := ctx.PopPos()
text := ctx.Buffer.Bytes()[pos:]
ctx.Buffer.Truncate(pos)
- err := h.ImageRenderer.RenderLink(
+ err := lr.RenderLink(
w,
linkContext{
page: ctx.DocumentContext().Document,
@@ -216,7 +158,7 @@ func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.N
},
)
- ctx.AddIdentity(h.ImageRenderer)
+ ctx.AddIdentity(lr)
return ast.WalkContinue, err
}
@@ -250,12 +192,15 @@ func (r *hookedRenderer) renderImageDefault(w util.BufWriter, source []byte, nod
func (r *hookedRenderer) renderLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
n := node.(*ast.Link)
- var h hooks.Renderers
+ var lr hooks.LinkRenderer
- ctx, ok := w.(*renderContext)
+ ctx, ok := w.(*render.Context)
if ok {
- h = ctx.RenderContext().RenderHooks
- ok = h.LinkRenderer != nil
+ h := ctx.RenderContext().GetRenderer(hooks.LinkRendererType, nil)
+ ok = h != nil
+ if ok {
+ lr = h.(hooks.LinkRenderer)
+ }
}
if !ok {
@@ -264,15 +209,15 @@ func (r *hookedRenderer) renderLink(w util.BufWriter, source []byte, node ast.No
if entering {
// Store the current pos so we can capture the rendered text.
- ctx.pushPos(ctx.Buffer.Len())
+ ctx.PushPos(ctx.Buffer.Len())
return ast.WalkContinue, nil
}
- pos := ctx.popPos()
+ pos := ctx.PopPos()
text := ctx.Buffer.Bytes()[pos:]
ctx.Buffer.Truncate(pos)
- err := h.LinkRenderer.RenderLink(
+ err := lr.RenderLink(
w,
linkContext{
page: ctx.DocumentContext().Document,
@@ -286,7 +231,7 @@ func (r *hookedRenderer) renderLink(w util.BufWriter, source []byte, node ast.No
// TODO(bep) I have a working branch that fixes these rather confusing identity types,
// but for now it's important that it's not .GetIdentity() that's added here,
// to make sure we search the entire chain on changes.
- ctx.AddIdentity(h.LinkRenderer)
+ ctx.AddIdentity(lr)
return ast.WalkContinue, err
}
@@ -319,12 +264,15 @@ func (r *hookedRenderer) renderAutoLink(w util.BufWriter, source []byte, node as
}
n := node.(*ast.AutoLink)
- var h hooks.Renderers
+ var lr hooks.LinkRenderer
- ctx, ok := w.(*renderContext)
+ ctx, ok := w.(*render.Context)
if ok {
- h = ctx.RenderContext().RenderHooks
- ok = h.LinkRenderer != nil
+ h := ctx.RenderContext().GetRenderer(hooks.LinkRendererType, nil)
+ ok = h != nil
+ if ok {
+ lr = h.(hooks.LinkRenderer)
+ }
}
if !ok {
@@ -337,7 +285,7 @@ func (r *hookedRenderer) renderAutoLink(w util.BufWriter, source []byte, node as
url = "mailto:" + url
}
- err := h.LinkRenderer.RenderLink(
+ err := lr.RenderLink(
w,
linkContext{
page: ctx.DocumentContext().Document,
@@ -350,7 +298,7 @@ func (r *hookedRenderer) renderAutoLink(w util.BufWriter, source []byte, node as
// TODO(bep) I have a working branch that fixes these rather confusing identity types,
// but for now it's important that it's not .GetIdentity() that's added here,
// to make sure we search the entire chain on changes.
- ctx.AddIdentity(h.LinkRenderer)
+ ctx.AddIdentity(lr)
return ast.WalkContinue, err
}
@@ -383,12 +331,15 @@ func (r *hookedRenderer) renderAutoLinkDefault(w util.BufWriter, source []byte,
func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
n := node.(*ast.Heading)
- var h hooks.Renderers
+ var hr hooks.HeadingRenderer
- ctx, ok := w.(*renderContext)
+ ctx, ok := w.(*render.Context)
if ok {
- h = ctx.RenderContext().RenderHooks
- ok = h.HeadingRenderer != nil
+ h := ctx.RenderContext().GetRenderer(hooks.HeadingRendererType, nil)
+ ok = h != nil
+ if ok {
+ hr = h.(hooks.HeadingRenderer)
+ }
}
if !ok {
@@ -397,11 +348,11 @@ func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast
if entering {
// Store the current pos so we can capture the rendered text.
- ctx.pushPos(ctx.Buffer.Len())
+ ctx.PushPos(ctx.Buffer.Len())
return ast.WalkContinue, nil
}
- pos := ctx.popPos()
+ pos := ctx.PopPos()
text := ctx.Buffer.Bytes()[pos:]
ctx.Buffer.Truncate(pos)
// All ast.Heading nodes are guaranteed to have an attribute called "id"
@@ -409,7 +360,7 @@ func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast
anchori, _ := n.AttributeString("id")
anchor := anchori.([]byte)
- err := h.HeadingRenderer.RenderHeading(
+ err := hr.RenderHeading(
w,
headingContext{
page: ctx.DocumentContext().Document,
@@ -417,11 +368,11 @@ func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast
anchor: string(anchor),
text: string(text),
plainText: string(n.Text(source)),
- attributesHolder: &attributesHolder{astAttributes: n.Attributes()},
+ AttributesHolder: attributes.New(n.Attributes(), attributes.AttributesOwnerGeneral),
},
)
- ctx.AddIdentity(h.HeadingRenderer)
+ ctx.AddIdentity(hr)
return ast.WalkContinue, err
}
@@ -432,7 +383,7 @@ func (r *hookedRenderer) renderHeadingDefault(w util.BufWriter, source []byte, n
_, _ = w.WriteString("<h")
_ = w.WriteByte("0123456"[n.Level])
if n.Attributes() != nil {
- r.renderAttributesForNode(w, node)
+ attributes.RenderASTAttributes(w, node.Attributes()...)
}
_ = w.WriteByte('>')
} else {