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 }