hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

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 }