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  }