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