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

@@ -115,14 +115,7 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err
isHTML := cp.p.m.markup == "html"
if !isHTML {
r, err := po.contentRenderer.RenderContent(ctx, cp.workContent, true)
if err != nil {
return err
}
cp.workContent = r.Bytes()
if tocProvider, ok := r.(converter.TableOfContentsProvider); ok {
createAndSetToC := func(tocProvider converter.TableOfContentsProvider) {
cfg := p.s.ContentSpec.Converters.GetMarkupConfig()
cp.tableOfContents = tocProvider.TableOfContents()
cp.tableOfContentsHTML = template.HTML(
@@ -132,6 +125,31 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err
cfg.TableOfContents.Ordered,
),
)
}
// If the converter supports doing the parsing separately, we do that.
parseResult, ok, err := po.contentRenderer.ParseContent(ctx, cp.workContent)
if err != nil {
return err
}
if ok {
// This is Goldmark.
// Store away the parse result for later use.
createAndSetToC(parseResult)
cp.astDoc = parseResult.Doc()
return nil
}
// This is Asciidoctor etc.
r, err := po.contentRenderer.ParseAndRenderContent(ctx, cp.workContent, true)
if err != nil {
return err
}
cp.workContent = r.Bytes()
if tocProvider, ok := r.(converter.TableOfContentsProvider); ok {
createAndSetToC(tocProvider)
} else {
tmpContent, tmpTableOfContents := helpers.ExtractTOC(cp.workContent)
cp.tableOfContentsHTML = helpers.BytesToHTML(tmpTableOfContents)
@@ -153,6 +171,19 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err
return nil
}
if cp.astDoc != nil {
// The content is parsed, but not rendered.
r, ok, err := po.contentRenderer.RenderContent(ctx, cp.workContent, cp.astDoc)
if err != nil {
return err
}
if !ok {
return errors.New("invalid state: astDoc is set but RenderContent returned false")
}
cp.workContent = r.Bytes()
}
if p.cmap.hasNonMarkdownShortcode || cp.placeholdersEnabled {
// There are one or more replacement tokens to be replaced.
var hasShortcodeVariants bool
@@ -210,7 +241,7 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err
}
}
} else if cp.p.m.summary != "" {
b, err := po.contentRenderer.RenderContent(ctx, []byte(cp.p.m.summary), false)
b, err := po.contentRenderer.ParseAndRenderContent(ctx, []byte(cp.p.m.summary), false)
if err != nil {
return err
}
@@ -282,6 +313,8 @@ type pageContentOutput struct {
summary template.HTML
tableOfContents *tableofcontents.Fragments
tableOfContentsHTML template.HTML
// For Goldmark we split Parse and Render.
astDoc any
truncated bool
@@ -682,15 +715,66 @@ func (p *pageContentOutput) setAutoSummary() error {
return nil
}
func (cp *pageContentOutput) RenderContent(ctx context.Context, content []byte, renderTOC bool) (converter.Result, error) {
func (cp *pageContentOutput) getContentConverter() (converter.Converter, error) {
if err := cp.initRenderHooks(); err != nil {
return nil, err
}
c := cp.p.getContentConverter()
return cp.p.getContentConverter(), nil
}
func (cp *pageContentOutput) ParseAndRenderContent(ctx context.Context, content []byte, renderTOC bool) (converter.ResultRender, error) {
c, err := cp.getContentConverter()
if err != nil {
return nil, err
}
return cp.renderContentWithConverter(ctx, c, content, renderTOC)
}
func (cp *pageContentOutput) renderContentWithConverter(ctx context.Context, c converter.Converter, content []byte, renderTOC bool) (converter.Result, error) {
func (cp *pageContentOutput) ParseContent(ctx context.Context, content []byte) (converter.ResultParse, bool, error) {
c, err := cp.getContentConverter()
if err != nil {
return nil, false, err
}
p, ok := c.(converter.ParseRenderer)
if !ok {
return nil, ok, nil
}
rctx := converter.RenderContext{
Src: content,
RenderTOC: true,
GetRenderer: cp.renderHooks.getRenderer,
}
r, err := p.Parse(rctx)
return r, ok, err
}
func (cp *pageContentOutput) RenderContent(ctx context.Context, content []byte, doc any) (converter.ResultRender, bool, error) {
c, err := cp.getContentConverter()
if err != nil {
return nil, false, err
}
p, ok := c.(converter.ParseRenderer)
if !ok {
return nil, ok, nil
}
rctx := converter.RenderContext{
Src: content,
RenderTOC: true,
GetRenderer: cp.renderHooks.getRenderer,
}
r, err := p.Render(rctx, doc)
if err == nil {
if ids, ok := r.(identity.IdentitiesProvider); ok {
for _, v := range ids.GetIdentities() {
cp.trackDependency(v)
}
}
}
return r, ok, err
}
func (cp *pageContentOutput) renderContentWithConverter(ctx context.Context, c converter.Converter, content []byte, renderTOC bool) (converter.ResultRender, error) {
r, err := c.Convert(
converter.RenderContext{
Src: content,