ADDED:
* tplx/sprigx
This commit is contained in:
brent saner
2026-01-24 13:41:54 -05:00
parent 2edbc9306d
commit 927ad08057
17 changed files with 3449 additions and 1 deletions

31
.githooks/pre-commit/01-docgen Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/bash
orig="${PWD}"
if ! command -v asciidoctor &> /dev/null;
then
exit 0
fi
set -e
for f in $(find . -type f -iname "README.adoc"); do
filename=$(basename -- "${f}")
docsdir=$(dirname -- "${f}")
nosuffix="${filename%.*}"
pfx="${docsdir}/${nosuffix}"
newf="${pfx}.html"
asciidoctor -a ROOTDIR="${orig}/" -o "${newf}" "${f}"
echo "Generated ${newf} from ${f}"
git add "${newf}"
if command -v pandoc &> /dev/null;
then
newf="${pfx}.md"
asciidoctor -a ROOTDIR="${orig}/" -b docbook -o - "${f}" | pandoc -f docbook -t markdown_strict -o "${newf}"
echo "Generated ${newf} from ${f}"
git add "${newf}"
fi
cd ${orig}
done
echo "Regenerated docs"

11
go.mod
View File

@@ -3,7 +3,9 @@ module r00t2.io/goutils
go 1.25
require (
github.com/Masterminds/sprig/v3 v3.3.0
github.com/coreos/go-systemd/v22 v22.6.0
github.com/davecgh/go-spew v1.1.1
github.com/google/uuid v1.6.0
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/sys v0.39.0
@@ -11,6 +13,15 @@ require (
)
require (
dario.cat/mergo v1.0.1 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/djherbis/times v1.6.0 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/sync v0.19.0 // indirect
)

41
go.sum
View File

@@ -1,15 +1,56 @@
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo=
github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
r00t2.io/sysutils v1.15.1 h1:0EVZZAxTFqQN6jjfjqUKkXye0LMshUA5MO7l3Wd6wH8=
r00t2.io/sysutils v1.15.1/go.mod h1:T0iOnaZaSG5NE1hbXTqojRZc0ia/u8TB73lV7zhMz58=

21
multierr/TODO Normal file
View File

@@ -0,0 +1,21 @@
- add unwrapping
https://go.dev/blog/go1.13-errors#the-unwrap-method
- add As method, takes a ptr to a slice of []error to return the first matching error type (errors.As) for each?
- add AsAll [][]error ptr param for multiple errors per type?
- add Map, returns map[string][]error, where key is k:
var sb strings.Builder
t = reflect.TypeOf(err)
if t.PkgPath() != "" {
sb.WriteString(t.PkgPath())
} else {
sb.WriteString("<UNKNOWN>")
}
sb.WriteString(".")
if t.Name() != "" {
sb.WriteString(t.Name())
} else {
sb.WriteString("<UNKNOWN>")
}
k = sb.String()
- support generics for similar to above?
- this might allow for "error filtering"

View File

@@ -13,7 +13,7 @@ import (
/*
AddrRfc returns an RFC-friendly string from an IP address ([net/netip.Addr]).
If addr is an IPv4 address, it will simmply be the string representation (e.g. "203.0.113.1").
If addr is an IPv4 address, it will simply be the string representation (e.g. "203.0.113.1").
If addr is an IPv6 address, it will be enclosed in brackets (e.g. "[2001:db8::1]").

631
tplx/sprigx/README.adoc Normal file
View File

@@ -0,0 +1,631 @@
= SprigX
Brent Saner <bts@square-r00t.net>
Last rendered {localdatetime}
:doctype: book
:docinfo: shared
:data-uri:
:imagesdir: images
:sectlinks:
:sectnums:
:sectnumlevels: 7
:toc: preamble
:toc2: left
:idprefix:
:toclevels: 7
:source-highlighter: rouge
:docinfo: shared
[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^]).
They provide functions that offer more enriched use cases and domain-specific data.
[id="use"]
== How do I Use SprigX?
[%collapsible]
.The same way you would `sprig`!
====
[source,go]
----
package main
import (
htmlTplLib "html/template"
txtTplLib "text/template"
"r00t2.io/goutils/tplx/sprigx"
)
var (
txtTpl *txtTplLib.Template = txtTplLib.
New("").
Funcs(
sprigx.TxtFuncMap(),
)
htmlTpl *htmlTplLib.Template = htmlTplLib.
New("").
Funcs(
sprigx.HtmlFuncMap(),
)
)
----
====
[%collapsible]
.They can even be combined/used together.
====
[source,go]
----
package main
import (
"text/template"
"github.com/Masterminds/sprig/v3"
"r00t2.io/goutils/tplx/sprigx"
)
var txtTpl *template.Template = template.
New("").
Funcs(
sprigx.TxtFuncMap(),
).
Funcs(
sprig.TxtFuncMap(),
)
// Or:
/*
var txtTpl *template.Template = template.
New("").
Funcs(
sprig.TxtFuncMap(),
).
Funcs(
sprigx.TxtFuncMap(),
)
*/
----
====
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`:
[%collapsible]
.this will use `foo` from `sprigx`
====
[source,go]
----
package main
import (
"text/template"
"github.com/Masterminds/sprig/v3"
"r00t2.io/goutils/tplx/sprigx"
)
const (
myTpl string = `{{ "This is an example template string." | foo }}`
)
var (
tpl *template.Template = template.Must(
template.
New("").
Funcs(sprig.TxtFuncMap()).
Parse(myTpl),
).
Funcs(sprigx.TxtFuncMap())
)
----
====
whereas
[%collapsible]
.this will use `foo` from `sprig`
====
[source,go]
----
package main
import (
"text/template"
"github.com/Masterminds/sprig/v3"
"r00t2.io/goutils/tplx/sprigx"
)
const (
myTpl string = `{{ "This is an example template string." | foo }}`
)
var (
tpl *template.Template = template.Must(
template.
New("").
Funcs(sprigx.TxtFuncMap()).
Parse(myTpl),
).
Funcs(sprig.TxtFuncMap())
)
----
====
and a function can even be
[%collapsible]
.explicitly overridden.
====
This would override a function `foo` and `foo2` in `sprigx` from `foo` and `foo2` from `sprig`, but leave all other `sprig` functions untouched.
[source,go]
----
package main
import (
"text/template"
"github.com/Masterminds/sprig/v3"
"r00t2.io/goutils/tplx/sprigx"
)
const (
myTpl string = `{{ "This is an example template string." | foo }}`
)
var (
overrideFuncs template.FuncMap = sprig.TxtFuncMap()
tpl *template.Template = template.Must(
template.
New("").
Funcs(sprigx.TxtFuncMap()).
Parse(myTpl),
).
Funcs(
template.FuncMap(
map[string]any{
"foo": overrideFuncs["foo"],
"foo2": overrideFuncs["foo2"],
},
),
)
)
----
====
[id="fn"]
== Functions
Expect this list to grow over time, and potentially more frequently than the `sprigx` functions.
[id="fn_sys"]
=== System/OS/Platform
[id="fn_sys_arch"]
==== `sysArch`
Returns the https://pkg.go.dev/runtime#GOARCH[`runtime.GOARCH`^] constant.
[id="fn_sys_numcpu"]
==== `sysNumCpu`
Returns the value from https://pkg.go.dev/runtime#NumCPU[`runtime.NumCPU`^].
[id="fn_sys_os"]
==== `sysOsName`
Returns the https://pkg.go.dev/runtime#GOOS[`runtime.GOOS`^] constant.
[id="fn_sys_rntm"]
==== `sysRuntime`
This function returns a `map[string]string` of various information from the https://pkg.go.dev/runtime[`runtime` stdlib library^].
Specifically, the following are returned.
[TIP]
====
The value type is a direct link to the `runtime` documentation providing more detail about the associated value.
Because all values are mapped as strings, they can be converted back to their native type via e.g. the https://masterminds.github.io/sprig/conversion.html[Sprig conversion functions^] if necessary.
====
.`sysRuntime` Values
[cols="^.^3m,^.^3",options="header"]
|===
| Key | Value Type
| compiler | https://pkg.go.dev/runtime#Compiler[string^]
| arch | https://pkg.go.dev/runtime#GOARCH[string^]
| os | https://pkg.go.dev/runtime#GOOS[string^]
| maxprocs | https://pkg.go.dev/runtime#GOMAXPROCS[int^] footnote:[For safety concerns, `sprigx` does not allow *setting* `GOMAXPROCS`, this value only contains the *current* `GOMAXPROCS` value.]
| cpu_cnt | https://pkg.go.dev/runtime#NumCPU[int^]
| num_cgo | https://pkg.go.dev/runtime#NumCgoCall[int^]
| num_go | https://pkg.go.dev/runtime#NumGoroutine[int^]
| go_ver | https://pkg.go.dev/runtime#Version[string^]
|===
As a convenience, some of these values also have their own dedicated functions as well:
* <<fn_sys_arch>>
* <<fn_sys_numcpu>>
* <<fn_sys_os>>
[id="fn_path"]
=== Paths
[id="fn_path_gnrc"]
==== Generic
These operate similar to https://pkg.go.dev/path[the `path` stdlib library^] and use a fixed `/` path separator.
[id="fn_path_gnrc_pj"]
===== `pathJoin`
`pathJoin` operates *exactly* like https://pkg.go.dev/path#Join[`path.Join`^] in stdlib.
[WARNING]
====
If you are joining paths in a pipeline, you almost assuredly want <<fn_path_gnrc_ppj>> or <<fn_path_gnrc_pspj>> instead unless you are explicitly *appending* a pipeline result to a path.
====
[source,gotemplate]
----
{{- pathJoin "a" "b" "c" }}
{{- pathJoin "/" "a" "b" "c" }}
{{- pathJoin "/a/b" "c" }}
----
renders as:
[source,text]
----
a/b/c
/a/b/c
/a/b/c
----
[id="fn_path_gnrc_ppj"]
===== `pathPipeJoin`
`pathPipeJoin` operates like <<fn_path_gnrc_pj>> with one deviation: the root/base path is expected to be *last* in the arguments.
This makes it much more suitable for use in template pipelines, as the previous value in a pipeline is passed in as the last element to the next pipe function.
[source,gotemplate]
----
{{- $myBase := "/a" -}}
{{- pathPipeJoin "b" "c" "a" }}
{{- pathPipeJoin "a" "b" "c" "/" }}
{{- $myBase | pathPipeJoin "b" "c" }}
----
renders as:
[source,text]
----
a/b/c
/a/b/c
/a/b/c
----
[id="fn_path_gnrc_psj"]
===== `pathSliceJoin`
`pathSliceJoin` joins a slice of path segment strings (`[]string`) instead of a variadic sequence of strings.
[TIP]
====
The `splitList` function shown below is from the https://masterminds.github.io/sprig/string_slice.html[`sprig` string slice functions^].
====
[source,gotemplate]
----
{{- $myList := "a,b,c" | splitList "," -}}
{{- $myList | pathSliceJoin }}
{{- ("a,b,c" | splitList ",") | pathSliceJoin }}
{{- ("/,a,b,c" | splitList ",") | pathSliceJoin }}
----
renders as:
[source,text]
----
a/b/c
a/b/c
/a/b/c
----
[id="fn_path_gnrc_pspj"]
===== `pathSlicePipeJoin`
`pathSlicePipeJoin` operates like <<fn_path_gnrc_ppj>> in that it is suitable for pipeline use in which the root/base path is passed in from the pipeline, but it is like <<fn_path_gnrc_psj>> in that it then also accepts a slice of path segments (`[]string`) to append to that base path.
[TIP]
====
The `splitList` function shown below is from the https://masterminds.github.io/sprig/string_slice.html[`sprig` string slice functions^].
====
[source,gotemplate]
----
{{- $myBase := "/a" -}}
{{- $myList := "b,c,d" | splitList "." -}}
{{- pathSlicePipeJoin $myList $myBase }}
{{- $myBase | pathSlicePipeJoin $myList }}
----
renders as:
[source,text]
----
/a/b/c
/a/b/c
----
[id="fn_path_gnrc_psubj"]
===== `pathSubJoin`
`pathSubJoin` operates like <<fn_path_gnrc_pj>> but it expects an explicit root/base path.
The pipeline-friendly equivalent of this is <<fn_path_gnrc_ppj>>.
[source,gotemplate]
----
{{- pathSubJoin "/a/b" "c" }}
{{- pathSubJoin "/" "a" "b" "c" }}
{{- "c" | pathSubJoin "/" "a" "b" }}
----
renders as:
[source,text]
----
/a/b/c
/a/b/c
/a/b/c
----
[id="fn_path_os"]
==== OS/Platform-Tailored
These operate similar to https://pkg.go.dev/path/filepath[the `path/filepath` stdlib library^], and use the OS-specific https://pkg.go.dev/os#PathSeparator[`os.PathSeparator`^].
[WARNING]
====
Take special note of the oddness around specifying Windows paths and drive letters in e.g. <<fn_path_os_pj>>!
It is recommended to make use of <<fn_sys_os>> to conditionally format path bases/roots if needed.
====
[id="fn_path_os_pj"]
===== `osPathJoin`
`osPathJoin` operates *exactly* like https://pkg.go.dev/path/filepath#Join[`path/filepath.Join`^] in stdlib.
[WARNING]
====
If you are joining paths in a pipeline, you almost assuredly want <<fn_path_os_ppj>> or <<fn_path_os_pspj>> instead unless you are explicitly *appending* a pipeline result to a path.
====
[source,gotemplate]
----
{{- osPathJoin "a" "b" "c" }}
{{- osPathJoin "/" "a" "b" "c" }}
{{- osPathJoin "C:\\" "a" "b" "c" }}
{{- osPathJoin "C:" "a" "b" "c" }}
----
renders as:
[cols="^.^2,.^4a",options="header"]
|===
| OS ^| Result
| Windows | [source,text]
----
a\b\c
\a\b\c
\a\b\c
C:\a\b\c
C:a\b\c
----
| Others (e.g. Linux, macOS) | [source,text]
----
a/b/c
/a/b/c
C:\/a/b/c
C:/a/b/c
----
|===
[id="fn_path_os_ppj"]
===== `osPathPipeJoin`
`osPathPipeJoin` operates like <<fn_path_gnrc_ppj>> (except using OS-specific path separators).
This makes it much more suitable for use in template pipelines, as the previous value in a pipeline is passed in as the last element to the next pipe function.
[source,gotemplate]
----
{{- $myBase := "/a" -}}
{{- osPathPipeJoin "b" "c" "a" }}
{{- osPathPipeJoin "a" "b" "c" "/" }}
{{- $myBase | osPathPipeJoin "b" "c" }}
----
renders as:
[cols="^.^2,.^4a",options="header"]
|===
| OS ^| Result
| Windows | [source,text]
----
a\b\c
\a\b\c
\a\b\c
----
| Others (e.g. Linux, macOS) | [source,text]
----
a/b/c
/a/b/c
/a/b/c
----
|===
[id="fn_path_ossep"]
===== `osPathSep`
`osPathSep` returns the https://pkg.go.dev/os#PathSeparator[`os.PathSeparator`^] for this OS.
[source,gotemplate]
----
{{- osPathSep }}
----
renders as:
[cols="^.^2,.^4a",options="header"]
|===
| OS ^| Result
| Windows | [source,text]
----
\
----
| Others (e.g. Linux, macOS) | [source,text]
----
/
----
|===
[id="fn_path_os_psj"]
===== `osPathSliceJoin`
`osPathSliceJoin` operates like <<fn_path_gnrc_psj>> but with OS-specific path separators.
[TIP]
====
The `splitList` function shown below is from the https://masterminds.github.io/sprig/string_slice.html[`sprig` string slice functions^].
====
[source,gotemplate]
----
{{- $myList := "a,b,c" | splitList "," -}}
{{- $myList | osPathSliceJoin }}
{{- ("a,b,c" | splitList ",") | osPathSliceJoin }}
{{- ("/,a,b,c" | splitList ",") | osPathSliceJoin }}
----
renders as:
[cols="^.^2,.^4a",options="header"]
|===
| OS ^| Result
| Windows | [source,text]
----
a\b\c
a\b\c
\a\b\c
----
| Others (e.g. Linux, macOS) | [source,text]
----
a/b/c
a/b/c
/a/b/c
----
|===
[id="fn_path_os_pspj"]
===== `osPathSlicePipeJoin`
`osPathSlicePipeJoin` operates like <<fn_path_gnrc_pspj>> but with OS-specific separators.
[TIP]
====
The `splitList` function shown below is from the https://masterminds.github.io/sprig/string_slice.html[`sprig` string slice functions^].
====
[source,gotemplate]
----
{{- $myBase := "/a" -}}
{{- $myList := "b,c,d" | splitList "." -}}
{{- osPathSlicePipeJoin $myList $myBase }}
{{- $myBase | osPathSlicePipeJoin $myList }}
----
renders as:
[cols="^.^2,.^4a",options="header"]
|===
| OS ^| Result
| Windows | [source,text]
----
\a\b\c\d
\a\b\c\d
----
| Others (e.g. Linux, macOS) | [source,text]
----
/a/b/c/d
/a/b/c/d
----
|===
[id="fn_path_os_psubj"]
===== `osPathSubJoin`
`osPathSubJoin` operates like <<fn_path_gnrc_psubj>> but with OS-specific separators.
The pipeline-friendly equivalent of this is <<fn_path_os_ppj>>.
[source,gotemplate]
----
{{- osPathSubJoin "/a/b" "c" }}
{{- osPathSubJoin "/" "a" "b" "c" }}
{{- "c" | osPathSubJoin "/" "a" "b" }}
----
renders as:
[cols="^.^2,.^4a",options="header"]
|===
| OS ^| Result
| Windows | [source,text]
----
\a\b\c
\a\b\c
\a\b\c
----
| Others (e.g. Linux, macOS) | [source,text]
----
/a/b/c
/a/b/c
/a/b/c
----
|===
[id="fn_str"]
=== Strings
[id="fn_str_extindent"]
==== `extIndent`
`extIndent` allows for a MUCH more flexible indenter than the `sprig` `indent` function.
It works with both Windows (`\r\n`) and POSIX (`\n`) linebreaks.
[TIP]
====
If `<indentString>` is set to `\n` and `<levels>` is always set to `1`, this function can even be used to doubelspace text!
====
It has quite a few arguments, however:
[source,gotemplate]
----
{{ extIndent <levels> <skipFirst> <skipEmpty> <skipWhitespace> <indentString> <input> }}
----
Where:
* `<levels>`: The level of indentation for the text. If less than or equal to `0`, `extIndent` just returns `<input>` as-is and NO-OPs otherwise.
* `<skipFirst>`: If true, skip indenting the first line. This is particularly handy if you like to visually align your function calls in your templates.
* `<skipEmpty>`: If true, do not add an indent to *empty* lines (where an "empty line" means "only has a linebreak").
* `<skipWhitespace>`: If true, do not add an indent to lines that *only* consist of whitespace (spaces, tabs, etc.) and a linebreak.
* `<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_dbg"]
=== Debugging
[id="fn_dbg_dump"]
==== `dump`
The `dump` function calls https://pkg.go.dev/github.com/davecgh/go-spew/spew#Sdump[the `Sdump` function^] from https://github.com/davecgh/go-spew[`go-spew`] (https://pkg.go.dev/github.com/davecgh/go-spew/spew[`github.com/davecgh/go-spew/spew`^]) for whatever object(s) is/are passed to it.

1532
tplx/sprigx/README.html Normal file

File diff suppressed because it is too large Load Diff

656
tplx/sprigx/README.md Normal file
View File

@@ -0,0 +1,656 @@
# What is SprigX?
SprigX are extensions to [the `sprig`
library](https://masterminds.github.io/sprig/) ([Go
docs](https://pkg.go.dev/github.com/Masterminds/sprig/v3)).
They provide functions that offer more enriched use cases and
domain-specific data.
# How do I Use SprigX?
package main
import (
htmlTplLib "html/template"
txtTplLib "text/template"
"r00t2.io/goutils/tplx/sprigx"
)
var (
txtTpl *txtTplLib.Template = txtTplLib.
New("").
Funcs(
sprigx.TxtFuncMap(),
)
htmlTpl *htmlTplLib.Template = htmlTplLib.
New("").
Funcs(
sprigx.HtmlFuncMap(),
)
)
package main
import (
"text/template"
"github.com/Masterminds/sprig/v3"
"r00t2.io/goutils/tplx/sprigx"
)
var txtTpl *template.Template = template.
New("").
Funcs(
sprigx.TxtFuncMap(),
).
Funcs(
sprig.TxtFuncMap(),
)
// Or:
/*
var txtTpl *template.Template = template.
New("").
Funcs(
sprig.TxtFuncMap(),
).
Funcs(
sprigx.TxtFuncMap(),
)
*/
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`:
package main
import (
"text/template"
"github.com/Masterminds/sprig/v3"
"r00t2.io/goutils/tplx/sprigx"
)
const (
myTpl string = `{{ "This is an example template string." | foo }}`
)
var (
tpl *template.Template = template.Must(
template.
New("").
Funcs(sprig.TxtFuncMap()).
Parse(myTpl),
).
Funcs(sprigx.TxtFuncMap())
)
whereas
package main
import (
"text/template"
"github.com/Masterminds/sprig/v3"
"r00t2.io/goutils/tplx/sprigx"
)
const (
myTpl string = `{{ "This is an example template string." | foo }}`
)
var (
tpl *template.Template = template.Must(
template.
New("").
Funcs(sprigx.TxtFuncMap()).
Parse(myTpl),
).
Funcs(sprig.TxtFuncMap())
)
and a function can even be
This would override a function `foo` and `foo2` in `sprigx` from `foo`
and `foo2` from `sprig`, but leave all other `sprig` functions
untouched.
package main
import (
"text/template"
"github.com/Masterminds/sprig/v3"
"r00t2.io/goutils/tplx/sprigx"
)
const (
myTpl string = `{{ "This is an example template string." | foo }}`
)
var (
overrideFuncs template.FuncMap = sprig.TxtFuncMap()
tpl *template.Template = template.Must(
template.
New("").
Funcs(sprigx.TxtFuncMap()).
Parse(myTpl),
).
Funcs(
template.FuncMap(
map[string]any{
"foo": overrideFuncs["foo"],
"foo2": overrideFuncs["foo2"],
},
),
)
)
# Functions
Expect this list to grow over time, and potentially more frequently than
the `sprigx` functions.
## System/OS/Platform
### `sysArch`
Returns the [`runtime.GOARCH`](https://pkg.go.dev/runtime#GOARCH)
constant.
### `sysNumCpu`
Returns the value from
[`runtime.NumCPU`](https://pkg.go.dev/runtime#NumCPU).
### `sysOsName`
Returns the [`runtime.GOOS`](https://pkg.go.dev/runtime#GOOS) constant.
### `sysRuntime`
This function returns a `map[string]string` of various information from
the [`runtime` stdlib library](https://pkg.go.dev/runtime).
Specifically, the following are returned.
The value type is a direct link to the `runtime` documentation providing
more detail about the associated value.
Because all values are mapped as strings, they can be converted back to
their native type via e.g. the [Sprig conversion
functions](https://masterminds.github.io/sprig/conversion.html) if
necessary.
<table>
<caption><code>sysRuntime</code> Values</caption>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<thead>
<tr>
<th style="text-align: center;">Key</th>
<th style="text-align: center;">Value Type</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;"><p><code>compiler</code></p></td>
<td style="text-align: center;"><p><a
href="https://pkg.go.dev/runtime#Compiler">string</a></p></td>
</tr>
<tr>
<td style="text-align: center;"><p><code>arch</code></p></td>
<td style="text-align: center;"><p><a
href="https://pkg.go.dev/runtime#GOARCH">string</a></p></td>
</tr>
<tr>
<td style="text-align: center;"><p><code>os</code></p></td>
<td style="text-align: center;"><p><a
href="https://pkg.go.dev/runtime#GOOS">string</a></p></td>
</tr>
<tr>
<td style="text-align: center;"><p><code>maxprocs</code></p></td>
<td style="text-align: center;"><p><a
href="https://pkg.go.dev/runtime#GOMAXPROCS">int</a> <a href="#fn1"
class="footnote-ref" id="fnref1"
role="doc-noteref"><sup>1</sup></a></p></td>
</tr>
<tr>
<td style="text-align: center;"><p><code>cpu_cnt</code></p></td>
<td style="text-align: center;"><p><a
href="https://pkg.go.dev/runtime#NumCPU">int</a></p></td>
</tr>
<tr>
<td style="text-align: center;"><p><code>num_cgo</code></p></td>
<td style="text-align: center;"><p><a
href="https://pkg.go.dev/runtime#NumCgoCall">int</a></p></td>
</tr>
<tr>
<td style="text-align: center;"><p><code>num_go</code></p></td>
<td style="text-align: center;"><p><a
href="https://pkg.go.dev/runtime#NumGoroutine">int</a></p></td>
</tr>
<tr>
<td style="text-align: center;"><p><code>go_ver</code></p></td>
<td style="text-align: center;"><p><a
href="https://pkg.go.dev/runtime#Version">string</a></p></td>
</tr>
</tbody>
</table>
<section id="footnotes" class="footnotes footnotes-end-of-document"
role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>For safety concerns, <code>sprigx</code> does not allow
<strong>setting</strong> <code>GOMAXPROCS</code>, this value only
contains the <strong>current</strong> <code>GOMAXPROCS</code> value.<a
href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>
As a convenience, some of these values also have their own dedicated
functions as well:
- [](#fn_sys_arch)
- [](#fn_sys_numcpu)
- [](#fn_sys_os)
## Paths
### Generic
These operate similar to [the `path` stdlib
library](https://pkg.go.dev/path) and use a fixed `/` path separator.
#### `pathJoin`
`pathJoin` operates **exactly** like
[`path.Join`](https://pkg.go.dev/path#Join) in stdlib.
If you are joining paths in a pipeline, you almost assuredly want
[](#fn_path_gnrc_ppj) or [](#fn_path_gnrc_pspj) instead.
{{- pathJoin "a" "b" "c" }}
{{- pathJoin "/" "a" "b" "c" }}
{{- pathJoin "/a/b" "c" }}
renders as:
a/b/c
/a/b/c
/a/b/c
#### `pathPipeJoin`
`pathPipeJoin` operates like [](#fn_path_gnrc_pj) with one deviation:
the root/base path is expected to be **last** in the arguments.
This makes it much more suitable for use in template pipelines, as the
previous value in a pipeline is passed in as the last element to the
next pipe function.
{{- $myBase := "/a" -}}
{{- pathPipeJoin "b" "c" "a" }}
{{- pathPipeJoin "a" "b" "c" "/" }}
{{- $myBase | pathPipeJoin "b" "c" }}
renders as:
a/b/c
/a/b/c
/a/b/c
#### `pathSliceJoin`
`pathSliceJoin` joins a slice of path segment strings (`[]string`)
instead of a variadic sequence of strings.
The `splitList` function shown below is from the [`sprig` string slice
functions](https://masterminds.github.io/sprig/string_slice.html).
{{- $myList := "a,b,c" | splitList "," -}}
{{- $myList | pathSliceJoin }}
{{- ("a,b,c" | splitList ",") | pathSliceJoin }}
{{- ("/,a,b,c" | splitList ",") | pathSliceJoin }}
renders as:
a/b/c
a/b/c
/a/b/c
#### `pathSlicePipeJoin`
`pathSlicePipeJoin` operates like [](#fn_path_gnrc_ppj) in that it is
suitable for pipeline use in which the root/base path is passed in from
the pipeline, but it is like [](#fn_path_gnrc_psj) in that it then also
accepts a slice of path segments (`[]string`) to append to that base
path.
The `splitList` function shown below is from the [`sprig` string slice
functions](https://masterminds.github.io/sprig/string_slice.html).
{{- $myBase := "/a" -}}
{{- $myList := "b,c,d" | splitList "." -}}
{{- pathSlicePipeJoin $myList $myBase }}
{{- $myBase | pathSlicePipeJoin $myList }}
renders as:
/a/b/c
/a/b/c
#### `pathSubJoin`
`pathSubJoin` operates like [](#fn_path_gnrc_pj) but it expects an
explicit root/base path.
The pipeline-friendly equivalent of this is [](#fn_path_gnrc_ppj).
{{- pathSubJoin "/a/b" "c" }}
{{- pathSubJoin "/" "a" "b" "c" }}
{{- "c" | pathSubJoin "/" "a" "b" }}
renders as:
/a/b/c
/a/b/c
/a/b/c
### OS/Platform-Tailored
These operate similar to [the `path/filepath` stdlib
library](https://pkg.go.dev/path/filepath), and use the OS-specific
[`os.PathSeparator`](https://pkg.go.dev/os#PathSeparator).
Take special note of the oddness around specifying Windows paths and
drive letters in e.g. [](#fn_path_os_pj)!
It is recommended to make use of [](#fn_sys_os) to conditionally format
path bases/roots if needed.
#### `osPathJoin`
`osPathJoin` operates **exactly** like
[`path/filepath.Join`](https://pkg.go.dev/path/filepath#Join) in stdlib.
If you are joining paths in a pipeline, you almost assuredly want
[](#fn_path_os_ppj) or [](#fn_path_os_pspj) instead.
{{- osPathJoin "a" "b" "c" }}
{{- osPathJoin "/" "a" "b" "c" }}
{{- osPathJoin "C:\\" "a" "b" "c" }}
{{- osPathJoin "C:" "a" "b" "c" }}
renders as:
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 66%" />
</colgroup>
<thead>
<tr>
<th style="text-align: center;">OS</th>
<th style="text-align: center;">Result</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;"><p>Windows</p></td>
<td style="text-align: left;"><pre class="text"><code>a\b\c
\a\b\c
\a\b\c
C:\a\b\c
C:a\b\c</code></pre></td>
</tr>
<tr>
<td style="text-align: center;"><p>Others (e.g. Linux, macOS)</p></td>
<td style="text-align: left;"><pre class="text"><code>a/b/c
/a/b/c
C:\/a/b/c
C:/a/b/c</code></pre></td>
</tr>
</tbody>
</table>
#### `osPathPipeJoin`
`osPathPipeJoin` operates like [](#fn_path_gnrc_ppj) (except using
OS-specific path separators).
This makes it much more suitable for use in template pipelines, as the
previous value in a pipeline is passed in as the last element to the
next pipe function.
{{- $myBase := "/a" -}}
{{- osPathPipeJoin "b" "c" "a" }}
{{- osPathPipeJoin "a" "b" "c" "/" }}
{{- $myBase | osPathPipeJoin "b" "c" }}
renders as:
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 66%" />
</colgroup>
<thead>
<tr>
<th style="text-align: center;">OS</th>
<th style="text-align: center;">Result</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;"><p>Windows</p></td>
<td style="text-align: left;"><pre class="text"><code>a\b\c
\a\b\c
\a\b\c</code></pre></td>
</tr>
<tr>
<td style="text-align: center;"><p>Others (e.g. Linux, macOS)</p></td>
<td style="text-align: left;"><pre class="text"><code>a/b/c
/a/b/c
/a/b/c</code></pre></td>
</tr>
</tbody>
</table>
#### `osPathSep`
`osPathSep` returns the
[`os.PathSeparator`](https://pkg.go.dev/os#PathSeparator) for this OS.
{{- osPathSep }}
renders as:
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 66%" />
</colgroup>
<thead>
<tr>
<th style="text-align: center;">OS</th>
<th style="text-align: center;">Result</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;"><p>Windows</p></td>
<td style="text-align: left;"><pre class="text"><code>\</code></pre></td>
</tr>
<tr>
<td style="text-align: center;"><p>Others (e.g. Linux, macOS)</p></td>
<td style="text-align: left;"><pre class="text"><code>/</code></pre></td>
</tr>
</tbody>
</table>
#### `osPathSliceJoin`
`osPathSliceJoin` operates like [](#fn_path_gnrc_psj) but with
OS-specific path separators.
The `splitList` function shown below is from the [`sprig` string slice
functions](https://masterminds.github.io/sprig/string_slice.html).
{{- $myList := "a,b,c" | splitList "," -}}
{{- $myList | osPathSliceJoin }}
{{- ("a,b,c" | splitList ",") | osPathSliceJoin }}
{{- ("/,a,b,c" | splitList ",") | osPathSliceJoin }}
renders as:
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 66%" />
</colgroup>
<thead>
<tr>
<th style="text-align: center;">OS</th>
<th style="text-align: center;">Result</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;"><p>Windows</p></td>
<td style="text-align: left;"><pre class="text"><code>a\b\c
a\b\c
\a\b\c</code></pre></td>
</tr>
<tr>
<td style="text-align: center;"><p>Others (e.g. Linux, macOS)</p></td>
<td style="text-align: left;"><pre class="text"><code>a/b/c
a/b/c
/a/b/c</code></pre></td>
</tr>
</tbody>
</table>
#### `osPathSlicePipeJoin`
`osPathSlicePipeJoin` operates like [](#fn_path_gnrc_pspj) but with
OS-specific separators.
The `splitList` function shown below is from the [`sprig` string slice
functions](https://masterminds.github.io/sprig/string_slice.html).
{{- $myBase := "/a" -}}
{{- $myList := "b,c,d" | splitList "." -}}
{{- osPathSlicePipeJoin $myList $myBase }}
{{- $myBase | osPathSlicePipeJoin $myList }}
renders as:
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 66%" />
</colgroup>
<thead>
<tr>
<th style="text-align: center;">OS</th>
<th style="text-align: center;">Result</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;"><p>Windows</p></td>
<td style="text-align: left;"><pre class="text"><code>\a\b\c\d
\a\b\c\d</code></pre></td>
</tr>
<tr>
<td style="text-align: center;"><p>Others (e.g. Linux, macOS)</p></td>
<td style="text-align: left;"><pre class="text"><code>/a/b/c/d
/a/b/c/d</code></pre></td>
</tr>
</tbody>
</table>
#### `osPathSubJoin`
`osPathSubJoin` operates like [](#fn_path_gnrc_psubj) but with
OS-specific separators.
The pipeline-friendly equivalent of this is [](#fn_path_os_ppj).
{{- osPathSubJoin "/a/b" "c" }}
{{- osPathSubJoin "/" "a" "b" "c" }}
{{- "c" | osPathSubJoin "/" "a" "b" }}
renders as:
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 66%" />
</colgroup>
<thead>
<tr>
<th style="text-align: center;">OS</th>
<th style="text-align: center;">Result</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;"><p>Windows</p></td>
<td style="text-align: left;"><pre class="text"><code>\a\b\c
\a\b\c
\a\b\c</code></pre></td>
</tr>
<tr>
<td style="text-align: center;"><p>Others (e.g. Linux, macOS)</p></td>
<td style="text-align: left;"><pre class="text"><code>/a/b/c
/a/b/c
/a/b/c</code></pre></td>
</tr>
</tbody>
</table>
## Strings
### `extIndent`
`extIndent` allows for a MUCH more flexible indenter than the `sprig`
`indent` function.
It works with both Windows (`\r\n`) and POSIX (`\n`) linebreaks.
It has quite a few arguments, however:
{{ extIndent <levels> <skipFirst> <skipEmpty> <skipWhitespace> <indentString> <input> }}
Where:
- `<levels>`: The level of indentation for the text. If less than or
equal to `0`, `extIndent` just returns `<input>` as-is and NO-OPs
otherwise.
- `<skipFirst>`: If true, skip indenting the first line. This is
particularly handy if you like to visually align your function calls
in your templates.
- `<skipEmpty>`: If true, do not add an indent to **empty** lines
(where an "empty line" means "only has a linebreak").
- `<skipWhitespace>`: If true, do not add an indent to lines that
**only** consist of whitespace (spaces, tabs, etc.) and a linebreak.
- `<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.

101
tplx/sprigx/_test.tpl Normal file
View File

@@ -0,0 +1,101 @@
################################################################################
# RUNTIME #
################################################################################
{{- $rntm := sysRuntime }}
Arch: {{ sysArch }}
CPUs: {{ sysNumCpu }}
OS: {{ sysNumCpu }}
RUNTIME: {{ $rntm }}
{{ range $rntmk, $rntmv := $rntm }}
{{ $rntmk }}:
{{ $rntmv }}
{{- end }}
{{ dump $rntm }}
################################################################################
# PATHS #
################################################################################
###########
# Generic #
###########
pathJoin "a" "b" "c"
{{ pathJoin "a" "b" "c" }}
pathJoin "/" "a" "b" "c"
{{ pathJoin "/" "a" "b" "c" }}
pathJoin "/a" "b" "c"
{{ pathJoin "/a" "b" "c" }}
#
pathPipeJoin "b" "c" "d" "a"
{{ pathPipeJoin "b" "c" "d" "a" }}
"a" | pathPipeJoin "b" "c" "d"
{{ "a" | pathPipeJoin "b" "c" "d"}}
#
$base := "/"
$myPsjSlice := "a,b,c" | splitList ","
pathSliceJoin $myPsjSlice
{{- $base := "/" }}
{{- $myPsjSlice := "a,b,c" | splitList "," }}
{{ pathSliceJoin $myPsjSlice }}
#
$base | pathSlicePipeJoin $myPsjSlice
{{ $base | pathSlicePipeJoin $myPsjSlice }}
#
pathSubJoin $base "a" "b" "c"
{{ pathSubJoin $base "a" "b" "c" }}
######################
# OS/System/Platform #
######################
osPathJoin "a" "b" "c"
{{ osPathJoin "a" "b" "c" }}
osPathJoin "/" "a" "b" "c"
{{ osPathJoin "a" "b" "c" }}
osPathJoin "/a" "b" "c"
{{ osPathJoin "a" "b" "c" }}
#
osPathPipeJoin "b" "c" "d" "a"
{{ osPathPipeJoin "b" "c" "d" "a" }}
"a" | osPathPipeJoin "b" "c" "d"
{{ "a" | osPathPipeJoin "b" "c" "d" }}
#
$osBase := "/"
$myOsPsjSlice := "a,b,c" | splitList ","
osPathSliceJoin $myOsPsjSlice
{{- $osBase := "/" }}
{{- $myOsPsjSlice := "a,b,c" | splitList "," }}
{{ osPathSliceJoin $myOsPsjSlice }}
#
$osBase | osPathSlicePipeJoin $myOsPsjSlice
{{ $osBase | osPathSlicePipeJoin $myOsPsjSlice }}
#
osPathSubJoin $osBase "a" "b" "c"
{{ osPathSubJoin $osBase "a" "b" "c" }}

40
tplx/sprigx/consts.go Normal file
View File

@@ -0,0 +1,40 @@
package sprigx
import (
"path"
"path/filepath"
)
var (
// genericMap holds functions usable/intended for use in either an [html/template.FuncMap] or [text/template.FuncMap].
genericMap map[string]any = map[string]any{
// Debugging
"dump": dump,
// Strings
"extIndent": extIndent, // PR in: https://github.com/Masterminds/sprig/pull/468
// OS/System
"sysArch": sysArch,
"sysNumCpu": sysNumCpu,
"sysOsName": sysOsNm,
"sysRuntime": sysRuntime,
// Paths: Generic
"pathJoin": path.Join,
"pathPipeJoin": pathPipeJoin,
"pathSliceJoin": pathSliceJoin,
"pathSlicePipeJoin": pathSlicePipeJoin,
"pathSubJoin": pathSubJoin,
// Paths: OS/Platform
"osPathJoin": filepath.Join,
"osPathPipeJoin": osPathPipeJoin,
"osPathSep": osPathSep,
"osPathSliceJoin": osPathSliceJoin,
"osPathSlicePipeJoin": osPathSlicePipeJoin,
"osPathSubJoin": osPathSubJoin,
}
// htmlMap holds functions usable/intended for use in only an [html/template.FuncMap].
htmlMap map[string]any = map[string]any{}
// txtMap holds functions usable/intended for use in only a [text/template.FuncMap].
txtMap map[string]any = map[string]any{}
)

16
tplx/sprigx/doc.go Normal file
View File

@@ -0,0 +1,16 @@
/*
Package sprigx aims to provide additional functions that the author believes are missing from [sprig] ([Go docs]).
It's a decent enough "basics" library, but I frequently find it falls short once you start needing domain-specific data.
These may get merged into sprig, they may not. It all depends on how responsive they are to PRs.
Given that they only update it every 6 months or so, however...
See the [full documentation] on the [repo].
[sprig]: https://masterminds.github.io/sprig/
[Go docs]: https://pkg.go.dev/github.com/Masterminds/sprig/v3
[full documentation]: https://git.r00t2.io/r00t2/go_goutils/src/branch/master/tplx/sprigx/README.adoc
[repo]: https://git.r00t2.io/r00t2/go_goutils
*/
package sprigx

64
tplx/sprigx/funcs.go Normal file
View File

@@ -0,0 +1,64 @@
package sprigx
import (
htpl "html/template"
ttpl "text/template"
)
/*
Many of these functions are modeled after sprig's.
*/
/*
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
}
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
}
}
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
}
}
return
}

33
tplx/sprigx/funcs_test.go Normal file
View File

@@ -0,0 +1,33 @@
package sprigx
import (
`bytes`
_ "embed"
"testing"
`text/template`
"github.com/Masterminds/sprig/v3"
)
var (
//go:embed "_test.tpl"
testTplBytes []byte
testTpl *template.Template = template.Must(
template.
New("").
Funcs(sprig.TxtFuncMap()).
Funcs(TxtFuncMap()).
Parse(string(testTplBytes)),
)
)
func TestFuncs(t *testing.T) {
var err error
var buf *bytes.Buffer = new(bytes.Buffer)
if err = testTpl.Execute(buf, nil); err != nil {
t.Fatal(err)
}
t.Log(buf.String())
}

View File

@@ -0,0 +1,17 @@
package sprigx
import (
`github.com/davecgh/go-spew/spew`
)
/*
dump calls [spew.Sdump] on obj.
[spew.Sdump]: https://pkg.go.dev/github.com/davecgh/go-spew/spew
*/
func dump(obj any) (out string) {
out = spew.Sdump(obj)
return
}

View File

@@ -0,0 +1,155 @@
package sprigx
import (
`os`
`path`
`path/filepath`
)
/*
//
// GENERIC
//
*/
/*
pathPipeJoin wraps path.Join with the root element at the *end* instead of the beginning.
{{ pathPipeJoin "b" "c" "a" }}
is equivalent to
path.Join("a", "b", "c")
This order variation is better suited for pipelines that pass the root path.
*/
func pathPipeJoin(elems ...string) (out string) {
var rootIdx int
if elems == nil || len(elems) == 0 {
return
}
rootIdx = len(elems) - 1
out = elems[rootIdx]
if len(elems) == 1 {
return
}
out = pathSubJoin(out, elems[:rootIdx]...)
return
}
// pathSliceJoin joins a slice of path segments.
func pathSliceJoin(sl []string) (out string) {
out = path.Join(sl...)
return
}
/*
pathSlicePipeJoin behaves like a mix of pathPipeJoin (in that it accepts the root element last)
and pathSliceJoin (in that it accepts a slice of subpath segments).
It's essentially like pathSubJoin in reverse, and with an explicit slice.
*/
func pathSlicePipeJoin(sl []string, root string) (out string) {
out = pathSubJoin(root, sl...)
return
}
/*
pathSubJoin is like path.Join except it takes an explicit root
and additional slice of subpaths to sequentially join to it.
*/
func pathSubJoin(root string, elems ...string) (out string) {
if elems == nil || len(elems) == 0 {
out = root
return
}
out = path.Join(
root,
path.Join(
elems...,
),
)
return
}
/*
//
// OS/PLATFORM
//
*/
/*
osPathPipeJoin is like pathPipeJoin but uses the rendering OS' path separator (os.PathSeparator).
*/
func osPathPipeJoin(elems ...string) (out string) {
var rootIdx int
if elems == nil || len(elems) == 0 {
return
}
rootIdx = len(elems) - 1
out = elems[rootIdx]
if len(elems) == 1 {
return
}
out = osPathSubJoin(out, elems[:rootIdx]...)
return
}
// osPathSep returns os.PathSeparator.
func osPathSep() (out string) {
out = string(os.PathSeparator)
return
}
// osPathSliceJoin is the OS-specific implementation of pathSliceJoin.
func osPathSliceJoin(sl []string) (out string) {
out = filepath.Join(sl...)
return
}
// osPathSlicePipeJoin is the OS-specific implementation of pathSlicePipeJoin.
func osPathSlicePipeJoin(sl []string, root string) (out string) {
out = osPathSubJoin(root, sl...)
return
}
// osPathSubJoin is the OS-specific implementation of pathSubJoin.
func osPathSubJoin(root string, elems ...string) (out string) {
if elems == nil || len(elems) == 0 {
out = root
return
}
out = filepath.Join(
root,
filepath.Join(
elems...,
),
)
return
}

View File

@@ -0,0 +1,52 @@
package sprigx
import (
`strings`
)
/*
extIndent serves as a much more flexible alternative to the Sprig `indent`.
It has 6 arguments (the last of which may be passed in via pipeline):
* levels: The level of indentation for the text. If less than or equal to `0`, `extIndent` just returns `<input>` as-is and NO-OPs otherwise.
* skipFirst: If true, skip indenting the first line. This is particularly handy if you like to visually align your function calls in your templates.
* skipEmpty: If true, do not add an indent to *empty* lines (where an "empty line" means "only has a linebreak").
* skipWhitespace: If true, do not add an indent to lines that *only* consist of whitespace (spaces, tabs, etc.) and a linebreak.
* indentString: The string to use as the "indent character". This can be any string, such as `" "`, `"\t"`, `"."`, `"|"`, `"=="` etc.
(In fact, if indentString is set to "\n" and levels is always set to 1, this function can even be used to doubelspace text!)
* input: The text to be indented. Because it is the last argument, `extIndent` works with pipelined text as well.
*/
func extIndent(levels int, skipFirst, skipEmpty, skipWhitespace bool, indentString, input string) (out string) {
var idx int
var pad string
var line string
var lines []string
if levels <= 0 {
out = input
return
}
pad = strings.Repeat(indentString, levels)
lines = strings.Split(input, "\n")
for idx, line = range lines {
if idx == 0 && skipFirst {
continue
}
if skipWhitespace && strings.TrimSpace(line) == "" && line != "" {
continue
}
if skipEmpty && (line == "" || line == "\r") {
continue
}
lines[idx] = pad + line
}
out = strings.Join(lines, "\n")
return
}

View File

@@ -0,0 +1,47 @@
package sprigx
import (
`fmt`
`runtime`
)
// sysRuntime returns various information from [runtime].
func sysRuntime() (out map[string]string) {
out = map[string]string{
"compiler": runtime.Compiler,
"arch": runtime.GOARCH,
"os": runtime.GOOS,
"maxprocs": fmt.Sprintf("%d", runtime.GOMAXPROCS(-1)),
"cpu_cnt": fmt.Sprintf("%d", runtime.NumCPU()),
"num_cgo": fmt.Sprintf("%d", runtime.NumCgoCall()),
"num_go": fmt.Sprintf("%d", runtime.NumGoroutine()),
"go_ver": runtime.Version(),
}
return
}
// sysArch returns [runtime.GOARCH].
func sysArch() (out string) {
out = runtime.GOARCH
return
}
// sysNumCpu returns the reuslt from [runtime.NumCPU].
func sysNumCpu() (out string) {
out = fmt.Sprintf("%d", runtime.NumCPU())
return
}
// sysOsNm returns [runtime.GOOS].
func sysOsNm() (out string) {
out = runtime.GOOS
return
}