github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/common/hreflect/helpers.go (about)

     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  func CallMethodByName(cxt context.Context, name string, v reflect.Value) []reflect.Value {
   212  	fn := v.MethodByName(name)
   213  	var args []reflect.Value
   214  	tp := fn.Type()
   215  	if tp.NumIn() > 0 {
   216  		if tp.NumIn() > 1 {
   217  			panic("not supported")
   218  		}
   219  		first := tp.In(0)
   220  		if first.Implements(ContextInterface) {
   221  			args = append(args, reflect.ValueOf(cxt))
   222  		}
   223  	}
   224  
   225  	return fn.Call(args)
   226  }
   227  
   228  // Based on: https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L931
   229  func indirectInterface(v reflect.Value) reflect.Value {
   230  	if v.Kind() != reflect.Interface {
   231  		return v
   232  	}
   233  	if v.IsNil() {
   234  		return reflect.Value{}
   235  	}
   236  	return v.Elem()
   237  }
   238  
   239  var ContextInterface = reflect.TypeOf((*context.Context)(nil)).Elem()