diff options
-rw-r--r-- | hugolib/page.go | 14 | ||||
-rw-r--r-- | hugolib/page_test.go | 179 | ||||
-rw-r--r-- | resources/page/page_lazy_contentprovider.go | 101 |
3 files changed, 294 insertions, 0 deletions
diff --git a/hugolib/page.go b/hugolib/page.go index d2d962044..509a083e8 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -940,6 +940,20 @@ func (p *pageState) shiftToOutputFormat(isRenderingSite bool, idx int) error { panic(fmt.Sprintf("pageOutput is nil for output idx %d", idx)) } + // 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. + p.pageOutput.ContentProvider = page.NewLazyContentProvider(func() (page.ContentProvider, error) { + cp, err := newPageContentOutput(p, p.pageOutput) + if err != nil { + return nil, err + } + return cp, nil + }) + // Reset any built paginator. This will trigger when re-rendering pages in // server mode. if isRenderingSite && p.pageOutput.paginator != nil && p.pageOutput.paginator.current != nil { diff --git a/hugolib/page_test.go b/hugolib/page_test.go index 7d55787c8..fc01bbf25 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", `<h2>Translations metadata</h2> +<ul> +{{ $p := .Page }} +{{ range $p.Translations}} +<li>Title: {{ .Title }}, {{ .Summary }}</li> +<li>Content: {{ .Content }}</li> +<li>Plain: {{ .Plain }}</li> +<li>PlainWords: {{ .PlainWords }}</li> +<li>Summary: {{ .Summary }}</li> +<li>Truncated: {{ .Truncated }}</li> +<li>FuzzyWordCount: {{ .FuzzyWordCount }}</li> +<li>ReadingTime: {{ .ReadingTime }}</li> +<li>Len: {{ .Len }}</li> +{{ end }} +</ul>`) + + b.WithTemplates("_default/baseof.html", `<html> + +<body> + {{ block "main" . }}{{ end }} +</body> + +</html>`) + + b.WithTemplates("_default/home.html", `{{ define "main" }} +<h2>Translations</h2> +<ul> +{{ $p := .Page }} +{{ range $p.Translations}} +<li>Title: {{ .Title }}, {{ .Summary }}</li> +<li>Content: {{ .Content }}</li> +<li>Plain: {{ .Plain }}</li> +<li>PlainWords: {{ .PlainWords }}</li> +<li>Summary: {{ .Summary }}</li> +<li>Truncated: {{ .Truncated }}</li> +<li>FuzzyWordCount: {{ .FuzzyWordCount }}</li> +<li>ReadingTime: {{ .ReadingTime }}</li> +<li>Len: {{ .Len }}</li> +{{ end }} +</ul> +{{ 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", `<html> + +<body> + +<h2>Translations</h2> +<ul> + + +<li>Title: Title (zh), Summary (zh)</li> +<li>Content: <p>这是一些内容</p> +</li> +<li>Plain: 这是一些内容 +</li> +<li>PlainWords: [这是一些内容]</li> +<li>Summary: Summary (zh)</li> +<li>Truncated: false</li> +<li>FuzzyWordCount: 100</li> +<li>ReadingTime: 1</li> +<li>Len: 26</li> + +</ul> + +</body> + +</html>`) + b.AssertFileContent("public/metadata.html", `<h2>Translations metadata</h2> +<ul> + + +<li>Title: Title (zh), Summary (zh)</li> +<li>Content: <p>这是一些内容</p> +</li> +<li>Plain: 这是一些内容 +</li> +<li>PlainWords: [这是一些内容]</li> +<li>Summary: Summary (zh)</li> +<li>Truncated: false</li> +<li>FuzzyWordCount: 100</li> +<li>ReadingTime: 1</li> +<li>Len: 26</li> + +</ul>`) + b.AssertFileContent("public/zh_cn/index.html", `<html> + +<body> + +<h2>Translations</h2> +<ul> + + +<li>Title: Title (en), Summary (en)</li> +<li>Content: <p>Here is some content.</p> +</li> +<li>Plain: Here is some content. +</li> +<li>PlainWords: [Here is some content.]</li> +<li>Summary: Summary (en)</li> +<li>Truncated: false</li> +<li>FuzzyWordCount: 100</li> +<li>ReadingTime: 1</li> +<li>Len: 29</li> + +</ul> + +</body> + +</html>`) + b.AssertFileContent("public/zh_cn/metadata.html", `<h2>Translations metadata</h2> +<ul> + + +<li>Title: Title (en), Summary (en)</li> +<li>Content: <p>Here is some content.</p> +</li> +<li>Plain: Here is some content. +</li> +<li>PlainWords: [Here is some content.]</li> +<li>Summary: Summary (en)</li> +<li>Truncated: false</li> +<li>FuzzyWordCount: 100</li> +<li>ReadingTime: 1</li> +<li>Len: 29</li> + +</ul>`) +} + 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 000000000..d8d92d7cd --- /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() + +} |