diff --git a/hugolib/page__output.go b/hugolib/page__output.go
index 377e16df522..7f1140e74d8 100644
--- a/hugolib/page__output.go
+++ b/hugolib/page__output.go
@@ -55,9 +55,21 @@ func newPageOutput(
}
po := &pageOutput{
- f: f,
- pagePerOutputProviders: providers,
- ContentProvider: page.NopPage,
+ f: f,
+ pagePerOutputProviders: providers,
+ // We attempt to assign pageContentOutputs while preparing each site
+ // for rendering and before rendering each site. This lets us share
+ // content between page outputs and conserve resources. However, this
+ // does not guarantee that all pageContentOutputs required by templates
+ // will be assigned. In the case of an unanticipated request, we lazily
+ // initialize a pageContentOutput.
+ ContentProvider: page.NewLazyContentProvider(func() (page.ContentProvider, error) {
+ cp, err := newPageContentOutput(ps, ps.pageOutput)
+ if err != nil {
+ return nil, err
+ }
+ return cp, nil
+ }),
TableOfContentsProvider: page.NopPage,
render: render,
paginator: pag,
diff --git a/hugolib/page_test.go b/hugolib/page_test.go
index 7d55787c8e3..fc01bbf25eb 100644
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -36,6 +36,7 @@ import (
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/resource"
"github.com/spf13/afero"
+ "github.com/spf13/jwalterweatherman"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/deps"
@@ -767,6 +768,184 @@ Here is the last report for commits in the year 2016. It covers hrev50718-hrev50
`)
}
+// Issue 8919
+func TestContentProviderWithCustomOutputFormat(t *testing.T) {
+ b := newTestSitesBuilder(t)
+ b.WithLogger(loggers.NewBasicLoggerForWriter(jwalterweatherman.LevelDebug, os.Stderr))
+ b.WithConfigFile("toml", `baseURL = 'http://example.org/'
+title = 'My New Hugo Site'
+
+timeout = 600000 # ten minutes in case we want to pause and debug
+
+defaultContentLanguage = "en"
+
+[languages]
+ [languages.en]
+ title = "Repro"
+ languageName = "English"
+ contentDir = "content/en"
+
+ [languages.zh_CN]
+ title = "Repro"
+ languageName = "简体中文"
+ contentDir = "content/zh_CN"
+
+[outputFormats]
+ [outputFormats.metadata]
+ baseName = "metadata"
+ mediaType = "text/html"
+ isPlainText = true
+ notAlternative = true
+
+[outputs]
+ home = ["HTML", "metadata"]`)
+
+ b.WithTemplates("home.metadata.html", `
Translations metadata
+
+{{ $p := .Page }}
+{{ range $p.Translations}}
+- Title: {{ .Title }}, {{ .Summary }}
+- Content: {{ .Content }}
+- Plain: {{ .Plain }}
+- PlainWords: {{ .PlainWords }}
+- Summary: {{ .Summary }}
+- Truncated: {{ .Truncated }}
+- FuzzyWordCount: {{ .FuzzyWordCount }}
+- ReadingTime: {{ .ReadingTime }}
+- Len: {{ .Len }}
+{{ end }}
+
`)
+
+ b.WithTemplates("_default/baseof.html", `
+
+
+ {{ block "main" . }}{{ end }}
+
+
+`)
+
+ b.WithTemplates("_default/home.html", `{{ define "main" }}
+Translations
+
+{{ $p := .Page }}
+{{ range $p.Translations}}
+- Title: {{ .Title }}, {{ .Summary }}
+- Content: {{ .Content }}
+- Plain: {{ .Plain }}
+- PlainWords: {{ .PlainWords }}
+- Summary: {{ .Summary }}
+- Truncated: {{ .Truncated }}
+- FuzzyWordCount: {{ .FuzzyWordCount }}
+- ReadingTime: {{ .ReadingTime }}
+- Len: {{ .Len }}
+{{ end }}
+
+{{ end }}`)
+
+ b.WithContent("en/_index.md", `---
+title: Title (en)
+summary: Summary (en)
+---
+
+Here is some content.
+`)
+
+ b.WithContent("zh_CN/_index.md", `---
+title: Title (zh)
+summary: Summary (zh)
+---
+
+这是一些内容
+`)
+
+ b.Build(BuildCfg{})
+
+ b.AssertFileContent("public/index.html", `
+
+
+
+Translations
+
+
+
+- Title: Title (zh), Summary (zh)
+- Content:
这是一些内容
+
+- Plain: 这是一些内容
+
+- PlainWords: [这是一些内容]
+- Summary: Summary (zh)
+- Truncated: false
+- FuzzyWordCount: 100
+- ReadingTime: 1
+- Len: 26
+
+
+
+
+
+`)
+ b.AssertFileContent("public/metadata.html", `Translations metadata
+
+
+
+- Title: Title (zh), Summary (zh)
+- Content:
这是一些内容
+
+- Plain: 这是一些内容
+
+- PlainWords: [这是一些内容]
+- Summary: Summary (zh)
+- Truncated: false
+- FuzzyWordCount: 100
+- ReadingTime: 1
+- Len: 26
+
+
`)
+ b.AssertFileContent("public/zh_cn/index.html", `
+
+
+
+Translations
+
+
+
+- Title: Title (en), Summary (en)
+- Content:
Here is some content.
+
+- Plain: Here is some content.
+
+- PlainWords: [Here is some content.]
+- Summary: Summary (en)
+- Truncated: false
+- FuzzyWordCount: 100
+- ReadingTime: 1
+- Len: 29
+
+
+
+
+
+`)
+ b.AssertFileContent("public/zh_cn/metadata.html", `Translations metadata
+
+
+
+- Title: Title (en), Summary (en)
+- Content:
Here is some content.
+
+- Plain: Here is some content.
+
+- PlainWords: [Here is some content.]
+- Summary: Summary (en)
+- Truncated: false
+- FuzzyWordCount: 100
+- ReadingTime: 1
+- Len: 29
+
+
`)
+}
+
func TestPageWithDate(t *testing.T) {
t.Parallel()
cfg, fs := newTestCfg()
diff --git a/resources/page/page_lazy_contentprovider.go b/resources/page/page_lazy_contentprovider.go
new file mode 100644
index 00000000000..d8d92d7cdc4
--- /dev/null
+++ b/resources/page/page_lazy_contentprovider.go
@@ -0,0 +1,101 @@
+// 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) 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()
+
+}