github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/tpl/collections/index.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 "errors" 18 "fmt" 19 "reflect" 20 21 "github.com/spf13/cast" 22 23 "github.com/gohugoio/hugo/common/maps" 24 ) 25 26 // Index returns the result of indexing its first argument by the following 27 // arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each 28 // indexed item must be a map, slice, or array. 29 // 30 // Adapted from Go stdlib src/text/template/funcs.go. 31 // 32 // We deviate from the stdlib mostly because of https://github.com/golang/go/issues/14751. 33 func (ns *Namespace) Index(item any, args ...any) (any, error) { 34 v, err := ns.doIndex(item, args...) 35 if err != nil { 36 return nil, fmt.Errorf("index of type %T with args %v failed: %s", item, args, err) 37 } 38 return v, nil 39 } 40 41 func (ns *Namespace) doIndex(item any, args ...any) (any, error) { 42 // TODO(moorereason): merge upstream changes. 43 v := reflect.ValueOf(item) 44 if !v.IsValid() { 45 // See issue 10489 46 // This used to be an error. 47 return nil, nil 48 } 49 50 var indices []any 51 52 if len(args) == 1 { 53 v := reflect.ValueOf(args[0]) 54 if v.Kind() == reflect.Slice { 55 for i := 0; i < v.Len(); i++ { 56 indices = append(indices, v.Index(i).Interface()) 57 } 58 } else { 59 indices = append(indices, args[0]) 60 } 61 } else { 62 indices = args 63 } 64 65 lowerm, ok := item.(maps.Params) 66 if ok { 67 return lowerm.Get(cast.ToStringSlice(indices)...), nil 68 } 69 70 for _, i := range indices { 71 index := reflect.ValueOf(i) 72 var isNil bool 73 if v, isNil = indirect(v); isNil { 74 // See issue 10489 75 // This used to be an error. 76 return nil, nil 77 } 78 switch v.Kind() { 79 case reflect.Array, reflect.Slice, reflect.String: 80 var x int64 81 switch index.Kind() { 82 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 83 x = index.Int() 84 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 85 x = int64(index.Uint()) 86 case reflect.Invalid: 87 return nil, errors.New("cannot index slice/array with nil") 88 default: 89 return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type()) 90 } 91 if x < 0 || x >= int64(v.Len()) { 92 // We deviate from stdlib here. Don't return an error if the 93 // index is out of range. 94 return nil, nil 95 } 96 v = v.Index(int(x)) 97 case reflect.Map: 98 index, err := prepareArg(index, v.Type().Key()) 99 if err != nil { 100 return nil, err 101 } 102 103 if x := v.MapIndex(index); x.IsValid() { 104 v = x 105 } else { 106 v = reflect.Zero(v.Type().Elem()) 107 } 108 case reflect.Invalid: 109 // the loop holds invariant: v.IsValid() 110 panic("unreachable") 111 default: 112 return nil, fmt.Errorf("can't index item of type %s", v.Type()) 113 } 114 } 115 return v.Interface(), nil 116 } 117 118 // prepareArg checks if value can be used as an argument of type argType, and 119 // converts an invalid value to appropriate zero if possible. 120 // 121 // Copied from Go stdlib src/text/template/funcs.go. 122 func prepareArg(value reflect.Value, argType reflect.Type) (reflect.Value, error) { 123 if !value.IsValid() { 124 if !canBeNil(argType) { 125 return reflect.Value{}, fmt.Errorf("value is nil; should be of type %s", argType) 126 } 127 value = reflect.Zero(argType) 128 } 129 if !value.Type().AssignableTo(argType) { 130 return reflect.Value{}, fmt.Errorf("value has type %s; should be %s", value.Type(), argType) 131 } 132 return value, nil 133 } 134 135 // canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero. 136 // 137 // Copied from Go stdlib src/text/template/exec.go. 138 func canBeNil(typ reflect.Type) bool { 139 switch typ.Kind() { 140 case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: 141 return true 142 } 143 return false 144 }