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