ADDED: * math, time functions to tplx/sprigx FIXED: * logging not initializing properly on some BSDs
357 lines
7.1 KiB
Go
357 lines
7.1 KiB
Go
package sprigx
|
|
|
|
import (
|
|
`errors`
|
|
htpl "html/template"
|
|
`math`
|
|
`reflect`
|
|
`strconv`
|
|
ttpl "text/template"
|
|
|
|
`github.com/Masterminds/sprig/v3`
|
|
)
|
|
|
|
/*
|
|
Many of these functions are modeled after sprig's.
|
|
*/
|
|
|
|
/*
|
|
CombinedFuncMap returns a generic function map (like [FuncMap]) combined with
|
|
[github.com/Masterminds/sprig/v3.GenericFuncMap].
|
|
|
|
If preferSprigx is true, SprigX function names will override Sprig
|
|
functions with the same name.
|
|
If false, Sprig functions will override conflicting SprigX functions
|
|
with the same name.
|
|
|
|
You probably want [CombinedHtmlFuncMap] or [CombinedTxtFuncMap] instead,
|
|
as they wrap this with the appropriate type.
|
|
*/
|
|
func CombinedFuncMap(preferSprigX bool) (fmap map[string]any) {
|
|
|
|
var fn any
|
|
var fnNm string
|
|
var sprigMap map[string]interface{} = sprig.GenericFuncMap()
|
|
var sprigxMap map[string]any = FuncMap()
|
|
|
|
if preferSprigX {
|
|
fmap = sprigMap
|
|
for fnNm, fn = range sprigxMap {
|
|
fmap[fnNm] = fn
|
|
}
|
|
} else {
|
|
fmap = sprigxMap
|
|
for fnNm, fn = range sprigMap {
|
|
fmap[fnNm] = fn
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
CombinedHtmlFuncMap returns an [htpl.FuncMap] (like [HtmlFuncMap]) combined with
|
|
[github.com/Masterminds/sprig/v3.HtmlFuncMap].
|
|
|
|
If preferSprigx is true, SprigX function names will override Sprig
|
|
functions with the same name.
|
|
If false, Sprig functions will override conflicting SprigX functions
|
|
with the same name.
|
|
*/
|
|
func CombinedHtmlFuncMap(preferSprigX bool) (fmap htpl.FuncMap) {
|
|
|
|
var fn any
|
|
var fnNm string
|
|
var sprigMap htpl.FuncMap = sprig.HtmlFuncMap()
|
|
var sprigxMap htpl.FuncMap = HtmlFuncMap()
|
|
|
|
if preferSprigX {
|
|
fmap = sprigMap
|
|
for fnNm, fn = range sprigxMap {
|
|
fmap[fnNm] = fn
|
|
}
|
|
} else {
|
|
fmap = sprigxMap
|
|
for fnNm, fn = range sprigMap {
|
|
fmap[fnNm] = fn
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
CombinedTxtFuncMap returns a [ttpl.FuncMap] (like [TxtFuncMap]) combined with
|
|
[github.com/Masterminds/sprig/v3.TxtFuncMap].
|
|
|
|
If preferSprigx is true, SprigX function names will override Sprig
|
|
functions with the same name.
|
|
If false, Sprig functions will override conflicting SprigX functions
|
|
with the same name.
|
|
*/
|
|
func CombinedTxtFuncMap(preferSprigX bool) (fmap ttpl.FuncMap) {
|
|
|
|
var fn any
|
|
var fnNm string
|
|
var sprigMap ttpl.FuncMap = sprig.TxtFuncMap()
|
|
var sprigxMap ttpl.FuncMap = TxtFuncMap()
|
|
|
|
if preferSprigX {
|
|
fmap = sprigMap
|
|
for fnNm, fn = range sprigxMap {
|
|
fmap[fnNm] = fn
|
|
}
|
|
} else {
|
|
fmap = sprigxMap
|
|
for fnNm, fn = range sprigMap {
|
|
fmap[fnNm] = fn
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
FuncMap returns a generic function map.
|
|
|
|
You probably want [HtmlFuncMap] or [TxtFuncMap] instead,
|
|
as they wrap this with the appropriate type.
|
|
*/
|
|
func FuncMap() (fmap map[string]any) {
|
|
|
|
var fn string
|
|
var f any
|
|
|
|
fmap = make(map[string]any, len(genericMap))
|
|
|
|
for fn, f = range genericMap {
|
|
fmap[fn] = f
|
|
}
|
|
if osGenericMap != nil && len(osGenericMap) > 0 {
|
|
for fn, f = range osGenericMap {
|
|
fmap[fn] = f
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// HtmlFuncMap returns an [html/template.FuncMap].
|
|
func HtmlFuncMap() (fmap htpl.FuncMap) {
|
|
|
|
var fn string
|
|
var f any
|
|
|
|
fmap = htpl.FuncMap(FuncMap())
|
|
|
|
if htmlMap != nil && len(htmlMap) > 0 {
|
|
for fn, f = range htmlMap {
|
|
fmap[fn] = f
|
|
}
|
|
}
|
|
|
|
if osHtmlMap != nil && len(osHtmlMap) > 0 {
|
|
for fn, f = range osHtmlMap {
|
|
fmap[fn] = f
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Nop explicitly performs a NO-OP and returns an empty string, allowing one to override "unsafe" functions.
|
|
func Nop(obj ...any) (s string) {
|
|
return
|
|
}
|
|
|
|
// TxtFuncMap returns a [text/template.FuncMap].
|
|
func TxtFuncMap() (fmap ttpl.FuncMap) {
|
|
|
|
var fn string
|
|
var f any
|
|
|
|
fmap = ttpl.FuncMap(FuncMap())
|
|
|
|
if txtMap != nil && len(txtMap) > 0 {
|
|
for fn, f = range txtMap {
|
|
fmap[fn] = f
|
|
}
|
|
}
|
|
|
|
if osTxtMap != nil && len(osTxtMap) > 0 {
|
|
for fn, f = range osTxtMap {
|
|
fmap[fn] = f
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
toFloat64 uses reflection to resolve any string or numeric type (even custom types) to a float64.
|
|
|
|
It wraps toString for string types but will fall back to checking numeric types.
|
|
|
|
If err != nil, then NaN (if true) indicates that:
|
|
|
|
* val is a string (or pointer to a string), but
|
|
* is not a valid numeric string
|
|
|
|
(you can do this from the caller as well by calling `errors.Is(err, strconv.ErrSyntax)`).
|
|
err will always be non-nil if NaN is true.
|
|
|
|
err will be ErrNilVal if val is nil.
|
|
*/
|
|
func toFloat64(val any) (f float64, NaN bool, err error) {
|
|
|
|
var s string
|
|
var k reflect.Kind
|
|
var rv reflect.Value
|
|
|
|
// toString will return ErrNilVal if nil.
|
|
if s, err = toString(val); err != nil {
|
|
if errors.Is(err, ErrBadType) {
|
|
// This is OK, it's (hopefully) a number type.
|
|
err = nil
|
|
} else {
|
|
// *probably* ErrNilVal.
|
|
return
|
|
}
|
|
} else {
|
|
// We can go ahead and parse this directly since it's already deref'd if a ptr.
|
|
if f, err = strconv.ParseFloat(s, 64); err != nil {
|
|
NaN = errors.Is(err, strconv.ErrSyntax)
|
|
}
|
|
// We can return regardless here; it's up to the caller to check NaN/err.
|
|
// If they're false/nil, f is parsed already!
|
|
return
|
|
}
|
|
|
|
rv = reflect.ValueOf(val)
|
|
k = rv.Kind()
|
|
|
|
if k == reflect.Ptr {
|
|
if rv.IsNil() {
|
|
// *technically* this should be handled above, but best be safe.
|
|
err = ErrNilVal
|
|
return
|
|
}
|
|
rv = rv.Elem()
|
|
k = rv.Kind()
|
|
}
|
|
|
|
switch k {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
f = float64(rv.Int())
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
f = float64(rv.Uint())
|
|
case reflect.Float32, reflect.Float64:
|
|
f = rv.Float()
|
|
default:
|
|
// No need to check for string types since we do that near the beginning.
|
|
err = ErrBadType
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
toInt wraps toFloat64, rounds it to the nearest integer,
|
|
and converts to an int.
|
|
|
|
NaN, err have the same meaning as in toFloat64.
|
|
|
|
This function will panic if float64(val)'s f return exceeds
|
|
math.MaxInt on your platform.
|
|
*/
|
|
func toInt(val any) (i int, NaN bool, err error) {
|
|
|
|
var f float64
|
|
|
|
if f, NaN, err = toFloat64(val); err != nil {
|
|
return
|
|
}
|
|
|
|
i = int(math.Round(f))
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
toPosFloat64 wraps toFloat64 and ensures that it is a positive float64.
|
|
|
|
NaN, err have the same meaning as in toFloat64.
|
|
*/
|
|
func toPosFloat64(val any) (f float64, NaN bool, err error) {
|
|
|
|
if f, NaN, err = toFloat64(val); err != nil {
|
|
return
|
|
}
|
|
|
|
f = math.Abs(f)
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
toPosInt wraps toPosFloat64, rounds it to the nearest integer,
|
|
and converts to an int.
|
|
|
|
NaN, err have the same meaning as in toPosFloat64 (and thus toFloat64).
|
|
|
|
This function will panic if float64(val)'s f return exceeds
|
|
math.MaxInt on your platform.
|
|
*/
|
|
func toPosInt(val any) (i int, NaN bool, err error) {
|
|
|
|
var f float64
|
|
|
|
if f, NaN, err = toPosFloat64(val); err != nil {
|
|
return
|
|
}
|
|
|
|
i = int(math.Round(f))
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
toString uses reflection to resolve any string value (even custom types and ptrs)
|
|
to a concrete string.
|
|
|
|
err will be ErrBadType if val is not a string type/string-derived type.
|
|
err will be ErrNilVal if val is nil.
|
|
*/
|
|
func toString(val any) (s string, err error) {
|
|
|
|
var rv reflect.Value
|
|
var k reflect.Kind
|
|
|
|
if val == nil {
|
|
err = ErrNilVal
|
|
return
|
|
}
|
|
|
|
rv = reflect.ValueOf(val)
|
|
k = rv.Kind()
|
|
|
|
if k == reflect.Ptr {
|
|
if rv.IsNil() {
|
|
// *technically* this should be handled above, but best be safe.
|
|
err = ErrNilVal
|
|
return
|
|
}
|
|
rv = rv.Elem()
|
|
k = rv.Kind()
|
|
}
|
|
|
|
if k == reflect.String {
|
|
s = rv.String()
|
|
} else {
|
|
err = ErrBadType
|
|
}
|
|
|
|
return
|
|
}
|