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  }