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