apply.go (4064B)
1 // Copyright 2017 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package collections 15 16 import ( 17 "context" 18 "errors" 19 "fmt" 20 "reflect" 21 "strings" 22 23 "github.com/gohugoio/hugo/common/hreflect" 24 "github.com/gohugoio/hugo/tpl" 25 ) 26 27 // Apply takes a map, array, or slice and returns a new slice with the function fname applied over it. 28 func (ns *Namespace) Apply(ctx context.Context, seq any, fname string, args ...any) (any, error) { 29 if seq == nil { 30 return make([]any, 0), nil 31 } 32 33 if fname == "apply" { 34 return nil, errors.New("can't apply myself (no turtles allowed)") 35 } 36 37 seqv := reflect.ValueOf(seq) 38 seqv, isNil := indirect(seqv) 39 if isNil { 40 return nil, errors.New("can't iterate over a nil value") 41 } 42 43 fnv, found := ns.lookupFunc(fname) 44 if !found { 45 return nil, errors.New("can't find function " + fname) 46 } 47 48 switch seqv.Kind() { 49 case reflect.Array, reflect.Slice: 50 r := make([]any, seqv.Len()) 51 for i := 0; i < seqv.Len(); i++ { 52 vv := seqv.Index(i) 53 54 vvv, err := applyFnToThis(ctx, fnv, vv, args...) 55 if err != nil { 56 return nil, err 57 } 58 59 r[i] = vvv.Interface() 60 } 61 62 return r, nil 63 default: 64 return nil, fmt.Errorf("can't apply over %v", seq) 65 } 66 } 67 68 func applyFnToThis(ctx context.Context, fn, this reflect.Value, args ...any) (reflect.Value, error) { 69 num := fn.Type().NumIn() 70 if num > 0 && fn.Type().In(0).Implements(hreflect.ContextInterface) { 71 args = append([]any{ctx}, args...) 72 } 73 74 n := make([]reflect.Value, len(args)) 75 for i, arg := range args { 76 if arg == "." { 77 n[i] = this 78 } else { 79 n[i] = reflect.ValueOf(arg) 80 } 81 } 82 83 if fn.Type().IsVariadic() { 84 num-- 85 } 86 87 // TODO(bep) see #1098 - also see template_tests.go 88 /*if len(args) < num { 89 return reflect.ValueOf(nil), errors.New("Too few arguments") 90 } else if len(args) > num { 91 return reflect.ValueOf(nil), errors.New("Too many arguments") 92 }*/ 93 94 for i := 0; i < num; i++ { 95 // AssignableTo reports whether xt is assignable to type targ. 96 if xt, targ := n[i].Type(), fn.Type().In(i); !xt.AssignableTo(targ) { 97 return reflect.ValueOf(nil), errors.New("called apply using " + xt.String() + " as type " + targ.String()) 98 } 99 } 100 101 res := fn.Call(n) 102 103 if len(res) == 1 || res[1].IsNil() { 104 return res[0], nil 105 } 106 return reflect.ValueOf(nil), res[1].Interface().(error) 107 } 108 109 func (ns *Namespace) lookupFunc(fname string) (reflect.Value, bool) { 110 namespace, methodName, ok := strings.Cut(fname, ".") 111 if !ok { 112 templ := ns.deps.Tmpl().(tpl.TemplateFuncGetter) 113 return templ.GetFunc(fname) 114 } 115 116 // Namespace 117 nv, found := ns.lookupFunc(namespace) 118 if !found { 119 return reflect.Value{}, false 120 } 121 122 fn, ok := nv.Interface().(func(...any) (any, error)) 123 if !ok { 124 return reflect.Value{}, false 125 } 126 v, err := fn() 127 if err != nil { 128 panic(err) 129 } 130 nv = reflect.ValueOf(v) 131 132 // method 133 m := hreflect.GetMethodByName(nv, methodName) 134 135 if m.Kind() == reflect.Invalid { 136 return reflect.Value{}, false 137 } 138 return m, true 139 } 140 141 // indirect is borrowed from the Go stdlib: 'text/template/exec.go' 142 func indirect(v reflect.Value) (rv reflect.Value, isNil bool) { 143 for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() { 144 if v.IsNil() { 145 return v, true 146 } 147 if v.Kind() == reflect.Interface && v.NumMethod() > 0 { 148 break 149 } 150 } 151 return v, false 152 } 153 154 func indirectInterface(v reflect.Value) (rv reflect.Value, isNil bool) { 155 for ; v.Kind() == reflect.Interface; v = v.Elem() { 156 if v.IsNil() { 157 return v, true 158 } 159 if v.Kind() == reflect.Interface && v.NumMethod() > 0 { 160 break 161 } 162 } 163 return v, false 164 }