URI: 
       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 }