panic("HTML output format not found")
}
s := &TemplateStore{
- opts: opts,
- siteOpts: siteOpts,
- optsOrig: opts,
- siteOptsOrig: siteOpts,
- htmlFormat: html,
- storeSite: configureSiteStorage(siteOpts, opts.Watching),
- treeMain: doctree.NewSimpleTree[map[nodeKey]*TemplInfo](),
- treeShortcodes: doctree.NewSimpleTree[map[string]map[TemplateDescriptor]*TemplInfo](),
- templatesByPath: maps.NewCache[string, *TemplInfo](),
- templateDescriptorByPath: maps.NewCache[string, PathTemplateDescriptor](),
+ opts: opts,
+ siteOpts: siteOpts,
+ optsOrig: opts,
+ siteOptsOrig: siteOpts,
+ htmlFormat: html,
+ storeSite: configureSiteStorage(siteOpts, opts.Watching),
+ treeMain: doctree.NewSimpleTree[map[nodeKey]*TemplInfo](),
+ treeShortcodes: doctree.NewSimpleTree[map[string]map[TemplateDescriptor]*TemplInfo](),
+ templatesByPath: maps.NewCache[string, *TemplInfo](),
+ cacheLookupPartials: maps.NewCache[string, *TemplInfo](),
// Note that the funcs passed below is just for name validation.
tns: newTemplateNamespace(siteOpts.TemplateFuncs),
siteOpts SiteOptions
htmlFormat output.Format
- treeMain *doctree.SimpleTree[map[nodeKey]*TemplInfo]
- treeShortcodes *doctree.SimpleTree[map[string]map[TemplateDescriptor]*TemplInfo]
- templatesByPath *maps.Cache[string, *TemplInfo]
- templateDescriptorByPath *maps.Cache[string, PathTemplateDescriptor]
+ treeMain *doctree.SimpleTree[map[nodeKey]*TemplInfo]
+ treeShortcodes *doctree.SimpleTree[map[string]map[TemplateDescriptor]*TemplInfo]
+ templatesByPath *maps.Cache[string, *TemplInfo]
dh descriptorHandler
// For testing benchmarking.
optsOrig StoreOptions
siteOptsOrig SiteOptions
+
+ // caches. These need to be refreshed when the templates are refreshed.
+ cacheLookupPartials *maps.Cache[string, *TemplInfo]
}
// NewFromOpts creates a new store with the same configuration as the original.
}
func (s *TemplateStore) LookupPartial(pth string) *TemplInfo {
- d := s.templateDescriptorFromPath(pth)
- desc := d.Desc
- if desc.Layout != "" {
- panic("shortcode template descriptor must not have a layout")
- }
- best := s.getBest()
- defer s.putBest(best)
- s.findBestMatchGet(s.key(path.Join(containerPartials, d.Path)), CategoryPartial, nil, desc, best)
- return best.templ
+ ti, _ := s.cacheLookupPartials.GetOrCreate(pth, func() (*TemplInfo, error) {
+ d := s.templateDescriptorFromPath(pth)
+ desc := d.Desc
+ if desc.Layout != "" {
+ panic("shortcode template descriptor must not have a layout")
+ }
+ best := s.getBest()
+ defer s.putBest(best)
+ s.findBestMatchGet(s.key(path.Join(containerPartials, d.Path)), CategoryPartial, nil, desc, best)
+ return best.templ, nil
+ })
+
+ return ti
}
func (s *TemplateStore) LookupShortcode(q TemplateQuery) *TemplInfo {
})
}
+func (s *TemplateStore) clearCaches() {
+ s.cacheLookupPartials.Reset()
+}
+
// RefreshFiles refreshes this store for the files matching the given predicate.
func (s *TemplateStore) RefreshFiles(include func(fi hugofs.FileMetaInfo) bool) error {
+ s.clearCaches()
+
if err := s.tns.createPrototypesParse(); err != nil {
return err
}
// templateDescriptorFromPath returns a template descriptor from the given path.
// This is currently used in partial lookups only.
func (s *TemplateStore) templateDescriptorFromPath(pth string) PathTemplateDescriptor {
- // Check cache first.
- d, _ := s.templateDescriptorByPath.GetOrCreate(pth, func() (PathTemplateDescriptor, error) {
- var (
- mt media.Type
- of output.Format
- )
-
- // Common cases.
- dotCount := strings.Count(pth, ".")
- if dotCount <= 1 {
- if dotCount == 0 {
- // Asume HTML.
- of, mt = s.resolveOutputFormatAndOrMediaType("html", "")
- } else {
- pth = strings.TrimPrefix(pth, "/")
- ext := path.Ext(pth)
- pth = strings.TrimSuffix(pth, ext)
- ext = ext[1:]
- of, mt = s.resolveOutputFormatAndOrMediaType("", ext)
- }
+ var (
+ mt media.Type
+ of output.Format
+ )
+
+ // Common cases.
+ dotCount := strings.Count(pth, ".")
+ if dotCount <= 1 {
+ if dotCount == 0 {
+ // Asume HTML.
+ of, mt = s.resolveOutputFormatAndOrMediaType("html", "")
} else {
- path := s.opts.PathParser.Parse(files.ComponentFolderLayouts, pth)
- pth = path.PathNoIdentifier()
- of, mt = s.resolveOutputFormatAndOrMediaType(path.OutputFormat(), path.Ext())
- }
-
- return PathTemplateDescriptor{
- Path: pth,
- Desc: TemplateDescriptor{
- OutputFormat: of.Name,
- MediaType: mt.Type,
- IsPlainText: of.IsPlainText,
- },
- }, nil
- })
+ pth = strings.TrimPrefix(pth, "/")
+ ext := path.Ext(pth)
+ pth = strings.TrimSuffix(pth, ext)
+ ext = ext[1:]
+ of, mt = s.resolveOutputFormatAndOrMediaType("", ext)
+ }
+ } else {
+ path := s.opts.PathParser.Parse(files.ComponentFolderLayouts, pth)
+ pth = path.PathNoIdentifier()
+ of, mt = s.resolveOutputFormatAndOrMediaType(path.OutputFormat(), path.Ext())
+ }
- return d
+ return PathTemplateDescriptor{
+ Path: pth,
+ Desc: TemplateDescriptor{
+ OutputFormat: of.Name,
+ MediaType: mt.Type,
+ IsPlainText: of.IsPlainText,
+ },
+ }
}
// resolveOutputFormatAndOrMediaType resolves the output format and/or media type
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/resources/kinds"
+ "github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/tpl/tplimpl"
)
disableKinds = ["taxonomy", "term", "home"]
-- layouts/all.html --
{{ .Title }}|
- {{ partial "p1.html" . }}
+{{ partial "p1.html" . }}
-- layouts/_partials/p1.html --
p1.
{{ partial "p2.html" . }}
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := store.ExecuteWithContext(context.Background(), ti, io.Discard, p)
- bb.Assert(err, qt.IsNil)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkLookupPartial(b *testing.B) {
+ files := `
+-- hugo.toml --
+disableKinds = ["taxonomy", "term", "home"]
+-- layouts/all.html --
+{{ .Title }}|
+-- layouts/_partials/p1.html --
+-- layouts/_partials/p2.html --
+-- layouts/_partials/p2.json --
+-- layouts/_partials/p3.html --
+`
+ bb := hugolib.Test(b, files)
+
+ store := bb.H.TemplateStore
+
+ for i := 0; i < b.N; i++ {
+ fi := store.LookupPartial("p3.html")
+ if fi == nil {
+ b.Fatal("not found")
+ }
}
}
+
+// Implemented by pageOutput.
+type getDescriptorProvider interface {
+ GetInternalTemplateBasePathAndDescriptor() (string, tplimpl.TemplateDescriptor)
+}
+
+func BenchmarkLookupShortcode(b *testing.B) {
+ files := `
+-- hugo.toml --
+disableKinds = ["taxonomy", "term", "home"]
+-- content/toplevelpage.md --
+-- content/a/b/c/nested.md --
+-- layouts/all.html --
+{{ .Title }}|
+-- layouts/_shortcodes/s.html --
+s1.
+-- layouts/_shortcodes/a/b/s.html --
+s2.
+
+`
+ bb := hugolib.Test(b, files)
+ store := bb.H.TemplateStore
+
+ runOne := func(p page.Page) {
+ pth, desc := p.(getDescriptorProvider).GetInternalTemplateBasePathAndDescriptor()
+ q := tplimpl.TemplateQuery{
+ Path: pth,
+ Name: "s",
+ Category: tplimpl.CategoryShortcode,
+ Desc: desc,
+ }
+ v := store.LookupShortcode(q)
+ if v == nil {
+ b.Fatal("not found")
+ }
+ }
+
+ b.Run("toplevelpage", func(b *testing.B) {
+ toplevelpage, _ := bb.H.Sites[0].GetPage("/toplevelpage")
+ for i := 0; i < b.N; i++ {
+ runOne(toplevelpage)
+ }
+ })
+
+ b.Run("nestedpage", func(b *testing.B) {
+ toplevelpage, _ := bb.H.Sites[0].GetPage("/a/b/c/nested")
+ for i := 0; i < b.N; i++ {
+ runOne(toplevelpage)
+ }
+ })
+}