github.com/shohhei1126/hugo@v0.42.2-0.20180623210752-3d5928889ad7/tpl/partials/partials.go (about) 1 // Copyright 2017 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package partials 15 16 import ( 17 "fmt" 18 "html/template" 19 "strings" 20 "sync" 21 texttemplate "text/template" 22 23 bp "github.com/gohugoio/hugo/bufferpool" 24 "github.com/gohugoio/hugo/deps" 25 ) 26 27 // TestTemplateProvider is global deps.ResourceProvider. 28 // NOTE: It's currently unused. 29 var TestTemplateProvider deps.ResourceProvider 30 31 // partialCache represents a cache of partials protected by a mutex. 32 type partialCache struct { 33 sync.RWMutex 34 p map[string]interface{} 35 } 36 37 // New returns a new instance of the templates-namespaced template functions. 38 func New(deps *deps.Deps) *Namespace { 39 return &Namespace{ 40 deps: deps, 41 cachedPartials: partialCache{p: make(map[string]interface{})}, 42 } 43 } 44 45 // Namespace provides template functions for the "templates" namespace. 46 type Namespace struct { 47 deps *deps.Deps 48 cachedPartials partialCache 49 } 50 51 // Include executes the named partial and returns either a string, 52 // when the partial is a text/template, or template.HTML when html/template. 53 func (ns *Namespace) Include(name string, contextList ...interface{}) (interface{}, error) { 54 if strings.HasPrefix("partials/", name) { 55 name = name[8:] 56 } 57 var context interface{} 58 59 if len(contextList) == 0 { 60 context = nil 61 } else { 62 context = contextList[0] 63 } 64 65 for _, n := range []string{"partials/" + name, "theme/partials/" + name} { 66 templ := ns.deps.Tmpl.Lookup(n) 67 if templ == nil { 68 // For legacy reasons. 69 templ = ns.deps.Tmpl.Lookup(n + ".html") 70 } 71 if templ != nil { 72 b := bp.GetBuffer() 73 defer bp.PutBuffer(b) 74 75 if err := templ.Execute(b, context); err != nil { 76 return "", err 77 } 78 79 if _, ok := templ.Template.(*texttemplate.Template); ok { 80 s := b.String() 81 if ns.deps.Metrics != nil { 82 ns.deps.Metrics.TrackValue(n, s) 83 } 84 return s, nil 85 } 86 87 s := b.String() 88 if ns.deps.Metrics != nil { 89 ns.deps.Metrics.TrackValue(n, s) 90 } 91 return template.HTML(s), nil 92 93 } 94 } 95 96 return "", fmt.Errorf("Partial %q not found", name) 97 } 98 99 // IncludeCached executes and caches partial templates. An optional variant 100 // string parameter (a string slice actually, but be only use a variadic 101 // argument to make it optional) can be passed so that a given partial can have 102 // multiple uses. The cache is created with name+variant as the key. 103 func (ns *Namespace) IncludeCached(name string, context interface{}, variant ...string) (interface{}, error) { 104 key := name 105 if len(variant) > 0 { 106 for i := 0; i < len(variant); i++ { 107 key += variant[i] 108 } 109 } 110 return ns.getOrCreate(key, name, context) 111 } 112 113 func (ns *Namespace) getOrCreate(key, name string, context interface{}) (interface{}, error) { 114 115 ns.cachedPartials.RLock() 116 p, ok := ns.cachedPartials.p[key] 117 ns.cachedPartials.RUnlock() 118 119 if ok { 120 return p, nil 121 } 122 123 p, err := ns.Include(name, context) 124 if err != nil { 125 return nil, err 126 } 127 128 ns.cachedPartials.Lock() 129 defer ns.cachedPartials.Unlock() 130 // Double-check. 131 if p2, ok := ns.cachedPartials.p[key]; ok { 132 return p2, nil 133 } 134 ns.cachedPartials.p[key] = p 135 136 return p, nil 137 }