github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/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  	"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 c and returns a new slice with the function fname applied over it.
    28  func (ns *Namespace) Apply(ctx context.Context, c any, fname string, args ...any) (any, error) {
    29  	if c == 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(c)
    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(ctx, 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", c)
    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(ctx context.Context, 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(ctx, namespace)
   118  	if !found {
   119  		return reflect.Value{}, false
   120  	}
   121  
   122  	fn, ok := nv.Interface().(func(context.Context, ...any) (any, error))
   123  	if !ok {
   124  		return reflect.Value{}, false
   125  	}
   126  	v, err := fn(ctx)
   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  }