mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
Fix some change detection issues on server reloads
* Fix change detection when .GetPage/site.GetPage is used from shortcode * Fix stale content for GetPage results with short name lookups on server reloads Fixes #7623 Fixes #7624 Fixes #7625
This commit is contained in:
parent
3ba7c92530
commit
4055c12184
14 changed files with 228 additions and 28 deletions
4
deps/deps.go
vendored
4
deps/deps.go
vendored
|
@ -97,6 +97,9 @@ type Deps struct {
|
||||||
// This is common/global for all sites.
|
// This is common/global for all sites.
|
||||||
BuildState *BuildState
|
BuildState *BuildState
|
||||||
|
|
||||||
|
// Whether we are in running (server) mode
|
||||||
|
Running bool
|
||||||
|
|
||||||
*globalErrHandler
|
*globalErrHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,6 +282,7 @@ func New(cfg DepsCfg) (*Deps, error) {
|
||||||
FileCaches: fileCaches,
|
FileCaches: fileCaches,
|
||||||
BuildStartListeners: &Listeners{},
|
BuildStartListeners: &Listeners{},
|
||||||
BuildState: buildState,
|
BuildState: buildState,
|
||||||
|
Running: cfg.Running,
|
||||||
Timeout: time.Duration(timeoutms) * time.Millisecond,
|
Timeout: time.Duration(timeoutms) * time.Millisecond,
|
||||||
globalErrHandler: errorHandler,
|
globalErrHandler: errorHandler,
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,21 +95,23 @@ func newContentMap(cfg contentMapConfig) *contentMap {
|
||||||
|
|
||||||
m.pageReverseIndex = &contentTreeReverseIndex{
|
m.pageReverseIndex = &contentTreeReverseIndex{
|
||||||
t: []*contentTree{m.pages, m.sections, m.taxonomies},
|
t: []*contentTree{m.pages, m.sections, m.taxonomies},
|
||||||
initFn: func(t *contentTree, m map[interface{}]*contentNode) {
|
contentTreeReverseIndexMap: &contentTreeReverseIndexMap{
|
||||||
t.Walk(func(s string, v interface{}) bool {
|
initFn: func(t *contentTree, m map[interface{}]*contentNode) {
|
||||||
n := v.(*contentNode)
|
t.Walk(func(s string, v interface{}) bool {
|
||||||
if n.p != nil && !n.p.File().IsZero() {
|
n := v.(*contentNode)
|
||||||
meta := n.p.File().FileInfo().Meta()
|
if n.p != nil && !n.p.File().IsZero() {
|
||||||
if meta.Path() != meta.PathFile() {
|
meta := n.p.File().FileInfo().Meta()
|
||||||
// Keep track of the original mount source.
|
if meta.Path() != meta.PathFile() {
|
||||||
mountKey := filepath.ToSlash(filepath.Join(meta.Module(), meta.PathFile()))
|
// Keep track of the original mount source.
|
||||||
addToReverseMap(mountKey, n, m)
|
mountKey := filepath.ToSlash(filepath.Join(meta.Module(), meta.PathFile()))
|
||||||
|
addToReverseMap(mountKey, n, m)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
k := strings.TrimPrefix(strings.TrimSuffix(path.Base(s), cmLeafSeparator), cmBranchSeparator)
|
||||||
k := strings.TrimPrefix(strings.TrimSuffix(path.Base(s), cmLeafSeparator), cmBranchSeparator)
|
addToReverseMap(k, n, m)
|
||||||
addToReverseMap(k, n, m)
|
return false
|
||||||
return false
|
})
|
||||||
})
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1030,12 +1032,21 @@ func (c *contentTreeRef) getSections() page.Pages {
|
||||||
|
|
||||||
type contentTreeReverseIndex struct {
|
type contentTreeReverseIndex struct {
|
||||||
t []*contentTree
|
t []*contentTree
|
||||||
m map[interface{}]*contentNode
|
*contentTreeReverseIndexMap
|
||||||
|
}
|
||||||
|
|
||||||
|
type contentTreeReverseIndexMap struct {
|
||||||
|
m map[interface{}]*contentNode
|
||||||
init sync.Once
|
init sync.Once
|
||||||
initFn func(*contentTree, map[interface{}]*contentNode)
|
initFn func(*contentTree, map[interface{}]*contentNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *contentTreeReverseIndex) Reset() {
|
||||||
|
c.contentTreeReverseIndexMap = &contentTreeReverseIndexMap{
|
||||||
|
initFn: c.initFn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *contentTreeReverseIndex) Get(key interface{}) *contentNode {
|
func (c *contentTreeReverseIndex) Get(key interface{}) *contentNode {
|
||||||
c.init.Do(func() {
|
c.init.Do(func() {
|
||||||
c.m = make(map[interface{}]*contentNode)
|
c.m = make(map[interface{}]*contentNode)
|
||||||
|
|
|
@ -997,7 +997,6 @@ func (h *HugoSites) resetPageStateFromEvents(idset identity.Identities) {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used in partial reloading to determine if the change is in a bundle.
|
// Used in partial reloading to determine if the change is in a bundle.
|
||||||
|
|
|
@ -259,3 +259,66 @@ Output Shortcode AMP Edited
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Issues #7623 #7625
|
||||||
|
func TestSitesRebuildOnFilesIncludedWithGetPage(t *testing.T) {
|
||||||
|
b := newTestSitesBuilder(t).Running()
|
||||||
|
b.WithContent("pages/p1.md", `---
|
||||||
|
title: p1
|
||||||
|
---
|
||||||
|
P3: {{< GetPage "pages/p3" >}}
|
||||||
|
`)
|
||||||
|
|
||||||
|
b.WithContent("pages/p2.md", `---
|
||||||
|
title: p2
|
||||||
|
---
|
||||||
|
P4: {{< site_GetPage "pages/p4" >}}
|
||||||
|
P5: {{< site_GetPage "p5" >}}
|
||||||
|
P6: {{< dot_site_GetPage "p6" >}}
|
||||||
|
`)
|
||||||
|
|
||||||
|
b.WithContent("pages/p3/index.md", "---\ntitle: p3\nheadless: true\n---\nP3 content")
|
||||||
|
b.WithContent("pages/p4/index.md", "---\ntitle: p4\nheadless: true\n---\nP4 content")
|
||||||
|
b.WithContent("pages/p5.md", "---\ntitle: p5\n---\nP5 content")
|
||||||
|
b.WithContent("pages/p6.md", "---\ntitle: p6\n---\nP6 content")
|
||||||
|
|
||||||
|
b.WithTemplates(
|
||||||
|
"_default/single.html", `{{ .Content }}`,
|
||||||
|
"shortcodes/GetPage.html", `
|
||||||
|
{{ $arg := .Get 0 }}
|
||||||
|
{{ $p := .Page.GetPage $arg }}
|
||||||
|
{{ $p.Content }}
|
||||||
|
`,
|
||||||
|
"shortcodes/site_GetPage.html", `
|
||||||
|
{{ $arg := .Get 0 }}
|
||||||
|
{{ $p := site.GetPage $arg }}
|
||||||
|
{{ $p.Content }}
|
||||||
|
`, "shortcodes/dot_site_GetPage.html", `
|
||||||
|
{{ $arg := .Get 0 }}
|
||||||
|
{{ $p := .Site.GetPage $arg }}
|
||||||
|
{{ $p.Content }}
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
|
||||||
|
b.Build(BuildCfg{})
|
||||||
|
|
||||||
|
b.AssertFileContent("public/pages/p1/index.html", "P3 content")
|
||||||
|
b.AssertFileContent("public/pages/p2/index.html", `P4 content
|
||||||
|
P5 content
|
||||||
|
P6 content
|
||||||
|
`)
|
||||||
|
|
||||||
|
b.EditFiles("content/pages/p3/index.md", "---\ntitle: p3\n---\nP3 changed content")
|
||||||
|
b.EditFiles("content/pages/p4/index.md", "---\ntitle: p4\n---\nP4 changed content")
|
||||||
|
b.EditFiles("content/pages/p5.md", "---\ntitle: p5\n---\nP5 changed content")
|
||||||
|
b.EditFiles("content/pages/p6.md", "---\ntitle: p6\n---\nP6 changed content")
|
||||||
|
|
||||||
|
b.Build(BuildCfg{})
|
||||||
|
|
||||||
|
b.AssertFileContent("public/pages/p1/index.html", "P3 changed content")
|
||||||
|
b.AssertFileContent("public/pages/p2/index.html", `P4 changed content
|
||||||
|
P5 changed content
|
||||||
|
P6 changed content
|
||||||
|
`)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -78,6 +78,7 @@ type pageContext interface {
|
||||||
posOffset(offset int) text.Position
|
posOffset(offset int) text.Position
|
||||||
wrapError(err error) error
|
wrapError(err error) error
|
||||||
getContentConverter() converter.Converter
|
getContentConverter() converter.Converter
|
||||||
|
addDependency(dep identity.Provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrapErr adds some context to the given error if possible.
|
// wrapErr adds some context to the given error if possible.
|
||||||
|
@ -93,6 +94,18 @@ type pageSiteAdapter struct {
|
||||||
s *Site
|
s *Site
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pa pageSiteAdapter) GetPageWithTemplateInfo(info tpl.Info, ref string) (page.Page, error) {
|
||||||
|
p, err := pa.GetPage(ref)
|
||||||
|
if p != nil {
|
||||||
|
// Track pages referenced by templates/shortcodes
|
||||||
|
// when in server mode.
|
||||||
|
if im, ok := info.(identity.Manager); ok {
|
||||||
|
im.Add(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
func (pa pageSiteAdapter) GetPage(ref string) (page.Page, error) {
|
func (pa pageSiteAdapter) GetPage(ref string) (page.Page, error) {
|
||||||
p, err := pa.s.getPageNew(pa.p, ref)
|
p, err := pa.s.getPageNew(pa.p, ref)
|
||||||
if p == nil {
|
if p == nil {
|
||||||
|
@ -127,6 +140,10 @@ func (p *pageState) Eq(other interface{}) bool {
|
||||||
return p == pp
|
return p == pp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *pageState) GetIdentity() identity.Identity {
|
||||||
|
return identity.NewPathIdentity(files.ComponentFolderContent, filepath.FromSlash(p.Path()))
|
||||||
|
}
|
||||||
|
|
||||||
func (p *pageState) GitInfo() *gitmap.GitInfo {
|
func (p *pageState) GitInfo() *gitmap.GitInfo {
|
||||||
return p.gitInfo
|
return p.gitInfo
|
||||||
}
|
}
|
||||||
|
|
|
@ -1538,6 +1538,7 @@ func (s *Site) resetBuildState(sourceChanged bool) {
|
||||||
s.init.Reset()
|
s.init.Reset()
|
||||||
|
|
||||||
if sourceChanged {
|
if sourceChanged {
|
||||||
|
s.pageMap.contentMap.pageReverseIndex.Reset()
|
||||||
s.PageCollections = newPageCollections(s.pageMap)
|
s.PageCollections = newPageCollections(s.pageMap)
|
||||||
s.pageMap.withEveryBundlePage(func(p *pageState) bool {
|
s.pageMap.withEveryBundlePage(func(p *pageState) bool {
|
||||||
p.pagePages = &pagePages{}
|
p.pagePages = &pagePages{}
|
||||||
|
@ -1587,6 +1588,18 @@ func (s *SiteInfo) GetPage(ref ...string) (page.Page, error) {
|
||||||
return p, err
|
return p, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SiteInfo) GetPageWithTemplateInfo(info tpl.Info, ref ...string) (page.Page, error) {
|
||||||
|
p, err := s.GetPage(ref...)
|
||||||
|
if p != nil {
|
||||||
|
// Track pages referenced by templates/shortcodes
|
||||||
|
// when in server mode.
|
||||||
|
if im, ok := info.(identity.Manager); ok {
|
||||||
|
im.Add(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Site) permalink(link string) string {
|
func (s *Site) permalink(link string) string {
|
||||||
return s.PathSpec.PermalinkForBaseURL(link, s.PathSpec.BaseURL.String())
|
return s.PathSpec.PermalinkForBaseURL(link, s.PathSpec.BaseURL.String())
|
||||||
}
|
}
|
||||||
|
|
|
@ -599,7 +599,7 @@ title: P1
|
||||||
|
|
||||||
idset := make(map[identity.Identity]bool)
|
idset := make(map[identity.Identity]bool)
|
||||||
collectIdentities(idset, templ.(tpl.Info))
|
collectIdentities(idset, templ.(tpl.Info))
|
||||||
b.Assert(idset, qt.HasLen, 10)
|
b.Assert(idset, qt.HasLen, 11)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -129,6 +129,8 @@ func (im *identityManager) Reset() {
|
||||||
im.Unlock()
|
im.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(bep) these identities are currently only read on server reloads
|
||||||
|
// so there should be no concurrency issues, but that may change.
|
||||||
func (im *identityManager) GetIdentities() Identities {
|
func (im *identityManager) GetIdentities() Identities {
|
||||||
im.Lock()
|
im.Lock()
|
||||||
defer im.Unlock()
|
defer im.Unlock()
|
||||||
|
|
|
@ -14,6 +14,9 @@
|
||||||
package identity
|
package identity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
qt "github.com/frankban/quicktest"
|
||||||
|
@ -29,6 +32,54 @@ func TestIdentityManager(t *testing.T) {
|
||||||
c.Assert(im.Search(testIdentity{name: "notfound"}), qt.Equals, nil)
|
c.Assert(im.Search(testIdentity{name: "notfound"}), qt.Equals, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkIdentityManager(b *testing.B) {
|
||||||
|
|
||||||
|
createIds := func(num int) []Identity {
|
||||||
|
ids := make([]Identity, num)
|
||||||
|
for i := 0; i < num; i++ {
|
||||||
|
ids[i] = testIdentity{name: fmt.Sprintf("id%d", i)}
|
||||||
|
}
|
||||||
|
return ids
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Run("Add", func(b *testing.B) {
|
||||||
|
c := qt.New(b)
|
||||||
|
b.StopTimer()
|
||||||
|
ids := createIds(b.N)
|
||||||
|
im := NewManager(testIdentity{"first"})
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
im.Add(ids[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
b.StopTimer()
|
||||||
|
c.Assert(im.GetIdentities(), qt.HasLen, b.N+1)
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("Search", func(b *testing.B) {
|
||||||
|
c := qt.New(b)
|
||||||
|
b.StopTimer()
|
||||||
|
ids := createIds(b.N)
|
||||||
|
im := NewManager(testIdentity{"first"})
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
im.Add(ids[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
name := "id" + strconv.Itoa(rand.Intn(b.N))
|
||||||
|
id := im.Search(testIdentity{name: name})
|
||||||
|
c.Assert(id.GetIdentity().Name(), qt.Equals, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
type testIdentity struct {
|
type testIdentity struct {
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,11 @@ package page
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/identity"
|
||||||
|
|
||||||
"github.com/bep/gitmap"
|
"github.com/bep/gitmap"
|
||||||
"github.com/gohugoio/hugo/config"
|
"github.com/gohugoio/hugo/config"
|
||||||
|
"github.com/gohugoio/hugo/tpl"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/hugo"
|
"github.com/gohugoio/hugo/common/hugo"
|
||||||
"github.com/gohugoio/hugo/common/maps"
|
"github.com/gohugoio/hugo/common/maps"
|
||||||
|
@ -97,6 +100,9 @@ type GetPageProvider interface {
|
||||||
// This will return nil when no page could be found, and will return
|
// This will return nil when no page could be found, and will return
|
||||||
// an error if the ref is ambiguous.
|
// an error if the ref is ambiguous.
|
||||||
GetPage(ref string) (Page, error)
|
GetPage(ref string) (Page, error)
|
||||||
|
|
||||||
|
// GetPageWithTemplateInfo is for internal use only.
|
||||||
|
GetPageWithTemplateInfo(info tpl.Info, ref string) (Page, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GitInfoProvider provides Git info.
|
// GitInfoProvider provides Git info.
|
||||||
|
@ -260,6 +266,9 @@ type PageWithoutContent interface {
|
||||||
// e.g. GetTerms("categories")
|
// e.g. GetTerms("categories")
|
||||||
GetTerms(taxonomy string) Pages
|
GetTerms(taxonomy string) Pages
|
||||||
|
|
||||||
|
// Used in change/dependency tracking.
|
||||||
|
identity.Provider
|
||||||
|
|
||||||
DeprecatedWarningPageMethods
|
DeprecatedWarningPageMethods
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,10 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/identity"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/hugofs/files"
|
"github.com/gohugoio/hugo/hugofs/files"
|
||||||
|
"github.com/gohugoio/hugo/tpl"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/hugofs"
|
"github.com/gohugoio/hugo/hugofs"
|
||||||
|
|
||||||
|
@ -170,6 +173,10 @@ func (p *nopPage) GetPage(ref string) (Page, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *nopPage) GetPageWithTemplateInfo(info tpl.Info, ref string) (Page, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *nopPage) GetParam(key string) interface{} {
|
func (p *nopPage) GetParam(key string) interface{} {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -484,3 +491,7 @@ func (p *nopPage) Weight() int {
|
||||||
func (p *nopPage) WordCount() int {
|
func (p *nopPage) WordCount() int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *nopPage) GetIdentity() identity.Identity {
|
||||||
|
return identity.NewPathIdentity("content", "foo/bar.md")
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/hugofs/files"
|
"github.com/gohugoio/hugo/hugofs/files"
|
||||||
|
"github.com/gohugoio/hugo/identity"
|
||||||
|
"github.com/gohugoio/hugo/tpl"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/modules"
|
"github.com/gohugoio/hugo/modules"
|
||||||
|
|
||||||
|
@ -218,6 +220,10 @@ func (p *testPage) GetPage(ref string) (Page, error) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *testPage) GetPageWithTemplateInfo(info tpl.Info, ref string) (Page, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
func (p *testPage) GetParam(key string) interface{} {
|
func (p *testPage) GetParam(key string) interface{} {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
@ -565,6 +571,10 @@ func (p *testPage) WordCount() int {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *testPage) GetIdentity() identity.Identity {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
func createTestPages(num int) Pages {
|
func createTestPages(num int) Pages {
|
||||||
pages := make(Pages, num)
|
pages := make(Pages, num)
|
||||||
|
|
||||||
|
|
|
@ -23,10 +23,17 @@ import (
|
||||||
|
|
||||||
// New returns a new instance of the fmt-namespaced template functions.
|
// New returns a new instance of the fmt-namespaced template functions.
|
||||||
func New(d *deps.Deps) *Namespace {
|
func New(d *deps.Deps) *Namespace {
|
||||||
return &Namespace{
|
ns := &Namespace{
|
||||||
errorLogger: helpers.NewDistinctLogger(d.Log.ERROR),
|
errorLogger: helpers.NewDistinctLogger(d.Log.ERROR),
|
||||||
warnLogger: helpers.NewDistinctLogger(d.Log.WARN),
|
warnLogger: helpers.NewDistinctLogger(d.Log.WARN),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.BuildStartListeners.Add(func() {
|
||||||
|
ns.errorLogger.Reset()
|
||||||
|
ns.warnLogger.Reset()
|
||||||
|
})
|
||||||
|
|
||||||
|
return ns
|
||||||
}
|
}
|
||||||
|
|
||||||
// Namespace provides template functions for the "fmt" namespace.
|
// Namespace provides template functions for the "fmt" namespace.
|
||||||
|
|
|
@ -64,7 +64,8 @@ var _ texttemplate.ExecHelper = (*templateExecHelper)(nil)
|
||||||
var zero reflect.Value
|
var zero reflect.Value
|
||||||
|
|
||||||
type templateExecHelper struct {
|
type templateExecHelper struct {
|
||||||
funcs map[string]reflect.Value
|
running bool // whether we're in server mode.
|
||||||
|
funcs map[string]reflect.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *templateExecHelper) GetFunc(tmpl texttemplate.Preparer, name string) (reflect.Value, bool) {
|
func (t *templateExecHelper) GetFunc(tmpl texttemplate.Preparer, name string) (reflect.Value, bool) {
|
||||||
|
@ -91,14 +92,15 @@ func (t *templateExecHelper) GetMapValue(tmpl texttemplate.Preparer, receiver, k
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *templateExecHelper) GetMethod(tmpl texttemplate.Preparer, receiver reflect.Value, name string) (method reflect.Value, firstArg reflect.Value) {
|
func (t *templateExecHelper) GetMethod(tmpl texttemplate.Preparer, receiver reflect.Value, name string) (method reflect.Value, firstArg reflect.Value) {
|
||||||
// This is a hot path and receiver.MethodByName really shows up in the benchmarks.
|
if t.running {
|
||||||
// Page.Render is the only method with a WithTemplateInfo as of now, so let's just
|
// This is a hot path and receiver.MethodByName really shows up in the benchmarks,
|
||||||
// check that for now.
|
// so we maintain a list of method names with that signature.
|
||||||
// TODO(bep) find a more flexible, but still fast, way.
|
switch name {
|
||||||
if name == "Render" {
|
case "GetPage", "Render":
|
||||||
if info, ok := tmpl.(tpl.Info); ok {
|
if info, ok := tmpl.(tpl.Info); ok {
|
||||||
if m := receiver.MethodByName(name + "WithTemplateInfo"); m.IsValid() {
|
if m := receiver.MethodByName(name + "WithTemplateInfo"); m.IsValid() {
|
||||||
return m, reflect.ValueOf(info)
|
return m, reflect.ValueOf(info)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,7 +135,8 @@ func newTemplateExecuter(d *deps.Deps) (texttemplate.Executer, map[string]reflec
|
||||||
}
|
}
|
||||||
|
|
||||||
exeHelper := &templateExecHelper{
|
exeHelper := &templateExecHelper{
|
||||||
funcs: funcsv,
|
running: d.Running,
|
||||||
|
funcs: funcsv,
|
||||||
}
|
}
|
||||||
|
|
||||||
return texttemplate.NewExecuter(
|
return texttemplate.NewExecuter(
|
||||||
|
|
Loading…
Reference in a new issue