Split parse and render for Goldmark

This also speeds up situations where you only need the fragments/toc and not the rendered content, e.g. Related
with fragments type indexing:

```bash

name            old time/op    new time/op    delta
RelatedSite-10    12.3ms ± 2%    10.7ms ± 1%  -12.95%  (p=0.029 n=4+4)

name            old alloc/op   new alloc/op   delta
RelatedSite-10    38.6MB ± 0%    38.2MB ± 0%   -1.08%  (p=0.029 n=4+4)

name            old allocs/op  new allocs/op  delta
RelatedSite-10      117k ± 0%      115k ± 0%   -1.36%  (p=0.029 n=4+4)
```

Fixes #10750
This commit is contained in:
Bjørn Erik Pedersen
2023-02-24 07:23:10 +01:00
parent e442a63bb7
commit 271318ad78
14 changed files with 258 additions and 45 deletions

View File

@@ -52,7 +52,7 @@ func (p provider) New(cfg converter.ProviderConfig) (converter.Provider, error)
}
type asciidocResult struct {
converter.Result
converter.ResultRender
toc *tableofcontents.Fragments
}
@@ -65,7 +65,7 @@ type asciidocConverter struct {
cfg converter.ProviderConfig
}
func (a *asciidocConverter) Convert(ctx converter.RenderContext) (converter.Result, error) {
func (a *asciidocConverter) Convert(ctx converter.RenderContext) (converter.ResultRender, error) {
b, err := a.getAsciidocContent(ctx.Src, a.ctx)
if err != nil {
return nil, err
@@ -75,8 +75,8 @@ func (a *asciidocConverter) Convert(ctx converter.RenderContext) (converter.Resu
return nil, err
}
return asciidocResult{
Result: converter.Bytes(content),
toc: toc,
ResultRender: converter.Bytes(content),
toc: toc,
}, nil
}

View File

@@ -74,7 +74,7 @@ var NopConverter = new(nopConverter)
type nopConverter int
func (nopConverter) Convert(ctx RenderContext) (Result, error) {
func (nopConverter) Convert(ctx RenderContext) (ResultRender, error) {
return &bytes.Buffer{}, nil
}
@@ -85,15 +85,29 @@ func (nopConverter) Supports(feature identity.Identity) bool {
// Converter wraps the Convert method that converts some markup into
// another format, e.g. Markdown to HTML.
type Converter interface {
Convert(ctx RenderContext) (Result, error)
Convert(ctx RenderContext) (ResultRender, error)
Supports(feature identity.Identity) bool
}
// Result represents the minimum returned from Convert.
type Result interface {
// ParseRenderer is an optional interface.
// The Goldmark converter implements this, and this allows us
// to extract the ToC without having to render the content.
type ParseRenderer interface {
Parse(RenderContext) (ResultParse, error)
Render(RenderContext, any) (ResultRender, error)
}
// ResultRender represents the minimum returned from Convert and Render.
type ResultRender interface {
Bytes() []byte
}
// ResultParse represents the minimum returned from Parse.
type ResultParse interface {
Doc() any
TableOfContents() *tableofcontents.Fragments
}
// DocumentInfo holds additional information provided by some converters.
type DocumentInfo interface {
AnchorSuffix() string

View File

@@ -18,6 +18,7 @@ import (
"bytes"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/markup/goldmark/codeblocks"
"github.com/gohugoio/hugo/markup/goldmark/images"
"github.com/gohugoio/hugo/markup/goldmark/internal/extensions/attributes"
@@ -26,6 +27,7 @@ import (
"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/markup/tableofcontents"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer"
@@ -158,26 +160,41 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
var _ identity.IdentitiesProvider = (*converterResult)(nil)
type converterResult struct {
converter.Result
type parserResult struct {
doc any
toc *tableofcontents.Fragments
}
func (p parserResult) Doc() any {
return p.doc
}
func (p parserResult) TableOfContents() *tableofcontents.Fragments {
return p.toc
}
type renderResult struct {
converter.ResultRender
ids identity.Identities
}
func (c converterResult) TableOfContents() *tableofcontents.Fragments {
return c.toc
func (r renderResult) GetIdentities() identity.Identities {
return r.ids
}
func (c converterResult) GetIdentities() identity.Identities {
return c.ids
type converterResult struct {
converter.ResultRender
tableOfContentsProvider
identity.IdentitiesProvider
}
type tableOfContentsProvider interface {
TableOfContents() *tableofcontents.Fragments
}
var converterIdentity = identity.KeyValueIdentity{Key: "goldmark", Value: "converter"}
func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result converter.Result, err error) {
buf := &render.BufWriter{Buffer: &bytes.Buffer{}}
result = buf
func (c *goldmarkConverter) Parse(ctx converter.RenderContext) (converter.ResultParse, error) {
pctx := c.newParserContext(ctx)
reader := text.NewReader(ctx.Src)
@@ -186,6 +203,16 @@ func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result convert
parser.WithContext(pctx),
)
return parserResult{
doc: doc,
toc: pctx.TableOfContents(),
}, nil
}
func (c *goldmarkConverter) Render(ctx converter.RenderContext, doc any) (converter.ResultRender, error) {
n := doc.(ast.Node)
buf := &render.BufWriter{Buffer: &bytes.Buffer{}}
rcx := &render.RenderContextDataHolder{
Rctx: ctx,
Dctx: c.ctx,
@@ -197,15 +224,32 @@ func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result convert
ContextData: rcx,
}
if err := c.md.Renderer().Render(w, ctx.Src, doc); err != nil {
if err := c.md.Renderer().Render(w, ctx.Src, n); err != nil {
return nil, err
}
return converterResult{
Result: buf,
ids: rcx.IDs.GetIdentities(),
toc: pctx.TableOfContents(),
return renderResult{
ResultRender: buf,
ids: rcx.IDs.GetIdentities(),
}, nil
}
func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (converter.ResultRender, error) {
parseResult, err := c.Parse(ctx)
if err != nil {
return nil, err
}
renderResult, err := c.Render(ctx, parseResult.Doc())
if err != nil {
return nil, err
}
return converterResult{
ResultRender: renderResult,
tableOfContentsProvider: parseResult,
IdentitiesProvider: renderResult.(identity.IdentitiesProvider),
}, nil
}
var featureSet = map[identity.Identity]bool{

View File

@@ -34,7 +34,7 @@ import (
qt "github.com/frankban/quicktest"
)
func convert(c *qt.C, mconf markup_config.Config, content string) converter.Result {
func convert(c *qt.C, mconf markup_config.Config, content string) converter.ResultRender {
p, err := Provider.New(
converter.ProviderConfig{
MarkupConfig: mconf,

View File

@@ -43,7 +43,7 @@ type orgConverter struct {
cfg converter.ProviderConfig
}
func (c *orgConverter) Convert(ctx converter.RenderContext) (converter.Result, error) {
func (c *orgConverter) Convert(ctx converter.RenderContext) (converter.ResultRender, error) {
logger := c.cfg.Logger
config := org.New()
config.Log = logger.Warn()

View File

@@ -43,7 +43,7 @@ type pandocConverter struct {
cfg converter.ProviderConfig
}
func (c *pandocConverter) Convert(ctx converter.RenderContext) (converter.Result, error) {
func (c *pandocConverter) Convert(ctx converter.RenderContext) (converter.ResultRender, error) {
b, err := c.getPandocContent(ctx.Src, c.ctx)
if err != nil {
return nil, err

View File

@@ -47,7 +47,7 @@ type rstConverter struct {
cfg converter.ProviderConfig
}
func (c *rstConverter) Convert(ctx converter.RenderContext) (converter.Result, error) {
func (c *rstConverter) Convert(ctx converter.RenderContext) (converter.ResultRender, error) {
b, err := c.getRstContent(ctx.Src, c.ctx)
if err != nil {
return nil, err