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()