github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/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/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/css" 40 _ "github.com/gohugoio/hugo/tpl/data" 41 _ "github.com/gohugoio/hugo/tpl/debug" 42 _ "github.com/gohugoio/hugo/tpl/diagrams" 43 _ "github.com/gohugoio/hugo/tpl/encoding" 44 _ "github.com/gohugoio/hugo/tpl/fmt" 45 _ "github.com/gohugoio/hugo/tpl/hugo" 46 _ "github.com/gohugoio/hugo/tpl/images" 47 _ "github.com/gohugoio/hugo/tpl/inflect" 48 _ "github.com/gohugoio/hugo/tpl/js" 49 _ "github.com/gohugoio/hugo/tpl/lang" 50 _ "github.com/gohugoio/hugo/tpl/math" 51 _ "github.com/gohugoio/hugo/tpl/openapi/openapi3" 52 _ "github.com/gohugoio/hugo/tpl/os" 53 _ "github.com/gohugoio/hugo/tpl/page" 54 _ "github.com/gohugoio/hugo/tpl/partials" 55 _ "github.com/gohugoio/hugo/tpl/path" 56 _ "github.com/gohugoio/hugo/tpl/reflect" 57 _ "github.com/gohugoio/hugo/tpl/resources" 58 _ "github.com/gohugoio/hugo/tpl/safe" 59 _ "github.com/gohugoio/hugo/tpl/site" 60 _ "github.com/gohugoio/hugo/tpl/strings" 61 _ "github.com/gohugoio/hugo/tpl/templates" 62 _ "github.com/gohugoio/hugo/tpl/time" 63 _ "github.com/gohugoio/hugo/tpl/transform" 64 _ "github.com/gohugoio/hugo/tpl/urls" 65 ) 66 67 var ( 68 _ texttemplate.ExecHelper = (*templateExecHelper)(nil) 69 zero reflect.Value 70 contextInterface = reflect.TypeOf((*context.Context)(nil)).Elem() 71 ) 72 73 type templateExecHelper struct { 74 running bool // whether we're in server mode. 75 funcs map[string]reflect.Value 76 } 77 78 func (t *templateExecHelper) GetFunc(ctx context.Context, tmpl texttemplate.Preparer, name string) (fn reflect.Value, firstArg reflect.Value, found bool) { 79 if fn, found := t.funcs[name]; found { 80 if fn.Type().NumIn() > 0 { 81 first := fn.Type().In(0) 82 if first.Implements(contextInterface) { 83 // TODO(bep) check if we can void this conversion every time -- and if that matters. 84 // The first argument may be context.Context. This is never provided by the end user, but it's used to pass down 85 // contextual information, e.g. the top level data context (e.g. Page). 86 return fn, reflect.ValueOf(ctx), true 87 } 88 } 89 90 return fn, zero, true 91 } 92 return zero, zero, false 93 } 94 95 func (t *templateExecHelper) Init(ctx context.Context, tmpl texttemplate.Preparer) { 96 } 97 98 func (t *templateExecHelper) GetMapValue(ctx context.Context, tmpl texttemplate.Preparer, receiver, key reflect.Value) (reflect.Value, bool) { 99 if params, ok := receiver.Interface().(maps.Params); ok { 100 // Case insensitive. 101 keystr := strings.ToLower(key.String()) 102 v, found := params[keystr] 103 if !found { 104 return zero, false 105 } 106 return reflect.ValueOf(v), true 107 } 108 109 v := receiver.MapIndex(key) 110 111 return v, v.IsValid() 112 } 113 114 func (t *templateExecHelper) GetMethod(ctx context.Context, tmpl texttemplate.Preparer, receiver reflect.Value, name string) (method reflect.Value, firstArg reflect.Value) { 115 if t.running { 116 switch name { 117 case "GetPage", "Render": 118 if info, ok := tmpl.(tpl.Info); ok { 119 if m := receiver.MethodByName(name + "WithTemplateInfo"); m.IsValid() { 120 return m, reflect.ValueOf(info) 121 } 122 } 123 } 124 } 125 126 fn := hreflect.GetMethodByName(receiver, name) 127 if !fn.IsValid() { 128 return zero, zero 129 } 130 131 if fn.Type().NumIn() > 0 { 132 first := fn.Type().In(0) 133 if first.Implements(contextInterface) { 134 // The first argument may be context.Context. This is never provided by the end user, but it's used to pass down 135 // contextual information, e.g. the top level data context (e.g. Page). 136 return fn, reflect.ValueOf(ctx) 137 } 138 } 139 140 return fn, zero 141 } 142 143 func newTemplateExecuter(d *deps.Deps) (texttemplate.Executer, map[string]reflect.Value) { 144 funcs := createFuncMap(d) 145 funcsv := make(map[string]reflect.Value) 146 147 for k, v := range funcs { 148 vv := reflect.ValueOf(v) 149 funcsv[k] = vv 150 } 151 152 // Duplicate Go's internal funcs here for faster lookups. 153 for k, v := range template.GoFuncs { 154 if _, exists := funcsv[k]; !exists { 155 vv, ok := v.(reflect.Value) 156 if !ok { 157 vv = reflect.ValueOf(v) 158 } 159 funcsv[k] = vv 160 } 161 } 162 163 for k, v := range texttemplate.GoFuncs { 164 if _, exists := funcsv[k]; !exists { 165 funcsv[k] = v 166 } 167 } 168 169 exeHelper := &templateExecHelper{ 170 running: d.Running, 171 funcs: funcsv, 172 } 173 174 return texttemplate.NewExecuter( 175 exeHelper, 176 ), funcsv 177 } 178 179 func createFuncMap(d *deps.Deps) map[string]any { 180 funcMap := template.FuncMap{} 181 182 // Merge the namespace funcs 183 for _, nsf := range internal.TemplateFuncsNamespaceRegistry { 184 ns := nsf(d) 185 if _, exists := funcMap[ns.Name]; exists { 186 panic(ns.Name + " is a duplicate template func") 187 } 188 funcMap[ns.Name] = ns.Context 189 for _, mm := range ns.MethodMappings { 190 for _, alias := range mm.Aliases { 191 if _, exists := funcMap[alias]; exists { 192 panic(alias + " is a duplicate template func") 193 } 194 funcMap[alias] = mm.Method 195 } 196 } 197 } 198 199 if d.OverloadedTemplateFuncs != nil { 200 for k, v := range d.OverloadedTemplateFuncs { 201 funcMap[k] = v 202 } 203 } 204 205 return funcMap 206 }