github.com/neohugo/neohugo@v0.123.8/tpl/tplimpl/template_funcs.go (about) 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/neohugo/neohugo/common/hreflect" 24 "github.com/neohugo/neohugo/common/maps" 25 "github.com/neohugo/neohugo/identity" 26 "github.com/neohugo/neohugo/tpl" 27 28 template "github.com/neohugo/neohugo/tpl/internal/go_templates/htmltemplate" 29 texttemplate "github.com/neohugo/neohugo/tpl/internal/go_templates/texttemplate" 30 31 "github.com/neohugo/neohugo/deps" 32 33 "github.com/neohugo/neohugo/tpl/internal" 34 35 // Init the namespaces 36 _ "github.com/neohugo/neohugo/tpl/cast" 37 _ "github.com/neohugo/neohugo/tpl/collections" 38 _ "github.com/neohugo/neohugo/tpl/compare" 39 _ "github.com/neohugo/neohugo/tpl/crypto" 40 _ "github.com/neohugo/neohugo/tpl/css" 41 _ "github.com/neohugo/neohugo/tpl/data" 42 _ "github.com/neohugo/neohugo/tpl/debug" 43 _ "github.com/neohugo/neohugo/tpl/diagrams" 44 _ "github.com/neohugo/neohugo/tpl/encoding" 45 _ "github.com/neohugo/neohugo/tpl/fmt" 46 _ "github.com/neohugo/neohugo/tpl/hugo" 47 _ "github.com/neohugo/neohugo/tpl/images" 48 _ "github.com/neohugo/neohugo/tpl/inflect" 49 _ "github.com/neohugo/neohugo/tpl/js" 50 _ "github.com/neohugo/neohugo/tpl/lang" 51 _ "github.com/neohugo/neohugo/tpl/math" 52 _ "github.com/neohugo/neohugo/tpl/openapi/openapi3" 53 _ "github.com/neohugo/neohugo/tpl/os" 54 _ "github.com/neohugo/neohugo/tpl/page" 55 _ "github.com/neohugo/neohugo/tpl/partials" 56 _ "github.com/neohugo/neohugo/tpl/path" 57 _ "github.com/neohugo/neohugo/tpl/reflect" 58 _ "github.com/neohugo/neohugo/tpl/resources" 59 _ "github.com/neohugo/neohugo/tpl/safe" 60 _ "github.com/neohugo/neohugo/tpl/site" 61 _ "github.com/neohugo/neohugo/tpl/strings" 62 _ "github.com/neohugo/neohugo/tpl/templates" 63 _ "github.com/neohugo/neohugo/tpl/time" 64 _ "github.com/neohugo/neohugo/tpl/transform" 65 _ "github.com/neohugo/neohugo/tpl/urls" 66 ) 67 68 var ( 69 _ texttemplate.ExecHelper = (*templateExecHelper)(nil) 70 zero reflect.Value 71 ) 72 73 type templateExecHelper struct { 74 running bool // whether we're in server mode. 75 site reflect.Value 76 siteParams reflect.Value 77 funcs map[string]reflect.Value 78 } 79 80 func (t *templateExecHelper) GetFunc(ctx context.Context, tmpl texttemplate.Preparer, name string) (fn reflect.Value, firstArg reflect.Value, found bool) { 81 if fn, found := t.funcs[name]; found { 82 if fn.Type().NumIn() > 0 { 83 first := fn.Type().In(0) 84 if hreflect.IsContextType(first) { 85 // TODO(bep) check if we can void this conversion every time -- and if that matters. 86 // The first argument may be context.Context. This is never provided by the end user, but it's used to pass down 87 // contextual information, e.g. the top level data context (e.g. Page). 88 return fn, reflect.ValueOf(ctx), true 89 } 90 } 91 92 return fn, zero, true 93 } 94 return zero, zero, false 95 } 96 97 func (t *templateExecHelper) Init(ctx context.Context, tmpl texttemplate.Preparer) { 98 if t.running { 99 _, ok := tmpl.(identity.IdentityProvider) 100 if ok { 101 t.trackDependencies(ctx, tmpl, "", reflect.Value{}) 102 } 103 104 } 105 } 106 107 func (t *templateExecHelper) GetMapValue(ctx context.Context, tmpl texttemplate.Preparer, receiver, key reflect.Value) (reflect.Value, bool) { 108 if params, ok := receiver.Interface().(maps.Params); ok { 109 // Case insensitive. 110 keystr := strings.ToLower(key.String()) 111 v, found := params[keystr] 112 if !found { 113 return zero, false 114 } 115 return reflect.ValueOf(v), true 116 } 117 118 v := receiver.MapIndex(key) 119 120 return v, v.IsValid() 121 } 122 123 var typeParams = reflect.TypeOf(maps.Params{}) 124 125 func (t *templateExecHelper) GetMethod(ctx context.Context, tmpl texttemplate.Preparer, receiver reflect.Value, name string) (method reflect.Value, firstArg reflect.Value) { 126 if strings.EqualFold(name, "mainsections") && receiver.Type() == typeParams && receiver.Pointer() == t.siteParams.Pointer() { 127 // Moved to site.MainSections in Hugo 0.112.0. 128 receiver = t.site 129 name = "MainSections" 130 } 131 132 if t.running { 133 ctx = t.trackDependencies(ctx, tmpl, name, receiver) 134 } 135 136 fn := hreflect.GetMethodByName(receiver, name) 137 if !fn.IsValid() { 138 return zero, zero 139 } 140 141 if fn.Type().NumIn() > 0 { 142 first := fn.Type().In(0) 143 if hreflect.IsContextType(first) { 144 // The first argument may be context.Context. This is never provided by the end user, but it's used to pass down 145 // contextual information, e.g. the top level data context (e.g. Page). 146 return fn, reflect.ValueOf(ctx) 147 } 148 } 149 150 return fn, zero 151 } 152 153 func (t *templateExecHelper) OnCalled(ctx context.Context, tmpl texttemplate.Preparer, name string, args []reflect.Value, result reflect.Value) { 154 if !t.running { 155 return 156 } 157 158 // This switch is mostly for speed. 159 switch name { 160 case "Unmarshal": 161 default: 162 return 163 } 164 idm := tpl.Context.GetDependencyManagerInCurrentScope(ctx) 165 if idm == nil { 166 return 167 } 168 169 for _, arg := range args { 170 identity.WalkIdentitiesShallow(arg.Interface(), func(level int, id identity.Identity) bool { 171 idm.AddIdentity(id) 172 return false 173 }) 174 } 175 } 176 177 func (t *templateExecHelper) trackDependencies(ctx context.Context, tmpl texttemplate.Preparer, name string, receiver reflect.Value) context.Context { 178 if tmpl == nil { 179 panic("must provide a template") 180 } 181 182 idm := tpl.Context.GetDependencyManagerInCurrentScope(ctx) 183 if idm == nil { 184 return ctx 185 } 186 187 if info, ok := tmpl.(identity.IdentityProvider); ok { 188 idm.AddIdentity(info.GetIdentity()) 189 } 190 191 // The receive is the "." in the method execution or map lookup, e.g. the Page in .Resources. 192 if hreflect.IsValid(receiver) { 193 in := receiver.Interface() 194 195 if idlp, ok := in.(identity.ForEeachIdentityByNameProvider); ok { 196 // This will skip repeated .RelPermalink usage on transformed resources 197 // which is not fingerprinted, e.g. to 198 // prevent all HTML pages to be re-rendered on a small CSS change. 199 idlp.ForEeachIdentityByName(name, func(id identity.Identity) bool { 200 idm.AddIdentity(id) 201 return false 202 }) 203 } else { 204 identity.WalkIdentitiesShallow(in, func(level int, id identity.Identity) bool { 205 idm.AddIdentity(id) 206 return false 207 }) 208 } 209 } 210 211 return ctx 212 } 213 214 func newTemplateExecuter(d *deps.Deps) (texttemplate.Executer, map[string]reflect.Value) { 215 funcs := createFuncMap(d) 216 funcsv := make(map[string]reflect.Value) 217 218 for k, v := range funcs { 219 vv := reflect.ValueOf(v) 220 funcsv[k] = vv 221 } 222 223 // Duplicate Go's internal funcs here for faster lookups. 224 for k, v := range template.GoFuncs { 225 if _, exists := funcsv[k]; !exists { 226 vv, ok := v.(reflect.Value) 227 if !ok { 228 vv = reflect.ValueOf(v) 229 } 230 funcsv[k] = vv 231 } 232 } 233 234 for k, v := range texttemplate.GoFuncs { 235 if _, exists := funcsv[k]; !exists { 236 funcsv[k] = v 237 } 238 } 239 240 exeHelper := &templateExecHelper{ 241 running: d.Conf.Running(), 242 funcs: funcsv, 243 site: reflect.ValueOf(d.Site), 244 siteParams: reflect.ValueOf(d.Site.Params()), 245 } 246 247 return texttemplate.NewExecuter( 248 exeHelper, 249 ), funcsv 250 } 251 252 func createFuncMap(d *deps.Deps) map[string]any { 253 funcMap := template.FuncMap{} 254 255 // Merge the namespace funcs 256 for _, nsf := range internal.TemplateFuncsNamespaceRegistry { 257 ns := nsf(d) 258 if _, exists := funcMap[ns.Name]; exists { 259 panic(ns.Name + " is a duplicate template func") 260 } 261 funcMap[ns.Name] = ns.Context 262 for _, mm := range ns.MethodMappings { 263 for _, alias := range mm.Aliases { 264 if _, exists := funcMap[alias]; exists { 265 panic(alias + " is a duplicate template func") 266 } 267 funcMap[alias] = mm.Method 268 } 269 } 270 } 271 272 if d.OverloadedTemplateFuncs != nil { 273 for k, v := range d.OverloadedTemplateFuncs { 274 funcMap[k] = v 275 } 276 } 277 278 return funcMap 279 }