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

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

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

1532
tplx/sprigx/README.html Normal file

File diff suppressed because it is too large Load Diff

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

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

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

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

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

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

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

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

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

@@ -0,0 +1,64 @@
package sprigx
import (
htpl "html/template"
ttpl "text/template"
)
/*
Many of these functions are modeled after sprig's.
*/
/*
FuncMap returns a generic function map.
You probably want [HtmlFuncMap] or [TxtFuncMap] instead,
as they wrap this with the appropriate type.
*/
func FuncMap() (fmap map[string]any) {
var fn string
var f any
fmap = make(map[string]any, len(genericMap))
for fn, f = range genericMap {
fmap[fn] = f
}
return
}
// HtmlFuncMap returns an [html/template.FuncMap].
func HtmlFuncMap() (fmap htpl.FuncMap) {
var fn string
var f any
fmap = htpl.FuncMap(FuncMap())
if htmlMap != nil && len(htmlMap) > 0 {
for fn, f = range htmlMap {
fmap[fn] = f
}
}
return
}
// TxtFuncMap returns a [text/template.FuncMap].
func TxtFuncMap() (fmap ttpl.FuncMap) {
var fn string
var f any
fmap = ttpl.FuncMap(FuncMap())
if txtMap != nil && len(txtMap) > 0 {
for fn, f = range txtMap {
fmap[fn] = f
}
}
return
}

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

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

View File

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

View File

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

View File

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

View File

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