hugo

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

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

reflect_helpers.go (4988B)

    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 	"fmt"
   18 	"reflect"
   19 
   20 	"errors"
   21 
   22 	"github.com/mitchellh/hashstructure"
   23 )
   24 
   25 var (
   26 	zero      reflect.Value
   27 	errorType = reflect.TypeOf((*error)(nil)).Elem()
   28 )
   29 
   30 func numberToFloat(v reflect.Value) (float64, error) {
   31 	switch kind := v.Kind(); {
   32 	case isFloat(kind):
   33 		return v.Float(), nil
   34 	case isInt(kind):
   35 		return float64(v.Int()), nil
   36 	case isUint(kind):
   37 		return float64(v.Uint()), nil
   38 	case kind == reflect.Interface:
   39 		return numberToFloat(v.Elem())
   40 	default:
   41 		return 0, fmt.Errorf("invalid kind %s in numberToFloat", kind)
   42 	}
   43 }
   44 
   45 // normalizes different numeric types if isNumber
   46 // or get the hash values if not Comparable (such as map or struct)
   47 // to make them comparable
   48 func normalize(v reflect.Value) any {
   49 	k := v.Kind()
   50 
   51 	switch {
   52 	case !v.Type().Comparable():
   53 		h, err := hashstructure.Hash(v.Interface(), nil)
   54 		if err != nil {
   55 			panic(err)
   56 		}
   57 		return h
   58 	case isNumber(k):
   59 		f, err := numberToFloat(v)
   60 		if err == nil {
   61 			return f
   62 		}
   63 	}
   64 	return v.Interface()
   65 }
   66 
   67 // collects identities from the slices in seqs into a set. Numeric values are normalized,
   68 // pointers unwrapped.
   69 func collectIdentities(seqs ...any) (map[any]bool, error) {
   70 	seen := make(map[any]bool)
   71 	for _, seq := range seqs {
   72 		v := reflect.ValueOf(seq)
   73 		switch v.Kind() {
   74 		case reflect.Array, reflect.Slice:
   75 			for i := 0; i < v.Len(); i++ {
   76 				ev, _ := indirectInterface(v.Index(i))
   77 
   78 				if !ev.Type().Comparable() {
   79 					return nil, errors.New("elements must be comparable")
   80 				}
   81 
   82 				seen[normalize(ev)] = true
   83 			}
   84 		default:
   85 			return nil, fmt.Errorf("arguments must be slices or arrays")
   86 		}
   87 	}
   88 
   89 	return seen, nil
   90 }
   91 
   92 // We have some different numeric and string types that we try to behave like
   93 // they were the same.
   94 func convertValue(v reflect.Value, to reflect.Type) (reflect.Value, error) {
   95 	if v.Type().AssignableTo(to) {
   96 		return v, nil
   97 	}
   98 	switch kind := to.Kind(); {
   99 	case kind == reflect.String:
  100 		s, err := toString(v)
  101 		return reflect.ValueOf(s), err
  102 	case isNumber(kind):
  103 		return convertNumber(v, kind)
  104 	default:
  105 		return reflect.Value{}, fmt.Errorf("%s is not assignable to %s", v.Type(), to)
  106 	}
  107 }
  108 
  109 // There are potential overflows in this function, but the downconversion of
  110 // int64 etc. into int8 etc. is coming from the synthetic unit tests for Union etc.
  111 // TODO(bep) We should consider normalizing the slices to int64 etc.
  112 func convertNumber(v reflect.Value, to reflect.Kind) (reflect.Value, error) {
  113 	var n reflect.Value
  114 	if isFloat(to) {
  115 		f, err := toFloat(v)
  116 		if err != nil {
  117 			return n, err
  118 		}
  119 		switch to {
  120 		case reflect.Float32:
  121 			n = reflect.ValueOf(float32(f))
  122 		default:
  123 			n = reflect.ValueOf(float64(f))
  124 		}
  125 	} else if isInt(to) {
  126 		i, err := toInt(v)
  127 		if err != nil {
  128 			return n, err
  129 		}
  130 		switch to {
  131 		case reflect.Int:
  132 			n = reflect.ValueOf(int(i))
  133 		case reflect.Int8:
  134 			n = reflect.ValueOf(int8(i))
  135 		case reflect.Int16:
  136 			n = reflect.ValueOf(int16(i))
  137 		case reflect.Int32:
  138 			n = reflect.ValueOf(int32(i))
  139 		case reflect.Int64:
  140 			n = reflect.ValueOf(int64(i))
  141 		}
  142 	} else if isUint(to) {
  143 		i, err := toUint(v)
  144 		if err != nil {
  145 			return n, err
  146 		}
  147 		switch to {
  148 		case reflect.Uint:
  149 			n = reflect.ValueOf(uint(i))
  150 		case reflect.Uint8:
  151 			n = reflect.ValueOf(uint8(i))
  152 		case reflect.Uint16:
  153 			n = reflect.ValueOf(uint16(i))
  154 		case reflect.Uint32:
  155 			n = reflect.ValueOf(uint32(i))
  156 		case reflect.Uint64:
  157 			n = reflect.ValueOf(uint64(i))
  158 		}
  159 
  160 	}
  161 
  162 	if !n.IsValid() {
  163 		return n, errors.New("invalid values")
  164 	}
  165 
  166 	return n, nil
  167 }
  168 
  169 func newSliceElement(items any) any {
  170 	tp := reflect.TypeOf(items)
  171 	if tp == nil {
  172 		return nil
  173 	}
  174 	switch tp.Kind() {
  175 	case reflect.Array, reflect.Slice:
  176 		tp = tp.Elem()
  177 		if tp.Kind() == reflect.Ptr {
  178 			tp = tp.Elem()
  179 		}
  180 
  181 		return reflect.New(tp).Interface()
  182 	}
  183 	return nil
  184 }
  185 
  186 func isNumber(kind reflect.Kind) bool {
  187 	return isInt(kind) || isUint(kind) || isFloat(kind)
  188 }
  189 
  190 func isInt(kind reflect.Kind) bool {
  191 	switch kind {
  192 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  193 		return true
  194 	default:
  195 		return false
  196 	}
  197 }
  198 
  199 func isUint(kind reflect.Kind) bool {
  200 	switch kind {
  201 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  202 		return true
  203 	default:
  204 		return false
  205 	}
  206 }
  207 
  208 func isFloat(kind reflect.Kind) bool {
  209 	switch kind {
  210 	case reflect.Float32, reflect.Float64:
  211 		return true
  212 	default:
  213 		return false
  214 	}
  215 }