github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/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/gohugoio/hugo/tpl" 24 25 "github.com/gohugoio/hugo/common/maps" 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 // This is a hot path and receiver.MethodByName really shows up in the benchmarks, 115 // so we maintain a list of method names with that signature. 116 // TODO(bep) I have a branch that makes this construct superflous. 117 switch name { 118 case "GetPage", "Render": 119 if info, ok := tmpl.(tpl.Info); ok { 120 if m := receiver.MethodByName(name + "WithTemplateInfo"); m.IsValid() { 121 return m, reflect.ValueOf(info) 122 } 123 } 124 } 125 } 126 127 fn := receiver.MethodByName(name) 128 if !fn.IsValid() { 129 return zero, zero 130 } 131 132 if fn.Type().NumIn() > 0 { 133 first := fn.Type().In(0) 134 if first.Implements(contextInterface) { 135 // The first argument may be context.Context. This is never provided by the end user, but it's used to pass down 136 // contextual information, e.g. the top level data context (e.g. Page). 137 return fn, reflect.ValueOf(ctx) 138 } 139 } 140 141 return fn, zero 142 } 143 144 func newTemplateExecuter(d *deps.Deps) (texttemplate.Executer, map[string]reflect.Value) { 145 funcs := createFuncMap(d) 146 funcsv := make(map[string]reflect.Value) 147 148 for k, v := range funcs { 149 vv := reflect.ValueOf(v) 150 funcsv[k] = vv 151 } 152 153 // Duplicate Go's internal funcs here for faster lookups. 154 for k, v := range template.GoFuncs { 155 if _, exists := funcsv[k]; !exists { 156 vv, ok := v.(reflect.Value) 157 if !ok { 158 vv = reflect.ValueOf(v) 159 } 160 funcsv[k] = vv 161 } 162 } 163 164 for k, v := range texttemplate.GoFuncs { 165 if _, exists := funcsv[k]; !exists { 166 funcsv[k] = v 167 } 168 } 169 170 exeHelper := &templateExecHelper{ 171 running: d.Running, 172 funcs: funcsv, 173 } 174 175 return texttemplate.NewExecuter( 176 exeHelper, 177 ), funcsv 178 } 179 180 func createFuncMap(d *deps.Deps) map[string]interface{} { 181 funcMap := template.FuncMap{} 182 183 // Merge the namespace funcs 184 for _, nsf := range internal.TemplateFuncsNamespaceRegistry { 185 ns := nsf(d) 186 if _, exists := funcMap[ns.Name]; exists { 187 panic(ns.Name + " is a duplicate template func") 188 } 189 funcMap[ns.Name] = ns.Context 190 for _, mm := range ns.MethodMappings { 191 for _, alias := range mm.Aliases { 192 if _, exists := funcMap[alias]; exists { 193 panic(alias + " is a duplicate template func") 194 } 195 funcMap[alias] = mm.Method 196 } 197 } 198 } 199 200 if d.OverloadedTemplateFuncs != nil { 201 for k, v := range d.OverloadedTemplateFuncs { 202 funcMap[k] = v 203 } 204 } 205 206 return funcMap 207 }