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 }