hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

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 }