diff --git a/hugolib/page.go b/hugolib/page.go index dd8d9445594..d5055e7c11e 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -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 } @@ -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) } @@ -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 diff --git a/resources/page/page_lazy_contentprovider.go b/resources/page/page_lazy_contentprovider.go new file mode 100644 index 00000000000..9979856f8c5 --- /dev/null +++ b/resources/page/page_lazy_contentprovider.go @@ -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() +}