github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/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  // 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 interface{}, args ...interface{}) (interface{}, 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 []interface{}
    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  }