partials.go - hugo - [fork] hugo port for 9front
HTML git clone https://git.drkhsh.at/hugo.git
DIR Log
DIR Files
DIR Refs
DIR Submodules
DIR README
DIR LICENSE
---
partials.go (7263B)
---
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 provides template functions for working with reusable
15 // templates.
16 package partials
17
18 import (
19 "context"
20 "fmt"
21 "html/template"
22 "io"
23 "strings"
24 "time"
25
26 "github.com/bep/lazycache"
27 "github.com/gohugoio/hugo/common/constants"
28 "github.com/gohugoio/hugo/common/hashing"
29 "github.com/gohugoio/hugo/identity"
30 texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
31
32 "github.com/gohugoio/hugo/tpl"
33 "github.com/gohugoio/hugo/tpl/tplimpl"
34
35 bp "github.com/gohugoio/hugo/bufferpool"
36 "github.com/gohugoio/hugo/deps"
37 )
38
39 type partialCacheKey struct {
40 Name string
41 Variants []any
42 }
43 type includeResult struct {
44 name string
45 result any
46 mangager identity.Manager
47 err error
48 }
49
50 func (k partialCacheKey) Key() string {
51 if k.Variants == nil {
52 return k.Name
53 }
54 return hashing.HashString(append([]any{k.Name}, k.Variants...)...)
55 }
56
57 // partialCache represents a LRU cache of partials.
58 type partialCache struct {
59 cache *lazycache.Cache[string, includeResult]
60 }
61
62 func (p *partialCache) clear() {
63 p.cache.DeleteFunc(func(s string, r includeResult) bool {
64 return true
65 })
66 }
67
68 // New returns a new instance of the templates-namespaced template functions.
69 func New(deps *deps.Deps) *Namespace {
70 // This lazycache was introduced in Hugo 0.111.0.
71 // We're going to expand and consolidate all memory caches in Hugo using this,
72 // so just set a high limit for now.
73 lru := lazycache.New(lazycache.Options[string, includeResult]{MaxEntries: 1000})
74
75 cache := &partialCache{cache: lru}
76 deps.BuildStartListeners.Add(
77 func(...any) bool {
78 cache.clear()
79 return false
80 })
81
82 return &Namespace{
83 deps: deps,
84 cachedPartials: cache,
85 }
86 }
87
88 // Namespace provides template functions for the "templates" namespace.
89 type Namespace struct {
90 deps *deps.Deps
91 cachedPartials *partialCache
92 }
93
94 // contextWrapper makes room for a return value in a partial invocation.
95 type contextWrapper struct {
96 Arg any
97 Result any
98 }
99
100 // Set sets the return value and returns an empty string.
101 func (c *contextWrapper) Set(in any) string {
102 c.Result = in
103 return ""
104 }
105
106 // Include executes the named partial.
107 // If the partial contains a return statement, that value will be returned.
108 // Else, the rendered output will be returned:
109 // A string if the partial is a text/template, or template.HTML when html/template.
110 // Note that ctx is provided by Hugo, not the end user.
111 func (ns *Namespace) Include(ctx context.Context, name string, contextList ...any) (any, error) {
112 res := ns.include(ctx, name, contextList...)
113 if res.err != nil {
114 return nil, res.err
115 }
116
117 if ns.deps.Metrics != nil {
118 ns.deps.Metrics.TrackValue(res.name, res.result, false)
119 }
120
121 return res.result, nil
122 }
123
124 func (ns *Namespace) include(ctx context.Context, name string, dataList ...any) includeResult {
125 v, err := ns.lookup(name)
126 if err != nil {
127 return includeResult{err: err}
128 }
129 return ns.doInclude(ctx, v, dataList...)
130 }
131
132 func (ns *Namespace) lookup(name string) (*tplimpl.TemplInfo, error) {
133 if strings.HasPrefix(name, "partials/") {
134 // This is most likely not what the user intended.
135 // This worked before Hugo 0.146.0.
136 ns.deps.Log.Warnidf(constants.WarnPartialSuperfluousPrefix, "Doubtful use of partial function in {{ partial \"%s\"}}), this is most likely not what you want. Consider removing superfluous prefix \"partials/\" from template name given as first function argument.", name)
137 }
138 v := ns.deps.TemplateStore.LookupPartial(name)
139 if v == nil {
140 return nil, fmt.Errorf("partial %q not found", name)
141 }
142 return v, nil
143 }
144
145 // include is a helper function that lookups and executes the named partial.
146 // Returns the final template name and the rendered output.
147 func (ns *Namespace) doInclude(ctx context.Context, templ *tplimpl.TemplInfo, dataList ...any) includeResult {
148 var data any
149 if len(dataList) > 0 {
150 data = dataList[0]
151 }
152
153 info := templ.ParseInfo
154
155 var w io.Writer
156
157 if info.HasReturn {
158 // Wrap the context sent to the template to capture the return value.
159 // Note that the template is rewritten to make sure that the dot (".")
160 // and the $ variable points to Arg.
161 data = &contextWrapper{
162 Arg: data,
163 }
164
165 // We don't care about any template output.
166 w = io.Discard
167 } else {
168 b := bp.GetBuffer()
169 defer bp.PutBuffer(b)
170 w = b
171 }
172
173 if err := ns.deps.GetTemplateStore().ExecuteWithContext(ctx, templ, w, data); err != nil {
174 return includeResult{err: err}
175 }
176
177 var result any
178
179 if ctx, ok := data.(*contextWrapper); ok {
180 result = ctx.Result
181 } else if _, ok := templ.Template.(*texttemplate.Template); ok {
182 result = w.(fmt.Stringer).String()
183 } else {
184 result = template.HTML(w.(fmt.Stringer).String())
185 }
186
187 return includeResult{
188 name: templ.Name(),
189 result: result,
190 }
191 }
192
193 // IncludeCached executes and caches partial templates. The cache is created with name+variants as the key.
194 // Note that ctx is provided by Hugo, not the end user.
195 func (ns *Namespace) IncludeCached(ctx context.Context, name string, context any, variants ...any) (any, error) {
196 start := time.Now()
197 key := partialCacheKey{
198 Name: name,
199 Variants: variants,
200 }
201 depsManagerIn := tpl.Context.GetDependencyManagerInCurrentScope(ctx)
202 ti, err := ns.lookup(name)
203 if err != nil {
204 return nil, err
205 }
206
207 if parent := tpl.Context.CurrentTemplate.Get(ctx); parent != nil {
208 for parent != nil {
209 if parent.CurrentTemplateInfoOps == ti {
210 // This will deadlock if we continue.
211 return nil, fmt.Errorf("circular call stack detected in partial %q", ti.Filename())
212 }
213 parent = parent.Parent
214 }
215 }
216
217 r, found, err := ns.cachedPartials.cache.GetOrCreate(key.Key(), func(string) (includeResult, error) {
218 var depsManagerShared identity.Manager
219 if ns.deps.Conf.Watching() {
220 // We need to create a shared dependency manager to pass downwards
221 // and add those same dependencies to any cached invocation of this partial.
222 depsManagerShared = identity.NewManager("partials")
223 ctx = tpl.Context.DependencyManagerScopedProvider.Set(ctx, depsManagerShared.(identity.DependencyManagerScopedProvider))
224 }
225 r := ns.doInclude(ctx, ti, context)
226 if ns.deps.Conf.Watching() {
227 r.mangager = depsManagerShared
228 }
229 return r, r.err
230 })
231 if err != nil {
232 return nil, err
233 }
234
235 if ns.deps.Metrics != nil {
236 if found {
237 // The templates that gets executed is measured in Execute.
238 // We need to track the time spent in the cache to
239 // get the totals correct.
240 ns.deps.Metrics.MeasureSince(r.name, start)
241 }
242 ns.deps.Metrics.TrackValue(r.name, r.result, found)
243 }
244
245 if r.mangager != nil && depsManagerIn != nil {
246 depsManagerIn.AddIdentity(r.mangager)
247 }
248
249 return r.result, nil
250 }