Skip to content

Commit

Permalink
Initialize LazyContentProvider in RenderString
Browse files Browse the repository at this point in the history
This introduces the *LazyContentProvider.Init method, which initializes
and returns a *LazyContentProvider.cp. This allows RenderString--and
any other *pageState methods we need to call within a template--to
initialize content providers just in time without having to modify
the *pageState itself while rendering a specific Page.
  • Loading branch information
ptgott committed Jan 18, 2022
1 parent ed31745 commit 10118f4
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 39 deletions.
70 changes: 31 additions & 39 deletions hugolib/page.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,50 +369,12 @@ func (p *pageState) TranslationKey() string {
// AllTranslations returns all translations, including the current Page.
func (p *pageState) AllTranslations() page.Pages {
p.s.h.init.translations.Do()

// Hugo attempts to reuse content providers while preparing each page for
// rendering. This approach means that sometimes, content providers for
// translations are not initialized. If needed, initialize them here.
for _, tr := range p.allTranslations {
tp, ok := tr.(*pageState)
if !ok {
continue
}
if tp.pageOutput.cp == nil {
cp, err := newPageContentOutput(tp, tp.pageOutput)
if err != nil {
// This will be caught by texttemplate.safeCall
panic(fmt.Errorf("error rendering page translation: %w", err))
}
tp.initContentProvider(cp)
}
}

return p.allTranslations
}

// Translations returns the translations excluding the current Page.
func (p *pageState) Translations() page.Pages {
p.s.h.init.translations.Do()

// Hugo attempts to reuse content providers while preparing each page for
// rendering. This approach means that sometimes, content providers for
// translations are not initialized. If needed, initialize them here.
for _, tr := range p.translations {
tp, ok := tr.(*pageState)
if !ok {
continue
}
if tp.pageOutput.cp == nil {
cp, err := newPageContentOutput(tp, tp.pageOutput)
if err != nil {
// This will be caught by texttemplate.safeCall
panic(fmt.Errorf("error rendering page translation: %w", err))
}
tp.initContentProvider(cp)
}
}

return p.translations
}

Expand Down Expand Up @@ -673,7 +635,19 @@ func (p *pageState) RenderString(args ...interface{}) (template.HTML, error) {
}
}

c, err := p.pageOutput.cp.renderContentWithConverter(conv, []byte(s), false)
var cp *pageContentOutput

// If the current content provider is not yet initialized, do so now.
if lcp, ok := p.pageOutput.ContentProvider.(*page.LazyContentProvider); ok {
c := lcp.Init()
if pco, ok := c.(*pageContentOutput); ok {
cp = pco
}
} else {
cp = p.pageOutput.cp
}

c, err := cp.renderContentWithConverter(conv, []byte(s), false)
if err != nil {
return "", p.wrapError(err)
}
Expand Down Expand Up @@ -1009,6 +983,24 @@ func (p *pageState) shiftToOutputFormat(isRenderingSite bool, idx int) error {
}
}
p.pageOutput.initContentProvider(cp)
} else {
// We attempt to assign pageContentOutputs while preparing each site
// for rendering and before rendering each site. This lets us share
// content between page outputs to conserve resources. But if a template
// unexpectedly calls a method of a ContentProvider that is not yet
// initialized, we assign a LazyContentProvider that performs the
// initialization just in time.
if lcp, ok := (p.pageOutput.ContentProvider.(*page.LazyContentProvider)); ok {
lcp.Reset()
} else {
p.pageOutput.ContentProvider = page.NewLazyContentProvider(func() (page.ContentProvider, error) {
cp, err := newPageContentOutput(p, p.pageOutput)
if err != nil {
return nil, err
}
return cp, nil
})
}
}

return nil
Expand Down
104 changes: 104 additions & 0 deletions resources/page/page_lazy_contentprovider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package page

import (
"html/template"

"github.com/gohugoio/hugo/lazy"
)

// LazyContentProvider initializes itself when read. Each method of the
// ContentProvider interface initializes a content provider and shares it
// with other methods.
//
// Used in cases where we cannot guarantee whether the content provider
// will be needed. Must create via NewLazyContentProvider.
type LazyContentProvider struct {
init *lazy.Init
cp ContentProvider
}

// NewLazyContentProvider returns a LazyContentProvider initialized with
// function f. The resulting LazyContentProvider calls f in order to
// retrieve a ContentProvider
func NewLazyContentProvider(f func() (ContentProvider, error)) *LazyContentProvider {
lcp := LazyContentProvider{
init: lazy.New(),
cp: NopPage,
}
lcp.init.Add(func() (interface{}, error) {
cp, err := f()
if err != nil {
return nil, err
}
lcp.cp = cp
return nil, nil
})
return &lcp
}

func (lcp *LazyContentProvider) Init() ContentProvider {
lcp.init.Do()
return lcp.cp
}

func (lcp *LazyContentProvider) Reset() {
lcp.init.Reset()
}

func (lcp *LazyContentProvider) Content() (interface{}, error) {
lcp.init.Do()
return lcp.cp.Content()
}

func (lcp *LazyContentProvider) Plain() string {
lcp.init.Do()
return lcp.cp.Plain()
}

func (lcp *LazyContentProvider) PlainWords() []string {
lcp.init.Do()
return lcp.cp.PlainWords()
}

func (lcp *LazyContentProvider) Summary() template.HTML {
lcp.init.Do()
return lcp.cp.Summary()
}

func (lcp *LazyContentProvider) Truncated() bool {
lcp.init.Do()
return lcp.cp.Truncated()
}

func (lcp *LazyContentProvider) FuzzyWordCount() int {
lcp.init.Do()
return lcp.cp.FuzzyWordCount()
}

func (lcp *LazyContentProvider) WordCount() int {
lcp.init.Do()
return lcp.cp.WordCount()
}

func (lcp *LazyContentProvider) ReadingTime() int {
lcp.init.Do()
return lcp.cp.ReadingTime()
}

func (lcp *LazyContentProvider) Len() int {
lcp.init.Do()
return lcp.cp.Len()
}

0 comments on commit 10118f4

Please sign in to comment.