summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2022-01-27 09:46:51 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2022-01-27 11:51:13 +0100
commitf22c4aba047e89130bf9921c5ded3823743a9ffa (patch)
treec8ab595f87d320359d3c80236fd9ac6d619817ae
parent85d31f7bfb7a13c9ab7655829a315a820dc1b403 (diff)
Make the RenderString content provider fix more general
Updates #9383
-rw-r--r--hugolib/page.go102
-rw-r--r--hugolib/page__common.go1
-rw-r--r--hugolib/page__new.go1
-rw-r--r--hugolib/page__output.go60
-rw-r--r--hugolib/page__per_output.go144
-rw-r--r--hugolib/page_test.go3
-rw-r--r--resources/page/page_lazy_contentprovider.go34
7 files changed, 178 insertions, 167 deletions
diff --git a/hugolib/page.go b/hugolib/page.go
index d5055e7c1..11b41e169 100644
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -16,15 +16,12 @@ package hugolib
import (
"bytes"
"fmt"
- "html/template"
"os"
"path"
"path/filepath"
"sort"
"strings"
- "github.com/mitchellh/mapstructure"
-
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/markup/converter"
@@ -47,7 +44,6 @@ import (
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/source"
- "github.com/spf13/cast"
"github.com/gohugoio/hugo/common/collections"
"github.com/gohugoio/hugo/common/text"
@@ -593,76 +589,6 @@ var defaultRenderStringOpts = renderStringOpts{
Markup: "", // Will inherit the page's value when not set.
}
-func (p *pageState) RenderString(args ...interface{}) (template.HTML, error) {
- if len(args) < 1 || len(args) > 2 {
- return "", errors.New("want 1 or 2 arguments")
- }
-
- var s string
- opts := defaultRenderStringOpts
- sidx := 1
-
- if len(args) == 1 {
- sidx = 0
- } else {
- m, ok := args[0].(map[string]interface{})
- if !ok {
- return "", errors.New("first argument must be a map")
- }
-
- if err := mapstructure.WeakDecode(m, &opts); err != nil {
- return "", errors.WithMessage(err, "failed to decode options")
- }
- }
-
- var err error
- s, err = cast.ToStringE(args[sidx])
- if err != nil {
- return "", err
- }
-
- if err = p.pageOutput.initRenderHooks(); err != nil {
- return "", err
- }
-
- conv := p.getContentConverter()
- if opts.Markup != "" && opts.Markup != p.m.markup {
- var err error
- // TODO(bep) consider cache
- conv, err = p.m.newContentConverter(p, opts.Markup, nil)
- if err != nil {
- return "", p.wrapError(err)
- }
- }
-
- 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)
- }
-
- b := c.Bytes()
-
- if opts.Display == "inline" {
- // We may have to rethink this in the future when we get other
- // renderers.
- b = p.s.ContentSpec.TrimShortHTML(b)
- }
-
- return template.HTML(string(b)), nil
-}
-
func (p *pageState) addDependency(dep identity.Provider) {
if !p.s.running() || p.pageOutput.cp == nil {
return
@@ -670,29 +596,6 @@ func (p *pageState) addDependency(dep identity.Provider) {
p.pageOutput.cp.dependencyTracker.Add(dep)
}
-func (p *pageState) RenderWithTemplateInfo(info tpl.Info, layout ...string) (template.HTML, error) {
- p.addDependency(info)
- return p.Render(layout...)
-}
-
-func (p *pageState) Render(layout ...string) (template.HTML, error) {
- templ, found, err := p.resolveTemplate(layout...)
- if err != nil {
- return "", p.wrapError(err)
- }
-
- if !found {
- return "", nil
- }
-
- p.addDependency(templ.(tpl.Info))
- res, err := executeToString(p.s.Tmpl(), templ, p)
- if err != nil {
- return "", p.wrapError(errors.Wrapf(err, "failed to execute template %q v", layout))
- }
- return template.HTML(res), nil
-}
-
// wrapError adds some more context to the given error if possible/needed
func (p *pageState) wrapError(err error) error {
if _, ok := err.(*herrors.ErrorWithFileContext); ok {
@@ -993,13 +896,16 @@ func (p *pageState) shiftToOutputFormat(isRenderingSite bool, idx int) error {
if lcp, ok := (p.pageOutput.ContentProvider.(*page.LazyContentProvider)); ok {
lcp.Reset()
} else {
- p.pageOutput.ContentProvider = page.NewLazyContentProvider(func() (page.ContentProvider, error) {
+ lcp = page.NewLazyContentProvider(func() (page.OutputFormatContentProvider, error) {
cp, err := newPageContentOutput(p, p.pageOutput)
if err != nil {
return nil, err
}
return cp, nil
})
+ p.pageOutput.ContentProvider = lcp
+ p.pageOutput.TableOfContentsProvider = lcp
+ p.pageOutput.PageRenderProvider = lcp
}
}
diff --git a/hugolib/page__common.go b/hugolib/page__common.go
index e718721f7..ccef79a3f 100644
--- a/hugolib/page__common.go
+++ b/hugolib/page__common.go
@@ -64,7 +64,6 @@ type pageCommon struct {
maps.Scratcher
navigation.PageMenusProvider
page.AuthorProvider
- page.PageRenderProvider
page.AlternativeOutputFormatsProvider
page.ChildCareProvider
page.FileProvider
diff --git a/hugolib/page__new.go b/hugolib/page__new.go
index 8c96d5014..17bdb30ff 100644
--- a/hugolib/page__new.go
+++ b/hugolib/page__new.go
@@ -86,7 +86,6 @@ func newPageBase(metaProvider *pageMeta) (*pageState, error) {
ps.Eqer = ps
ps.TranslationKeyProvider = ps
ps.ShortcodeInfoProvider = ps
- ps.PageRenderProvider = ps
ps.AlternativeOutputFormatsProvider = ps
return ps, nil
diff --git a/hugolib/page__output.go b/hugolib/page__output.go
index 377e16df5..413323477 100644
--- a/hugolib/page__output.go
+++ b/hugolib/page__output.go
@@ -14,7 +14,6 @@
package hugolib
import (
- "github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/resource"
@@ -59,6 +58,7 @@ func newPageOutput(
pagePerOutputProviders: providers,
ContentProvider: page.NopPage,
TableOfContentsProvider: page.NopPage,
+ PageRenderProvider: page.NopPage,
render: render,
paginator: pag,
}
@@ -84,73 +84,19 @@ type pageOutput struct {
pagePerOutputProviders
page.ContentProvider
page.TableOfContentsProvider
+ page.PageRenderProvider
// May be nil.
cp *pageContentOutput
}
-func (o *pageOutput) initRenderHooks() error {
- if o.cp == nil {
- return nil
- }
-
- var initErr error
-
- o.cp.renderHooks.init.Do(func() {
- ps := o.cp.p
-
- c := ps.getContentConverter()
- if c == nil || !c.Supports(converter.FeatureRenderHooks) {
- return
- }
-
- h, err := ps.createRenderHooks(o.f)
- if err != nil {
- initErr = err
- return
- }
- o.cp.renderHooks.hooks = h
-
- if !o.cp.renderHooksHaveVariants || h.IsZero() {
- // Check if there is a different render hooks template
- // for any of the other page output formats.
- // If not, we can reuse this.
- for _, po := range ps.pageOutputs {
- if po.f.Name != o.f.Name {
- h2, err := ps.createRenderHooks(po.f)
- if err != nil {
- initErr = err
- return
- }
-
- if h2.IsZero() {
- continue
- }
-
- if o.cp.renderHooks.hooks.IsZero() {
- o.cp.renderHooks.hooks = h2
- }
-
- o.cp.renderHooksHaveVariants = !h2.Eq(o.cp.renderHooks.hooks)
-
- if o.cp.renderHooksHaveVariants {
- break
- }
-
- }
- }
- }
- })
-
- return initErr
-}
-
func (p *pageOutput) initContentProvider(cp *pageContentOutput) {
if cp == nil {
return
}
p.ContentProvider = cp
p.TableOfContentsProvider = cp
+ p.PageRenderProvider = cp
p.cp = cp
}
diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go
index f59b5f9b5..bd4e35a5b 100644
--- a/hugolib/page__per_output.go
+++ b/hugolib/page__per_output.go
@@ -24,6 +24,9 @@ import (
"unicode/utf8"
"github.com/gohugoio/hugo/identity"
+ "github.com/mitchellh/mapstructure"
+ "github.com/pkg/errors"
+ "github.com/spf13/cast"
"github.com/gohugoio/hugo/markup/converter/hooks"
@@ -94,7 +97,7 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err
}
}()
- if err := po.initRenderHooks(); err != nil {
+ if err := po.cp.initRenderHooks(); err != nil {
return err
}
@@ -349,6 +352,145 @@ func (p *pageContentOutput) WordCount() int {
return p.wordCount
}
+func (p *pageContentOutput) RenderString(args ...interface{}) (template.HTML, error) {
+ if len(args) < 1 || len(args) > 2 {
+ return "", errors.New("want 1 or 2 arguments")
+ }
+
+ var s string
+ opts := defaultRenderStringOpts
+ sidx := 1
+
+ if len(args) == 1 {
+ sidx = 0
+ } else {
+ m, ok := args[0].(map[string]interface{})
+ if !ok {
+ return "", errors.New("first argument must be a map")
+ }
+
+ if err := mapstructure.WeakDecode(m, &opts); err != nil {
+ return "", errors.WithMessage(err, "failed to decode options")
+ }
+ }
+
+ var err error
+ s, err = cast.ToStringE(args[sidx])
+ if err != nil {
+ return "", err
+ }
+
+ if err = p.initRenderHooks(); err != nil {
+ return "", err
+ }
+
+ conv := p.p.getContentConverter()
+ if opts.Markup != "" && opts.Markup != p.p.m.markup {
+ var err error
+ // TODO(bep) consider cache
+ conv, err = p.p.m.newContentConverter(p.p, opts.Markup, nil)
+ if err != nil {
+ return "", p.p.wrapError(err)
+ }
+ }
+
+ c, err := p.renderContentWithConverter(conv, []byte(s), false)
+ if err != nil {
+ return "", p.p.wrapError(err)
+ }
+
+ b := c.Bytes()
+
+ if opts.Display == "inline" {
+ // We may have to rethink this in the future when we get other
+ // renderers.
+ b = p.p.s.ContentSpec.TrimShortHTML(b)
+ }
+
+ return template.HTML(string(b)), nil
+}
+
+func (p *pageContentOutput) RenderWithTemplateInfo(info tpl.Info, layout ...string) (template.HTML, error) {
+ p.p.addDependency(info)
+ return p.Render(layout...)
+}
+
+func (p *pageContentOutput) Render(layout ...string) (template.HTML, error) {
+ templ, found, err := p.p.resolveTemplate(layout...)
+ if err != nil {
+ return "", p.p.wrapError(err)
+ }
+
+ if !found {
+ return "", nil
+ }
+
+ p.p.addDependency(templ.(tpl.Info))
+
+ // Make sure to send the *pageState and not the *pageContentOutput to the template.
+ res, err := executeToString(p.p.s.Tmpl(), templ, p.p)
+ if err != nil {
+ return "", p.p.wrapError(errors.Wrapf(err, "failed to execute template %q v", layout))
+ }
+ return template.HTML(res), nil
+}
+
+func (p *pageContentOutput) initRenderHooks() error {
+ if p == nil {
+ return nil
+ }
+
+ var initErr error
+
+ p.renderHooks.init.Do(func() {
+ ps := p.p
+
+ c := ps.getContentConverter()
+ if c == nil || !c.Supports(converter.FeatureRenderHooks) {
+ return
+ }
+
+ h, err := ps.createRenderHooks(p.f)
+ if err != nil {
+ initErr = err
+ return
+ }
+ p.renderHooks.hooks = h
+
+ if !p.renderHooksHaveVariants || h.IsZero() {
+ // Check if there is a different render hooks template
+ // for any of the other page output formats.
+ // If not, we can reuse this.
+ for _, po := range ps.pageOutputs {
+ if po.f.Name != p.f.Name {
+ h2, err := ps.createRenderHooks(po.f)
+ if err != nil {
+ initErr = err
+ return
+ }
+
+ if h2.IsZero() {
+ continue
+ }
+
+ if p.renderHooks.hooks.IsZero() {
+ p.renderHooks.hooks = h2
+ }
+
+ p.renderHooksHaveVariants = !h2.Eq(p.renderHooks.hooks)
+
+ if p.renderHooksHaveVariants {
+ break
+ }
+
+ }
+ }
+ }
+ })
+
+ return initErr
+}
+
func (p *pageContentOutput) setAutoSummary() error {
if p.p.source.hasSummaryDivider || p.p.m.summary != "" {
return nil
diff --git a/hugolib/page_test.go b/hugolib/page_test.go
index 48a81ee4a..5e89278e1 100644
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -772,7 +772,7 @@ Here is the last report for commits in the year 2016. It covers hrev50718-hrev50
func TestRenderStringForRegularPageTranslations(t *testing.T) {
c := qt.New(t)
b := newTestSitesBuilder(t)
- b.WithLogger(loggers.NewBasicLoggerForWriter(jwalterweatherman.LevelDebug, os.Stderr))
+ b.WithLogger(loggers.NewBasicLoggerForWriter(jwalterweatherman.LevelError, os.Stderr))
b.WithConfigFile("toml",
`baseurl = "https://example.org/"
@@ -828,7 +828,6 @@ home = ["HTML", "JSON"]`)
<p>bar</p>
<p>bar</p>
`)
-
}
// Issue 8919
diff --git a/resources/page/page_lazy_contentprovider.go b/resources/page/page_lazy_contentprovider.go
index 9979856f8..206695d18 100644
--- a/resources/page/page_lazy_contentprovider.go
+++ b/resources/page/page_lazy_contentprovider.go
@@ -19,6 +19,16 @@ import (
"github.com/gohugoio/hugo/lazy"
)
+// OutputFormatContentProvider represents the method set that is "outputFormat aware" and that we
+// provide lazy initialization for in case they get invoked outside of their normal rendering context, e.g. via .Translations.
+// Note that this set is currently not complete, but should cover the most common use cases.
+// For the others, the implementation will be from the page.NoopPage.
+type OutputFormatContentProvider interface {
+ ContentProvider
+ TableOfContentsProvider
+ PageRenderProvider
+}
+
// LazyContentProvider initializes itself when read. Each method of the
// ContentProvider interface initializes a content provider and shares it
// with other methods.
@@ -27,13 +37,13 @@ import (
// will be needed. Must create via NewLazyContentProvider.
type LazyContentProvider struct {
init *lazy.Init
- cp ContentProvider
+ cp OutputFormatContentProvider
}
// 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 {
+func NewLazyContentProvider(f func() (OutputFormatContentProvider, error)) *LazyContentProvider {
lcp := LazyContentProvider{
init: lazy.New(),
cp: NopPage,
@@ -49,11 +59,6 @@ func NewLazyContentProvider(f func() (ContentProvider, error)) *LazyContentProvi
return &lcp
}
-func (lcp *LazyContentProvider) Init() ContentProvider {
- lcp.init.Do()
- return lcp.cp
-}
-
func (lcp *LazyContentProvider) Reset() {
lcp.init.Reset()
}
@@ -102,3 +107,18 @@ func (lcp *LazyContentProvider) Len() int {
lcp.init.Do()
return lcp.cp.Len()
}
+
+func (lcp *LazyContentProvider) Render(layout ...string) (template.HTML, error) {
+ lcp.init.Do()
+ return lcp.cp.Render(layout...)
+}
+
+func (lcp *LazyContentProvider) RenderString(args ...interface{}) (template.HTML, error) {
+ lcp.init.Do()
+ return lcp.cp.RenderString(args...)
+}
+
+func (lcp *LazyContentProvider) TableOfContents() template.HTML {
+ lcp.init.Do()
+ return lcp.cp.TableOfContents()
+}