helpers.go (5850B)
1 // Copyright 2019 The Hugo Authors. All rights reserved. 2 // Some functions in this file (see comments) is based on the Go source code, 3 // copyright The Go Authors and governed by a BSD-style license. 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 // Package hreflect contains reflect helpers. 17 package hreflect 18 19 import ( 20 "context" 21 "reflect" 22 "sync" 23 "time" 24 25 "github.com/gohugoio/hugo/common/htime" 26 "github.com/gohugoio/hugo/common/types" 27 ) 28 29 // TODO(bep) replace the private versions in /tpl with these. 30 // IsNumber returns whether the given kind is a number. 31 func IsNumber(kind reflect.Kind) bool { 32 return IsInt(kind) || IsUint(kind) || IsFloat(kind) 33 } 34 35 // IsInt returns whether the given kind is an int. 36 func IsInt(kind reflect.Kind) bool { 37 switch kind { 38 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 39 return true 40 default: 41 return false 42 } 43 } 44 45 // IsUint returns whether the given kind is an uint. 46 func IsUint(kind reflect.Kind) bool { 47 switch kind { 48 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 49 return true 50 default: 51 return false 52 } 53 } 54 55 // IsFloat returns whether the given kind is a float. 56 func IsFloat(kind reflect.Kind) bool { 57 switch kind { 58 case reflect.Float32, reflect.Float64: 59 return true 60 default: 61 return false 62 } 63 } 64 65 // IsTruthful returns whether in represents a truthful value. 66 // See IsTruthfulValue 67 func IsTruthful(in any) bool { 68 switch v := in.(type) { 69 case reflect.Value: 70 return IsTruthfulValue(v) 71 default: 72 return IsTruthfulValue(reflect.ValueOf(in)) 73 } 74 } 75 76 var zeroType = reflect.TypeOf((*types.Zeroer)(nil)).Elem() 77 78 // IsTruthfulValue returns whether the given value has a meaningful truth value. 79 // This is based on template.IsTrue in Go's stdlib, but also considers 80 // IsZero and any interface value will be unwrapped before it's considered 81 // for truthfulness. 82 // 83 // Based on: 84 // https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L306 85 func IsTruthfulValue(val reflect.Value) (truth bool) { 86 val = indirectInterface(val) 87 88 if !val.IsValid() { 89 // Something like var x interface{}, never set. It's a form of nil. 90 return 91 } 92 93 if val.Type().Implements(zeroType) { 94 return !val.Interface().(types.Zeroer).IsZero() 95 } 96 97 switch val.Kind() { 98 case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 99 truth = val.Len() > 0 100 case reflect.Bool: 101 truth = val.Bool() 102 case reflect.Complex64, reflect.Complex128: 103 truth = val.Complex() != 0 104 case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface: 105 truth = !val.IsNil() 106 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 107 truth = val.Int() != 0 108 case reflect.Float32, reflect.Float64: 109 truth = val.Float() != 0 110 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 111 truth = val.Uint() != 0 112 case reflect.Struct: 113 truth = true // Struct values are always true. 114 default: 115 return 116 } 117 118 return 119 } 120 121 type methodKey struct { 122 typ reflect.Type 123 name string 124 } 125 126 type methods struct { 127 sync.RWMutex 128 cache map[methodKey]int 129 } 130 131 var methodCache = &methods{cache: make(map[methodKey]int)} 132 133 // GetMethodByName is the same as reflect.Value.MethodByName, but it caches the 134 // type lookup. 135 func GetMethodByName(v reflect.Value, name string) reflect.Value { 136 index := GetMethodIndexByName(v.Type(), name) 137 138 if index == -1 { 139 return reflect.Value{} 140 } 141 142 return v.Method(index) 143 } 144 145 // GetMethodIndexByName returns the index of the method with the given name, or 146 // -1 if no such method exists. 147 func GetMethodIndexByName(tp reflect.Type, name string) int { 148 k := methodKey{tp, name} 149 methodCache.RLock() 150 index, found := methodCache.cache[k] 151 methodCache.RUnlock() 152 if found { 153 return index 154 } 155 156 methodCache.Lock() 157 defer methodCache.Unlock() 158 159 m, ok := tp.MethodByName(name) 160 index = m.Index 161 if !ok { 162 index = -1 163 } 164 methodCache.cache[k] = index 165 166 if !ok { 167 return -1 168 } 169 170 return m.Index 171 } 172 173 var ( 174 timeType = reflect.TypeOf((*time.Time)(nil)).Elem() 175 asTimeProviderType = reflect.TypeOf((*htime.AsTimeProvider)(nil)).Elem() 176 ) 177 178 // IsTime returns whether tp is a time.Time type or if it can be converted into one 179 // in ToTime. 180 func IsTime(tp reflect.Type) bool { 181 if tp == timeType { 182 return true 183 } 184 185 if tp.Implements(asTimeProviderType) { 186 return true 187 } 188 return false 189 } 190 191 // AsTime returns v as a time.Time if possible. 192 // The given location is only used if the value implements AsTimeProvider (e.g. go-toml local). 193 // A zero Time and false is returned if this isn't possible. 194 // Note that this function does not accept string dates. 195 func AsTime(v reflect.Value, loc *time.Location) (time.Time, bool) { 196 if v.Kind() == reflect.Interface { 197 return AsTime(v.Elem(), loc) 198 } 199 200 if v.Type() == timeType { 201 return v.Interface().(time.Time), true 202 } 203 204 if v.Type().Implements(asTimeProviderType) { 205 return v.Interface().(htime.AsTimeProvider).AsTime(loc), true 206 } 207 208 return time.Time{}, false 209 } 210 211 // Based on: https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L931 212 func indirectInterface(v reflect.Value) reflect.Value { 213 if v.Kind() != reflect.Interface { 214 return v 215 } 216 if v.IsNil() { 217 return reflect.Value{} 218 } 219 return v.Elem() 220 } 221 222 var ContextInterface = reflect.TypeOf((*context.Context)(nil)).Elem()