URI: 
       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 }