github.com/neohugo/neohugo@v0.123.8/common/hreflect/helpers.go (about)

     1  // Copyright 2024 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/neohugo/neohugo/common/htime"
    26  	"github.com/neohugo/neohugo/common/maps"
    27  	"github.com/neohugo/neohugo/common/types"
    28  )
    29  
    30  // TODO(bep) replace the private versions in /tpl with these.
    31  // IsNumber returns whether the given kind is a number.
    32  func IsNumber(kind reflect.Kind) bool {
    33  	return IsInt(kind) || IsUint(kind) || IsFloat(kind)
    34  }
    35  
    36  // IsInt returns whether the given kind is an int.
    37  func IsInt(kind reflect.Kind) bool {
    38  	switch kind {
    39  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
    40  		return true
    41  	default:
    42  		return false
    43  	}
    44  }
    45  
    46  // IsUint returns whether the given kind is an uint.
    47  func IsUint(kind reflect.Kind) bool {
    48  	switch kind {
    49  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
    50  		return true
    51  	default:
    52  		return false
    53  	}
    54  }
    55  
    56  // IsFloat returns whether the given kind is a float.
    57  func IsFloat(kind reflect.Kind) bool {
    58  	switch kind {
    59  	case reflect.Float32, reflect.Float64:
    60  		return true
    61  	default:
    62  		return false
    63  	}
    64  }
    65  
    66  // IsTruthful returns whether in represents a truthful value.
    67  // See IsTruthfulValue
    68  func IsTruthful(in any) bool {
    69  	switch v := in.(type) {
    70  	case reflect.Value:
    71  		return IsTruthfulValue(v)
    72  	default:
    73  		return IsTruthfulValue(reflect.ValueOf(in))
    74  	}
    75  }
    76  
    77  var zeroType = reflect.TypeOf((*types.Zeroer)(nil)).Elem()
    78  
    79  // IsTruthfulValue returns whether the given value has a meaningful truth value.
    80  // This is based on template.IsTrue in Go's stdlib, but also considers
    81  // IsZero and any interface value will be unwrapped before it's considered
    82  // for truthfulness.
    83  //
    84  // Based on:
    85  // https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L306
    86  func IsTruthfulValue(val reflect.Value) (truth bool) {
    87  	val = indirectInterface(val)
    88  
    89  	if !val.IsValid() {
    90  		// Something like var x interface{}, never set. It's a form of nil.
    91  		return
    92  	}
    93  
    94  	if val.Type().Implements(zeroType) {
    95  		return !val.Interface().(types.Zeroer).IsZero()
    96  	}
    97  
    98  	switch val.Kind() {
    99  	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
   100  		truth = val.Len() > 0
   101  	case reflect.Bool:
   102  		truth = val.Bool()
   103  	case reflect.Complex64, reflect.Complex128:
   104  		truth = val.Complex() != 0
   105  	case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
   106  		truth = !val.IsNil()
   107  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   108  		truth = val.Int() != 0
   109  	case reflect.Float32, reflect.Float64:
   110  		truth = val.Float() != 0
   111  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
   112  		truth = val.Uint() != 0
   113  	case reflect.Struct:
   114  		truth = true // Struct values are always true.
   115  	default:
   116  		return
   117  	}
   118  
   119  	return
   120  }
   121  
   122  type methodKey struct {
   123  	typ  reflect.Type
   124  	name string
   125  }
   126  
   127  type methods struct {
   128  	sync.RWMutex
   129  	cache map[methodKey]int
   130  }
   131  
   132  var methodCache = &methods{cache: make(map[methodKey]int)}
   133  
   134  // GetMethodByName is the same as reflect.Value.MethodByName, but it caches the
   135  // type lookup.
   136  func GetMethodByName(v reflect.Value, name string) reflect.Value {
   137  	index := GetMethodIndexByName(v.Type(), name)
   138  
   139  	if index == -1 {
   140  		return reflect.Value{}
   141  	}
   142  
   143  	return v.Method(index)
   144  }
   145  
   146  // GetMethodIndexByName returns the index of the method with the given name, or
   147  // -1 if no such method exists.
   148  func GetMethodIndexByName(tp reflect.Type, name string) int {
   149  	k := methodKey{tp, name}
   150  	methodCache.RLock()
   151  	index, found := methodCache.cache[k]
   152  	methodCache.RUnlock()
   153  	if found {
   154  		return index
   155  	}
   156  
   157  	methodCache.Lock()
   158  	defer methodCache.Unlock()
   159  
   160  	m, ok := tp.MethodByName(name)
   161  	index = m.Index
   162  	if !ok {
   163  		index = -1
   164  	}
   165  	methodCache.cache[k] = index
   166  
   167  	if !ok {
   168  		return -1
   169  	}
   170  
   171  	return m.Index
   172  }
   173  
   174  var (
   175  	timeType           = reflect.TypeOf((*time.Time)(nil)).Elem()
   176  	asTimeProviderType = reflect.TypeOf((*htime.AsTimeProvider)(nil)).Elem()
   177  )
   178  
   179  // IsTime returns whether tp is a time.Time type or if it can be converted into one
   180  // in ToTime.
   181  func IsTime(tp reflect.Type) bool {
   182  	if tp == timeType {
   183  		return true
   184  	}
   185  
   186  	if tp.Implements(asTimeProviderType) {
   187  		return true
   188  	}
   189  	return false
   190  }
   191  
   192  // IsValid returns whether v is not nil and a valid value.
   193  func IsValid(v reflect.Value) bool {
   194  	if !v.IsValid() {
   195  		return false
   196  	}
   197  
   198  	switch v.Kind() {
   199  	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
   200  		return !v.IsNil()
   201  	}
   202  
   203  	return true
   204  }
   205  
   206  // AsTime returns v as a time.Time if possible.
   207  // The given location is only used if the value implements AsTimeProvider (e.g. go-toml local).
   208  // A zero Time and false is returned if this isn't possible.
   209  // Note that this function does not accept string dates.
   210  func AsTime(v reflect.Value, loc *time.Location) (time.Time, bool) {
   211  	if v.Kind() == reflect.Interface {
   212  		return AsTime(v.Elem(), loc)
   213  	}
   214  
   215  	if v.Type() == timeType {
   216  		return v.Interface().(time.Time), true
   217  	}
   218  
   219  	if v.Type().Implements(asTimeProviderType) {
   220  		return v.Interface().(htime.AsTimeProvider).AsTime(loc), true
   221  	}
   222  
   223  	return time.Time{}, false
   224  }
   225  
   226  func CallMethodByName(cxt context.Context, name string, v reflect.Value) []reflect.Value {
   227  	fn := v.MethodByName(name)
   228  	var args []reflect.Value
   229  	tp := fn.Type()
   230  	if tp.NumIn() > 0 {
   231  		if tp.NumIn() > 1 {
   232  			panic("not supported")
   233  		}
   234  		first := tp.In(0)
   235  		if IsContextType(first) {
   236  			args = append(args, reflect.ValueOf(cxt))
   237  		}
   238  	}
   239  
   240  	return fn.Call(args)
   241  }
   242  
   243  // Based on: https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L931
   244  func indirectInterface(v reflect.Value) reflect.Value {
   245  	if v.Kind() != reflect.Interface {
   246  		return v
   247  	}
   248  	if v.IsNil() {
   249  		return reflect.Value{}
   250  	}
   251  	return v.Elem()
   252  }
   253  
   254  var contextInterface = reflect.TypeOf((*context.Context)(nil)).Elem()
   255  
   256  var isContextCache = maps.NewCache[reflect.Type, bool]()
   257  
   258  type k string
   259  
   260  var contextTypeValue = reflect.TypeOf(context.WithValue(context.Background(), k("key"), 32))
   261  
   262  // IsContextType returns whether tp is a context.Context type.
   263  func IsContextType(tp reflect.Type) bool {
   264  	if tp == contextTypeValue {
   265  		return true
   266  	}
   267  	if tp == contextInterface {
   268  		return true
   269  	}
   270  
   271  	return isContextCache.GetOrCreate(tp, func() bool {
   272  		return tp.Implements(contextInterface)
   273  	})
   274  }