4 Commits

Author SHA1 Message Date
brent saner
1bd6e1256c v1.16.3
ADDED:
* Much more functions to tplx/sprigx
2026-01-29 19:02:21 -05:00
brent saner
64a7648fbc v1.16.2
...why didn't my docs regen hook run?
2026-01-28 09:20:34 -05:00
brent saner
9cce861b2e v1.16.1
FIXED:
* *Some* documentation weirdness on pkg.go dev rendering. It still uses
  the Markdown render by default, and it seems if you use anchor links
  in a bulletpoint list, pandoc just says "lol screw you"...

ADDED:
* tplx/sprigx tpl function `osHostname`
2026-01-28 09:16:18 -05:00
brent saner
927ad08057 v1.16.0
ADDED:
* tplx/sprigx
2026-01-24 13:41:54 -05:00
23 changed files with 5158 additions and 10 deletions

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

@@ -0,0 +1,40 @@
#!/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"
set +e
asciidoctor -a ROOTDIR="${orig}/" -b docbook -o - "${f}" | pandoc -f docbook -t markdown_strict -o "${newf}"
if [ $? -eq 0 ];
then
echo "Generated ${newf} from ${f}"
git add "${newf}"
else
echo "Failed to generate ${newf} from ${f}"
git rm "${newf}"
fi
set -e
fi
cd ${orig}
done
echo "Regenerated docs"

25
go.mod
View File

@@ -3,14 +3,33 @@ module r00t2.io/goutils
go 1.25 go 1.25
require ( require (
github.com/coreos/go-systemd/v22 v22.6.0 github.com/Masterminds/sprig/v3 v3.3.0
github.com/coreos/go-systemd/v22 v22.7.0
github.com/davecgh/go-spew v1.1.1
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/shirou/gopsutil/v4 v4.25.12
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/sys v0.39.0 golang.org/x/sys v0.40.0
r00t2.io/sysutils v1.15.1 r00t2.io/sysutils v1.16.0
) )
require ( require (
dario.cat/mergo v1.0.2 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/djherbis/times v1.6.0 // indirect github.com/djherbis/times v1.6.0 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/crypto v0.47.0 // indirect
golang.org/x/sync v0.19.0 // indirect golang.org/x/sync v0.19.0 // indirect
) )

68
go.sum
View File

@@ -1,15 +1,71 @@
github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
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.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.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.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
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 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
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/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 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/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/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
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/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
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/shirou/gopsutil/v4 v4.25.12 h1:e7PvW/0RmJ8p8vPGJH4jvNkOyLmbkXgXW4m6ZPic6CY=
github.com/shirou/gopsutil/v4 v4.25.12/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
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.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= 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= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= 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/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
r00t2.io/sysutils v1.15.1 h1:0EVZZAxTFqQN6jjfjqUKkXye0LMshUA5MO7l3Wd6wH8= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
r00t2.io/sysutils v1.15.1/go.mod h1:T0iOnaZaSG5NE1hbXTqojRZc0ia/u8TB73lV7zhMz58= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

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]). 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]"). If addr is an IPv6 address, it will be enclosed in brackets (e.g. "[2001:db8::1]").

1460
tplx/sprigx/README.adoc Normal file

File diff suppressed because it is too large Load Diff

2733
tplx/sprigx/README.html Normal file

File diff suppressed because it is too large Load Diff

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" }}

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

@@ -0,0 +1,130 @@
package sprigx
import (
`os`
`os/user`
`path`
`path/filepath`
`runtime`
`github.com/davecgh/go-spew/spew`
`github.com/shirou/gopsutil/v4/cpu`
`github.com/shirou/gopsutil/v4/disk`
`github.com/shirou/gopsutil/v4/host`
`github.com/shirou/gopsutil/v4/load`
`github.com/shirou/gopsutil/v4/mem`
psnet `github.com/shirou/gopsutil/v4/net`
`github.com/shirou/gopsutil/v4/process`
`github.com/shirou/gopsutil/v4/sensors`
`r00t2.io/sysutils`
)
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": spew.Sdump,
/*
"Meta"/Template-Helpers
*/
"metaIsNil": metaIsNil,
/*
OS
*/
"osFQDN": osFQDN,
"osGroupById": osGroupById,
"osGroupByName": user.LookupGroup,
"osHost": osHost,
"osHostname": os.Hostname,
"osIdState": sysutils.GetIDState,
"osUser": user.Current,
"osUserById": osUserById,
"osUserByName": user.Lookup,
/*
Paths
*/
// 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,
/*
PSUtil
(https://pkg.go.dev/github.com/shirou/gopsutil/v4)
*/
// .../cpu
"psCpuCnts": cpu.Counts,
"psCpuInfo": cpu.Info,
"psCpuPct": cpu.Percent,
"psCpuTimes": cpu.Times,
// .../disk
"psDiskIoCnts": disk.IOCounters,
"psDiskLabel": disk.Label,
"psDiskParts": disk.Partitions,
"psDiskSerial": disk.SerialNumber,
"psDiskUsage": disk.Usage,
// .../host
"psHostBoot": host.BootTime,
"psHostId": host.HostID,
"psHostInfo": host.Info,
"psHostKernArch": host.KernelArch,
"psHostKernVer": host.KernelVersion,
"psHostPlatInfo": psHostPlatInfo,
"psHostUptime": host.Uptime,
"psHostUsers": host.Users,
"psHostVirt": psHostVirt,
// .../load
"psLoadAvg": load.Avg,
"psLoadMisc": load.Misc,
// .../mem
"psMemSwap": mem.SwapMemory,
"psMemSwapDevs": mem.SwapDevices,
"psMemVMem": mem.VirtualMemory,
// .../net
"psNetConns": psnet.Connections,
"psNetConnsMax": psnet.ConnectionsMax,
"psNetConnsPid": psnet.ConnectionsPid,
"psNetConnsPidMax": psnet.ConnectionsPidMax,
"psNetCTStats": psnet.ConntrackStats,
"psNetCTStatList": psnet.NewConntrackStatList,
"psNetFilterCnts": psnet.FilterCounters,
"psNetIoCnts": psnet.IOCounters,
"psNetIoCntsFile": psnet.IOCountersByFile,
"psNetIfaces": psnet.Interfaces,
"psNetPids": psnet.Pids,
"psNetProtoCnt": psnet.ProtoCounters,
// .../process
"psProcs": process.Processes,
"psProcNew": process.NewProcess,
"psProcPids": process.Pids,
"psProcPidExists": process.PidExists,
// .../sensors
"psSensorTemps": sensors.SensorsTemperatures,
/*
Strings
*/
"extIndent": extIndent, // PR in: https://github.com/Masterminds/sprig/pull/468
/*
System/Platform
*/
"sysArch": sysArch,
"sysNumCpu": runtime.NumCPU,
"sysOsName": sysOsNm,
"sysRuntime": sysRuntime,
}
// 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{}
)

View File

@@ -0,0 +1,9 @@
//go:build darwin
package sprigx
var (
osGenericMap map[string]any = map[string]any{}
osHtmlMap map[string]any = map[string]any{}
osTxtMap map[string]any = map[string]any{}
)

View File

@@ -0,0 +1,25 @@
//go:build linux
package sprigx
import (
`github.com/shirou/gopsutil/v4/mem`
psnet `github.com/shirou/gopsutil/v4/net`
)
var (
osGenericMap map[string]any = map[string]any{
/*
PSUtil
(https://pkg.go.dev/github.com/shirou/gopsutil/v4)
*/
// .../mem
"psMemExVMem": mem.NewExLinux().VirtualMemory,
// .../net
"psNetRev": psnet.Reverse,
// .../sensors
"psSensorExTemp": psSensorExTemp,
}
osHtmlMap map[string]any = map[string]any{}
osTxtMap map[string]any = map[string]any{}
)

View File

@@ -0,0 +1,9 @@
//go:build !(linux || windows || darwin)
package sprigx
var (
osGenericMap map[string]any = map[string]any{}
osHtmlMap map[string]any = map[string]any{}
osTxtMap map[string]any = map[string]any{}
)

View File

@@ -0,0 +1,24 @@
//go:build windows
package sprigx
import (
`github.com/shirou/gopsutil/v4/mem`
`github.com/shirou/gopsutil/v4/winservices`
)
var (
osGenericMap map[string]any = map[string]any{
/*
PSUtil
(https://pkg.go.dev/github.com/shirou/gopsutil/v4)
*/
// .../mem
"psMemExVMem": mem.NewExWindows().VirtualMemory,
// .../winservices
"psWinsvcList": winservices.ListServices,
"psWinsvcNew": winservices.NewService,
}
osHtmlMap map[string]any = map[string]any{}
osTxtMap 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

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

@@ -0,0 +1,81 @@
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
}
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
}
// 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
}

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,14 @@
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) {
isNil = obj == nil
return
}

View File

@@ -0,0 +1,73 @@
package sprigx
import (
`os`
`os/user`
`strconv`
`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) {
var gidStr string
switch t := any(gid).(type) {
case string:
gidStr = t
case int:
gidStr = strconv.Itoa(t)
}
g, err = user.LookupGroupId(gidStr)
return
}
/*
osFQDN (tries to) return the FQDN of this host.
Currently it just calls os.Hostname() but may be extended to "try harder" in the future.
*/
func osFQDN() (fqdn string, err error) {
fqdn, err = os.Hostname()
return
}
/*
osHost returns the system's "host shortname".
Currently it just calls os.Hostname() and takes the first
"host label" (as RFCs refer to it), but it may be extended
in the future.
*/
func osHost() (hostNm string, err error) {
hostNm, err = os.Hostname()
if hostNm == "" {
return
}
hostNm = strings.Split(hostNm, ".")[0]
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) {
var uidStr string
switch t := any(uid).(type) {
case string:
uidStr = t
case int:
uidStr = strconv.Itoa(t)
}
u, err = user.LookupId(uidStr)
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,43 @@
package sprigx
import (
`github.com/shirou/gopsutil/v4/host`
)
/*
psHostPlatInfo returns a "squashed" github.com/shirou/gopsutil/v4/host.PlatformInformation;
normally it returns a (string, string, string, error)
but you can only have a (any) or (any, error) return in Golang templates.
*/
func psHostPlatInfo() (platInfo [3]string, err error) {
var s1 string
var s2 string
var s3 string
if s1, s2, s3, err = host.PlatformInformation(); err != nil {
return
}
platInfo = [3]string{s1, s2, s3}
return
}
/*
psHostVirt returns a "squared" github.com/shirou/gopsutil/v4/host.Virtualization;
normally it returns a (string, string, error) but Go templates etc.
*/
func psHostVirt() (virtInfo [2]string, err error) {
var s1 string
var s2 string
if s1, s2, err = host.Virtualization(); err != nil {
return
}
virtInfo = [2]string{s1, s2}
return
}

View File

@@ -0,0 +1,15 @@
package sprigx
import (
`context`
`github.com/shirou/gopsutil/v4/sensors`
)
// psSensorExTemp wraps github.com/shirou/gopsutil/v4/sensors.NewExLinux().TemperatureWithContext() to not require a context.
func psSensorExTemp() (exTemps []sensors.ExTemperature, err error) {
exTemps, err = sensors.NewExLinux().TemperatureWithContext(context.Background())
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,39 @@
package sprigx
import (
`fmt`
`runtime`
)
// sysArch returns [runtime.GOARCH].
func sysArch() (out string) {
out = runtime.GOARCH
return
}
// sysOsNm returns [runtime.GOOS].
func sysOsNm() (out string) {
out = runtime.GOOS
return
}
// 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
}