summaryrefslogtreecommitdiffstats
path: root/tpl/partials
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2019-12-02 21:10:27 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2019-12-03 00:13:47 +0100
commit0efb00c2a86ec3f52000a643f26f54bb2a9dfbd6 (patch)
tree370d64135277eaa825ead0c25d33e99eb218087e /tpl/partials
parent40a092b0687d44ecb53ef1fd53001a6299345780 (diff)
tpl/partials: Allow any key type in partialCached
Fixes #6572
Diffstat (limited to 'tpl/partials')
-rw-r--r--tpl/partials/partials.go74
-rw-r--r--tpl/partials/partials_test.go41
2 files changed, 100 insertions, 15 deletions
diff --git a/tpl/partials/partials.go b/tpl/partials/partials.go
index 2599a5d01..da42acb20 100644
--- a/tpl/partials/partials.go
+++ b/tpl/partials/partials.go
@@ -16,14 +16,18 @@
package partials
import (
+ "errors"
"fmt"
"html/template"
"io"
"io/ioutil"
+ "reflect"
"strings"
"sync"
texttemplate "text/template"
+ "github.com/gohugoio/hugo/helpers"
+
"github.com/gohugoio/hugo/tpl"
bp "github.com/gohugoio/hugo/bufferpool"
@@ -34,21 +38,26 @@ import (
// NOTE: It's currently unused.
var TestTemplateProvider deps.ResourceProvider
+type partialCacheKey struct {
+ name string
+ variant interface{}
+}
+
// partialCache represents a cache of partials protected by a mutex.
type partialCache struct {
sync.RWMutex
- p map[string]interface{}
+ p map[partialCacheKey]interface{}
}
func (p *partialCache) clear() {
p.Lock()
defer p.Unlock()
- p.p = make(map[string]interface{})
+ p.p = make(map[partialCacheKey]interface{})
}
// New returns a new instance of the templates-namespaced template functions.
func New(deps *deps.Deps) *Namespace {
- cache := &partialCache{p: make(map[string]interface{})}
+ cache := &partialCache{p: make(map[partialCacheKey]interface{})}
deps.BuildStartListeners.Add(
func() {
cache.clear()
@@ -151,21 +160,56 @@ func (ns *Namespace) Include(name string, contextList ...interface{}) (interface
}
-// IncludeCached executes and caches partial templates. An optional variant
-// string parameter (a string slice actually, but be only use a variadic
-// argument to make it optional) can be passed so that a given partial can have
-// multiple uses. The cache is created with name+variant as the key.
-func (ns *Namespace) IncludeCached(name string, context interface{}, variant ...string) (interface{}, error) {
- key := name
- if len(variant) > 0 {
- for i := 0; i < len(variant); i++ {
- key += variant[i]
+// IncludeCached executes and caches partial templates. The cache is created with name+variants as the key.
+func (ns *Namespace) IncludeCached(name string, context interface{}, variants ...interface{}) (interface{}, error) {
+ key, err := createKey(name, variants...)
+ if err != nil {
+ return nil, err
+ }
+
+ result, err := ns.getOrCreate(key, context)
+ if err == errUnHashable {
+ // Try one more
+ key.variant = helpers.HashString(key.variant)
+ result, err = ns.getOrCreate(key, context)
+ }
+
+ return result, err
+}
+
+func createKey(name string, variants ...interface{}) (partialCacheKey, error) {
+ var variant interface{}
+
+ if len(variants) > 1 {
+ variant = helpers.HashString(variants...)
+ } else if len(variants) == 1 {
+ variant = variants[0]
+ t := reflect.TypeOf(variant)
+ switch t.Kind() {
+ // This isn't an exhaustive list of unhashable types.
+ // There may be structs with slices,
+ // but that should be very rare. We do recover from that situation
+ // below.
+ case reflect.Slice, reflect.Array, reflect.Map:
+ variant = helpers.HashString(variant)
}
}
- return ns.getOrCreate(key, name, context)
+
+ return partialCacheKey{name: name, variant: variant}, nil
}
-func (ns *Namespace) getOrCreate(key, name string, context interface{}) (interface{}, error) {
+var errUnHashable = errors.New("unhashable")
+
+func (ns *Namespace) getOrCreate(key partialCacheKey, context interface{}) (result interface{}, err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ err = r.(error)
+ if strings.Contains(err.Error(), "unhashable type") {
+ ns.cachedPartials.RUnlock()
+ err = errUnHashable
+ }
+ }
+ }()
ns.cachedPartials.RLock()
p, ok := ns.cachedPartials.p[key]
@@ -175,7 +219,7 @@ func (ns *Namespace) getOrCreate(key, name string, context interface{}) (interfa
return p, nil
}
- p, err := ns.Include(name, context)
+ p, err = ns.Include(key.name, context)
if err != nil {
return nil, err
}
diff --git a/tpl/partials/partials_test.go b/tpl/partials/partials_test.go
new file mode 100644
index 000000000..60e9dd721
--- /dev/null
+++ b/tpl/partials/partials_test.go
@@ -0,0 +1,41 @@
+// 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 partials
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestCreateKey(t *testing.T) {
+ c := qt.New(t)
+ m := make(map[interface{}]bool)
+
+ create := func(name string, variants ...interface{}) partialCacheKey {
+ k, err := createKey(name, variants...)
+ c.Assert(err, qt.IsNil)
+ m[k] = true
+ return k
+ }
+
+ for i := 0; i < 123; i++ {
+ c.Assert(create("a", "b"), qt.Equals, partialCacheKey{name: "a", variant: "b"})
+ c.Assert(create("a", "b", "c"), qt.Equals, partialCacheKey{name: "a", variant: "9629524865311698396"})
+ c.Assert(create("a", 1), qt.Equals, partialCacheKey{name: "a", variant: 1})
+ c.Assert(create("a", map[string]string{"a": "av"}), qt.Equals, partialCacheKey{name: "a", variant: "4809626101226749924"})
+ c.Assert(create("a", []string{"a", "b"}), qt.Equals, partialCacheKey{name: "a", variant: "2712570657419664240"})
+ }
+
+}