template_funcs.go (5993B)
1 // Copyright 2017-present The Hugo Authors. All rights reserved.
2 //
3 // Portions Copyright The Go Authors.
4
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15
16 package tplimpl
17
18 import (
19 "context"
20 "reflect"
21 "strings"
22
23 "github.com/gohugoio/hugo/common/hreflect"
24 "github.com/gohugoio/hugo/common/maps"
25 "github.com/gohugoio/hugo/tpl"
26
27 template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
28 texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
29
30 "github.com/gohugoio/hugo/deps"
31
32 "github.com/gohugoio/hugo/tpl/internal"
33
34 // Init the namespaces
35 _ "github.com/gohugoio/hugo/tpl/cast"
36 _ "github.com/gohugoio/hugo/tpl/collections"
37 _ "github.com/gohugoio/hugo/tpl/compare"
38 _ "github.com/gohugoio/hugo/tpl/crypto"
39 _ "github.com/gohugoio/hugo/tpl/data"
40 _ "github.com/gohugoio/hugo/tpl/debug"
41 _ "github.com/gohugoio/hugo/tpl/diagrams"
42 _ "github.com/gohugoio/hugo/tpl/encoding"
43 _ "github.com/gohugoio/hugo/tpl/fmt"
44 _ "github.com/gohugoio/hugo/tpl/hugo"
45 _ "github.com/gohugoio/hugo/tpl/images"
46 _ "github.com/gohugoio/hugo/tpl/inflect"
47 _ "github.com/gohugoio/hugo/tpl/js"
48 _ "github.com/gohugoio/hugo/tpl/lang"
49 _ "github.com/gohugoio/hugo/tpl/math"
50 _ "github.com/gohugoio/hugo/tpl/openapi/openapi3"
51 _ "github.com/gohugoio/hugo/tpl/os"
52 _ "github.com/gohugoio/hugo/tpl/partials"
53 _ "github.com/gohugoio/hugo/tpl/path"
54 _ "github.com/gohugoio/hugo/tpl/reflect"
55 _ "github.com/gohugoio/hugo/tpl/resources"
56 _ "github.com/gohugoio/hugo/tpl/safe"
57 _ "github.com/gohugoio/hugo/tpl/site"
58 _ "github.com/gohugoio/hugo/tpl/strings"
59 _ "github.com/gohugoio/hugo/tpl/templates"
60 _ "github.com/gohugoio/hugo/tpl/time"
61 _ "github.com/gohugoio/hugo/tpl/transform"
62 _ "github.com/gohugoio/hugo/tpl/urls"
63 )
64
65 var (
66 _ texttemplate.ExecHelper = (*templateExecHelper)(nil)
67 zero reflect.Value
68 contextInterface = reflect.TypeOf((*context.Context)(nil)).Elem()
69 )
70
71 type templateExecHelper struct {
72 running bool // whether we're in server mode.
73 funcs map[string]reflect.Value
74 }
75
76 func (t *templateExecHelper) GetFunc(ctx context.Context, tmpl texttemplate.Preparer, name string) (fn reflect.Value, firstArg reflect.Value, found bool) {
77 if fn, found := t.funcs[name]; found {
78 if fn.Type().NumIn() > 0 {
79 first := fn.Type().In(0)
80 if first.Implements(contextInterface) {
81 // TODO(bep) check if we can void this conversion every time -- and if that matters.
82 // The first argument may be context.Context. This is never provided by the end user, but it's used to pass down
83 // contextual information, e.g. the top level data context (e.g. Page).
84 return fn, reflect.ValueOf(ctx), true
85 }
86 }
87
88 return fn, zero, true
89 }
90 return zero, zero, false
91 }
92
93 func (t *templateExecHelper) Init(ctx context.Context, tmpl texttemplate.Preparer) {
94 }
95
96 func (t *templateExecHelper) GetMapValue(ctx context.Context, tmpl texttemplate.Preparer, receiver, key reflect.Value) (reflect.Value, bool) {
97 if params, ok := receiver.Interface().(maps.Params); ok {
98 // Case insensitive.
99 keystr := strings.ToLower(key.String())
100 v, found := params[keystr]
101 if !found {
102 return zero, false
103 }
104 return reflect.ValueOf(v), true
105 }
106
107 v := receiver.MapIndex(key)
108
109 return v, v.IsValid()
110 }
111
112 func (t *templateExecHelper) GetMethod(ctx context.Context, tmpl texttemplate.Preparer, receiver reflect.Value, name string) (method reflect.Value, firstArg reflect.Value) {
113 if t.running {
114 switch name {
115 case "GetPage", "Render":
116 if info, ok := tmpl.(tpl.Info); ok {
117 if m := receiver.MethodByName(name + "WithTemplateInfo"); m.IsValid() {
118 return m, reflect.ValueOf(info)
119 }
120 }
121 }
122 }
123
124 fn := hreflect.GetMethodByName(receiver, name)
125 if !fn.IsValid() {
126 return zero, zero
127 }
128
129 if fn.Type().NumIn() > 0 {
130 first := fn.Type().In(0)
131 if first.Implements(contextInterface) {
132 // The first argument may be context.Context. This is never provided by the end user, but it's used to pass down
133 // contextual information, e.g. the top level data context (e.g. Page).
134 return fn, reflect.ValueOf(ctx)
135 }
136 }
137
138 return fn, zero
139 }
140
141 func newTemplateExecuter(d *deps.Deps) (texttemplate.Executer, map[string]reflect.Value) {
142 funcs := createFuncMap(d)
143 funcsv := make(map[string]reflect.Value)
144
145 for k, v := range funcs {
146 vv := reflect.ValueOf(v)
147 funcsv[k] = vv
148 }
149
150 // Duplicate Go's internal funcs here for faster lookups.
151 for k, v := range template.GoFuncs {
152 if _, exists := funcsv[k]; !exists {
153 vv, ok := v.(reflect.Value)
154 if !ok {
155 vv = reflect.ValueOf(v)
156 }
157 funcsv[k] = vv
158 }
159 }
160
161 for k, v := range texttemplate.GoFuncs {
162 if _, exists := funcsv[k]; !exists {
163 funcsv[k] = v
164 }
165 }
166
167 exeHelper := &templateExecHelper{
168 running: d.Running,
169 funcs: funcsv,
170 }
171
172 return texttemplate.NewExecuter(
173 exeHelper,
174 ), funcsv
175 }
176
177 func createFuncMap(d *deps.Deps) map[string]any {
178 funcMap := template.FuncMap{}
179
180 // Merge the namespace funcs
181 for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
182 ns := nsf(d)
183 if _, exists := funcMap[ns.Name]; exists {
184 panic(ns.Name + " is a duplicate template func")
185 }
186 funcMap[ns.Name] = ns.Context
187 for _, mm := range ns.MethodMappings {
188 for _, alias := range mm.Aliases {
189 if _, exists := funcMap[alias]; exists {
190 panic(alias + " is a duplicate template func")
191 }
192 funcMap[alias] = mm.Method
193 }
194 }
195 }
196
197 if d.OverloadedTemplateFuncs != nil {
198 for k, v := range d.OverloadedTemplateFuncs {
199 funcMap[k] = v
200 }
201 }
202
203 return funcMap
204 }