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 }