v1.16.4
ADDED: * math, time functions to tplx/sprigx FIXED: * logging not initializing properly on some BSDs
This commit is contained in:
@@ -17,7 +17,7 @@ Last rendered {localdatetime}
|
||||
|
||||
[id="wat"]
|
||||
== What is SprigX?
|
||||
SprigX are extensions to https://masterminds.github.io/sprig/[the `sprig` library^] (https://pkg.go.dev/github.com/Masterminds/sprig/v3[Go docs^]).
|
||||
SprigX is a suite of extensions to https://masterminds.github.io/sprig/[the `sprig` library^] (https://pkg.go.dev/github.com/Masterminds/sprig/v3[Go docs^]).
|
||||
|
||||
They provide functions that offer more enriched use cases and domain-specific data.
|
||||
|
||||
@@ -102,6 +102,8 @@ var txtTpl *template.Template = template.
|
||||
----
|
||||
====
|
||||
|
||||
Or, as a convenience, you can simply use the <<lib_cmbtfmap, `sprigx.CombinedTxtFuncMap`>> and/or <<lib_cmbhfmap, `sprigx.CombinedHtmlFuncMap`>> functions.
|
||||
|
||||
If a `<template>.FuncMap` is added via `.Funcs()` *after* template parsing, it will override any functions of the same name of a `<template>.FuncMap` *before* parsing.
|
||||
|
||||
For example, if both `sprig` and `sprigx` provide a function `foo`:
|
||||
@@ -212,8 +214,103 @@ var (
|
||||
----
|
||||
====
|
||||
|
||||
[id="lib"]
|
||||
== Library Functions
|
||||
These are generally intended to be used *outside* the template in the actual Go code.
|
||||
|
||||
[id="lib_cmbfmap"]
|
||||
=== `CombinedFuncMap`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func CombinedFuncMap(preferSprigX bool) (fmap map[string]any)
|
||||
----
|
||||
|
||||
This function returns a generic function map (like <<lib_fmap>>) combined with
|
||||
https://pkg.go.dev/github.com/Masterminds/sprig/v3#GenericFuncMap[`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 <<lib_cmbtfmap>> or <<lib_cmbhfmap>> instead,
|
||||
as they wrap this with the appropriate type.
|
||||
|
||||
[id="lib_cmbhfmap"]
|
||||
=== `CombinedHtmlFuncMap`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func CombinedHtmlFuncMap(preferSprigX bool) (fmap (html/template).FuncMap)
|
||||
----
|
||||
|
||||
This function returns an https://pkg.go.dev/html/template#FuncMap[`(html/template).FuncMap`] function map (like <<lib_hfmap>>) combined with
|
||||
https://pkg.go.dev/github.com/Masterminds/sprig/v3#HtmlFuncMap[`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.
|
||||
|
||||
[id="lib_cmbtfmap"]
|
||||
=== `CombinedTxtFuncMap`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func CombinedTxtFuncMap(preferSprigX bool) (fmap (text/template).FuncMap)
|
||||
----
|
||||
|
||||
This function returns a https://pkg.go.dev/text/template#FuncMap[`(text/template).FuncMap`] function map (like <<lib_tfmap>>) combined with
|
||||
https://pkg.go.dev/github.com/Masterminds/sprig/v3#TxtFuncMap[`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.
|
||||
|
||||
[id="lib_fmap"]
|
||||
=== `FuncMap`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func FuncMap() (fmap map[string]any)
|
||||
----
|
||||
|
||||
This function returns a generic SprigX function map.
|
||||
|
||||
You probably want <<lib_tfmap>> or <<lib_hfmap>> instead,
|
||||
as they wrap this with the appropriate type.
|
||||
|
||||
[id="lib_hfmap"]
|
||||
=== `HtmlFuncMap`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func HtmlFuncMap() (fmap (html/template).FuncMap)
|
||||
----
|
||||
|
||||
This function returns a SprigX https://pkg.go.dev/html/template#FuncMap[`(html/template).FuncMap`^].
|
||||
|
||||
[id="lib_nop"]
|
||||
==== `Nop`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func Nop(obj ...any) (s string)
|
||||
----
|
||||
|
||||
`Nop` is a NO-OP function that one can use in an <<override, override map>> to explicitly disable
|
||||
certain Sprig/SprigX functions that may be deemed "unsafe" and/or to sanitize templates from untrusted input.
|
||||
|
||||
It will *never* error or panic, and `s` is *always* an empty string.
|
||||
|
||||
[id="lib_tfmap"]
|
||||
=== `TxtFuncMap`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func TxtFuncMap() (fmap (text/template).FuncMap)
|
||||
----
|
||||
|
||||
This function returns a SprigX https://pkg.go.dev/text/template#FuncMap[`(text/template).FuncMap`^].
|
||||
|
||||
[id="fn"]
|
||||
== Functions
|
||||
== Template Functions
|
||||
Expect this list to grow over time, and potentially more frequently than the `sprigx` functions.
|
||||
|
||||
Each function includes its function signature to indicate what types of arguments/parameters it accepts,
|
||||
@@ -248,19 +345,50 @@ func metaIsNil(obj any) (isNil bool)
|
||||
|
||||
This function fills in the gap that https://pkg.go.dev/text/template#IsTrue[`text/template.IsTrue`^] and https://pkg.go.dev/html/template#IsTrue[`html/template.IsTrue`^] (expressed in templates as `{{ if ... }}`) leaves, as those functions/expressions return false for e.g. `false` booleans AND nils.
|
||||
|
||||
[id="fn_meta_nop"]
|
||||
==== `Nop`
|
||||
[id="fn_num"]
|
||||
=== Numbers/Math
|
||||
|
||||
[id="fn_num_f32s"]
|
||||
==== `numFloat32Str`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func Nop(obj ...any) (s string)
|
||||
func numFloat32Str(f float32) (s string)
|
||||
----
|
||||
|
||||
`Nop` is not actually a *template* function, but rather an exported *Go* function that one can use in
|
||||
an <<override, override map>> to explicitly disable certain template functions that may be deemed "unsafe" and/or to sanitize templates from
|
||||
untrusted input.
|
||||
`numFloat32Str` returns a *complete* non-truncated non-right-padded string representation of a `float32`.
|
||||
|
||||
It will *never* error or panic, and `s` is *always* an empty string.
|
||||
[id="fn_num_f64"]
|
||||
==== `numFloat64`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func numFloat64(val any) (f float64, err error)
|
||||
----
|
||||
|
||||
`numFloat64` returns any string representation of a numeric value or any type of numeric value to a `float64`.
|
||||
|
||||
[id="fn_num_f64s"]
|
||||
==== `numFloat64Str`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func numFloat64Str(f float64) (s string)
|
||||
----
|
||||
|
||||
`numFloat64Str` returns a *complete* non-truncated non-right-padded string representation of a `float64`.
|
||||
|
||||
[id="fn_num_fs"]
|
||||
==== `numFloatStr`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func numFloatStr(val any) (s string, err error)
|
||||
----
|
||||
|
||||
`numFloatStr` wraps <<fn_num_f32s>> and <<fn_num_f64s>>.
|
||||
|
||||
`val` can be a string representation of any numeric value or any type of numeric value.
|
||||
|
||||
[id="fn_os"]
|
||||
=== Operating System
|
||||
@@ -818,7 +946,7 @@ renders as:
|
||||
|===
|
||||
|
||||
[id="fn_ps"]
|
||||
=== PSUtils
|
||||
=== PSUtil
|
||||
These are functions from https://pkg.go.dev/github.com/shirou/gopsutil/v4[`github.com/shirou/gopsutil/v4`^] packages.
|
||||
|
||||
[id="fn_ps_cpu"]
|
||||
@@ -1386,6 +1514,173 @@ Where:
|
||||
* `<indentString>`: The string to use as the "indent character". This can be any string, such as `" "`, `"\t"`, `"."`, `"|"`, `"=="` etc.
|
||||
* `<input>`: The text to be indented. Because it is the last argument, `extIndent` works with pipelined text as well.
|
||||
|
||||
[id="fn_tm"]
|
||||
=== Time/Dates/Timestamps
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Some of these functions duplicate Sprig functionality, but are included here for predictable API.
|
||||
|
||||
Care has been taken to name these functions differently from the Sprig functions where possible and sensible.
|
||||
====
|
||||
|
||||
[id="fn_tm_date"]
|
||||
==== `tmDate`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func tmDate(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) (date time.Time)
|
||||
----
|
||||
|
||||
`tmDate` directly calls https://pkg.go.dev/time#Date[`time.Date`^].
|
||||
|
||||
[id="fn_tm_fltmic"]
|
||||
==== `tmFloatMicro`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func tmFloatMicro(t time.Time) (f64 float64)
|
||||
----
|
||||
|
||||
`tmFloatMicro` directly calls https://pkg.go.dev/r00t2.io/goutils/timex#F64Microseconds[`(r00t2.io/goutils/timex).F64Microseconds`^].
|
||||
|
||||
[id="fn_tm_fltmill"]
|
||||
==== `tmFloatMilli`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func tmFloatMilli(t time.Time) (f64 float64)
|
||||
----
|
||||
|
||||
`tmFloatMilli` directly calls https://pkg.go.dev/r00t2.io/goutils/timex#F64Milliseconds[`(r00t2.io/goutils/timex).F64Milliseconds`^].
|
||||
|
||||
[id="fn_tm_fltnano"]
|
||||
==== `tmFloatNano`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func tmFloatNano(t time.Time) (f64 float64)
|
||||
----
|
||||
|
||||
`tmFloatNano` directly calls https://pkg.go.dev/r00t2.io/goutils/timex#F64Nanoseconds[`(r00t2.io/goutils/timex).F64Nanoseconds`^].
|
||||
|
||||
[id="fn_tm_flt"]
|
||||
==== `tmFloat`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func tmFloat(t time.Time) (f64 float64)
|
||||
----
|
||||
|
||||
`tmFloat` directly calls https://pkg.go.dev/r00t2.io/goutils/timex#F64Seconds[`(r00t2.io/goutils/timex).F64Seconds`^].
|
||||
|
||||
[id="fn_tm_fmt"]
|
||||
==== `tmFmt`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func tmFmt(fstr string, t time.Time) (out string)
|
||||
----
|
||||
|
||||
`tmFormat` provides a more pipeline-friendly alternative to calling e.g.
|
||||
|
||||
[source,gotemplate]
|
||||
----
|
||||
{{- $t := tmNow -}}
|
||||
{{ $t.Format "<some time format string>" }}
|
||||
----
|
||||
|
||||
[id="fn_tm_now"]
|
||||
==== `tmNow`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func tmNow() (now time.Time)
|
||||
----
|
||||
|
||||
`tmNow` directly calls https://pkg.go.dev/time#Now[`time.Now`^].
|
||||
|
||||
[id="fn_tm_pdur8n"]
|
||||
==== `tmParseDur8n`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func tmParseDur8n(s string) (d time.Duration, err error)
|
||||
----
|
||||
|
||||
`tmParseDur8n` directly calls https://pkg.go.dev/time#ParseDuration[`time.ParseDuration`^].
|
||||
|
||||
[id="fn_tm_pmnth"]
|
||||
==== `tmParseMonth`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func tmParseMonth(v any) (mon time.Month, err error)
|
||||
----
|
||||
|
||||
`tmParseMonth` attempts to first try <<fn_tm_pmnthi>> and then tries <<fn_tm_pmnths>> if `v` is not "numeric".
|
||||
|
||||
[id="fn_tm_pmnthi"]
|
||||
==== `tmParseMonthInt`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func tmParseMonthInt(n any) (mon time.Month, err error)
|
||||
----
|
||||
|
||||
`tmParseMonthInt` parses a number representation of month `n` to a https://pkg.go.dev/time#Month[`time.Month`^].
|
||||
`n` may be any numeric type or a string representation of a number (or a custom type derived from those).
|
||||
|
||||
A negative integer (or float, etc.) will be converted to a positive one (e.g. `-6` -> `6` -> `time.June`).
|
||||
|
||||
Floats are rounded to the nearest integer.
|
||||
|
||||
The integer should map directly to the https://pkg.go.dev/time#example-Month[`time.Month` constants^] in the `time` module:
|
||||
|
||||
* `1`: `time.January`
|
||||
* `2`: `time.February`
|
||||
* `3`: `time.March`
|
||||
* `4`: `time.April`
|
||||
* `5`: `time.May`
|
||||
* `6`: `time.June`
|
||||
* `7`: `time.July`
|
||||
* `8`: `time.August`
|
||||
* `9`: `time.September`
|
||||
* `10`: `time.October`
|
||||
* `11`: `time.November`
|
||||
* `12`: `time.December`
|
||||
|
||||
If `n` resolves to `0`, `mon` will be the current month (as determined by https://pkg.go.dev/time#Now[`time.Now`^]).
|
||||
|
||||
If `n` resolves to > `12`, `err` will be `sprigx.ErrBadMonth` (though be sure to use https://pkg.go.dev/errors#Is[`errors.Is`^]; it will be wrapped by link:https://pkg.go.dev/text/template#ExecError[`(text/template).ExecError`]/link:https://pkg.go.dev/html/template#ExecError[`(html/template).ExecError`].
|
||||
|
||||
[id="fn_tm_pmnths"]
|
||||
==== `tmParseMonthStr`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func tmParseMonthStr(s string) (mon time.Month, err error)
|
||||
----
|
||||
|
||||
`tmParseMonthStr` parses a string representation `s` of a month to a https://pkg.go.dev/time#Month[`time.Month`^].
|
||||
|
||||
It normalizes `s` to lowercase and only uses the first 3 characters (the minimum length needed to determine month name
|
||||
uniqueness - "June" vs. "July", "March" vs. "May").
|
||||
|
||||
An empty (or whitespace-only) string will use the current month (as determined by https://pkg.go.dev/time#Now[`time.Now`^]).`
|
||||
|
||||
[id="fn_tm_ptm"]
|
||||
==== `tmParseTime`
|
||||
[source,go]
|
||||
.Function Signature
|
||||
----
|
||||
func tmParseTime(layout, value string) (t time.Time, err error)
|
||||
----
|
||||
|
||||
`tmParseTime` directly calls https://pkg.go.dev/time#Parse[`time.Parse`^].
|
||||
|
||||
Be sure that `layout` is a properly parseable https://pkg.go.dev/time#Layout[layout format^].
|
||||
|
||||
[id="fn_sys"]
|
||||
=== System/Platform/Architecture
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
5303
tplx/sprigx/README.md
Normal file
5303
tplx/sprigx/README.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ import (
|
||||
`path`
|
||||
`path/filepath`
|
||||
`runtime`
|
||||
`time`
|
||||
|
||||
`github.com/davecgh/go-spew/spew`
|
||||
`github.com/shirou/gopsutil/v4/cpu`
|
||||
@@ -16,6 +17,7 @@ import (
|
||||
psnet `github.com/shirou/gopsutil/v4/net`
|
||||
`github.com/shirou/gopsutil/v4/process`
|
||||
`github.com/shirou/gopsutil/v4/sensors`
|
||||
`r00t2.io/goutils/timex`
|
||||
`r00t2.io/sysutils`
|
||||
)
|
||||
|
||||
@@ -28,6 +30,13 @@ var (
|
||||
"Meta"/Template-Helpers
|
||||
*/
|
||||
"metaIsNil": metaIsNil,
|
||||
/*
|
||||
Numbers/Math
|
||||
*/
|
||||
"numFloat32Str": numFloat32Str,
|
||||
"numFloat64": numFloat64,
|
||||
"numFloat64Str": numFloat64Str,
|
||||
"numFloatStr": numFloatStr,
|
||||
/*
|
||||
OS
|
||||
*/
|
||||
@@ -119,7 +128,21 @@ var (
|
||||
"sysNumCpu": runtime.NumCPU,
|
||||
"sysOsName": sysOsNm,
|
||||
"sysRuntime": sysRuntime,
|
||||
|
||||
/*
|
||||
Time/Dates/Timestamps
|
||||
*/
|
||||
"tmDate": time.Date,
|
||||
"tmFmt": tmFmt,
|
||||
"tmFloatMicro": timex.F64Microseconds,
|
||||
"tmFloatMilli": timex.F64Milliseconds,
|
||||
"tmFloatNano": timex.F64Nanoseconds,
|
||||
"tmFloat": timex.F64Seconds,
|
||||
"tmNow": time.Now,
|
||||
"tmParseDur8n": time.ParseDuration,
|
||||
"tmParseMonth": tmParseMonth,
|
||||
"tmParseMonthInt": tmParseMonthInt,
|
||||
"tmParseMonthStr": tmParseMonthStr,
|
||||
"tmParseTime": time.Parse,
|
||||
}
|
||||
|
||||
// htmlMap holds functions usable/intended for use in only an [html/template.FuncMap].
|
||||
|
||||
11
tplx/sprigx/errs.go
Normal file
11
tplx/sprigx/errs.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package sprigx
|
||||
|
||||
import (
|
||||
`errors`
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBadMonth error = errors.New("could not determine/parse month")
|
||||
ErrBadType error = errors.New("an invalid/unknown type was passed")
|
||||
ErrNilVal error = errors.New("a nil value was passed")
|
||||
)
|
||||
@@ -1,14 +1,116 @@
|
||||
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.
|
||||
|
||||
@@ -57,6 +159,11 @@ func HtmlFuncMap() (fmap htpl.FuncMap) {
|
||||
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) {
|
||||
|
||||
@@ -79,3 +186,171 @@ func TxtFuncMap() (fmap ttpl.FuncMap) {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
package sprigx
|
||||
|
||||
// Nop explicitly performs a NO-OP and returns an empty string, allowing one to override "unsafe" functions.
|
||||
func Nop(obj ...any) (s string) {
|
||||
return
|
||||
}
|
||||
|
||||
// metaIsNil returns true if obj is explicitly nil.
|
||||
func metaIsNil(obj any) (isNil bool) {
|
||||
|
||||
|
||||
51
tplx/sprigx/funcs_tpl_nums.go
Normal file
51
tplx/sprigx/funcs_tpl_nums.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package sprigx
|
||||
|
||||
import (
|
||||
`math/big`
|
||||
)
|
||||
|
||||
// numFloat64 returns any string representation of a numeric value or any type of numeric value to a float64.
|
||||
func numFloat64(val any) (f float64, err error) {
|
||||
|
||||
if f, _, err = toFloat64(val); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
numFloatStr wraps numFloat32Str and numFloat64Str.
|
||||
|
||||
val can be a string representation of any numeric value or any type of numeric value.
|
||||
*/
|
||||
func numFloatStr(val any) (s string, err error) {
|
||||
|
||||
var f float64
|
||||
|
||||
if f, _, err = toFloat64(val); err != nil {
|
||||
return
|
||||
}
|
||||
s = numFloat64Str(f)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// numFloat32Str returns float32 f as a complete string representation with no truncation (or right-padding).
|
||||
func numFloat32Str(f float32) (s string) {
|
||||
|
||||
s = numFloat64Str(float64(f))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// numFloat64Str returns float64 f as a complete string representation with no truncation (or right-padding).
|
||||
func numFloat64Str(f float64) (s string) {
|
||||
|
||||
var bf *big.Float
|
||||
|
||||
bf = big.NewFloat(f)
|
||||
s = bf.String()
|
||||
|
||||
return
|
||||
}
|
||||
@@ -7,16 +7,29 @@ import (
|
||||
`strings`
|
||||
)
|
||||
|
||||
// osGroupById returns os/user.LookupGroupId. Can accept either an integer or a string.
|
||||
func osGroupById[T string | int](gid T) (g *user.Group, err error) {
|
||||
/*
|
||||
osGroupById returns os/user.LookupGroupId.
|
||||
|
||||
Can accept either a string (`"1000"`) or any
|
||||
numeric type (`1000`, `-1000`, `1000.0`, `MyCustomType(1000)`, etc.)
|
||||
*/
|
||||
func osGroupById(gid any) (g *user.Group, err error) {
|
||||
|
||||
var i int
|
||||
var NaN bool
|
||||
var gidStr string
|
||||
|
||||
switch t := any(gid).(type) {
|
||||
case string:
|
||||
gidStr = t
|
||||
case int:
|
||||
gidStr = strconv.Itoa(t)
|
||||
if i, NaN, err = toPosInt(gid); err != nil {
|
||||
if NaN {
|
||||
err = nil
|
||||
if gidStr, err = toString(gid); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
gidStr = strconv.Itoa(i)
|
||||
}
|
||||
|
||||
g, err = user.LookupGroupId(gidStr)
|
||||
@@ -55,16 +68,29 @@ func osHost() (hostNm string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// osUserById returns an os/user.LookupId. Can accept either an integer or a string.
|
||||
func osUserById[T string | int](uid T) (u *user.User, err error) {
|
||||
/*
|
||||
osUserById returns an os/user.LookupId.
|
||||
|
||||
Can accept either a string (`"1000"`) or any
|
||||
numeric type (`1000`, `-1000`, `1000.0`, `MyCustomType(1000)`, etc.)
|
||||
*/
|
||||
func osUserById(uid any) (u *user.User, err error) {
|
||||
|
||||
var i int
|
||||
var NaN bool
|
||||
var uidStr string
|
||||
|
||||
switch t := any(uid).(type) {
|
||||
case string:
|
||||
uidStr = t
|
||||
case int:
|
||||
uidStr = strconv.Itoa(t)
|
||||
if i, NaN, err = toPosInt(uid); err != nil {
|
||||
if NaN {
|
||||
err = nil
|
||||
if uidStr, err = toString(uid); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
uidStr = strconv.Itoa(i)
|
||||
}
|
||||
|
||||
u, err = user.LookupId(uidStr)
|
||||
|
||||
139
tplx/sprigx/funcs_tpl_time.go
Normal file
139
tplx/sprigx/funcs_tpl_time.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package sprigx
|
||||
|
||||
import (
|
||||
`errors`
|
||||
`strconv`
|
||||
`strings`
|
||||
`time`
|
||||
)
|
||||
|
||||
/*
|
||||
tmFmt formats time t using format string fstr.
|
||||
|
||||
While one certainly can do the same via e.g.
|
||||
|
||||
{{- $t := tmNow -}}
|
||||
{{ $t.Format $fstr }}
|
||||
|
||||
This takes a time.Time as the second (and last) parameter,
|
||||
allowing it to work in pipelines.
|
||||
*/
|
||||
func tmFmt(fstr string, t time.Time) (out string) {
|
||||
|
||||
out = t.Format(fstr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
tmParseMonth attempts to first try tmParseMonthInt
|
||||
and then tries tmParseMonthStr if v is not "numeric".
|
||||
*/
|
||||
func tmParseMonth(v any) (mon time.Month, err error) {
|
||||
|
||||
var s string
|
||||
|
||||
if mon, err = tmParseMonthInt(v); err != nil {
|
||||
if errors.Is(err, strconv.ErrSyntax) {
|
||||
// NaN
|
||||
err = nil
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If it gets here, it's a non-numeric string.
|
||||
if s, err = toString(v); err != nil {
|
||||
return
|
||||
}
|
||||
if mon, err = tmParseMonthStr(s); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
tmParseMonthInt parses a number representation of month n to a time.Month.
|
||||
n may be any numeric type or a string representation of a number
|
||||
(or a custom type derived from those).
|
||||
|
||||
A negative integer (or float, etc.) will be converted to a positive one (e.g. -6 => 6 => time.June).
|
||||
|
||||
floats are rounded to the nearest integer.
|
||||
|
||||
The integer should map directly to the month constants in the time module:
|
||||
|
||||
* 1: January
|
||||
* 2: February
|
||||
* 3: March
|
||||
* 4: April
|
||||
* 5: May
|
||||
* 6: June
|
||||
* 7: July
|
||||
* 8: August
|
||||
* 9: September
|
||||
* 10: October
|
||||
* 11: November
|
||||
* 12: December
|
||||
|
||||
If n resolves to 0, mon will be the current month (as determined by time.Now).
|
||||
|
||||
If n resolves to > 12, err will be ErrBadMonth.
|
||||
*/
|
||||
func tmParseMonthInt(n any) (mon time.Month, err error) {
|
||||
|
||||
var i int
|
||||
|
||||
if i, _, err = toPosInt(n); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
mon = time.Now().Month()
|
||||
return
|
||||
}
|
||||
|
||||
if i > 12 {
|
||||
err = ErrBadMonth
|
||||
return
|
||||
}
|
||||
|
||||
mon = time.Month(i)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
tmParseMonthStr parses a string representation of month s to a time.Month.
|
||||
|
||||
It normalizes s to lowercase and only uses the first 3 characters
|
||||
(the minimum length needed to determine month name
|
||||
uniqueness - "June" vs. "July", "March" vs. "May").
|
||||
|
||||
An empty (or whitespace-only) string will use the current month (as determined by time.Now).
|
||||
*/
|
||||
func tmParseMonthStr(s string) (mon time.Month, err error) {
|
||||
|
||||
var i int
|
||||
var m time.Month
|
||||
|
||||
if strings.TrimSpace(s) == "" {
|
||||
mon = time.Now().Month()
|
||||
return
|
||||
}
|
||||
|
||||
s = strings.ToLower(strings.TrimSpace(s))[0:3]
|
||||
|
||||
for i = range 12 {
|
||||
m = time.Month(i + 1)
|
||||
if strings.ToLower(m.String())[0:3] == s {
|
||||
mon = m
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = ErrBadMonth
|
||||
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user