= 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
// BEGIN variable attributes
:sprig_ver: 3
:psutil_ver: 4
:git_owner: r00t2
:git_repo: go_goutils
:git_repo_full: {git_owner}/{git_repo}
:mod_me: r00t2.io/goutils
:pkg_me: tplx/sprigx
:src_root: https://git.r00t2.io
:godoc_root: https://pkg.go.dev
:sprig_web: https://masterminds.github.io/sprig
:mod_sprig: github.com/Masterminds/sprig/v{sprig_ver}
:mod_psutil: github.com/shirou/gopsutil/v{psutil_ver}
:import_sprig: {mod_sprig}
:src_base: {src_root}/{git_repo_full}
:src_git: {src_base}.git
:src_tree: {src_base}/src/branch/master
:src_raw: {src_base}/raw/branch/master
:src_dir: {src_tree}/{pkg_me}
:src_dir_raw: {src_raw}/{pkg_me}
:import_me: {mod_me}/{pkg_me}
:godoc_me: {godoc_root}/{import_me}
:godoc_sprig: {godoc_root}/{import_sprig}
:funcsig_tpl: ./_docs/includes/funcsig.adoc
// Gorram it AsciiDoc stop snarfing whitespace
////
These are only currently used by extIndent and strsxIsAsciiSpcl,
but there's no way to get rid of the blank lines/extra newlines.

If I specify it without the plus, it just renders the tabs in indnt.
If I specify it without the slash, I can't enxapsulate the newline.
If I specify the source block in the include without +post_replacements, it leaves the plus signs in.
If I specifiy it with, it double-spaces the thing.
Oh well.
TODO: File a bug for that nonsense.
////
:tab: pass:[	]
:nl: pass:[ + \
]
:indnt: pass:a[{nl}{tab}{tab}]
// END variable attributes

[id="wat"]
== What is SprigX?
SprigX is a suite of extensions to {sprig_web}/[the `sprig` library^] ({godoc_sprig}[Go docs^]).

They provide functions that offer more enriched use cases and domain-specific data.

[TIP]
====
If you are reading this README on the Go Module Directory documentation ({godoc_me})
or the directory landing page ({src_dir}), it may not render correctly.
Anchor-links (links within this document to other sections of this document) will likely also not work.

Be sure to view it at properly via {src_dir}/README.adoc[the in-repo AsciiDoc rendering^]
or by downloading and viewing the {src_dir_raw}/README.html[HTML version^] in a browser locally
and/or <<rndr_pdf, rendering a PDF version>>.
====

[id="rndr"]
== How do I Render These Docs?
This documentation is written in https://asciidoc.org/[AsciiDoc^] (with https://asciidoctor.org/[AsciiDoctor^] extensions).

To re-render all docs (including <<rndr_pdf>>):

[source,bash, subs="attributes"]
----
git clone {src_git}
cd {git_repo}
.githooks/pre-commit/01-docgen
----

[id="rndr_html"]
=== HTML
HTML output is re-rendered and included in git {src_dir}/.githooks/pre-commit/01-docgen[on each commit^] automatically (via https://github.com/gabyx/Githooks[`github:gabyx/Githooks`^])
but can be re-rendered on-demand locally via:

[source,bash, subs="attributes"]
----
git clone {src_git}
cd {git_repo}
export orig_dir="$(pwd)"
cd {pkg_me}
asciidoctor -a ROOTDIR="${orig_dir}/" -o README.html README.adoc
----

[id="rndr_pdf"]
=== PDF
This documentation can be rendered to PDF via https://docs.asciidoctor.org/pdf-converter/latest/[`asciidoctor-pdf`^].
It is not included in git automatically because binary files that change on each commit is not a good idea for git,
especially for a repo that gets cloned as part of a library inclusion in a module/package dependency system (like `gomod`).

To render as PDF:

[source,bash,subs="attributes"]
----
git clone {src_git}
cd {git_repo}
export orig_dir="$(pwd)"
cd {pkg_me}
asciidoctor-pdf -a ROOTDIR="${orig_dir}/" -o README.pdf README.adoc
----

[id="use"]
== How do I Use SprigX?

The same way you would `sprig`!

[%collapsible]
.Like this.
====
[source,go,subs="attributes",opts=novalidate]
----
package main

import (
	htmlTplLib "html/template"
	txtTplLib "text/template"

	"{import_me}"
)

var (
	txtTpl *txtTplLib.Template = txtTplLib.
		New("").
		Funcs(
			sprigx.TxtFuncMap(),
		)
	htmlTpl *htmlTplLib.Template = htmlTplLib.
		New("").
		Funcs(
			sprigx.HtmlFuncMap(),
		)
)
----
====

They can even be combined/used together,

[%collapsible]
.like this.
====
[source,go,subs="attributes",opts=novalidate]
----
package main

import (
	"text/template"

	"{import_sprig}"
	"{import_me}"
)

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(),
		)
 */
----
====

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`:

[%collapsible]
.this will use `foo` from `sprigx`
====
[source,go,subs="attributes",opts=novalidate]
----
package main

import (
	"text/template"

	"{import_sprig}"
	"{import_me}"
)

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())
)
----
====

[%collapsible]
.whereas this will use `foo` from `sprig`
====
[source,go,subs="attributes",opts=novalidate]
----
package main

import (
	"text/template"

	"{import_sprig}"
	"{import_me}"
)

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 explicitly [[override]]overridden,

[%collapsible]
.like this.
====
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,subs="attributes",opts=novalidate]
----
package main

import (
	"text/template"

	"{import_sprig}"
	"{import_me}"
)

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="lib"]
== Library Functions

These are generally intended to be used *outside* the template in the actual Go code.

[id="lib_cmbfmap"]
=== `CombinedFuncMap`
:func: CombinedFuncMap
:sig: (preferSprigX bool) (fmap map[string]any)
include::{funcsig_tpl}[]

This function returns a generic function map (like <<lib_fmap>>) combined with
{godoc_sprig}#GenericFuncMap[`{import_sprig}.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`
:func: CombinedHtmlFuncMap
:sig: (preferSprigX bool) (fmap template.FuncMap)
include::{funcsig_tpl}[]

This function returns an {godoc_root}/html/template#FuncMap[`html/template.FuncMap`] function map (like <<lib_hfmap>>) combined with
{godoc_sprig}#HtmlFuncMap[`{import_sprig}.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`
:func: CombinedTxtFuncMap
:sig: (preferSprigX bool) (fmap template.FuncMap)
include::{funcsig_tpl}[]

This function returns a {godoc_root}/text/template#FuncMap[`text/template.FuncMap`] function map (like <<lib_tfmap>>) combined with
{godoc_sprig}#TxtFuncMap[`{import_sprig}.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`
:func: FuncMap
:sig: () (fmap map[string]any)
include::{funcsig_tpl}[]

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`
:func: HtmlFuncMap
:sig: () (fmap template.FuncMap)
include::{funcsig_tpl}[]

This function returns a SprigX {godoc_root}/html/template#FuncMap[`html/template.FuncMap`^].

[id="lib_nop"]
=== `Nop`
:func: Nop
:sig: (obj ...any) (s string)
include::{funcsig_tpl}[]

`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`
:func: TxtFuncMap
:sig: () (fmap template.FuncMap)
include::{funcsig_tpl}[]

This function returns a SprigX {godoc_root}/text/template#FuncMap[`text/template.FuncMap`^].

[id="fn"]
== 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 arguments/parameters it accepts, their type(s),
what it returns, and the returned value(s) type(s).

Because Go template functions can only return either:

* a single value of any type, or
* a value of any type and an `error` (in that order)

you can easily determine whether a function can return an error or not by simply referring to the Function Signature.

[id="fn_dbg"]
=== Debugging

[id="fn_dbg_dump"]
==== `dump`
:func: dump
:sig: (a ...interface{}) (out string)
include::{funcsig_tpl}[]

The `dump` function directly calls {godoc_root}/davecgh/go-spew/spew#Sdump[`github.com/davecgh/go-spew/spew.Sdump`^]
for whatever object(s) is/are passed to it.

[id="fn_meta"]
=== "Meta"/Template Helpers

[id="fn_meta_isnil"]
==== `metaIsNil`
:func: metaIsNil
:sig: (obj any) (isNil bool)
include::{funcsig_tpl}[]

`metaIsNil` returns `true` if `obj` is explicitly nil, otherwise it returns false.

This function fills in the gap that {godoc_root}/text/template#IsTrue[`text/template.IsTrue`^] and {godoc_root}/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_net_all"]
=== Networking
These template functions use capabilities from:

* <<fn_net>>
* <<fn_netip>>
* <<fn_netipx>>
* <<fn_netx>>
* <<fn_dnsx>>

The function prefix is used to indicate which module/package a function is added from.

[id="fn_net"]
==== `net`
These template functions contain capabilities from {godoc_root}/net[`net`^].

[id="fn_net_cidrmask"]
===== `netCidrMask`
:func: netCidrMask
:sig: (ones, bits int) (mask net.IPMask)
include::{funcsig_tpl}[]

`netCidrMask` directly calls {godoc_root}/net#CIDRMask[`net.CIDRMask`^].

[id="fn_net_cidra"]
===== `netExtractAddr`
:func: netExtractAddr
:sig: (s string) (addr net.IP, err error)
include::{funcsig_tpl}[]

`netExtractAddr` wraps {godoc_root}/net#ParseCIDR[`net.ParseCIDR`^] and returns the {godoc_root}/net#IP[`net.IP`^] component.

[id="fn_net_hph"]
===== `netExtractHost`
:func: netExtractHost
:sig: (hostPort string) (host string, err error)
include::{funcsig_tpl}[]

`netExtractHost` wraps {godoc_root}/net#SplitHostPort[`net.SplitHostPort`^] and returns the host component (as a string).

[id="fn_net_cidrn"]
===== `netExtractIpnet`
:func: netExtractIpnet
:sig: (s string) (ipNet *net.IPNet, err error)
include::{funcsig_tpl}[]

`netExtractIpnet` wraps {godoc_root}/net#ParseCIDR[`net.ParseCIDR`^] and returns the {godoc_root}/net#IPNet[`net.IPNet`^] component.

[id="fn_net_hpp"]
===== `netExtractPort`
:func: netExtractPort
:sig: (hostPort string) (port uint16, err error)
include::{funcsig_tpl}[]

`netExtractPort` wraps {godoc_root}/net#SplitHostPort[`net.SplitHostPort`^] and returns the port component (as a uint16).

[id='fn_net_ifaces']
===== `netIfaces`
:func: netIfaces
:sig: () (ifaces []net.Interface, err error)
include::{funcsig_tpl}[]

`netIfaces` directly calls {godoc_root}/net#Interfaces[`net.Interfaces`^].

[id="fn_net_ip4mask"]
===== `netIp4Mask`
:func: netIp4Mask
:sig: (a, b, c, d any) (mask net.IPMask, err error)
include::{funcsig_tpl}[]

`netIp4Mask` wraps {godoc_root}/net#IPv4Mask[`net.IPv4Mask`^].

It is wrapped so that `a`, `b`, `c`, and `d` may be a string:

[source,gotemplate]
----
{{- $mask := netIp4Mask "198" "51" "100" "10" -}}
----

or integers/other numeric:

[source,gotemplate]
----
{{- $mask := netIp4Mask 198 51 100 10 -}}
----

or bytes:

[source,gotemplate]
----
{{- $mask := netIp4Mask 0xc6 0x33 0x64 0x0a -}}
----

or even a mix:

[source,gotemplate]
----
{{- $mask := netIp4Mask "198" 51 "100" 0x0a -}}
----

[id="fn_net_jhp"]
===== `netJoinHostPort`
:func: netJoinHostPort
:sig: (host, port string) (out string)
include::{funcsig_tpl}[]

`netJoinHostPort` directly calls {godoc_root}/net#JoinHostPort[`net.JoinHostPort`^].

[id="fn_net_parseip"]
===== `netParseIP`
:func: netParseIP
:sig: (s string) (ip net.IP)
include::{funcsig_tpl}[]

`netParseIP` directly calls {godoc_root}/net#ParseIP[`net.ParseIP`^].

[id="fn_netip"]
==== `net/netip`
These template functions contain capabilities from {godoc_root}/net/netip[`net/netip`^].

[id="fn_netip_addrport"]
===== `netipAddrPort`
:func: netipAddrPort
:sig: (ip netip.Addr, port uint16) (addrPort netip.AddrPort)
include::{funcsig_tpl}[]

`netipAddrPort` directly calls {godoc_root}/net/netip#AddrPortFrom[`net/netip.AddrPortFrom`^].

[id="fn_netip_parseaddr"]
===== `netipParseAddr`
:func: netipParseAddr
:sig: (s string) (addr netip.Addr, err error)
include::{funcsig_tpl}[]

`netipParseAddr` directly calls {godoc_root}/net/netip#ParseAddr[`net/netip.ParseAddr`^].

[id="fn_netip_pap"]
===== `netipParseAddrPort`
:func: netipParseAddrPort
:sig: (s string) (addrPort netip.AddrPort, err error)
include::{funcsig_tpl}[]

`netipParseAddrPort` directly calls {godoc_root}/net/netip#ParseAddrPort[`net/netip.ParseAddrPort`^].

[id="fn_netip_parsepfx"]
===== `netipParsePrefix`
:func: netipParsePrefix
:sig: (s string) (pfx netip.Prefix, err error)
include::{funcsig_tpl}[]

`netipParsePrefix` directly calls {godoc_root}/net/netip#ParsePrefix[`net/netip.ParsePrefix`^].

[id="fn_netip_pfx"]
===== `netipPrefix`
:func: netipPrefix
:sig: (ip netip.Addr, bits int) (pfx netip.Prefix)
include::{funcsig_tpl}[]

`netipPrefix` directly calls {godoc_root}/net/netip#PrefixFrom[`net/netip.PrefixFrom`^].

[id="fn_netipx"]
==== `go4.org/netipx`
These template functions contain capabilities from {godoc_root}/go4.org/netipx[`go4.org/netipx`^].

[id="fn_netipx_addripnet"]
===== `netipxAddrIpNet`
:func: netipxAddrIpNet
:sig: (addr netip.Addr) (ipNet *net.IPNet)
include::{funcsig_tpl}[]

`netipxAddrIpNet` directly calls {godoc_root}/go4.org/netipx#AddrIPNet[`go4.org/netipx.AddrIPNet`^].

[id="fn_netipx_cmppfx"]
===== `netipxCmpPfx`
:func: netipxCmpPfx
:sig: (a, b netip.Prefix) (cmp int)
include::{funcsig_tpl}[]

`netipxCmpPfx` directly calls {godoc_root}/go4.org/netipx#ComparePrefix[`go4.org/netipx.ComparePrefix`^].

[id="fn_netipx_fromstdaddr"]
===== `netipxFromStdAddr`
:func: netipxFromStdAddr
:sig: (ip net.IP, port int, zone string) (addrPort netip.AddrPort, err error)
include::{funcsig_tpl}[]

`netipxFromStdAddr` wraps {godoc_root}/go4.org/netipx#FromStdAddr[`go4.org/netipx.FromStdAddr`^]. Instead of returning a boolean as the second value, it will instead be an error if the wrapped boolean is false.

[id="fn_netipx_fromip"]
===== `netipxFromIp`
:func: netipxFromIp
:sig: (ip net.IP) (addr netip.Addr, err error)
include::{funcsig_tpl}[]

`netipxFromIp` wraps {godoc_root}/go4.org/netipx#FromStdIP[`go4.org/netipx.FromStdIP`^]. Instead of returning a boolean as the second value, it will instead be an error if the wrapped boolean is false.

[id="fn_netipx_fromipnet"]
===== `netipxFromIpNet`
:func: netipxFromIpNet
:sig: (ipNet *net.IPNet) (pfx netip.Prefix, err error)
include::{funcsig_tpl}[]

`netipxFromIpNet` wraps {godoc_root}/go4.org/netipx#FromStdIPNet[`go4.org/netipx.FromStdIPNet`^]. Instead of returning a boolean as the second value, it will instead be an error if the wrapped boolean is false.

[id="fn_netipx_parserange"]
===== `netipxParseRange`
:func: netipxParseRange
:sig: (s string) (ipRange netipx.IPRange, err error)
include::{funcsig_tpl}[]

`netipxParseRange` directly calls {godoc_root}/go4.org/netipx#ParseIPRange[`go4.org/netipx.ParseIPRange`^].

[id="fn_netipx_pfxaddr"]
===== `netipxPfxAddr`
:func: netipxPfxAddr
:sig: (s string) (addr netip.Addr, err error)
include::{funcsig_tpl}[]

`netipxPfxAddr` directly calls {godoc_root}/go4.org/netipx#ParsePrefixOrAddr[`go4.org/netipx.ParsePrefixOrAddr`^].

[id="fn_netipx_pfxipnet"]
===== `netipxPfxIpNet`
:func: netipxPfxIpNet
:sig: (pfx netip.Prefix) (ipNet *net.IPNet)
include::{funcsig_tpl}[]

`netipxPfxIpNet` directly calls {godoc_root}/go4.org/netipx#PrefixIPNet[`go4.org/netipx.PrefixIPNet`^].

[id="fn_netipx_pfxlast"]
===== `netipxPfxLast`
:func: netipxPfxLast
:sig: (pfx netip.Prefix) (addr netip.Addr)
include::{funcsig_tpl}[]

`netipxPfxLast` directly calls {godoc_root}/go4.org/netipx#PrefixLastIP[`go4.org/netipx.PrefixLastIP`^].

[id="fn_netipx_pfxrange"]
===== `netipxPfxRange`
:func: netipxPfxRange
:sig: (pfx netip.Prefix) (ipRange netipx.IPRange)
include::{funcsig_tpl}[]

`netipxPfxRange` directly calls {godoc_root}/go4.org/netipx#RangeOfPrefix[`go4.org/netipx.RangeOfPrefix`^].

[id="fn_netipx_range"]
===== `netipxRange`
:func: netipxRange
:sig: (from, to netip.Addr) (ipRange netipx.IPRange)
include::{funcsig_tpl}[]

`netipxRange` directly calls {godoc_root}/go4.org/netipx#IPRangeFrom[`go4.org/netipx.IPRangeFrom`^].

[id="fn_netx"]
==== `r00t2.io/goutils/netx`
These template functions contain capabilities from {godoc_root}/{mod_me}/netx[`{mod_me}/netx`^].

[id="fn_netx_addrrfc"]
===== `netxAddrRfc`
:func: netxAddrRfc
:sig: (addr netip.Addr) (rfcStr string)
include::{funcsig_tpl}[]

`netxAddrRfc` directly calls {godoc_root}/{mod_me}/netx#AddrRfc[`{mod_me}/netx.AddrRfc`^].

[id="fn_netx_cidr4ipmask"]
===== `netxAddrRfc`
:func: netxAddrRfc
:sig: (cidr uint8) (ipMask net.IPMask, err error)
include::{funcsig_tpl}[]

`netxCidr4IpMask` directly calls {godoc_root}/{mod_me}/netx#Cidr4ToIPMask[`{mod_me}/netx.Cidr4ToIPMask`^].

[id="fn_netx_cidr4mask"]
===== `netxCidr4Mask`
:func: netxCidr4Mask
:sig: (cidr uint8) (mask uint32, err error)
include::{funcsig_tpl}[]

`netxCidr4IpMask` directly calls {godoc_root}/{mod_me}/netx#Cidr4ToMask[`{mod_me}/netx.Cidr4ToMask`^].

[id="fn_netx_cidr4str"]
===== `netxCidr4Str`
:func: netxCidr4Str
:sig: (cidr uint8) (maskStr string, err error)
include::{funcsig_tpl}[]

`netxCidr4Str` directly calls {godoc_root}/{mod_me}/netx#Cidr4ToStr[`{mod_me}/netx.Cidr4ToStr`^].

[id="fn_netx_familyver"]
===== `netxFamilyVer`
:func: netxFamilyVer
:sig: (family uint16) (ipVer int)
include::{funcsig_tpl}[]

`netxFamilyVer` directly calls {godoc_root}/{mod_me}/netx#FamilyToVer[`{mod_me}/netx.FamilyToVer`^].

[id="fn_netx_getaddrfam"]
===== `netxGetAddrFam`
:func: netxGetAddrFam
:sig: (addr netip.Addr) (family uint16)
include::{funcsig_tpl}[]

`netxGetAddrFam` directly calls {godoc_root}/{mod_me}/netx#GetAddrFamily[`{mod_me}/netx.GetAddrFamily`^].

[id="fn_netx_getipfam"]
===== `netxGetIpFam`
:func: netxGetIpFam
:sig: (ip net.IP) (family uint16)
include::{funcsig_tpl}[]

`netxGetIpFam` directly calls {godoc_root}/{mod_me}/netx#GetAddrFamily[`{mod_me}/netx.GetIpFamily`^].

[id="fn_netx_iprfc"]
===== `netxIpRfc`
:func: netxIpRfc
:sig: (ip net.IP) (rfcStr string)
include::{funcsig_tpl}[]

`netxIpRfc` directly calls {godoc_root}/{mod_me}/netx#IpRfc[`{mod_me}/netx.IpRfc`^].

[id="fn_netx_iprfcstr"]
===== `netxIpRfcStr`
:func: netxIpRfcStr
:sig: (s string) (rfcStr string)
include::{funcsig_tpl}[]

`netxIpRfcStr` directly calls {godoc_root}/{mod_me}/netx#IpRfcStr[`{mod_me}/netx.IpRfcStr`^].

[id="fn_netx_ipstriprfc"]
===== `netxIpStripRfc`
:func: netxIpStripRfc
:sig: (s string) (stripStr string)
include::{funcsig_tpl}[]

`netxIpStripRfc` directly calls {godoc_root}/{mod_me}/netx#IpStripRfcStr[`{mod_me}/netx.IpStripRfcStr`^].

[id="fn_netx_ip4maskcidr"]
===== `netxIp4MaskCidr`
:func: netxIp4MaskCidr
:sig: (ipMask net.IPMask) (cidr uint8, err error)
include::{funcsig_tpl}[]

`netxIp4MaskCidr` directly calls {godoc_root}/{mod_me}/netx#IPMask4ToCidr[`{mod_me}/netx.IPMask4ToCidr`^].

[id="fn_netx_ip4maskmask"]
===== `netxIp4MaskMask`
:func: netxIp4MaskMask
:sig: (ipMask net.IPMask) (mask uint32, err error)
include::{funcsig_tpl}[]

`netxIp4MaskMask` directly calls {godoc_root}/{mod_me}/netx#IPMask4ToMask[`{mod_me}/netx.IPMask4ToMask`^].

[id="fn_netx_ip4maskstr"]
===== `netxIp4MaskStr`
:func: netxIp4MaskStr
:sig: (ipMask net.IPMask) (maskStr string, err error)
include::{funcsig_tpl}[]

`netxIp4MaskStr` directly calls {godoc_root}/{mod_me}/netx#IPMask4ToStr[`{mod_me}/netx.IPMask4ToStr`^].

[id="fn_netx_ipverstr"]
===== `netxIpVerStr`
:func: netxIpVerStr
:sig: (s string) (ipVer int)
include::{funcsig_tpl}[]

`netxIpVerStr` directly calls {godoc_root}/{mod_me}/netx#IpVerStr[`{mod_me}/netx.IpVerStr`^].

[id="fn_netx_isbrktd6"]
===== `netxIsBrktd6`
:func: netxIsBrktd6
:sig: (s string) (isBrktdIp bool)
include::{funcsig_tpl}[]

`netxIsBrktd6` directly calls {godoc_root}/{mod_me}/netx#IsBracketedIp6[`{mod_me}/netx.IsBracketedIp6`^].

[id="fn_netx_isip"]
===== `netxIsIp`
:func: netxIsIp
:sig: (s string) (isIp bool)
include::{funcsig_tpl}[]

`netxIsIp` directly calls {godoc_root}/{mod_me}/netx#IsIpAddr[`{mod_me}/netx.IsIpAddr`^].

[id="fn_netx_ispfx"]
===== `netxIsPfx`
:func: netxIsPfx
:sig: (s string) (isNet bool)
include::{funcsig_tpl}[]

`netxIsPfx` directly calls {godoc_root}/{mod_me}/netx#IsPrefixNet[`{mod_me}/netx.IsPrefixNet`^].

[id="fn_netx_mask4cidr"]
===== `netxMask4Cidr`
:func: netxMask4Cidr
:sig: (mask uint32) (cidr uint8, err error)
include::{funcsig_tpl}[]

`netxMask4Cidr` directly calls {godoc_root}/{mod_me}/netx#Mask4ToCidr[`{mod_me}/netx.Mask4ToCidr`^].

[id="fn_netx_mask4Strcidr"]
===== `netxMask4StrCidr`
:func: netxMask4StrCidr
:sig: (maskStr string) (cidr uint8, err error)
include::{funcsig_tpl}[]

`netxMask4StrCidr` directly calls {godoc_root}/{mod_me}/netx#Mask4StrToCidr[`{mod_me}/netx.Mask4StrToCidr`^].

[id="fn_netx_mask4stripmask"]
===== `netxMask4StrIpMask`
:func: netxMask4StrIpMask
:sig: (maskStr string) (mask net.IPMask, err error)
include::{funcsig_tpl}[]

`netxMask4StrIpMask` directly calls {godoc_root}/{mod_me}/netx#Mask4StrToIPMask[`{mod_me}/netx.Mask4StrToIPMask`^].

[id="fn_netx_mask4strmask"]
===== `netxMask4StrMask`
:func: netxMask4StrMask
:sig: (maskStr string) (mask uint32, err error)
include::{funcsig_tpl}[]

`netxMask4StrMask` directly calls {godoc_root}/{mod_me}/netx#Mask4StrToMask[`{mod_me}/netx.Mask4StrToMask`^].

[id="fn_netx_ver2fam"]
===== `netxVerFamily`
:func: netxVerFamily
:sig: (ipVer int) (family uint16)
include::{funcsig_tpl}[]

`netxVerFamily` directly calls {godoc_root}/{mod_me}/netx#VerToFamily[`{mod_me}/netx.VerToFamily`^].

[id="fn_dnsx"]
==== `r00t2.io/goutils/netx/dnsx`
These template functions contain capabilities from {godoc_root}/{import_me}/netx/dnsx[`{import_me}/netx/dnsx`^].

[id="fn_dnsx_ptraddr"]
===== `dnsxPtrAddr`
:func: dnsxPtrAddr
:sig: (s string) (ip netip.Addr, err error)
include::{funcsig_tpl}[]

`dnsxPtrAddr` directly calls {godoc_root}/{import_me}/netx/dnsx#AddrFromPtr[`{import_me}/netx/dnsx.AddrFromPtr`^].

[id="fn_dnsx_addrptr"]
===== `dnsxAddrPtr`
:func: dnsxAddrPtr
:sig: (ip netip.Addr) (s string)
include::{funcsig_tpl}[]

`dnsxAddrPtr` directly calls {godoc_root}/{import_me}/netx/dnsx#AddrToPtr[`{import_me}/netx/dnsx.AddrToPtr`^].

[id="fn_dnsx_str2wire"]
===== `dnsxStrWire`
:func: dnsxStrWire
:sig: (recordNm string) (recordNmBytes []byte, err error)
include::{funcsig_tpl}[]

`dnsxStrWire` directly calls {godoc_root}/{import_me}/netx/dnsx#DnsStrToWire[`{import_me}/netx/dnsx.DnsStrToWire`^].

[id="fn_dnsx_wire2str"]
===== `dnsxWireStr`
:func: dnsxWireStr
:sig: (recordNmBytes []byte) (recordNm string, err error)
include::{funcsig_tpl}[]

`dnsxWireStr` directly calls {godoc_root}/{import_me}/netx/dnsx#DnsWireToStr[`{import_me}/netx/dnsx.DnsWireToStr`^].

[id="fn_dnsx_ptrip"]
===== `dnsxPtrIp`
:func: dnsxPtrIp
:sig: (s string) (ip net.IP, err error)
include::{funcsig_tpl}[]

`dnsxPtrIp` directly calls {godoc_root}/{import_me}/netx/dnsx#IpFromPtr[`{import_me}/netx/dnsx.IpFromPtr`^].

[id="fn_dnsx_ipptr"]
===== `dnsxIpPtr`
:func: dnsxIpPtr
:sig: (ip net.IP) (s string)
include::{funcsig_tpl}[]

`dnsxIpPtr` directly calls {godoc_root}/{import_me}/netx/dnsx#IpToPtr[`{import_me}/netx/dnsx.IpToPtr`^].

[id="fn_dnsx_isfqdn"]
===== `dnsxIsFqdn`
:func: dnsxIsFqdn
:sig: (s string) (fqdn bool)
include::{funcsig_tpl}[]

`dnsxIsFqdn` directly calls {godoc_root}/{import_me}/netx/dnsx#IsFqdn[`{import_me}/netx/dnsx.IsFqdn`^].

[id="fn_dnsx_istxt"]
===== `dnsxIsTxt`
:func: dnsxIsTxt
:sig: (fqdn string) (isOk bool)
include::{funcsig_tpl}[]

`dnsxIsTxt` directly calls {godoc_root}/{import_me}/netx/dnsx#IsFqdnDefinedTxt[`{import_me}/netx/dnsx.IsFqdnDefinedTxt`^].

[id="fn_dnsx_isnsec3"]
===== `dnsxIsNsec3`
:func: dnsxIsNsec3
:sig: (s string) (maybeNsec3 bool)
include::{funcsig_tpl}[]

`dnsxIsNsec3` directly calls {godoc_root}/{import_me}/netx/dnsx#IsFqdnNsec3[`{import_me}/netx/dnsx.IsFqdnNsec3`^].

[id="fn_dnsx_issrv"]
===== `dnsxIsSrv`
:func: dnsxIsSrv
:sig: (s string) (srv bool)
include::{funcsig_tpl}[]

`dnsxIsSrv` directly calls {godoc_root}/{import_me}/netx/dnsx#IsFqdnSrv[`{import_me}/netx/dnsx.IsFqdnSrv`^].

[id="fn_dnsx_iswild"]
===== `dnsxIsWild`
:func: dnsxIsWild
:sig: (s string) (wildcard bool)
include::{funcsig_tpl}[]

`dnsxIsWild` directly calls {godoc_root}/{import_me}/netx/dnsx#IsFqdnWildcard[`{import_me}/netx/dnsx.IsFqdnWildcard`^].

[id="fn_dnsx_islbl"]
===== `dnsxIsLbl`
:func: dnsxIsLbl
:sig: (s string) (isLbl bool)
include::{funcsig_tpl}[]

`dnsxIsLbl` directly calls {godoc_root}/{import_me}/netx/dnsx#IsLabel[`{import_me}/netx/dnsx.IsLabel`^].

[id="fn_dnsx_isptr"]
===== `dnsxIsPtr`
:func: dnsxIsPtr
:sig: (s string) (isPtr bool, addr net.IP)
include::{funcsig_tpl}[]

`dnsxIsPtr` directly calls {godoc_root}/{import_me}/netx/dnsx#IsPtr[`{import_me}/netx/dnsx.IsPtr`^].

[id="fn_num"]
=== Numbers/Math

[id="fn_num_f32s"]
==== `numFloat32Str`
:func: numFloat32Str
:sig: (f float32) (s string)
include::{funcsig_tpl}[]

`numFloat32Str` returns a *complete* non-truncated non-right-padded string representation of a `float32`.

[id="fn_num_f64"]
==== `numFloat64`
:func: numFloat64
:sig: (val any) (f float64, err error)
include::{funcsig_tpl}[]

`numFloat64` returns any string representation of a numeric value or any type of numeric value to a `float64`.

[id="fn_num_f64s"]
==== `numFloat64Str`
:func: numFloat64Str
:sig: (f float64) (s string)
include::{funcsig_tpl}[]

`numFloat64Str` returns a *complete* non-truncated non-right-padded string representation of a `float64`.

[id="fn_num_fs"]
==== `numFloatStr`
:func: numFloatStr
:sig: (val any) (s string, err error)
include::{funcsig_tpl}[]

`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

[id="fn_os_fqdn"]
==== `osFQDN`
:func: osFQDN
:sig: () (fqdn string, err error)
include::{funcsig_tpl}[]

`osFQDN` currently just directly calls {godoc_root}/os#Hostname[`os.Hostname`^].

As such, it comes with the same caveats -- namely that it isn't guaranteed to be
an FQDN, it will be precisely/exactly whatever the kernel/OS hostname is set as.

In the future, it may be extended to support a more diligent effort to determine
an actual FQDN, and return an error if it is unable to be derived.

To (relatively) predictably get the "short hostname", use <<fn_os_hst>>.
To directly/predictably use {godoc_root}/os#Hostname[`os.Hostname`^], use <<fn_os_hstnm>>.

[id="fn_os_grpid"]
==== `osGroupById`
:func: osGroupById
:sig: [T string | int](gid T) (g *user.Group, err error)
include::{funcsig_tpl}[]

`osGroupById` returns an {godoc_root}/os/user#Group[`os/user.Group`^] from a given group ID/GID.

It more or less behaves exactly like {godoc_root}/os/user#LookupGroupId[`os/user.LookupGroupId`^],
except it will accept either a `string` *or* an `int` as the GID.

[id="fn_os_grpnm"]
==== `osGroupByName`
:func: osGroupByName
:sig: (grpNm string) (g *user.Group, err error)
include::{funcsig_tpl}[]

`osGroupByName` returns an {godoc_root}/os/user#Group[`os/user.Group`^] from a given group name.

It behaves exactly like {godoc_root}/os/user#LookupGroup[`os/user.LookupGroup`^].

[id="fn_os_hst"]
==== `osHost`
:func: osHost
:sig: () (out string, err error)
include::{funcsig_tpl}[]

`osHost` returns the "short hostname" by calling {godoc_root}/os#Hostname[`os.Hostname`^]
and returning the first "host label" (as RFCs refer to it). This is commonly/colloquially called the "hostname" or "hostname without the domain part".

e.g.:

[source,gotemplate]
----
{{- $fqdn := osFQDN -}}
{{- $h := osHost -}}
{{- $cmp := index ($fqdn | splitList ".") 0 -}}
osHost {{ $h }} should be equal to first label of FQDN {{ $cmp }}.
----

[TIP]
====
The `splitList` function shown above is from the {sprig_web}/string_slice.html[`sprig` string slice functions^].
====

To (try to) get the FQDN, use <<fn_os_fqdn>>.
To directly use {godoc_root}/os#Hostname[`os.Hostname`^], use <<fn_os_hstnm>>.

[id="fn_os_hstnm"]
==== `osHostname`
:func: osHostname
:sig: () (out string, err error)
include::{funcsig_tpl}[]

`osHostname` directly calls {godoc_root}/os#Hostname[`os.Hostname`^].

[id="fn_os_idst"]
==== `osIdState`
:func: osIdState
:sig: () (idst sysutils.IDState)
include::{funcsig_tpl}[]

`osIdState` returns the current runtime process' {godoc_root}/r00t2.io/sysutils#IDState[`r00t2.io/sysutils.IDState`^].

It directly calls {godoc_root}/r00t2.io/sysutils#GetIDState[`r00t2.io/sysutils.GetIDState`^].

[WARNING]
====
This is more or less useless on Windows; it returns only a dummy struct for cross-platform compatibility.
====

[id="fn_os_usr"]
==== `osUser`
:func: osUser
:sig: () (u *user.User, err error)
include::{funcsig_tpl}[]

`osUser` returns the current runtime process' {godoc_root}/os/user#User[`os/user.User`^].

It directly calls {godoc_root}/os/user#Current[`os/user.Current`^].

[id="fn_os_usrid"]
==== `osUserById`
:func: osUserById
:sig: [T string | int](uid T) (u *user.User, err error)
include::{funcsig_tpl}[]

`osUserById` returns an {godoc_root}/os/user#User[`os/user.User`^] from a given user ID/UID.

It more or less behaves exactly like {godoc_root}/os/user#LookupId[`os/user.LookupId`^],
except it will accept either a `string` *or* an `int` as the UID.

[id="fn_os_usrnm"]
==== `osUserByName`
:func: osUserByName
:sig: (userNm string) (u *user.User, err error)
include::{funcsig_tpl}[]

`osUserByName` returns an {godoc_root}/os/user#User[`os/user.User`^] from a given username.

It directly calls {godoc_root}/os/user#Lookup[`os/user.Lookup`^].

[id="fn_path"]
=== Paths

[id="fn_path_gnrc"]
==== Generic
These operate similar to {godoc_root}/path[the `path` stdlib library^] and use a fixed `/` path separator.

[id="fn_path_gnrc_pj"]
===== `pathJoin`
:func: pathJoin
:sig: (elem ...string) (out string)
include::{funcsig_tpl}[]

`pathJoin` directly calls {godoc_root}/path#Join[`path.Join`^].

[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`
:func: pathPipeJoin
:sig: (elems ...string) (out string)
include::{funcsig_tpl}[]

`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`
:func: pathSliceJoin
:sig: (sl []string) (out string)
include::{funcsig_tpl}[]

`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 {sprig_web}/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`
:func: pathSlicePipeJoin
:sig: (sl []string, root string) (out string)
include::{funcsig_tpl}[]

`pathSlicePipeJoin` operates like a hybrid of <<fn_path_gnrc_ppj>> and <<fn_path_gnrc_psj>>:

* Like <<fn_path_gnrc_ppj>>, it is suitable for pipeline use -- the root/base path is passed in from the pipeline
* Like <<fn_path_gnrc_psj>>, it *also* accepts a slice of path segments (`[]string`) to append to that base path

[TIP]
====
The `splitList` function shown below is from the {sprig_web}/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`
:func: pathSubJoin
:sig: (root string, elems ...string) (out string)
include::{funcsig_tpl}[]

`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 {godoc_root}/path/filepath[the `path/filepath` stdlib library^], and use the OS-specific {godoc_root}/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`
:func: osPathJoin
:sig: (elem ...string) (out string)
include::{funcsig_tpl}[]

`osPathJoin` directly calls {godoc_root}/path/filepath#Join[`path/filepath.Join`^].

[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`
:func: osPathJoin
:sig: (elems ...string) (out string)
include::{funcsig_tpl}[]

`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 argument 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`
:func: osPathSep
:sig: () (out string)
include::{funcsig_tpl}[]

`osPathSep` returns the {godoc_root}/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`
:func: osPathSliceJoin
:sig: (sl []string) (out string)
include::{funcsig_tpl}[]

`osPathSliceJoin` operates like <<fn_path_gnrc_psj>> but with OS-specific path separators.

[TIP]
====
The `splitList` function shown below is from the {sprig_web}/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`
:func: osPathSlicePipeJoin
:sig: (sl []string, root string) (out string)
include::{funcsig_tpl}[]

`osPathSlicePipeJoin` operates like <<fn_path_gnrc_pspj>> but with OS-specific separators.

[TIP]
====
The `splitList` function shown below is from the {sprig_web}/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`
:func: osPathSubJoin
:sig: (root string, elems ...string) (out string)
include::{funcsig_tpl}[]

`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_ps"]
=== PSUtil
These are functions from {godoc_root}/{mod_psutil}[`{mod_psutil}`^] packages.

[id="fn_ps_cpu"]
==== CPU/Processor

[id="fn_ps_cpu_cnts"]
===== `psCpuCnts`
:func: psCpuCnts
:sig: (logical bool) (numCpu int, err error)
include::{funcsig_tpl}[]

`psCpuCnts` directly calls {godoc_root}/{mod_psutil}/cpu#Counts[`{mod_psutil}/cpu.Counts`^].

[id="fn_ps_cpu_info"]
===== `psCpuInfo`
:func: psCpuInfo
:sig: () (cpuInfo []cpu.Info, err error)
include::{funcsig_tpl}[]

`psCpuInfo` directly calls {godoc_root}/{mod_psutil}/cpu#Info[`{mod_psutil}/cpu.Info`^].

[id="fn_ps_cpu_pct"]
===== `psCpuPct`
:func: psCpuPct
:sig: (interval time.Duration, percpu bool) (pcts []float64, err error)
include::{funcsig_tpl}[]

`psCpuPct` directly calls {godoc_root}/{mod_psutil}/cpu#Percent[`{mod_psutil}/cpu.Percent`^].

[id="fn_ps_cpu_tms"]
===== `psCpuTimes`
:func: psCpuTimes
:sig: (percpu bool) (cpuTimes []cpu.TimesStat, err error)
include::{funcsig_tpl}[]

`psCpuTimes` directly calls {godoc_root}/{mod_psutil}/cpu#Times[`{mod_psutil}/cpu.Times`^].

[id="fn_ps_dsk"]
==== Disk

[id="fn_ps_dsk_iocnts"]
===== `psDiskIoCnts`
:func: psDiskIoCnts
:sig: (names ...string) (stats map[string]disk.IOCountersStat, err error)
include::{funcsig_tpl}[]

`psDiskIoCnts` directly calls {godoc_root}/{mod_psutil}/disk#IOCounters[`{mod_psutil}/disk.IOCounters`^].

[id="fn_ps_dsk_lbl"]
===== `psDiskLabel`
:func: psDiskLabel
:sig: (name string) (label string, err error)
include::{funcsig_tpl}[]

`psDiskLabel` directly calls {godoc_root}/{mod_psutil}/disk#Label[`{mod_psutil}/disk.Label`^].

[id="fn_ps_dsk_parts"]
===== `psDiskParts`
:func: psDiskParts
:sig: (all bool) (parts []disk.PartitionStat, err error)
include::{funcsig_tpl}[]

`psDiskParts` directly calls {godoc_root}/{mod_psutil}/disk#Partitions[`{mod_psutil}/disk.Partitions`^].

[id="fn_ps_dsk_srl"]
===== `psDiskSerial`
:func: psDiskSerial
:sig: (name string) (serial string, err error)
include::{funcsig_tpl}[]

`psDiskSerial` directly calls {godoc_root}/{mod_psutil}/disk#SerialNumber[`{mod_psutil}/disk.SerialNumber`^].

[id="fn_ps_dsk_usg"]
===== `psDiskUsage`
:func: psDiskUsage
:sig: (path string) (usage *disk.UsageStat, err error)
include::{funcsig_tpl}[]

`psDiskUsage` directly calls {godoc_root}/{mod_psutil}/disk#Usage[`{mod_psutil}/disk.Usage`^].

[id="fn_ps_hst"]
==== Host

[id="fn_ps_hst_boot"]
===== `psHostBoot`
:func: psHostBoot
:sig: () (bootEpoch uint64, err error)
include::{funcsig_tpl}[]

`psHostBoot` directly calls {godoc_root}/{mod_psutil}/host#BootTime[`{mod_psutil}/host.BootTime`^].

[id="fn_ps_hst_id"]
===== `psHostId`
:func: psHostId
:sig: () (hostId string, err error)
include::{funcsig_tpl}[]

`psHostId` directly calls {godoc_root}/{mod_psutil}/host#HostID[`{mod_psutil}/host.HostID`^].

[id="fn_ps_hst_info"]
===== `psHostInfo`
:func: psHostInfo
:sig: () (info *host.InfoStat, err error)
include::{funcsig_tpl}[]

`psHostInfo` directly calls {godoc_root}/{mod_psutil}/host#Info[`{mod_psutil}/host.Info`^].

[id="fn_ps_hst_krnarch"]
===== `psHostKernArch`
:func: psHostKernArch
:sig: () (arch string, err error)
include::{funcsig_tpl}[]

`psHostKernArch` directly calls {godoc_root}/{mod_psutil}/host#KernelArch[`{mod_psutil}/host.KernelArch`^].

[id="fn_ps_hst_krnver"]
===== `psHostKernVer`
:func: psHostKernVer
:sig: () (ver string, err error)
include::{funcsig_tpl}[]

`psHostKernVer` directly calls {godoc_root}/{mod_psutil}/host#KernelVersion[`{mod_psutil}/host.KernelVersion`^].

[id="fn_ps_hst_plat"]
===== `psHostPlatInfo`
:func: psHostPlatInfo
:sig: () (platInfo [3]string, err error)
include::{funcsig_tpl}[]

`psHostPlatInfo` wraps {godoc_root}/{mod_psutil}/host#PlatformInformation[`{mod_psutil}/host.PlatformInformation`^].

It is necessary to wrap because the function normally returns `(string, string, string, error)` but a template function may only return either a single value (of any type) or a single value of any type and an error, so the three `string` returns are consolidated into an ordered `[3]string`.

[id="fn_ps_hst_uptm"]
===== `psHostPlatUptime`
:func: psHostPlatUptime
:sig: () (uptimeSecs uint64, err error)
include::{funcsig_tpl}[]

`psHostPlatUptime` directly calls {godoc_root}/{mod_psutil}/host#Uptime[`{mod_psutil}/host.Uptime`^].

[id="fn_ps_hst_usrs"]
===== `psHostUsers`
:func: psHostUsers
:sig: () (users []host.UserStat, err error)
include::{funcsig_tpl}[]

`psHostUsers` directly calls {godoc_root}/{mod_psutil}/host#Users[`{mod_psutil}/host.Users`^].

[id="fn_ps_hst_virt"]
===== `psHostPlatVirt`
:func: psHostPlatVirt
:sig: () (virtInfo [2]string, err error)
include::{funcsig_tpl}[]

`psHostPlatVirt` wraps {godoc_root}/{mod_psutil}/host#Virtualization[`{mod_psutil}/host.Virtualization`^].

It is necessary to wrap because the function normally returns `(string, string, error)` but a template function may only return either a single value (of any type) or a single value of any type and an error, so the two `string` returns are consolidated into an ordered `[2]string`.

[id="fn_ps_ld"]
==== Load

[id="fn_ps_ld_avg"]
===== `psLoadAvg`
:func: psLoadAvg
:sig: () (avg *load.AvgStat, err error)
include::{funcsig_tpl}[]

`psLoadAvg` directly calls {godoc_root}/{mod_psutil}/load#Avg[`{mod_psutil}/load.Avg`^].

[id="fn_ps_ld_misc"]
===== `psLoadMisc`
:func: psLoadMisc
:sig: () (misc *load.MiscStat, err error)
include::{funcsig_tpl}[]

`psLoadMisc` directly calls {godoc_root}/{mod_psutil}/load#Misc[`{mod_psutil}/load.Misc`^].

[id="fn_ps_mem"]
==== Memory

[id="fn_ps_mem_exvmem"]
===== `psMemExVMem`
:func: psMemExVMem
:sig: () (exVMem *mem.ExVirtualMemory, err error)
include::{funcsig_tpl}[]

[WARNING]
====
This function is available on Windows and Linux platforms *only*.
====

[WARNING]
====
This function returns very different types depending on platform.

* Linux: {godoc_root}/{mod_psutil}/mem?GOOS=linux#ExVirtualMemory[`mem.ExVirtualMemory`^]
* Windows: {godoc_root}/{mod_psutil}/mem?GOOS=windows#ExVirtualMemory[`mem.ExVirtualMemory`^]
====

This function wraps link:{godoc_root}/{mod_psutil}/mem?GOOS=linux#NewExLinux[`{mod_psutil}/mem.NewExLinux`^].link:{godoc_root}/{mod_psutil}/mem?GOOS=linux#ExLinux.VirtualMemory[`VirtualMemory`^] on Linux and
link:{godoc_root}/{mod_psutil}/mem?GOOS=windows#NewExWindows[`{mod_psutil}/mem.NewExWindows`^].link:{godoc_root}/{mod_psutil}/mem?GOOS=windows#ExWindows.VirtualMemory[`VirtualMemory`^] on Windows.

[id="fn_ps_mem_swap"]
===== `psMemSwap`
:func: psMemSwap
:sig: () (swap *mem.SwapMemoryStat, err error)
include::{funcsig_tpl}[]

`psMemSwap` directly calls {godoc_root}/{mod_psutil}/mem#SwapMemory[`{mod_psutil}/mem.SwapMemory`^].

[id="fn_ps_mem_swapdevs"]
===== `psMemSwapDevs`
:func: psMemSwapDevs
:sig: () (swapDevs []*mem.SwapDevice, err error)
include::{funcsig_tpl}[]

`psMemSwapDevs` directly calls {godoc_root}/{mod_psutil}/mem#SwapDevices[`{mod_psutil}/mem.SwapDevices`^].

[id="fn_ps_mem_vmem"]
===== `psMemVMem`
:func: psMemVMem
:sig: () (vmem *mem.VirtualMemoryStat, err error)
include::{funcsig_tpl}[]

`psMemVMem` directly calls {godoc_root}/{mod_psutil}/mem#VirtualMemory[`{mod_psutil}/mem.VirtualMemory`^].

[id="fn_ps_net"]
==== Network

[id="fn_ps_net_conns"]
===== `psNetConns`
:func: psNetConns
:sig: (kind string) (conns []net.ConnectionStat, err error)
include::{funcsig_tpl}[]

`psNetConns` directly calls {godoc_root}/{mod_psutil}/net#Connections[`{mod_psutil}/net.Connections`^].

[id="fn_ps_net_connsmax"]
===== `psNetConnsMax`
:func: psNetConnsMax
:sig: (kind string, maxConn int) (conns []net.ConnectionStat, err error)
include::{funcsig_tpl}[]

`psNetConnsMax` directly calls {godoc_root}/{mod_psutil}/net#ConnectionsMax[`{mod_psutil}/net.ConnectionsMax`^].

[id="fn_ps_net_connspid"]
===== `psNetConnsPid`
:func: psNetConnsPid
:sig: (kind string, pid int32) (conns []net.ConnectionStat, err error)
include::{funcsig_tpl}[]

`psNetConnsPid` directly calls {godoc_root}/{mod_psutil}/net#ConnectionsPid[`{mod_psutil}/net.ConnectionsPid`^].

[id="fn_ps_net_connspidmax"]
===== `psNetConnsPidMax`
:func: psNetConnsPidMax
:sig: (kind string, pid int32, maxConn int) (conns []net.ConnectionStat, err error)
include::{funcsig_tpl}[]

`psNetConnsPidMax` directly calls {godoc_root}/{mod_psutil}/net#ConnectionsPidMax[`{mod_psutil}/net.ConnectionsPidMax`^].

[id="fn_ps_net_ct"]
===== `psNetCTStats`
:func: psNetCTStats
:sig: (percCpu bool) (ctStats []net.ConntrackStat, err error)
include::{funcsig_tpl}[]

`psNetCTStats` directly calls {godoc_root}/{mod_psutil}/net#ConntrackStats[`{mod_psutil}/net.ConntrackStats`^].

[id="fn_ps_net_ctlist"]
===== `psNetCTStatList`
:func: psNetCTStatList
:sig: () (ctStats *net.ConntrackStatList, err error)
include::{funcsig_tpl}[]

`psNetCTStatList` directly calls {godoc_root}/{mod_psutil}/net#ConntrackStatList[`{mod_psutil}/net.ConntrackStatList`^].

[id="fn_ps_net_fltcnt"]
===== `psNetFilterCnts`
:func: psNetFilterCnts
:sig: () (filterCnts []net.FilterStat, err error)
include::{funcsig_tpl}[]

`psNetFilterCnts` directly calls {godoc_root}/{mod_psutil}/net#FilterCounters[`{mod_psutil}/net.FilterCounters`^].

[id="fn_ps_net_iocnts"]
===== `psNetIoCnts`
:func: psNetIoCnts
:sig: (perNIC bool) (ioCnts []net.IOCountersStat, err error)
include::{funcsig_tpl}[]

`psNetIoCnts` directly calls {godoc_root}/{mod_psutil}/net#IOCounters[`{mod_psutil}/net.IOCounters`^].

[id="fn_ps_net_iocntsfl"]
===== `psNetIoCntsFile`
:func: psNetIoCntsFile
:sig: (perNIC bool, filepath string) (ioCnts []net.IOCountersStat, err error)
include::{funcsig_tpl}[]

`psNetIoCntsFile` directly calls {godoc_root}/{mod_psutil}/net#IOCountersByFile[`{mod_psutil}/net.IOCountersByFile`^].

[id="fn_ps_net_ifaces"]
===== `psNetIfaces`
:func: psNetIfaces
:sig: () (ioCnts []net.InterfaceStatList, err error)
include::{funcsig_tpl}[]

`psNetIfaces` directly calls {godoc_root}/{mod_psutil}/net#Interfaces[`{mod_psutil}/net.Interfaces`^].

[id="fn_ps_net_pids"]
===== `psNetPids`
:func: psNetPids
:sig: () (pids []int32, err error)
include::{funcsig_tpl}[]

`psNetPids` directly calls {godoc_root}/{mod_psutil}/net#Pids[`{mod_psutil}/net.Pids`^].

[id="fn_ps_net_protocnts"]
===== `psNetProtoCnt`
:func: psNetProtoCnt
:sig: (protos []string) (protoCnts []net.ProtoCountersStat, err error)
include::{funcsig_tpl}[]

[WARNING]
====
This only works properly on Linux currently per upstream.
====

`psNetProtoCnt` directly calls {godoc_root}/{mod_psutil}/net#ProtoCounters[`{mod_psutil}/net.ProtoCounters`^].

[id="fn_ps_net_rev"]
===== `psNetRev`
:func: psNetRev
:sig: (b []byte) (out []byte)
include::{funcsig_tpl}[]

[WARNING]
====
This function only exists on Linux.
====

`psNetRev` directly calls {godoc_root}/{mod_psutil}/net?GOOS=linux#Reverse[`{mod_psutil}/net.Reverse`^].

[id="fn_ps_proc"]
==== Processes

[id="fn_ps_procs_procs"]
===== `psProcs`
:func: psProcs
:sig: (pid int32) (procs []*process.Process, err error)
include::{funcsig_tpl}[]

`psProcs` directly calls {godoc_root}/{mod_psutil}/process#Processes[`{mod_psutil}/proc.Processes`^].

[id="fn_ps_proc_new"]
===== `psProcNew`
:func: psProcNew
:sig: (pid int32) (proc *process.Process, err error)
include::{funcsig_tpl}[]

`psProcNew` directly calls {godoc_root}/{mod_psutil}/process#NewProcess[`{mod_psutil}/proc.NewProcess`^].

[id="fn_ps_proc_pids"]
===== `psProcPids`
:func: psProcPids
:sig: () (pids []int32, err error)
include::{funcsig_tpl}[]

`psProcPids` directly calls {godoc_root}/{mod_psutil}/process#Pids[`{mod_psutil}/proc.Pids`^].

[id="fn_ps_proc_pidxst"]
===== `psProcPidExists`
:func: psProcPidExists
:sig: (pid int32) (exists bool, err error)
include::{funcsig_tpl}[]

`psProcPidExists` directly calls {godoc_root}/{mod_psutil}/process#PidExists[`{mod_psutil}/proc.PidExists`^].

[id="fn_ps_sns"]
==== Sensors/Thermals

[id="fn_ps_sns_extemp"]
===== `psSensorExTemp`
:func: psSensorExTemp
:sig: () (temps []sensors.ExTemperature, err error)
include::{funcsig_tpl}[]

[WARNING]
====
This function only exists on Linux.
====

`psSensorExTemp` wraps link:{godoc_root}/{mod_psutil}/sensors?GOOS=linux#NewExLinux[`{mod_psutil}/sensors.NewExLinux`^].link:{godoc_root}/{mod_psutil}/sensors?GOOS=linux#TemperatureWithContext[`{mod_psutil}/sensors.TemperatureWithContext`^].

[id="fn_ps_sns_temps"]
===== `psSensorTemps`
:func: psSensorTemps
:sig: () (temps []sensors.TemperatureStat, err error)
include::{funcsig_tpl}[]

`psSensorTemps` directly calls {godoc_root}/{mod_psutil}/sensors#SensorsTemperatures[`{mod_psutil}/sensors.SensorsTemperatures`^].

[id="fn_ps_winsvc"]
==== Windows Services

[WARNING]
====
All of these functions are only available on Windows.
====

[id="fn_ps_winsvc_list"]
===== `psWinsvcList`
:func: psWinsvcList
:sig: () (svcs []winservices.Service, err error)
include::{funcsig_tpl}[]

[WARNING]
====
This function is only available on Windows.
====

`psWinsvcList` directly calls {godoc_root}/{mod_psutil}/winservices?GOOS=windows#ListServices[`{mod_psutil}/winservices.ListServices`^].

[id="fn_ps_winsvc_new"]
===== `psWinsvcNew`
:func: psWinsvcNew
:sig: (svcName string) (svc *winservices.Service, err error)
include::{funcsig_tpl}[]

[WARNING]
====
This function is only available on Windows.
====

`psWinsvcNew` directly calls {godoc_root}/{mod_psutil}/winservices?GOOS=windows#NewService[`{mod_psutil}/winservices.NewService`^].

[id="fn_str"]
=== Strings

These template functions use capabilities from:

* <<fn_str_stnd>>
* <<fn_str_strsx>>

[id="fn_str_stnd"]
==== Standalone

These functions are standalone developed purely for this library.
For legacy reasons, these have the special prefix `ext`.

[id="fn_str_stnd_extindent"]
===== `extIndent`
:func: extIndent
:sig: ({indnt}levels int,{indnt}skipFirst, skipEmpty, skipWhitespace bool,{indnt}indentString, input string,{nl}) (out string)
include::{funcsig_tpl}[]

`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 doublespace text!
====

It has quite a few arguments, however:

[cols="^.^1m,^.^1m,.^4a",options="header"]
|===
| Argument ^| Type | Description

| levels | int | 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 | bool | If true, skip indenting the first line. This is particularly handy if you like to visually align your function calls in your templates.
| skipEmpty | bool | If true, do not add an indent to *empty* lines (where an "empty line" means "only has a linebreak").
| skipWhitespace | bool | If true, do not add an indent to lines that *only* consist of whitespace (spaces, tabs, etc.) and a linebreak.
| indentString | string | The string to use as the "indent character". This can be any string, such as `"<SP>"` (https://asciiref.dev/#c32[`0x20`^]), `"\t"`, `"."`, `"\|"`, `"=="` etc.
| input |  string | The text to be indented. Because it is the last argument, `extIndent` works with pipelined text as well.
|===

[id="fn_str_strsx"]
==== `r00t2.io/goutils/stringsx`
These template functions contain capabilities from {godoc_root}/{mod_me}/netx[`{mod_me}/stringsx`^].

[id="fn_str_strsx_isascii"]
===== `strsxIsAscii`
:func: strsxIsAscii
:sig: (s string, allowCtl, allowExt bool) (isAscii bool, err error)
include::{funcsig_tpl}[]

`strsxIsAscii` directly calls {godoc_root}/{import_me}/stringsx#IsAscii[`{import_me}/stringsx.IsAscii`^].

[id="fn_str_strsx_isasciibuf"]
===== `strsxIsAsciiBuf`
:func: strsxIsAsciiBuf
:sig: (r io.RuneReader, allowCtl, allowExt bool) (isAscii bool, err error)
include::{funcsig_tpl}[]

`strsxIsAsciiBuf` directly calls {godoc_root}/{import_me}/stringsx#IsAsciiBuf[`{import_me}/stringsx.IsAsciiBuf`^].

[id="fn_str_strsx_isasciispcl"]
===== `strsxIsAsciiSpcl`
:func: strsxIsAsciiSpcl
:sig: ({indnt}s string,{indnt}allowCtl, allowPrint, allowExt, allowWs bool,{indnt}incl, excl []byte{nl}) (isAscii bool, err error)
include::{funcsig_tpl}[]

`strsxIsAsciiSpcl` directly calls {godoc_root}/{import_me}/stringsx#IsAsciiSpecial[`{import_me}/stringsx.IsAsciiSpecial`^].

[id="fn_str_strsx_isasciibufspcl"]
===== `strsxIsAsciiBufSpcl`
:func: strsxIsAsciiBufSpcl
:sig: ({indnt}r io.RuneReader,{indnt}allowCtl, allowPrint, allowExt, allowWs bool,{indnt}incl, excl []byte{nl}) (isAscii bool, err error)
include::{funcsig_tpl}[]

`strsxIsAsciiBufSpcl` directly calls {godoc_root}/{import_me}/stringsx#IsAsciiSpecial[`{import_me}/stringsx.IsAsciiSpecial`^].

[id="fn_str_strsx_lenspl"]
===== `strsxLenSpl`
:func: strsxLenSpl
:sig: (s string, width uint) (out []string)
include::{funcsig_tpl}[]

`strsxLenSpl` directly calls {godoc_root}/{import_me}/stringsx#LenSplit[`{import_me}/stringsx.LenSplit`^].

[id="fn_str_strsx_lensplstr"]
===== `strsxLenSplStr`
:func: strsxLenSplStr
:sig: (s string, width uint, winNewline bool) (out string)
include::{funcsig_tpl}[]

`strsxLenSplStr` directly calls {godoc_root}/{import_me}/stringsx#LenSplitStr[`{import_me}/stringsx.LenSplitStr`^].

[id="fn_str_strsx_pad"]
===== `strsxPad`
:func: strsxPad
:sig: (s []string, width uint, pad string, leftPad bool) (out []string)
include::{funcsig_tpl}[]

`strsxPad` directly calls {godoc_root}/{import_me}/stringsx#Pad[`{import_me}/stringsx.Pad`^].

[id="fn_str_strsx_rdct"]
===== `strsxRedact`
:func: strsxRedact
:sig: (s, maskStr string, leading, trailing uint, newlines bool) (redacted string)
include::{funcsig_tpl}[]

`strsxRedact` directly calls {godoc_root}/{import_me}/stringsx#Redact[`{import_me}/stringsx.Redact`^].

[id="fn_str_strsx_rev"]
===== `strsxRev`
:func: strsxRev
:sig: (s string) (revS string)
include::{funcsig_tpl}[]

`strsxRev` directly calls {godoc_root}/{import_me}/stringsx#Reverse[`{import_me}/stringsx.Reverse`^].

[id="fn_str_strsx_trimlns"]
===== `strsxTrimLns`
:func: strsxTrimLns
:sig: (s string, left, right bool) (trimmed string)
include::{funcsig_tpl}[]

`strsxTrimLns` directly calls {godoc_root}/{import_me}/stringsx#TrimLines[`{import_me}/stringsx.TrimLines`^].

[id="fn_str_strsx_trimspcl"]
===== `strsxTrimSpcLft`
:func: strsxTrimSpcLft
:sig: (s string) (trimmed string)
include::{funcsig_tpl}[]

`strsxTrimSpcLft` directly calls {godoc_root}/{import_me}/stringsx#TrimSpaceLeft[`{import_me}/stringsx.TrimSpaceLeft`^].

[id="fn_str_strsx_trimspcr"]
===== `strsxTrimSpcRt`
:func: strsxTrimSpcRt
:sig: (s string) (trimmed string)
include::{funcsig_tpl}[]

`strsxTrimSpcRt` directly calls {godoc_root}/{import_me}/stringsx#TrimSpaceRight[`{import_me}/stringsx.TrimSpaceRight`^].

[id="fn_sys"]
=== System/Platform/Architecture

[id="fn_sys_arch"]
==== `sysArch`
:func: sysArch
:sig: () (out string)
include::{funcsig_tpl}[]

Returns the {godoc_root}/runtime#GOARCH[`runtime.GOARCH`^] constant.

[id="fn_sys_numcpu"]
==== `sysNumCpu`
:func: sysNumCpu
:sig: () (cnt int)
include::{funcsig_tpl}[]

`sysNumCpu` directly calls {godoc_root}/runtime#NumCPU[`runtime.NumCPU`^].

[id="fn_sys_os"]
==== `sysOsName`
:func: sysOsName
:sig: () (out string)
include::{funcsig_tpl}[]

Returns the {godoc_root}/runtime#GOOS[`runtime.GOOS`^] constant.

[id="fn_sys_rntm"]
==== `sysRuntime`
:func: sysRuntime
:sig: () (out map[string]string)
include::{funcsig_tpl}[]

This function returns a `map[string]string` of various information from the {godoc_root}/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 {sprig_web}/conversion.html[Sprig conversion functions^] if necessary.
====

.`sysRuntime` Values
[cols="^.^3m,^.^3",options="header"]
|===
| Key | Value Type

| compiler | {godoc_root}/runtime#Compiler[string^]
| arch | {godoc_root}/runtime#GOARCH[string^]
| os | {godoc_root}/runtime#GOOS[string^]
| maxprocs | {godoc_root}/runtime#GOMAXPROCS[int^] footnote:[For safety concerns, `sprigx` does not allow *setting* `GOMAXPROCS`, this value only contains the *current* `GOMAXPROCS` value.]
| cpu_cnt | {godoc_root}/runtime#NumCPU[int^]
| num_cgo | {godoc_root}/runtime#NumCgoCall[int^]
| num_go | {godoc_root}/runtime#NumGoroutine[int^]
| go_ver | {godoc_root}/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_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`
:func: tmDate
:sig: (year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) (date time.Time)
include::{funcsig_tpl}[]

`tmDate` directly calls {godoc_root}/time#Date[`time.Date`^].

[id="fn_tm_fltmic"]
==== `tmFloatMicro`
:func: tmFloatMicro
:sig: (t time.Time) (f64 float64)
include::{funcsig_tpl}[]

`tmFloatMicro` directly calls {godoc_root}/r00t2.io/goutils/timex#F64Microseconds[`(r00t2.io/goutils/timex).F64Microseconds`^].

[id="fn_tm_fltmill"]
==== `tmFloatMilli`
:func: tmFloatMilli
:sig: (t time.Time) (f64 float64)
include::{funcsig_tpl}[]

`tmFloatMilli` directly calls {godoc_root}/r00t2.io/goutils/timex#F64Milliseconds[`(r00t2.io/goutils/timex).F64Milliseconds`^].

[id="fn_tm_fltnano"]
==== `tmFloatNano`
:func: tmFloatNano
:sig: (t time.Time) (f64 float64)
include::{funcsig_tpl}[]

`tmFloatNano` directly calls {godoc_root}/r00t2.io/goutils/timex#F64Nanoseconds[`(r00t2.io/goutils/timex).F64Nanoseconds`^].

[id="fn_tm_flt"]
==== `tmFloat`
:func: tmFloat
:sig: (t time.Time) (f64 float64)
include::{funcsig_tpl}[]

`tmFloat` directly calls {godoc_root}/r00t2.io/goutils/timex#F64Seconds[`(r00t2.io/goutils/timex).F64Seconds`^].

[id="fn_tm_fmt"]
==== `tmFmt`
:func: tmFmt
:sig: (fstr string, t time.Time) (out string)
include::{funcsig_tpl}[]

`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`
:func: tmNow
:sig: () (now time.Time)
include::{funcsig_tpl}[]

`tmNow` directly calls {godoc_root}/time#Now[`time.Now`^].

[id="fn_tm_pdur8n"]
==== `tmParseDur8n`
:func: tmParseDur8n
:sig: (s string) (d time.Duration, err error)
include::{funcsig_tpl}[]

`tmParseDur8n` directly calls {godoc_root}/time#ParseDuration[`time.ParseDuration`^].

[id="fn_tm_pmnth"]
==== `tmParseMonth`
:func: tmParseMonth
:sig: (v any) (mon time.Month, err error)
include::{funcsig_tpl}[]

`tmParseMonth` attempts to first try <<fn_tm_pmnthi>> and then tries <<fn_tm_pmnths>> if `v` is not "numeric".

[id="fn_tm_pmnthi"]
==== `tmParseMonthInt`
:func: tmParseMonthInt
:sig: (n any) (mon time.Month, err error)
include::{funcsig_tpl}[]

`tmParseMonthInt` parses a number representation of month `n` to a {godoc_root}/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 {godoc_root}/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 {godoc_root}/time#Now[`time.Now`^]).

If `n` resolves to > `12`, `err` will be `sprigx.ErrBadMonth` (though be sure to use {godoc_root}/errors#Is[`errors.Is`^]; it will be wrapped by link:{godoc_root}/text/template#ExecError[`(text/template).ExecError`]/link:{godoc_root}/html/template#ExecError[`(html/template).ExecError`].

[id="fn_tm_pmnths"]
==== `tmParseMonthStr`
:func: tmParseMonthStr
:sig: (s string) (mon time.Month, err error)
include::{funcsig_tpl}[]

`tmParseMonthStr` parses a string representation `s` of a month to a {godoc_root}/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 {godoc_root}/time#Now[`time.Now`^]).

[id="fn_tm_ptm"]
==== `tmParseTime`
:func: tmParseTime
:sig: (layout, value string) (t time.Time, err error)
include::{funcsig_tpl}[]

`tmParseTime` directly calls {godoc_root}/time#Parse[`time.Parse`^].

Be sure that `layout` is a properly parseable {godoc_root}/time#Layout[layout format^].

[id="todo"]
== TODO/Wishlist

[id="todo_collctns"]
=== Function Collections
Functions should be split into more granular function maps that allows a conusmer to only load a certain subset of relevant functions to reduce surface - string functions, network functions, etc.

This would ideally also allow for e.g. FuncMap "generators" that would return a FuncMap of methods bound to a struct with certain contextual settings (e.g. a root path restriction for <<todo_os>>).

[id="todo_fallible"]
=== Fallibility
Requires <<todo_collctns>> first.

Modeled after Vector's VRL concept of https://vector.dev/docs/reference/vrl/#fallibility[_Fallibility_^] (https://vector.dev/docs/reference/vrl/expressions/#function-fallibility[see also^]).

Functions should be grouped/selectable by "fallibility" - if they are guaranteed to return a *meaningful* value always, if they are guaranteed to return a value that *may be empty* but no error will be returned, or if a function *may* return an error.

[id="todo_safe"]
=== Function Scope Segregation
Requires <<todo_collctns>> first.

Modeled (slightly) after Vector's VRL concept of https://vector.dev/docs/reference/vrl/expressions/#purity[purity^].

As more functions get added (and even with the present function set), it may be desired that only functions that work only with provided data are exposed to templates.

The goal is to scope/separate functions in such a way that they are "tagged" with multiple attributes/characteristics:

* <<todo_fallible>>
* External Read (e.g. <<todo_os_fs, reading arbitrary files>>)
* Internal Modify (would potentially transform source data/data structures)
* External Modify (would affect the invoking system, e.g. <<todo_os_fs>>)
* Arbitrary Execution (e.g. `osExec` -- see <<todo_os_fs>>)
* Network Access (e.g. would involve creating network connections instead of operating on provided data locally)

A risk "score" from 1-10 should also be implemented with the above scopes/classes for further/alternative filtering by consumers.

[id="todo_os"]
=== Extend OS functions
Requires <<todo_safe>> first. (Not technically, but can provide a pretty big risk of not split out first.)

[id="todo_os_fs"]
Adding the ability to read files from the filesystem should be added. As part of this, however, a package-level variable should be implemented
(or <<todo_collctns>>) that can restrict reading to a specific prefix.

Off the top of my head:

* `osReadFile`
** Returns an `*os.File`.
** Closing the handler gets tricky, though... it can be done from within the template, but templates also have no `defer` to my knowledge. Caveat emptor.
* `osReadFileBuf`
** Will return a `*bytes.Buffer` with the contents of a file.
** A little safer than `osReadFile` as it's backed by a static set of bytes and doesn't need to be ``.Close()``'d.
* `osReadFileBytes`
** Should include an optional length limiter, so data from e.g. `/dev/urandom` can be read from.
* `osReadFileStr`
** Would return `string` instead of `[]byte`.
* `osReadDir`
** Returns a `[]fs.DirEntry` of a given path via `os.ReadDir`.
* `osStat`
** Returns an `fs.FileInfo` from an `os.Stat` or `os.LStat` (controlled by tpl function parameter)
* `osExec`
** Exactly what you'd think. Returns a command via an `os/exec.Cmd`, with params passed to `os/exec.Command` (plus ``*bytes.Buffer``s set to `<exec.Cmd>.Stdin` and `<exec.Cmd>.Stdout`).

These obviously have the potential to be *incredibly* dangerous if using untrusted template strings.

[id="todo_os_env"]
==== Environment Variables

* `osEnvGet` (`os.Getenv`)
* `osEnvs` (returns `os.Environ` result)
* `osEnvsMap` and other https://pkg.go.dev/r00t2.io/sysutils/envs[`r00t2.io/sysutils/envs`^] funcs

[id="todo_sys"]
=== System Functions
This would be... particularly tricky.

I essentially want to expose a large amount of functionality from https://pkg.go.dev/golang.org/x/sys/unix[`golang.org/x/sys/unix`^] and https://pkg.go.dev/golang.org/x/sys@v0.46.0/windows[`golang.org/x/sys/windows`^]
(and its https://pkg.go.dev/golang.org/x/sys@v0.46.0/windows#section-directories[subpackages^]).

At the least I should have a way to (attempt to) convert/coerce an `fs.FileInfo.Sys()` into the platform-appropriate definite object
(e.g. a https://pkg.go.dev/golang.org/x/sys/unix#Stat_t[`golang.org/x/sys/unix.Stat_t`^] and/or 
https://pkg.go.dev/golang.org/x/sys/unix#Statx_t[`golang.org/x/sys/unix.Statx_t`^],
or a https://pkg.go.dev/golang.org/x/sys/windows#Win32FileAttributeData[`golang.org/x/sys/windows.Win32FileAttributeData`^]).

[id="todo_encode"]
=== Encoding Functions
Should require <<todo_collctns>> first (at least for grouping).

* `hex*` functions ({godoc_root}/encoding/hex[`encoding/hex`^])
** Technically hex is "base16"
* `hexx*` functions ({godoc_root}/r00t2.io/goutils/encodingx/hexx[`r00t2.io/goutils/encodingx/hexx`^])
* `b64*` functions ({godoc_root}/encoding/base64[`encoding/base64`^])

[id="todo_uuid"]
=== UUID Functions
Should require <<todo_collctns>> first (at least for grouping).

* `uuid*` functions for {godoc_root}/github.com/google/uuid[`github.com/google/uuid`^]
* `uuidx*` functions for {godoc_root}/r00t2.io/goutils/uuidx[`r00t2.io/goutils/uuidx`^]

[id="todo_url"]
=== URL Functions
Expose {godoc_root}net/url/[`net/url`^] functions to derive URLs.

[id="todo_net"]
=== Live Networking Functions
Requires <<todo_safe>> first.

Sometimes networking data lookup is needed at time of render/contextual to render.

* `dns*` (DNS lookup options from {godoc_root}/[`net`^])
** With the ability to use a custom resolver (would need <<todo_collctns>>)
* `http*` functions
** `httpReq` for a {godoc_root}/net/http#Request[`net/http.Request`^]
** `http<Method>` for a shorthand/one-shot version of `httpReq` above
** `resty*` functions for {godoc_root}/resty.dev/v3[`resty.dev/v3`^]?
*** Maybe via a configured <<todo_collctns>> instead
