apply.go - hugo - [fork] hugo port for 9front
HTML git clone https://git.drkhsh.at/hugo.git
DIR Log
DIR Files
DIR Refs
DIR Submodules
DIR README
DIR LICENSE
---
apply.go (4016B)
---
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 )
25
26 // Apply takes an array or slice c and returns a new slice with the function fname applied over it.
27 func (ns *Namespace) Apply(ctx context.Context, c any, fname string, args ...any) (any, error) {
28 if c == nil {
29 return make([]any, 0), nil
30 }
31
32 if fname == "apply" {
33 return nil, errors.New("can't apply myself (no turtles allowed)")
34 }
35
36 seqv := reflect.ValueOf(c)
37 seqv, isNil := indirect(seqv)
38 if isNil {
39 return nil, errors.New("can't iterate over a nil value")
40 }
41
42 fnv, found := ns.lookupFunc(ctx, fname)
43 if !found {
44 return nil, errors.New("can't find function " + fname)
45 }
46
47 switch seqv.Kind() {
48 case reflect.Array, reflect.Slice:
49 r := make([]any, seqv.Len())
50 for i := range seqv.Len() {
51 vv := seqv.Index(i)
52
53 vvv, err := applyFnToThis(ctx, fnv, vv, args...)
54 if err != nil {
55 return nil, err
56 }
57
58 r[i] = vvv.Interface()
59 }
60
61 return r, nil
62 default:
63 return nil, fmt.Errorf("can't apply over %v", c)
64 }
65 }
66
67 func applyFnToThis(ctx context.Context, fn, this reflect.Value, args ...any) (reflect.Value, error) {
68 num := fn.Type().NumIn()
69 if num > 0 && hreflect.IsContextType(fn.Type().In(0)) {
70 args = append([]any{ctx}, args...)
71 }
72
73 n := make([]reflect.Value, len(args))
74 for i, arg := range args {
75 if arg == "." {
76 n[i] = this
77 } else {
78 n[i] = reflect.ValueOf(arg)
79 }
80 }
81
82 if fn.Type().IsVariadic() {
83 num--
84 }
85
86 // TODO(bep) see #1098 - also see template_tests.go
87 /*if len(args) < num {
88 return reflect.ValueOf(nil), errors.New("Too few arguments")
89 } else if len(args) > num {
90 return reflect.ValueOf(nil), errors.New("Too many arguments")
91 }*/
92
93 for i := range num {
94 // AssignableTo reports whether xt is assignable to type targ.
95 if xt, targ := n[i].Type(), fn.Type().In(i); !xt.AssignableTo(targ) {
96 return reflect.ValueOf(nil), errors.New("called apply using " + xt.String() + " as type " + targ.String())
97 }
98 }
99
100 res := fn.Call(n)
101
102 if len(res) == 1 || res[1].IsNil() {
103 return res[0], nil
104 }
105 return reflect.ValueOf(nil), res[1].Interface().(error)
106 }
107
108 func (ns *Namespace) lookupFunc(ctx context.Context, fname string) (reflect.Value, bool) {
109 namespace, methodName, ok := strings.Cut(fname, ".")
110 if !ok {
111 return ns.deps.GetTemplateStore().GetFunc(fname)
112 }
113
114 // Namespace
115 nv, found := ns.lookupFunc(ctx, namespace)
116 if !found {
117 return reflect.Value{}, false
118 }
119
120 fn, ok := nv.Interface().(func(context.Context, ...any) (any, error))
121 if !ok {
122 return reflect.Value{}, false
123 }
124 v, err := fn(ctx)
125 if err != nil {
126 panic(err)
127 }
128 nv = reflect.ValueOf(v)
129
130 // method
131 m := hreflect.GetMethodByName(nv, methodName)
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 }