github.com/shohhei1126/hugo@v0.42.2-0.20180623210752-3d5928889ad7/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 22 // Index returns the result of indexing its first argument by the following 23 // arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each 24 // indexed item must be a map, slice, or array. 25 // 26 // Copied from Go stdlib src/text/template/funcs.go. 27 // 28 // We deviate from the stdlib due to https://github.com/golang/go/issues/14751. 29 // 30 // TODO(moorereason): merge upstream changes. 31 func (ns *Namespace) Index(item interface{}, indices ...interface{}) (interface{}, error) { 32 v := reflect.ValueOf(item) 33 if !v.IsValid() { 34 return nil, errors.New("index of untyped nil") 35 } 36 for _, i := range indices { 37 index := reflect.ValueOf(i) 38 var isNil bool 39 if v, isNil = indirect(v); isNil { 40 return nil, errors.New("index of nil pointer") 41 } 42 switch v.Kind() { 43 case reflect.Array, reflect.Slice, reflect.String: 44 var x int64 45 switch index.Kind() { 46 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 47 x = index.Int() 48 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 49 x = int64(index.Uint()) 50 case reflect.Invalid: 51 return nil, errors.New("cannot index slice/array with nil") 52 default: 53 return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type()) 54 } 55 if x < 0 || x >= int64(v.Len()) { 56 // We deviate from stdlib here. Don't return an error if the 57 // index is out of range. 58 return nil, nil 59 } 60 v = v.Index(int(x)) 61 case reflect.Map: 62 index, err := prepareArg(index, v.Type().Key()) 63 if err != nil { 64 return nil, err 65 } 66 if x := v.MapIndex(index); x.IsValid() { 67 v = x 68 } else { 69 v = reflect.Zero(v.Type().Elem()) 70 } 71 case reflect.Invalid: 72 // the loop holds invariant: v.IsValid() 73 panic("unreachable") 74 default: 75 return nil, fmt.Errorf("can't index item of type %s", v.Type()) 76 } 77 } 78 return v.Interface(), nil 79 } 80 81 // prepareArg checks if value can be used as an argument of type argType, and 82 // converts an invalid value to appropriate zero if possible. 83 // 84 // Copied from Go stdlib src/text/template/funcs.go. 85 func prepareArg(value reflect.Value, argType reflect.Type) (reflect.Value, error) { 86 if !value.IsValid() { 87 if !canBeNil(argType) { 88 return reflect.Value{}, fmt.Errorf("value is nil; should be of type %s", argType) 89 } 90 value = reflect.Zero(argType) 91 } 92 if !value.Type().AssignableTo(argType) { 93 return reflect.Value{}, fmt.Errorf("value has type %s; should be %s", value.Type(), argType) 94 } 95 return value, nil 96 } 97 98 // canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero. 99 // 100 // Copied from Go stdlib src/text/template/exec.go. 101 func canBeNil(typ reflect.Type) bool { 102 switch typ.Kind() { 103 case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: 104 return true 105 } 106 return false 107 }