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 }