From 7815dd94ff3a60ada828ddab8a988a6984aee4d7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?=
Date: Wed, 27 Nov 2019 13:42:36 +0100
Subject: [PATCH] Add render template hooks for links and images
This commit also revises the change detection for templates used by content files in server mode.
Fixes #6545
Fixes #4663
---
deps/deps.go | 15 +-
docs/content/en/content-management/formats.md | 8 +-
docs/layouts/_markup/render-link.html | 1 +
docs/layouts/partials/deleteme.html | 1 +
docs/layouts/shortcodes/deleteme.html | 3 +
go.mod | 2 +
helpers/content.go | 13 +-
helpers/content_test.go | 8 +-
helpers/general_test.go | 12 +-
helpers/testhelpers_test.go | 9 +-
hugolib/content_render_hooks_test.go | 92 +++++++++++
hugolib/filesystems/basefs.go | 46 +++++-
hugolib/hugo_modules_test.go | 3 +
hugolib/hugo_sites.go | 68 +++++++-
hugolib/hugo_sites_build.go | 2 +-
hugolib/page.go | 79 +++++++++
hugolib/page__meta.go | 16 +-
hugolib/page__new.go | 23 ++-
hugolib/page__output.go | 41 +++--
hugolib/page__per_output.go | 45 ++++--
hugolib/page_test.go | 6 -
hugolib/pagecollections.go | 10 --
hugolib/shortcode_page.go | 1 +
hugolib/site.go | 122 +++++++-------
hugolib/testhelpers_test.go | 9 +-
identity/identity.go | 120 ++++++++++++++
identity/identity_test.go | 42 +++++
markup/asciidoc/convert.go | 5 +
markup/blackfriday/convert.go | 5 +
markup/converter/converter.go | 13 +-
markup/converter/hooks/hooks.go | 54 +++++++
markup/goldmark/ast_hooks.go | 150 ++++++++++++++++++
.../{toc_test.go => ast_hooks_test.go} | 0
markup/goldmark/convert.go | 108 ++++++++++---
markup/goldmark/convert_test.go | 15 +-
markup/goldmark/render_link.go | 145 +++++++++++++++++
markup/goldmark/toc.go | 102 ------------
markup/mmark/convert.go | 5 +
markup/org/convert.go | 6 +
markup/pandoc/convert.go | 5 +
markup/rst/convert.go | 5 +
output/layout.go | 38 +++--
output/layout_test.go | 2 +
public/categories/index.xml | 13 ++
public/index.xml | 13 ++
public/sitemap.xml | 17 ++
public/tags/index.xml | 13 ++
tpl/template_info.go | 6 +
tpl/tplimpl/ace.go | 5 +-
tpl/tplimpl/template.go | 95 ++++++++---
tpl/tplimpl/template_ast_transformers.go | 111 ++++++++++---
tpl/tplimpl/template_ast_transformers_test.go | 30 ++--
tpl/tplimpl/template_funcs_test.go | 5 +
53 files changed, 1400 insertions(+), 363 deletions(-)
create mode 100644 docs/layouts/_markup/render-link.html
create mode 100644 docs/layouts/partials/deleteme.html
create mode 100644 docs/layouts/shortcodes/deleteme.html
create mode 100644 hugolib/content_render_hooks_test.go
create mode 100644 identity/identity.go
create mode 100644 identity/identity_test.go
create mode 100644 markup/converter/hooks/hooks.go
create mode 100644 markup/goldmark/ast_hooks.go
rename markup/goldmark/{toc_test.go => ast_hooks_test.go} (100%)
create mode 100644 markup/goldmark/render_link.go
delete mode 100644 markup/goldmark/toc.go
create mode 100644 public/categories/index.xml
create mode 100644 public/index.xml
create mode 100644 public/sitemap.xml
create mode 100644 public/tags/index.xml
diff --git a/deps/deps.go b/deps/deps.go
index d7b381ce92e..3236f7244c0 100644
--- a/deps/deps.go
+++ b/deps/deps.go
@@ -4,6 +4,7 @@ import (
"sync"
"time"
+ "github.com/gohugoio/hugo/markup/converter"
"github.com/pkg/errors"
"github.com/gohugoio/hugo/cache/filecache"
@@ -223,11 +224,6 @@ func New(cfg DepsCfg) (*Deps, error) {
return nil, err
}
- contentSpec, err := helpers.NewContentSpec(cfg.Language, logger, ps.BaseFs.Content.Fs)
- if err != nil {
- return nil, err
- }
-
sp := source.NewSourceSpec(ps, fs.Source)
timeoutms := cfg.Language.GetInt("timeout")
@@ -247,7 +243,6 @@ func New(cfg DepsCfg) (*Deps, error) {
translationProvider: cfg.TranslationProvider,
WithTemplate: cfg.WithTemplate,
PathSpec: ps,
- ContentSpec: contentSpec,
SourceSpec: sp,
ResourceSpec: resourceSpec,
Cfg: cfg.Language,
@@ -277,7 +272,13 @@ func (d Deps) ForLanguage(cfg DepsCfg, onCreated func(d *Deps) error) (*Deps, er
return nil, err
}
- d.ContentSpec, err = helpers.NewContentSpec(l, d.Log, d.BaseFs.Content.Fs)
+ d.ContentSpec, err = helpers.NewContentSpec(
+ converter.ProviderConfig{
+ Cfg: l,
+ Logger: d.Log,
+ ContentFs: d.BaseFs.Content.Fs,
+ },
+ )
if err != nil {
return nil, err
}
diff --git a/docs/content/en/content-management/formats.md b/docs/content/en/content-management/formats.md
index ea056861657..3704ed8b071 100644
--- a/docs/content/en/content-management/formats.md
+++ b/docs/content/en/content-management/formats.md
@@ -23,7 +23,13 @@ You can put any file type into your `/content` directories, but Hugo uses the `m
* [Shortcodes](/content-management/shortcodes/) processed
* Layout applied
-## List of content formats
+{{< deleteme >}}
+
+
+## List of content formats.
+
+
+
The current list of content formats in Hugo:
diff --git a/docs/layouts/_markup/render-link.html b/docs/layouts/_markup/render-link.html
new file mode 100644
index 00000000000..0df3929f6b6
--- /dev/null
+++ b/docs/layouts/_markup/render-link.html
@@ -0,0 +1 @@
+😉😉{{ .Text | safeHTML }} 😉😉
\ No newline at end of file
diff --git a/docs/layouts/partials/deleteme.html b/docs/layouts/partials/deleteme.html
new file mode 100644
index 00000000000..e4df2ce650f
--- /dev/null
+++ b/docs/layouts/partials/deleteme.html
@@ -0,0 +1 @@
+THIS IS PARTIAL!!!
\ No newline at end of file
diff --git a/docs/layouts/shortcodes/deleteme.html b/docs/layouts/shortcodes/deleteme.html
new file mode 100644
index 00000000000..b85b58fc1fc
--- /dev/null
+++ b/docs/layouts/shortcodes/deleteme.html
@@ -0,0 +1,3 @@
+DELETEME PARTIAL:
+
+{{ partial "deleteme" }}
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 787bb27d511..740ae91c4c8 100644
--- a/go.mod
+++ b/go.mod
@@ -74,4 +74,6 @@ require (
replace github.com/markbates/inflect => github.com/markbates/inflect v0.0.0-20171215194931-a12c3aec81a6
+replace github.com/yuin/goldmark => /Users/bep/dev/go/dump/goldmark
+
go 1.12
diff --git a/helpers/content.go b/helpers/content.go
index 4dc4cd413bd..4e5903ccd7a 100644
--- a/helpers/content.go
+++ b/helpers/content.go
@@ -23,15 +23,12 @@ import (
"unicode"
"unicode/utf8"
- "github.com/gohugoio/hugo/common/loggers"
-
"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/markup"
bp "github.com/gohugoio/hugo/bufferpool"
"github.com/gohugoio/hugo/config"
- "github.com/spf13/afero"
"strings"
)
@@ -62,7 +59,8 @@ type ContentSpec struct {
// NewContentSpec returns a ContentSpec initialized
// with the appropriate fields from the given config.Provider.
-func NewContentSpec(cfg config.Provider, logger *loggers.Logger, contentFs afero.Fs) (*ContentSpec, error) {
+func NewContentSpec(pcfg converter.ProviderConfig) (*ContentSpec, error) {
+ cfg := pcfg.Cfg
spec := &ContentSpec{
summaryLength: cfg.GetInt("summaryLength"),
@@ -73,11 +71,8 @@ func NewContentSpec(cfg config.Provider, logger *loggers.Logger, contentFs afero
Cfg: cfg,
}
- converterProvider, err := markup.NewConverterProvider(converter.ProviderConfig{
- Cfg: cfg,
- ContentFs: contentFs,
- Logger: logger,
- })
+ converterProvider, err := markup.NewConverterProvider(pcfg)
+
if err != nil {
return nil, err
}
diff --git a/helpers/content_test.go b/helpers/content_test.go
index 7f82abc9da0..268bb8e9aca 100644
--- a/helpers/content_test.go
+++ b/helpers/content_test.go
@@ -19,6 +19,8 @@ import (
"strings"
"testing"
+ "github.com/gohugoio/hugo/markup/converter"
+
"github.com/spf13/afero"
"github.com/gohugoio/hugo/common/loggers"
@@ -110,7 +112,11 @@ func TestNewContentSpec(t *testing.T) {
cfg.Set("buildExpired", true)
cfg.Set("buildDrafts", true)
- spec, err := NewContentSpec(cfg, loggers.NewErrorLogger(), afero.NewMemMapFs())
+ spec, err := NewContentSpec(converter.ProviderConfig{
+ Cfg: cfg,
+ Logger: loggers.NewErrorLogger(),
+ ContentFs: afero.NewMemMapFs(),
+ })
c.Assert(err, qt.IsNil)
c.Assert(spec.summaryLength, qt.Equals, 32)
diff --git a/helpers/general_test.go b/helpers/general_test.go
index 104a4c35def..8b8d531d165 100644
--- a/helpers/general_test.go
+++ b/helpers/general_test.go
@@ -21,16 +21,22 @@ import (
"github.com/spf13/viper"
- "github.com/gohugoio/hugo/common/loggers"
-
qt "github.com/frankban/quicktest"
+ "github.com/gohugoio/hugo/common/loggers"
+ "github.com/gohugoio/hugo/markup/converter"
"github.com/spf13/afero"
)
func TestResolveMarkup(t *testing.T) {
c := qt.New(t)
cfg := viper.New()
- spec, err := NewContentSpec(cfg, loggers.NewErrorLogger(), afero.NewMemMapFs())
+ spec, err := NewContentSpec(
+ converter.ProviderConfig{
+ Cfg: cfg,
+ Logger: loggers.NewErrorLogger(),
+ ContentFs: afero.NewMemMapFs(),
+ },
+ )
c.Assert(err, qt.IsNil)
for i, this := range []struct {
diff --git a/helpers/testhelpers_test.go b/helpers/testhelpers_test.go
index bf249059d76..fe3151d09a4 100644
--- a/helpers/testhelpers_test.go
+++ b/helpers/testhelpers_test.go
@@ -2,6 +2,7 @@ package helpers
import (
"github.com/gohugoio/hugo/common/loggers"
+ "github.com/gohugoio/hugo/markup/converter"
"github.com/spf13/afero"
"github.com/spf13/viper"
@@ -58,7 +59,13 @@ func newTestCfg() *viper.Viper {
func newTestContentSpec() *ContentSpec {
v := viper.New()
- spec, err := NewContentSpec(v, loggers.NewErrorLogger(), afero.NewMemMapFs())
+ spec, err := NewContentSpec(
+ converter.ProviderConfig{
+ Cfg: v,
+ Logger: loggers.NewErrorLogger(),
+ ContentFs: afero.NewMemMapFs(),
+ },
+ )
if err != nil {
panic(err)
}
diff --git a/hugolib/content_render_hooks_test.go b/hugolib/content_render_hooks_test.go
new file mode 100644
index 00000000000..bc6b6c7c9b5
--- /dev/null
+++ b/hugolib/content_render_hooks_test.go
@@ -0,0 +1,92 @@
+// 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 hugolib
+
+import "testing"
+
+func TestRenderHooks(t *testing.T) {
+ config := `
+baseURL="https://example.org"
+workingDir="/mywork"
+`
+ b := newTestSitesBuilder(t).WithWorkingDir("/mywork").WithConfigFile("toml", config).Running()
+ b.WithTemplatesAdded("_default/single.html", `{{ .Content }}`)
+ b.WithTemplatesAdded("shortcodes/myshortcode1.html", `{{ partial "mypartial1" }}`)
+ b.WithTemplatesAdded("shortcodes/myshortcode2.html", `{{ partial "mypartial2" }}`)
+ b.WithTemplatesAdded("shortcodes/myshortcode3.html", `SHORT3|`)
+ b.WithTemplatesAdded("partials/mypartial1.html", `PARTIAL1`)
+ b.WithTemplatesAdded("partials/mypartial2.html", `PARTIAL2 {{ partial "mypartial3.html" }}`)
+ b.WithTemplatesAdded("partials/mypartial3.html", `PARTIAL3`)
+ b.WithTemplatesAdded("_default/_markup/render-link.html", `{{ .Page.Title }}|{{ .Destination | safeURL }}|Title: {{ .Title | safeHTML }}|Text: {{ .Text | safeHTML }}|END`)
+ b.WithTemplatesAdded("docs/_markup/render-link.html", `Link docs section: {{ .Text | safeHTML }}|END`)
+
+ b.WithContent("blog/p1.md", `---
+title: Cool Page
+---
+
+[First Link](https://www.google.com "Google's Homepage")
+
+{{< myshortcode3 >}}
+
+[Second Link](https://www.google.com "Google's Homepage")
+
+
+`, "blog/p2.md", `---
+title: Cool Page2
+layout: mylayout
+---
+
+{{< myshortcode1 >}}
+
+[Some Text](https://www.google.com "Google's Homepage")
+
+
+
+`, "blog/p3.md", `---
+title: Cool Page3
+---
+
+{{< myshortcode2 >}}
+
+
+`, "docs/docs1.md", `---
+title: Docs 1
+---
+
+
+[Docs 1](https://www.google.com "Google's Homepage")
+
+
+`)
+ b.Build(BuildCfg{})
+ b.AssertFileContent("public/blog/p1/index.html", `Cool Page|https://www.google.com|Title: Google's Homepage|Text: First Link|END
`, `Text: Second`, "SHORT3|")
+ b.AssertFileContent("public/blog/p2/index.html", `PARTIAL`)
+ b.AssertFileContent("public/blog/p3/index.html", `PARTIAL3`)
+ b.AssertFileContent("public/docs/docs1/index.html", `Link docs section: Docs 1|END`)
+
+ b.EditFiles(
+ "layouts/_default/_markup/render-link.html", `EDITED: {{ .Destination | safeURL }}|`,
+ "layouts/docs/_markup/render-link.html", `DOCS EDITED: {{ .Destination | safeURL }}|`,
+ "layouts/partials/mypartial1.html", `PARTIAL1_EDITED`,
+ "layouts/partials/mypartial3.html", `PARTIAL3_EDITED`,
+ "layouts/shortcodes/myshortcode3.html", `SHORT3_EDITED|`,
+ )
+ b.Build(BuildCfg{})
+
+ b.AssertFileContent("public/blog/p1/index.html", `EDITED: https://www.google.com|
`, "SHORT3_EDITED|")
+ b.AssertFileContent("public/blog/p2/index.html", `PARTIAL1_EDITED`)
+ b.AssertFileContent("public/blog/p3/index.html", `PARTIAL3_EDITED`)
+ b.AssertFileContent("public/docs/docs1/index.html", `DOCS EDITED: https://www.google.com|
`)
+
+}
diff --git a/hugolib/filesystems/basefs.go b/hugolib/filesystems/basefs.go
index de6baa130d7..66690310c49 100644
--- a/hugolib/filesystems/basefs.go
+++ b/hugolib/filesystems/basefs.go
@@ -126,10 +126,25 @@ type SourceFilesystems struct {
StaticDirs []hugofs.FileMetaInfo
}
+func (s *SourceFilesystems) FileSystems() []*SourceFilesystem {
+ return []*SourceFilesystem{
+ s.Content,
+ s.Data,
+ s.I18n,
+ s.Layouts,
+ s.Archetypes,
+ // TODO1 static
+ }
+
+}
+
// A SourceFilesystem holds the filesystem for a given source type in Hugo (data,
// i18n, layouts, static) and additional metadata to be able to use that filesystem
// in server mode.
type SourceFilesystem struct {
+ // Name matches one in files.ComponentFolders
+ Name string
+
// This is a virtual composite filesystem. It expects path relative to a context.
Fs afero.Fs
@@ -275,6 +290,19 @@ func (d *SourceFilesystem) Contains(filename string) bool {
return false
}
+// Path returns the relative path to the given filename if it is a member of
+// of the current filesystem, an empty string if not.
+func (d *SourceFilesystem) Path(filename string) string {
+ for _, dir := range d.Dirs {
+ meta := dir.Meta()
+ if strings.HasPrefix(filename, meta.Filename()) {
+ p := strings.TrimPrefix(strings.TrimPrefix(filename, meta.Filename()), filePathSeparator)
+ return p
+ }
+ }
+ return ""
+}
+
// RealDirs gets a list of absolute paths to directories starting from the given
// path.
func (d *SourceFilesystem) RealDirs(from string) []string {
@@ -349,12 +377,14 @@ func newSourceFilesystemsBuilder(p *paths.Paths, logger *loggers.Logger, b *Base
return &sourceFilesystemsBuilder{p: p, logger: logger, sourceFs: sourceFs, theBigFs: b.theBigFs, result: &SourceFilesystems{}}
}
-func (b *sourceFilesystemsBuilder) newSourceFilesystem(fs afero.Fs, dirs []hugofs.FileMetaInfo) *SourceFilesystem {
+func (b *sourceFilesystemsBuilder) newSourceFilesystem(name string, fs afero.Fs, dirs []hugofs.FileMetaInfo) *SourceFilesystem {
return &SourceFilesystem{
+ Name: name,
Fs: fs,
Dirs: dirs,
}
}
+
func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
if b.theBigFs == nil {
@@ -369,12 +399,12 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
createView := func(componentID string) *SourceFilesystem {
if b.theBigFs == nil || b.theBigFs.overlayMounts == nil {
- return b.newSourceFilesystem(hugofs.NoOpFs, nil)
+ return b.newSourceFilesystem(componentID, hugofs.NoOpFs, nil)
}
dirs := b.theBigFs.overlayDirs[componentID]
- return b.newSourceFilesystem(afero.NewBasePathFs(b.theBigFs.overlayMounts, componentID), dirs)
+ return b.newSourceFilesystem(componentID, afero.NewBasePathFs(b.theBigFs.overlayMounts, componentID), dirs)
}
@@ -392,14 +422,14 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
return nil, err
}
- b.result.Data = b.newSourceFilesystem(dataFs, dataDirs)
+ b.result.Data = b.newSourceFilesystem(files.ComponentFolderData, dataFs, dataDirs)
i18nDirs := b.theBigFs.overlayDirs[files.ComponentFolderI18n]
i18nFs, err := hugofs.NewSliceFs(i18nDirs...)
if err != nil {
return nil, err
}
- b.result.I18n = b.newSourceFilesystem(i18nFs, i18nDirs)
+ b.result.I18n = b.newSourceFilesystem(files.ComponentFolderI18n, i18nFs, i18nDirs)
contentDirs := b.theBigFs.overlayDirs[files.ComponentFolderContent]
contentBfs := afero.NewBasePathFs(b.theBigFs.overlayMountsContent, files.ComponentFolderContent)
@@ -409,7 +439,7 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
return nil, errors.Wrap(err, "create content filesystem")
}
- b.result.Content = b.newSourceFilesystem(contentFs, contentDirs)
+ b.result.Content = b.newSourceFilesystem(files.ComponentFolderContent, contentFs, contentDirs)
b.result.Work = afero.NewReadOnlyFs(b.theBigFs.overlayFull)
@@ -421,13 +451,13 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
if b.theBigFs.staticPerLanguage != nil {
// Multihost mode
for k, v := range b.theBigFs.staticPerLanguage {
- sfs := b.newSourceFilesystem(v, b.result.StaticDirs)
+ sfs := b.newSourceFilesystem(files.ComponentFolderStatic, v, b.result.StaticDirs)
sfs.PublishFolder = k
ms[k] = sfs
}
} else {
bfs := afero.NewBasePathFs(b.theBigFs.overlayMountsStatic, files.ComponentFolderStatic)
- ms[""] = b.newSourceFilesystem(bfs, b.result.StaticDirs)
+ ms[""] = b.newSourceFilesystem(files.ComponentFolderStatic, bfs, b.result.StaticDirs)
}
return b.result, nil
diff --git a/hugolib/hugo_modules_test.go b/hugolib/hugo_modules_test.go
index 9ba039c7430..ddc0ef59b46 100644
--- a/hugolib/hugo_modules_test.go
+++ b/hugolib/hugo_modules_test.go
@@ -40,6 +40,9 @@ import (
// TODO(bep) this fails when testmodBuilder is also building ...
func TestHugoModules(t *testing.T) {
+ if !isCI() {
+ t.Skip("skip (relative) long running modules test when running locally")
+ }
t.Parallel()
if hugo.GoMinorVersion() < 12 {
diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
index c0d75c09f52..026a4c3ff9c 100644
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -20,6 +20,10 @@ import (
"strings"
"sync"
+ "github.com/gohugoio/hugo/identity"
+
+ "github.com/gohugoio/hugo/markup/converter"
+
radix "github.com/armon/go-radix"
"github.com/gohugoio/hugo/output"
@@ -412,6 +416,19 @@ func applyDeps(cfg deps.DepsCfg, sites ...*Site) error {
d.OutputFormatsConfig = s.outputFormatsConfig
}
+ contentSpec, err := helpers.NewContentSpec(
+ converter.ProviderConfig{
+ Cfg: d.Language,
+ Logger: s.Log,
+ ContentFs: s.BaseFs.Content.Fs,
+ },
+ )
+ if err != nil {
+ return err
+ }
+
+ d.ContentSpec = contentSpec
+
}
return nil
@@ -806,12 +823,55 @@ func (h *HugoSites) findPagesByKindIn(kind string, inPages page.Pages) page.Page
return h.Sites[0].findPagesByKindIn(kind, inPages)
}
-func (h *HugoSites) findPagesByShortcode(shortcode string) page.Pages {
- var pages page.Pages
+func (h *HugoSites) resetPageStateFromEvents(ids identity.Identities) {
+ idset := ids.ToIdentitySet()
+ hasIdentify := func(v interface{}) bool {
+ if id, ok := v.(identity.Provider); ok {
+ if idset[id.GetIdentity()] {
+ return true
+ }
+ }
+ if idp, ok := v.(identity.ChildIdentitiesProvider); ok {
+ for _, id := range idp.GetChildIdentities() {
+ if idset[id.GetIdentity()] {
+ return true
+ }
+ }
+ }
+ return false
+ }
+
for _, s := range h.Sites {
- pages = append(pages, s.findPagesByShortcode(shortcode)...)
+ PAGES:
+ for _, p := range s.rawAllPages {
+ OUTPUTS:
+ for _, po := range p.pageOutputs {
+ if c := po.cp; c != nil {
+ if converted := c.convertedResult; converted != nil {
+ if hasIdentify(converted) {
+ c.Reset()
+ p.forceRender = true
+ continue OUTPUTS
+ }
+ }
+ }
+ }
+
+ for _, s := range p.shortcodeState.shortcodes {
+ for _, id := range ids {
+ if s.info.Search(id.GetIdentity()) != nil {
+ for _, po := range p.pageOutputs {
+ if po.cp != nil {
+ po.cp.Reset()
+ }
+ }
+ p.forceRender = true
+ continue PAGES
+ }
+ }
+ }
+ }
}
- return pages
}
// Used in partial reloading to determine if the change is in a bundle.
diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go
index a70a19e7c31..d749ff581d5 100644
--- a/hugolib/hugo_sites_build.go
+++ b/hugolib/hugo_sites_build.go
@@ -71,7 +71,7 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
if conf.whatChanged == nil {
// Assume everything has changed
- conf.whatChanged = &whatChanged{source: true, other: true}
+ conf.whatChanged = &whatChanged{source: true}
}
var prepareErr error
diff --git a/hugolib/page.go b/hugolib/page.go
index b0e8c4359fd..d69ceed4eeb 100644
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -23,6 +23,10 @@ import (
"sort"
"strings"
+ "github.com/gohugoio/hugo/tpl"
+
+ "github.com/gohugoio/hugo/identity"
+
"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/common/maps"
@@ -46,6 +50,7 @@ import (
"github.com/gohugoio/hugo/common/collections"
"github.com/gohugoio/hugo/common/text"
+ "github.com/gohugoio/hugo/markup/converter/hooks"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/resource"
@@ -317,6 +322,80 @@ func (ps *pageState) initCommonProviders(pp pagePaths) error {
return nil
}
+func (ps *pageState) initOutputFormats() error {
+
+ if len(ps.pageOutputs) == 0 {
+ return nil
+ }
+
+ c := ps.getContentConverter()
+ if c == nil || !c.Supports(converter.FeatureRenderHooks) {
+ return nil
+ }
+
+ templSet := make(map[identity.Identity]bool)
+ canReuse := true
+
+ for _, o := range ps.pageOutputs {
+ if !o.render || o.cp == nil {
+ continue
+ }
+ hooks, err := ps.createRenderHooks(o.f)
+ if err != nil {
+ return err
+ }
+ if canReuse {
+ if len(templSet) != 0 {
+ // There may be a template per output format.
+ // In that case we need to re-render.
+ canReuse = templSet[hooks.LinkRenderer.GetIdentity()]
+ }
+ templSet[hooks.LinkRenderer.GetIdentity()] = true
+ }
+ o.cp.renderHooks = hooks
+ o.cp.renderHooksHaveVariants = !canReuse
+ }
+
+ return nil
+}
+
+func (p *pageState) createRenderHooks(f output.Format) (*hooks.Render, error) {
+
+ layoutDescriptor := p.getLayoutDescriptor()
+ layoutDescriptor.RenderingHook = true
+ layoutDescriptor.LayoutOverride = false
+
+ layoutDescriptor.Kind = "render-link"
+ linkLayouts, err := p.s.layoutHandler.For(layoutDescriptor, f)
+ if err != nil {
+ return nil, err
+ }
+
+ layoutDescriptor.Kind = "render-image"
+ imageLayouts, err := p.s.layoutHandler.For(layoutDescriptor, f)
+ if err != nil {
+ return nil, err
+ }
+
+ if linkLayouts == nil && imageLayouts == nil {
+ return nil, nil
+ }
+
+ var linkRenderer hooks.LinkRenderer
+ //var imageRenderer hooks.ImageRenderer
+
+ if templ, found := p.s.lookupTemplate(linkLayouts...); found {
+ linkRenderer = contentLinkRenderer{
+ Provider: templ.(tpl.TemplateInfoProvider).TemplateInfo(),
+ templ: templ,
+ }
+ }
+
+ return &hooks.Render{
+ LinkRenderer: linkRenderer,
+ }, nil
+}
+
func (p *pageState) getLayoutDescriptor() output.LayoutDescriptor {
p.layoutDescriptorInit.Do(func() {
var section string
diff --git a/hugolib/page__meta.go b/hugolib/page__meta.go
index 1fc69c21826..84a052f92bc 100644
--- a/hugolib/page__meta.go
+++ b/hugolib/page__meta.go
@@ -592,7 +592,7 @@ func (pm *pageMeta) setMetadata(bucket *pagesMapBucket, p *pageState, frontmatte
return nil
}
-func (p *pageMeta) applyDefaultValues() error {
+func (p *pageMeta) applyDefaultValues(ps *pageState) error {
if p.markup == "" {
if !p.File().IsZero() {
// Fall back to file extension
@@ -656,15 +656,19 @@ func (p *pageMeta) applyDefaultValues() error {
return errors.Errorf("no content renderer found for markup %q", p.markup)
}
- cpp, err := cp.New(converter.DocumentContext{
- DocumentID: p.f.UniqueID(),
- DocumentName: p.f.Path(),
- ConfigOverrides: renderingConfigOverrides,
- })
+ cpp, err := cp.New(
+ converter.DocumentContext{
+ Document: newPageForShortcode(ps),
+ DocumentID: p.f.UniqueID(),
+ DocumentName: p.f.Path(),
+ ConfigOverrides: renderingConfigOverrides,
+ },
+ )
if err != nil {
return err
}
+
p.contentConverter = cpp
}
diff --git a/hugolib/page__new.go b/hugolib/page__new.go
index 99bf305aa58..37db148f8bd 100644
--- a/hugolib/page__new.go
+++ b/hugolib/page__new.go
@@ -112,7 +112,7 @@ func newPageFromMeta(meta map[string]interface{}, metaProvider *pageMeta) (*page
}
}
- if err := metaProvider.applyDefaultValues(); err != nil {
+ if err := metaProvider.applyDefaultValues(ps); err != nil {
return err
}
@@ -134,7 +134,7 @@ func newPageFromMeta(meta map[string]interface{}, metaProvider *pageMeta) (*page
}
makeOut := func(f output.Format, render bool) *pageOutput {
- return newPageOutput(nil, ps, pp, f, render)
+ return newPageOutput(ps, pp, f, render)
}
if ps.m.standalone {
@@ -158,6 +158,10 @@ func newPageFromMeta(meta map[string]interface{}, metaProvider *pageMeta) (*page
return nil, err
}
+ if err := ps.initOutputFormats(); err != nil {
+ return nil, err
+ }
+
return nil, nil
})
@@ -234,7 +238,7 @@ func newPageWithContent(f *fileInfo, s *Site, bundled bool, content resource.Ope
return ps.wrapError(err)
}
- if err := metaProvider.applyDefaultValues(); err != nil {
+ if err := metaProvider.applyDefaultValues(ps); err != nil {
return err
}
@@ -264,18 +268,17 @@ func newPageWithContent(f *fileInfo, s *Site, bundled bool, content resource.Ope
}
_, render := outputFormatsForPage.GetByName(f.Name)
- var contentProvider *pageContentOutput
+ po := newPageOutput(ps, pp, f, render)
if reuseContent && i > 0 {
- contentProvider = ps.pageOutputs[0].cp
+ po.initContentProvider(ps.pageOutputs[0].cp)
} else {
- var err error
- contentProvider, err = contentPerOutput(f)
+ contentProvider, err := contentPerOutput(po)
if err != nil {
return nil, err
}
+ po.initContentProvider(contentProvider)
}
- po := newPageOutput(contentProvider, ps, pp, f, render)
ps.pageOutputs[i] = po
created[f.Name] = po
}
@@ -284,6 +287,10 @@ func newPageWithContent(f *fileInfo, s *Site, bundled bool, content resource.Ope
return nil, err
}
+ if err := ps.initOutputFormats(); err != nil {
+ return nil, err
+ }
+
return nil, nil
})
diff --git a/hugolib/page__output.go b/hugolib/page__output.go
index 764c46a937b..1af666ed275 100644
--- a/hugolib/page__output.go
+++ b/hugolib/page__output.go
@@ -20,7 +20,6 @@ import (
)
func newPageOutput(
- cp *pageContentOutput, // may be nil
ps *pageState,
pp pagePaths,
f output.Format,
@@ -45,36 +44,23 @@ func newPageOutput(
paginatorProvider = pag
}
- var (
- contentProvider page.ContentProvider = page.NopPage
- tableOfContentsProvider page.TableOfContentsProvider = page.NopPage
- )
-
- if cp != nil {
- contentProvider = cp
- tableOfContentsProvider = cp
- }
-
providers := struct {
- page.ContentProvider
- page.TableOfContentsProvider
page.PaginatorProvider
resource.ResourceLinksProvider
targetPather
}{
- contentProvider,
- tableOfContentsProvider,
paginatorProvider,
linksProvider,
targetPathsProvider,
}
po := &pageOutput{
- f: f,
- cp: cp,
- pagePerOutputProviders: providers,
- render: render,
- paginator: pag,
+ f: f,
+ pagePerOutputProviders: providers,
+ ContentProvider: page.NopPage,
+ TableOfContentsProvider: page.NopPage,
+ render: render,
+ paginator: pag,
}
return po
@@ -98,12 +84,25 @@ type pageOutput struct {
// output format.
pagePerOutputProviders
- // This may be nil.
+ page.ContentProvider
+ page.TableOfContentsProvider
+
+ // May be nil.
cp *pageContentOutput
}
+func (p *pageOutput) initContentProvider(cp *pageContentOutput) {
+ if cp == nil {
+ return
+ }
+ p.ContentProvider = cp
+ p.TableOfContentsProvider = cp
+ p.cp = cp
+}
+
func (p *pageOutput) enablePlaceholders() {
if p.cp != nil {
p.cp.enablePlaceholders()
}
+
}
diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go
index bc2a0accc04..5bbb5800937 100644
--- a/hugolib/page__per_output.go
+++ b/hugolib/page__per_output.go
@@ -23,6 +23,8 @@ import (
"sync"
"unicode/utf8"
+ "github.com/gohugoio/hugo/markup/converter/hooks"
+
"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/lazy"
@@ -58,14 +60,14 @@ var (
}
)
-func newPageContentOutput(p *pageState) func(f output.Format) (*pageContentOutput, error) {
+func newPageContentOutput(p *pageState) func(po *pageOutput) (*pageContentOutput, error) {
parent := p.init
- return func(f output.Format) (*pageContentOutput, error) {
+ return func(po *pageOutput) (*pageContentOutput, error) {
cp := &pageContentOutput{
p: p,
- f: f,
+ f: po.f,
}
initContent := func() (err error) {
@@ -83,13 +85,22 @@ func newPageContentOutput(p *pageState) func(f output.Format) (*pageContentOutpu
var hasVariants bool
+ f := po.f
cp.contentPlaceholders, hasVariants, err = p.shortcodeState.renderShortcodesForPage(p, f)
if err != nil {
return err
}
- if p.render && !hasVariants {
- // We can reuse this for the other output formats
+ enableReuse := p.render && !hasVariants
+ enableReuse = enableReuse && !cp.renderHooksHaveVariants
+
+ if enableReuse {
+ // Reuse this for the other output formats.
+ // We may improve on this, but we really want to avoid re-rendering the content
+ // to all output formats.
+ // The current rule is that if you need output format-aware shortcodes or
+ // content rendering hooks, create a output format-specific template, e.g.
+ // myshortcode.amp.html.
cp.enableReuse()
}
@@ -177,6 +188,7 @@ func newPageContentOutput(p *pageState) func(f output.Format) (*pageContentOutpu
// Recursive loops can only happen in content files with template code (shortcodes etc.)
// Avoid creating new goroutines if we don't have to.
+ // TODO1
needTimeout := !p.renderable || p.shortcodeState.hasShortcodes()
if needTimeout {
@@ -212,6 +224,7 @@ type pageContentOutput struct {
f output.Format
// If we can safely reuse this for other output formats.
+ // TODO1
reuse bool
reuseInit sync.Once
@@ -224,6 +237,11 @@ type pageContentOutput struct {
placeholdersEnabled bool
placeholdersEnabledInit sync.Once
+ // May be nil.
+ renderHooks *hooks.Render
+ // Set if there are more than one output format variant
+ renderHooksHaveVariants bool
+
// Content state
workContent []byte
@@ -232,6 +250,7 @@ type pageContentOutput struct {
// Temporary storage of placeholders mapped to their content.
// These are shortcodes etc. Some of these will need to be replaced
// after any markup is rendered, so they share a common prefix.
+ // TODO1
contentPlaceholders map[string]string
// Content sections
@@ -248,6 +267,12 @@ type pageContentOutput struct {
readingTime int
}
+func (p *pageContentOutput) Reset() {
+ p.p.initOutputFormats()
+ p.initMain.Reset()
+ p.initPlain.Reset()
+}
+
func (p *pageContentOutput) Content() (interface{}, error) {
if p.p.s.initInit(p.initMain, p.p) {
return p.content, nil
@@ -332,10 +357,12 @@ func (p *pageContentOutput) setAutoSummary() error {
}
func (cp *pageContentOutput) renderContent(content []byte) (converter.Result, error) {
- return cp.p.getContentConverter().Convert(
+ c := cp.p.getContentConverter()
+ return c.Convert(
converter.RenderContext{
- Src: content,
- RenderTOC: true,
+ Src: content,
+ RenderTOC: true,
+ RenderHooks: cp.renderHooks,
})
}
@@ -392,9 +419,7 @@ func (p *pageContentOutput) enableReuse() {
// these will be shifted out when rendering a given output format.
type pagePerOutputProviders interface {
targetPather
- page.ContentProvider
page.PaginatorProvider
- page.TableOfContentsProvider
resource.ResourceLinksProvider
}
diff --git a/hugolib/page_test.go b/hugolib/page_test.go
index dc8bc821c15..8fb57676657 100644
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -93,12 +93,6 @@ Summary Next Line. {{