github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/tpl/collections/apply.go (about) 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 "errors" 18 "fmt" 19 "reflect" 20 "strings" 21 22 "github.com/gohugoio/hugo/tpl" 23 ) 24 25 // Apply takes a map, array, or slice and returns a new slice with the function fname applied over it. 26 func (ns *Namespace) Apply(seq interface{}, fname string, args ...interface{}) (interface{}, error) { 27 if seq == nil { 28 return make([]interface{}, 0), nil 29 } 30 31 if fname == "apply" { 32 return nil, errors.New("can't apply myself (no turtles allowed)") 33 } 34 35 seqv := reflect.ValueOf(seq) 36 seqv, isNil := indirect(seqv) 37 if isNil { 38 return nil, errors.New("can't iterate over a nil value") 39 } 40 41 fnv, found := ns.lookupFunc(fname) 42 if !found { 43 return nil, errors.New("can't find function " + fname) 44 } 45 46 // fnv := reflect.ValueOf(fn) 47 48 switch seqv.Kind() { 49 case reflect.Array, reflect.Slice: 50 r := make([]interface{}, seqv.Len()) 51 for i := 0; i < seqv.Len(); i++ { 52 vv := seqv.Index(i) 53 54 vvv, err := applyFnToThis(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(fn, this reflect.Value, args ...interface{}) (reflect.Value, error) { 69 n := make([]reflect.Value, len(args)) 70 for i, arg := range args { 71 if arg == "." { 72 n[i] = this 73 } else { 74 n[i] = reflect.ValueOf(arg) 75 } 76 } 77 78 num := fn.Type().NumIn() 79 80 if fn.Type().IsVariadic() { 81 num-- 82 } 83 84 // TODO(bep) see #1098 - also see template_tests.go 85 /*if len(args) < num { 86 return reflect.ValueOf(nil), errors.New("Too few arguments") 87 } else if len(args) > num { 88 return reflect.ValueOf(nil), errors.New("Too many arguments") 89 }*/ 90 91 for i := 0; i < num; i++ { 92 // AssignableTo reports whether xt is assignable to type targ. 93 if xt, targ := n[i].Type(), fn.Type().In(i); !xt.AssignableTo(targ) { 94 return reflect.ValueOf(nil), errors.New("called apply using " + xt.String() + " as type " + targ.String()) 95 } 96 } 97 98 res := fn.Call(n) 99 100 if len(res) == 1 || res[1].IsNil() { 101 return res[0], nil 102 } 103 return reflect.ValueOf(nil), res[1].Interface().(error) 104 } 105 106 func (ns *Namespace) lookupFunc(fname string) (reflect.Value, bool) { 107 if !strings.ContainsRune(fname, '.') { 108 templ := ns.deps.Tmpl().(tpl.TemplateFuncGetter) 109 return templ.GetFunc(fname) 110 } 111 112 ss := strings.SplitN(fname, ".", 2) 113 114 // Namespace 115 nv, found := ns.lookupFunc(ss[0]) 116 if !found { 117 return reflect.Value{}, false 118 } 119 120 fn, ok := nv.Interface().(func(...interface{}) (interface{}, error)) 121 if !ok { 122 return reflect.Value{}, false 123 } 124 v, err := fn() 125 if err != nil { 126 panic(err) 127 } 128 nv = reflect.ValueOf(v) 129 130 // method 131 m := nv.MethodByName(ss[1]) 132 133 if m.Kind() == reflect.Invalid { 134 return reflect.Value{}, false 135 } 136 return m, true 137 } 138 139 // indirect is borrowed from the Go stdlib: 'text/template/exec.go' 140 func indirect(v reflect.Value) (rv reflect.Value, isNil bool) { 141 for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() { 142 if v.IsNil() { 143 return v, true 144 } 145 if v.Kind() == reflect.Interface && v.NumMethod() > 0 { 146 break 147 } 148 } 149 return v, false 150 } 151 152 func indirectInterface(v reflect.Value) (rv reflect.Value, isNil bool) { 153 for ; v.Kind() == reflect.Interface; v = v.Elem() { 154 if v.IsNil() { 155 return v, true 156 } 157 if v.Kind() == reflect.Interface && v.NumMethod() > 0 { 158 break 159 } 160 } 161 return v, false 162 }