github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/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 // method 121 m := nv.MethodByName(ss[1]) 122 // if reflect.DeepEqual(m, reflect.Value{}) { 123 if m.Kind() == reflect.Invalid { 124 return reflect.Value{}, false 125 } 126 return m, true 127 } 128 129 // indirect is borrowed from the Go stdlib: 'text/template/exec.go' 130 func indirect(v reflect.Value) (rv reflect.Value, isNil bool) { 131 for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() { 132 if v.IsNil() { 133 return v, true 134 } 135 if v.Kind() == reflect.Interface && v.NumMethod() > 0 { 136 break 137 } 138 } 139 return v, false 140 } 141 142 func indirectInterface(v reflect.Value) (rv reflect.Value, isNil bool) { 143 for ; 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 }