Skip to content

Commit

Permalink
markup/goldmark: Fix mangling of headers/links in render hooks
Browse files Browse the repository at this point in the history
```bash

name                    old time/op    new time/op    delta
SiteWithRenderHooks-10    11.9ms ± 1%    11.9ms ± 1%    ~     (p=0.486 n=4+4)

name                    old alloc/op   new alloc/op   delta
SiteWithRenderHooks-10    11.2MB ± 0%    11.3MB ± 0%  +0.16%  (p=0.029 n=4+4)

name                    old allocs/op  new allocs/op  delta
SiteWithRenderHooks-10      145k ± 0%      145k ± 0%  +0.14%  (p=0.029 n=4+4)
```

Fixes #9504
  • Loading branch information
bep committed Feb 16, 2022
1 parent 77c7059 commit b2a827c
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 24 deletions.
16 changes: 13 additions & 3 deletions markup/goldmark/convert.go
Expand Up @@ -47,8 +47,7 @@ import (
// Provider is the package entry point.
var Provider converter.ProviderProvider = provide{}

type provide struct {
}
type provide struct{}

func (p provide) New(cfg converter.ProviderConfig) (converter.Provider, error) {
md := newMarkdown(cfg)
Expand Down Expand Up @@ -199,10 +198,21 @@ func (b *bufWriter) Flush() error {

type renderContext struct {
*bufWriter
pos int
positions []int
renderContextData
}

func (ctx *renderContext) pushPos(n int) {
ctx.positions = append(ctx.positions, n)
}

func (ctx *renderContext) popPos() int {
i := len(ctx.positions) - 1
p := ctx.positions[i]
ctx.positions = ctx.positions[:i]
return p
}

type renderContextData interface {
RenderContext() converter.RenderContext
DocumentContext() converter.DocumentContext
Expand Down
36 changes: 36 additions & 0 deletions markup/goldmark/integration_test.go
Expand Up @@ -61,6 +61,42 @@ foo
`)
}

// Issue 9504
func TestLinkInTitle(t *testing.T) {
t.Parallel()

files := `
-- config.toml --
-- content/p1.md --
---
title: "p1"
---
## Hello [Test](https://example.com)
-- layouts/_default/single.html --
{{ .Content }}
-- layouts/_default/_markup/render-heading.html --
<h{{ .Level }} id="{{ .Anchor | safeURL }}">
{{ .Text | safeHTML }}
<a class="anchor" href="#{{ .Anchor | safeURL }}">#</a>
</h{{ .Level }}>
-- layouts/_default/_markup/render-link.html --
<a href="{{ .Destination | safeURL }}"{{ with .Title}} title="{{ . }}"{{ end }}>{{ .Text | safeHTML }}</a>
`

b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: false,
},
).Build()

b.AssertFileContent("public/p1/index.html",
"<h2 id=\"hello-testhttpsexamplecom\">\n Hello <a href=\"https://example.com\">Test</a>\n\n <a class=\"anchor\" href=\"#hello-testhttpsexamplecom\">#</a>\n</h2>",
)
}

func BenchmarkSiteWithRenderHooks(b *testing.B) {
files := `
-- config.toml --
Expand Down
41 changes: 20 additions & 21 deletions markup/goldmark/render_hooks.go
Expand Up @@ -144,16 +144,13 @@ func (r *hookedRenderer) renderAttributesForNode(w util.BufWriter, node ast.Node
renderAttributes(w, false, node.Attributes()...)
}

var (

// Attributes with special meaning that does not make sense to render in HTML.
attributeExcludes = map[string]bool{
"hl_lines": true,
"hl_style": true,
"linenos": true,
"linenostart": true,
}
)
// 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 {
Expand Down Expand Up @@ -197,12 +194,13 @@ 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.pos = ctx.Buffer.Len()
ctx.pushPos(ctx.Buffer.Len())
return ast.WalkContinue, nil
}

text := ctx.Buffer.Bytes()[ctx.pos:]
ctx.Buffer.Truncate(ctx.pos)
pos := ctx.popPos()
text := ctx.Buffer.Bytes()[pos:]
ctx.Buffer.Truncate(pos)

err := h.ImageRenderer.RenderLink(
w,
Expand Down Expand Up @@ -263,12 +261,13 @@ 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.pos = ctx.Buffer.Len()
ctx.pushPos(ctx.Buffer.Len())
return ast.WalkContinue, nil
}

text := ctx.Buffer.Bytes()[ctx.pos:]
ctx.Buffer.Truncate(ctx.pos)
pos := ctx.popPos()
text := ctx.Buffer.Bytes()[pos:]
ctx.Buffer.Truncate(pos)

err := h.LinkRenderer.RenderLink(
w,
Expand Down Expand Up @@ -395,12 +394,13 @@ 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.pos = ctx.Buffer.Len()
ctx.pushPos(ctx.Buffer.Len())
return ast.WalkContinue, nil
}

text := ctx.Buffer.Bytes()[ctx.pos:]
ctx.Buffer.Truncate(ctx.pos)
pos := ctx.popPos()
text := ctx.Buffer.Bytes()[pos:]
ctx.Buffer.Truncate(pos)
// All ast.Heading nodes are guaranteed to have an attribute called "id"
// that is an array of bytes that encode a valid string.
anchori, _ := n.AttributeString("id")
Expand Down Expand Up @@ -440,8 +440,7 @@ func (r *hookedRenderer) renderHeadingDefault(w util.BufWriter, source []byte, n
return ast.WalkContinue, nil
}

type links struct {
}
type links struct{}

// Extend implements goldmark.Extender.
func (e *links) Extend(m goldmark.Markdown) {
Expand Down

0 comments on commit b2a827c

Please sign in to comment.