Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1eea0c2672
|
||
|
|
67c7faf449
|
||
|
|
82c69ec542
|
||
|
|
07e0e587fa
|
@@ -19,19 +19,27 @@ for f in $(find . -type f -iname "README.adoc"); do
|
|||||||
asciidoctor -a ROOTDIR="${orig}/" -o "${newf}" "${f}"
|
asciidoctor -a ROOTDIR="${orig}/" -o "${newf}" "${f}"
|
||||||
echo "Generated ${newf} from ${f}"
|
echo "Generated ${newf} from ${f}"
|
||||||
git add "${newf}"
|
git add "${newf}"
|
||||||
|
if command -v asciidoctor-pdf &> /dev/null;
|
||||||
|
then
|
||||||
|
newf="${pfx}.pdf"
|
||||||
|
|
||||||
|
asciidoctor-pdf -a ROOTDIR="${orig}/" -o "${newf}" "${f}"
|
||||||
|
fi
|
||||||
if command -v pandoc &> /dev/null;
|
if command -v pandoc &> /dev/null;
|
||||||
then
|
then
|
||||||
newf="${pfx}.md"
|
newf="${pfx}.md"
|
||||||
|
|
||||||
set +e
|
set +e
|
||||||
asciidoctor -a ROOTDIR="${orig}/" -b docbook -o - "${f}" | pandoc -f docbook -t markdown_strict -o "${newf}"
|
#asciidoctor -a ROOTDIR="${orig}/" -b docbook -o - "${f}" | pandoc -f docbook -t markdown_strict -o "${newf}"
|
||||||
|
#asciidoctor -a ROOTDIR="${orig}/" -b html -o - "${f}" | pandoc -f html -t markdown_strict -o "${newf}"
|
||||||
|
asciidoctor -a ROOTDIR="${orig}/" -b html -o - "${f}" | pandoc -f html -t gfm -o "${newf}"
|
||||||
if [ $? -eq 0 ];
|
if [ $? -eq 0 ];
|
||||||
then
|
then
|
||||||
echo "Generated ${newf} from ${f}"
|
echo "Generated ${newf} from ${f}"
|
||||||
git add "${newf}"
|
git add "${newf}"
|
||||||
else
|
else
|
||||||
echo "Failed to generate ${newf} from ${f}"
|
echo "Failed to generate ${newf} from ${f}"
|
||||||
git rm "${newf}"
|
git rm "${newf}" 2>/dev/null
|
||||||
fi
|
fi
|
||||||
set -e
|
set -e
|
||||||
fi
|
fi
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -19,12 +19,13 @@
|
|||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
# https://github.com/github/gitignore/blob/master/Go.gitignore
|
# https://github.com/github/gitignore/blob/master/Go.gitignore
|
||||||
# Binaries for programs and plugins
|
# Binaries for programs and plugins and other data
|
||||||
*.exe
|
*.exe
|
||||||
*.exe~
|
*.exe~
|
||||||
*.dll
|
*.dll
|
||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
|
*.pdf
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
# Test binary, built with `go test -c`
|
||||||
*.test
|
*.test
|
||||||
|
|||||||
1
TODO
Normal file
1
TODO
Normal file
@@ -0,0 +1 @@
|
|||||||
|
- validx: validator functions for https://pkg.go.dev/github.com/go-playground/validator/v10
|
||||||
19
chkplat.sh
Executable file
19
chkplat.sh
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# go tool dist list for all valid GOOS/GOARCH targets.
|
||||||
|
|
||||||
|
for tgt in $(go tool dist list);
|
||||||
|
do
|
||||||
|
o="$(echo ${tgt} | cut -f1 -d '/')"
|
||||||
|
a="$(echo ${tgt} | cut -f2 -d '/')"
|
||||||
|
out="$(env GOOS=${o} GOARCH=${a} go build ./... 2>&1)"
|
||||||
|
ret=${?}
|
||||||
|
if [ $ret -ne 0 ];
|
||||||
|
then
|
||||||
|
echo "OS: ${o}"
|
||||||
|
echo "ARCH: ${a}"
|
||||||
|
echo "${out}"
|
||||||
|
echo
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
done
|
||||||
2
go.mod
2
go.mod
@@ -10,7 +10,7 @@ require (
|
|||||||
github.com/shirou/gopsutil/v4 v4.25.12
|
github.com/shirou/gopsutil/v4 v4.25.12
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/sys v0.40.0
|
golang.org/x/sys v0.40.0
|
||||||
r00t2.io/sysutils v1.16.0
|
r00t2.io/sysutils v1.16.2
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
1
go.sum
1
go.sum
@@ -69,3 +69,4 @@ golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
|||||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
r00t2.io/sysutils v1.16.2/go.mod h1:iXK+ALOwIdRKjAJIE5USlkZ669SVDHBNNuYhunsznH8=
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build !(windows || plan9 || wasip1 || js || ios)
|
//go:build !(windows || plan9 || wasip1 || js || ios)
|
||||||
// +build !windows,!plan9,!wasip1,!js,!ios
|
|
||||||
|
|
||||||
// I mean maybe it works for plan9 and ios, I don't know.
|
// I mean maybe it works for plan9 and ios, I don't know.
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build !(windows || plan9 || wasip1 || js || ios || linux)
|
||||||
|
|
||||||
package logging
|
package logging
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build !(windows || plan9 || wasip1 || js || ios || linux)
|
//go:build !(windows || plan9 || wasip1 || js || ios || linux)
|
||||||
// +build !windows,!plan9,!wasip1,!js,!ios,!linux
|
|
||||||
|
|
||||||
// Linux is excluded because it has its own.
|
// Linux is excluded because it has its own.
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
6391
tplx/sprigx/README.md
Normal file
6391
tplx/sprigx/README.md
Normal file
File diff suppressed because it is too large
Load Diff
14
tplx/sprigx/TODO
Normal file
14
tplx/sprigx/TODO
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
- osReadFileBytes
|
||||||
|
- osReadFileStr
|
||||||
|
- osReadDir
|
||||||
|
|
||||||
|
- `dns*` funcs (net)
|
||||||
|
- `url*` funcs (net/url)
|
||||||
|
- `uuid*` funcs (github.com/google/uuid and r00t2.io/goutils/uuidx)
|
||||||
|
|
||||||
|
- `http*` funcs:
|
||||||
|
-- `httpReq`: returns a net/http.Request
|
||||||
|
-- `http<Method>`: performs <Method> (? seems redundant if exposing httpReq)
|
||||||
|
-- also have `resty*` funcs?
|
||||||
|
|
||||||
|
- i should probably explicitly provide a "safe" set vs. "full" set. can just mod the map func getters to accept a "safeOnly" bool param.
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
package sprigx
|
package sprigx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
`net`
|
||||||
|
`net/netip`
|
||||||
`os`
|
`os`
|
||||||
`os/user`
|
`os/user`
|
||||||
`path`
|
`path`
|
||||||
`path/filepath`
|
`path/filepath`
|
||||||
`runtime`
|
`runtime`
|
||||||
|
`time`
|
||||||
|
|
||||||
`github.com/davecgh/go-spew/spew`
|
`github.com/davecgh/go-spew/spew`
|
||||||
`github.com/shirou/gopsutil/v4/cpu`
|
`github.com/shirou/gopsutil/v4/cpu`
|
||||||
@@ -16,6 +19,8 @@ import (
|
|||||||
psnet `github.com/shirou/gopsutil/v4/net`
|
psnet `github.com/shirou/gopsutil/v4/net`
|
||||||
`github.com/shirou/gopsutil/v4/process`
|
`github.com/shirou/gopsutil/v4/process`
|
||||||
`github.com/shirou/gopsutil/v4/sensors`
|
`github.com/shirou/gopsutil/v4/sensors`
|
||||||
|
`go4.org/netipx`
|
||||||
|
`r00t2.io/goutils/timex`
|
||||||
`r00t2.io/sysutils`
|
`r00t2.io/sysutils`
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,6 +33,47 @@ var (
|
|||||||
"Meta"/Template-Helpers
|
"Meta"/Template-Helpers
|
||||||
*/
|
*/
|
||||||
"metaIsNil": metaIsNil,
|
"metaIsNil": metaIsNil,
|
||||||
|
/*
|
||||||
|
Networking (net)
|
||||||
|
*/
|
||||||
|
"netCidrMask": net.CIDRMask,
|
||||||
|
"netExtractAddr": netExtractAddr,
|
||||||
|
"netExtractHost": netExtractHost,
|
||||||
|
"netExtractIpnet": netExtractIpnet,
|
||||||
|
"netExtractPort": netExtractPort,
|
||||||
|
"netIfaces": net.Interfaces,
|
||||||
|
"netIp4Mask": netIp4Mask,
|
||||||
|
"netJoinHostPort": net.JoinHostPort,
|
||||||
|
"netParseIP": net.ParseIP,
|
||||||
|
/*
|
||||||
|
Networking (net/netip)
|
||||||
|
*/
|
||||||
|
"netipAddrPort": netip.AddrPortFrom,
|
||||||
|
"netipParseAddr": netip.ParseAddr,
|
||||||
|
"netipParseAddrPort": netip.ParseAddrPort,
|
||||||
|
"netipParsePrefix": netip.ParsePrefix,
|
||||||
|
"netipPrefix": netip.PrefixFrom,
|
||||||
|
/*
|
||||||
|
Networking (go4.org/netipx)
|
||||||
|
*/
|
||||||
|
"netipxAddrIpNet": netipx.AddrIPNet,
|
||||||
|
"netipxCmpPfx": netipx.ComparePrefix,
|
||||||
|
"netipxFromStdAddr": netipxFromStdAddr,
|
||||||
|
"netipxFromIp": netipxFromIp,
|
||||||
|
"netipxFromIpNet": netipxFromIpNet,
|
||||||
|
"netipxParseRange": netipx.ParseIPRange,
|
||||||
|
"netipxPfxAddr": netipx.ParsePrefixOrAddr,
|
||||||
|
"netipxPfxIpNet": netipx.PrefixIPNet,
|
||||||
|
"netipxPfxLast": netipx.PrefixLastIP,
|
||||||
|
"netipxPfxRange": netipx.RangeOfPrefix,
|
||||||
|
"netipxRange": netipx.IPRangeFrom,
|
||||||
|
/*
|
||||||
|
Numbers/Math
|
||||||
|
*/
|
||||||
|
"numFloat32Str": numFloat32Str,
|
||||||
|
"numFloat64": numFloat64,
|
||||||
|
"numFloat64Str": numFloat64Str,
|
||||||
|
"numFloatStr": numFloatStr,
|
||||||
/*
|
/*
|
||||||
OS
|
OS
|
||||||
*/
|
*/
|
||||||
@@ -119,7 +165,21 @@ var (
|
|||||||
"sysNumCpu": runtime.NumCPU,
|
"sysNumCpu": runtime.NumCPU,
|
||||||
"sysOsName": sysOsNm,
|
"sysOsName": sysOsNm,
|
||||||
"sysRuntime": sysRuntime,
|
"sysRuntime": sysRuntime,
|
||||||
|
/*
|
||||||
|
Time/Dates/Timestamps
|
||||||
|
*/
|
||||||
|
"tmDate": time.Date,
|
||||||
|
"tmFmt": tmFmt,
|
||||||
|
"tmFloatMicro": timex.F64Microseconds,
|
||||||
|
"tmFloatMilli": timex.F64Milliseconds,
|
||||||
|
"tmFloatNano": timex.F64Nanoseconds,
|
||||||
|
"tmFloat": timex.F64Seconds,
|
||||||
|
"tmNow": time.Now,
|
||||||
|
"tmParseDur8n": time.ParseDuration,
|
||||||
|
"tmParseMonth": tmParseMonth,
|
||||||
|
"tmParseMonthInt": tmParseMonthInt,
|
||||||
|
"tmParseMonthStr": tmParseMonthStr,
|
||||||
|
"tmParseTime": time.Parse,
|
||||||
}
|
}
|
||||||
|
|
||||||
// htmlMap holds functions usable/intended for use in only an [html/template.FuncMap].
|
// htmlMap holds functions usable/intended for use in only an [html/template.FuncMap].
|
||||||
|
|||||||
77
tplx/sprigx/docinfo.html
Normal file
77
tplx/sprigx/docinfo.html
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<!-- https://stackoverflow.com/a/34481639 -->
|
||||||
|
<!-- Generate a nice TOC -->
|
||||||
|
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
|
||||||
|
<script src="https://code.jquery.com/ui/1.11.4/jquery-ui.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.tocify/1.9.0/javascripts/jquery.tocify.min.js"></script>
|
||||||
|
<!-- We do not need the tocify CSS because the asciidoc CSS already provides most of what we neeed -->
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tocify-header {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tocify-subheader {
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tocify ul {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tocify-focus {
|
||||||
|
color: #7a2518;
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tocify-focus > a {
|
||||||
|
color: #7a2518;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function () {
|
||||||
|
// Add a new container for the tocify toc into the existing toc so we can re-use its
|
||||||
|
// styling
|
||||||
|
$("#toc").append("<div id='generated-toc'></div>");
|
||||||
|
$("#generated-toc").tocify({
|
||||||
|
extendPage: true,
|
||||||
|
context: "#content",
|
||||||
|
highlightOnScroll: true,
|
||||||
|
hideEffect: "slideUp",
|
||||||
|
// Use the IDs that asciidoc already provides so that TOC links and intra-document
|
||||||
|
// links are the same. Anything else might confuse users when they create bookmarks.
|
||||||
|
hashGenerator: function(text, element) {
|
||||||
|
return $(element).attr("id");
|
||||||
|
},
|
||||||
|
// Smooth scrolling doesn't work properly if we use the asciidoc IDs
|
||||||
|
smoothScroll: false,
|
||||||
|
// Set to 'none' to use the tocify classes
|
||||||
|
theme: "none",
|
||||||
|
// Handle book (may contain h1) and article (only h2 deeper)
|
||||||
|
selectors: $( "#content" ).has( "h1" ).size() > 0 ? "h1,h2,h3,h4,h5" : "h2,h3,h4,h5",
|
||||||
|
ignoreSelector: ".discrete"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Switch between static asciidoc toc and dynamic tocify toc based on browser size
|
||||||
|
// This is set to match the media selectors in the asciidoc CSS
|
||||||
|
// Without this, we keep the dynamic toc even if it is moved from the side to preamble
|
||||||
|
// position which will cause odd scrolling behavior
|
||||||
|
var handleTocOnResize = function() {
|
||||||
|
if ($(document).width() < 768) {
|
||||||
|
$("#generated-toc").hide();
|
||||||
|
$(".sectlevel0").show();
|
||||||
|
$(".sectlevel1").show();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$("#generated-toc").show();
|
||||||
|
$(".sectlevel0").hide();
|
||||||
|
$(".sectlevel1").hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(window).resize(handleTocOnResize);
|
||||||
|
handleTocOnResize();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
16
tplx/sprigx/errs.go
Normal file
16
tplx/sprigx/errs.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package sprigx
|
||||||
|
|
||||||
|
import (
|
||||||
|
`errors`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrBadAddr error = errors.New("invalid/bad address")
|
||||||
|
ErrBadAddrPort error = errors.New("invalid/bad address/port")
|
||||||
|
ErrBadMonth error = errors.New("could not determine/parse month")
|
||||||
|
ErrBadNet error = errors.New("invalid/bad network")
|
||||||
|
ErrOverflow error = errors.New("integer/buffer overflow")
|
||||||
|
ErrBadType error = errors.New("an invalid/unknown type was passed")
|
||||||
|
ErrNilVal error = errors.New("a nil value was passed")
|
||||||
|
ErrUnderflow error = errors.New("integer/buffer underflow")
|
||||||
|
)
|
||||||
@@ -1,14 +1,116 @@
|
|||||||
package sprigx
|
package sprigx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
`errors`
|
||||||
htpl "html/template"
|
htpl "html/template"
|
||||||
|
`math`
|
||||||
|
`reflect`
|
||||||
|
`strconv`
|
||||||
ttpl "text/template"
|
ttpl "text/template"
|
||||||
|
|
||||||
|
`github.com/Masterminds/sprig/v3`
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Many of these functions are modeled after sprig's.
|
Many of these functions are modeled after sprig's.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
CombinedFuncMap returns a generic function map (like [FuncMap]) combined with
|
||||||
|
[github.com/Masterminds/sprig/v3.GenericFuncMap].
|
||||||
|
|
||||||
|
If preferSprigx is true, SprigX function names will override Sprig
|
||||||
|
functions with the same name.
|
||||||
|
If false, Sprig functions will override conflicting SprigX functions
|
||||||
|
with the same name.
|
||||||
|
|
||||||
|
You probably want [CombinedHtmlFuncMap] or [CombinedTxtFuncMap] instead,
|
||||||
|
as they wrap this with the appropriate type.
|
||||||
|
*/
|
||||||
|
func CombinedFuncMap(preferSprigX bool) (fmap map[string]any) {
|
||||||
|
|
||||||
|
var fn any
|
||||||
|
var fnNm string
|
||||||
|
var sprigMap map[string]interface{} = sprig.GenericFuncMap()
|
||||||
|
var sprigxMap map[string]any = FuncMap()
|
||||||
|
|
||||||
|
if preferSprigX {
|
||||||
|
fmap = sprigMap
|
||||||
|
for fnNm, fn = range sprigxMap {
|
||||||
|
fmap[fnNm] = fn
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmap = sprigxMap
|
||||||
|
for fnNm, fn = range sprigMap {
|
||||||
|
fmap[fnNm] = fn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
CombinedHtmlFuncMap returns an [htpl.FuncMap] (like [HtmlFuncMap]) combined with
|
||||||
|
[github.com/Masterminds/sprig/v3.HtmlFuncMap].
|
||||||
|
|
||||||
|
If preferSprigx is true, SprigX function names will override Sprig
|
||||||
|
functions with the same name.
|
||||||
|
If false, Sprig functions will override conflicting SprigX functions
|
||||||
|
with the same name.
|
||||||
|
*/
|
||||||
|
func CombinedHtmlFuncMap(preferSprigX bool) (fmap htpl.FuncMap) {
|
||||||
|
|
||||||
|
var fn any
|
||||||
|
var fnNm string
|
||||||
|
var sprigMap htpl.FuncMap = sprig.HtmlFuncMap()
|
||||||
|
var sprigxMap htpl.FuncMap = HtmlFuncMap()
|
||||||
|
|
||||||
|
if preferSprigX {
|
||||||
|
fmap = sprigMap
|
||||||
|
for fnNm, fn = range sprigxMap {
|
||||||
|
fmap[fnNm] = fn
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmap = sprigxMap
|
||||||
|
for fnNm, fn = range sprigMap {
|
||||||
|
fmap[fnNm] = fn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
CombinedTxtFuncMap returns a [ttpl.FuncMap] (like [TxtFuncMap]) combined with
|
||||||
|
[github.com/Masterminds/sprig/v3.TxtFuncMap].
|
||||||
|
|
||||||
|
If preferSprigx is true, SprigX function names will override Sprig
|
||||||
|
functions with the same name.
|
||||||
|
If false, Sprig functions will override conflicting SprigX functions
|
||||||
|
with the same name.
|
||||||
|
*/
|
||||||
|
func CombinedTxtFuncMap(preferSprigX bool) (fmap ttpl.FuncMap) {
|
||||||
|
|
||||||
|
var fn any
|
||||||
|
var fnNm string
|
||||||
|
var sprigMap ttpl.FuncMap = sprig.TxtFuncMap()
|
||||||
|
var sprigxMap ttpl.FuncMap = TxtFuncMap()
|
||||||
|
|
||||||
|
if preferSprigX {
|
||||||
|
fmap = sprigMap
|
||||||
|
for fnNm, fn = range sprigxMap {
|
||||||
|
fmap[fnNm] = fn
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmap = sprigxMap
|
||||||
|
for fnNm, fn = range sprigMap {
|
||||||
|
fmap[fnNm] = fn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
FuncMap returns a generic function map.
|
FuncMap returns a generic function map.
|
||||||
|
|
||||||
@@ -57,6 +159,11 @@ func HtmlFuncMap() (fmap htpl.FuncMap) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Nop explicitly performs a NO-OP and returns an empty string, allowing one to override "unsafe" functions.
|
||||||
|
func Nop(obj ...any) (s string) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// TxtFuncMap returns a [text/template.FuncMap].
|
// TxtFuncMap returns a [text/template.FuncMap].
|
||||||
func TxtFuncMap() (fmap ttpl.FuncMap) {
|
func TxtFuncMap() (fmap ttpl.FuncMap) {
|
||||||
|
|
||||||
@@ -79,3 +186,171 @@ func TxtFuncMap() (fmap ttpl.FuncMap) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
toFloat64 uses reflection to resolve any string or numeric type (even custom types) to a float64.
|
||||||
|
|
||||||
|
It wraps toString for string types but will fall back to checking numeric types.
|
||||||
|
|
||||||
|
If err != nil, then NaN (if true) indicates that:
|
||||||
|
|
||||||
|
* val is a string (or pointer to a string), but
|
||||||
|
* is not a valid numeric string
|
||||||
|
|
||||||
|
(you can do this from the caller as well by calling `errors.Is(err, strconv.ErrSyntax)`).
|
||||||
|
err will always be non-nil if NaN is true.
|
||||||
|
|
||||||
|
err will be ErrNilVal if val is nil.
|
||||||
|
*/
|
||||||
|
func toFloat64(val any) (f float64, NaN bool, err error) {
|
||||||
|
|
||||||
|
var s string
|
||||||
|
var k reflect.Kind
|
||||||
|
var rv reflect.Value
|
||||||
|
|
||||||
|
// toString will return ErrNilVal if nil.
|
||||||
|
if s, err = toString(val); err != nil {
|
||||||
|
if errors.Is(err, ErrBadType) {
|
||||||
|
// This is OK, it's (hopefully) a number type.
|
||||||
|
err = nil
|
||||||
|
} else {
|
||||||
|
// *probably* ErrNilVal.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We can go ahead and parse this directly since it's already deref'd if a ptr.
|
||||||
|
if f, err = strconv.ParseFloat(s, 64); err != nil {
|
||||||
|
NaN = errors.Is(err, strconv.ErrSyntax)
|
||||||
|
}
|
||||||
|
// We can return regardless here; it's up to the caller to check NaN/err.
|
||||||
|
// If they're false/nil, f is parsed already!
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = reflect.ValueOf(val)
|
||||||
|
k = rv.Kind()
|
||||||
|
|
||||||
|
if k == reflect.Ptr {
|
||||||
|
if rv.IsNil() {
|
||||||
|
// *technically* this should be handled above, but best be safe.
|
||||||
|
err = ErrNilVal
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rv = rv.Elem()
|
||||||
|
k = rv.Kind()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch k {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
f = float64(rv.Int())
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
f = float64(rv.Uint())
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
f = rv.Float()
|
||||||
|
default:
|
||||||
|
// No need to check for string types since we do that near the beginning.
|
||||||
|
err = ErrBadType
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
toInt wraps toFloat64, rounds it to the nearest integer,
|
||||||
|
and converts to an int.
|
||||||
|
|
||||||
|
NaN, err have the same meaning as in toFloat64.
|
||||||
|
|
||||||
|
This function will panic if float64(val)'s f return exceeds
|
||||||
|
math.MaxInt on your platform.
|
||||||
|
*/
|
||||||
|
func toInt(val any) (i int, NaN bool, err error) {
|
||||||
|
|
||||||
|
var f float64
|
||||||
|
|
||||||
|
if f, NaN, err = toFloat64(val); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
i = int(math.Round(f))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
toPosFloat64 wraps toFloat64 and ensures that it is a positive float64.
|
||||||
|
|
||||||
|
NaN, err have the same meaning as in toFloat64.
|
||||||
|
*/
|
||||||
|
func toPosFloat64(val any) (f float64, NaN bool, err error) {
|
||||||
|
|
||||||
|
if f, NaN, err = toFloat64(val); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f = math.Abs(f)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
toPosInt wraps toPosFloat64, rounds it to the nearest integer,
|
||||||
|
and converts to an int.
|
||||||
|
|
||||||
|
NaN, err have the same meaning as in toPosFloat64 (and thus toFloat64).
|
||||||
|
|
||||||
|
This function will panic if float64(val)'s f return exceeds
|
||||||
|
math.MaxInt on your platform.
|
||||||
|
*/
|
||||||
|
func toPosInt(val any) (i int, NaN bool, err error) {
|
||||||
|
|
||||||
|
var f float64
|
||||||
|
|
||||||
|
if f, NaN, err = toPosFloat64(val); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
i = int(math.Round(f))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
toString uses reflection to resolve any string value (even custom types and ptrs)
|
||||||
|
to a concrete string.
|
||||||
|
|
||||||
|
err will be ErrBadType if val is not a string type/string-derived type.
|
||||||
|
err will be ErrNilVal if val is nil.
|
||||||
|
*/
|
||||||
|
func toString(val any) (s string, err error) {
|
||||||
|
|
||||||
|
var rv reflect.Value
|
||||||
|
var k reflect.Kind
|
||||||
|
|
||||||
|
if val == nil {
|
||||||
|
err = ErrNilVal
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = reflect.ValueOf(val)
|
||||||
|
k = rv.Kind()
|
||||||
|
|
||||||
|
if k == reflect.Ptr {
|
||||||
|
if rv.IsNil() {
|
||||||
|
// *technically* this should be handled above, but best be safe.
|
||||||
|
err = ErrNilVal
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rv = rv.Elem()
|
||||||
|
k = rv.Kind()
|
||||||
|
}
|
||||||
|
|
||||||
|
if k == reflect.String {
|
||||||
|
s = rv.String()
|
||||||
|
} else {
|
||||||
|
err = ErrBadType
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
package sprigx
|
package sprigx
|
||||||
|
|
||||||
// Nop explicitly performs a NO-OP and returns an empty string, allowing one to override "unsafe" functions.
|
|
||||||
func Nop(obj ...any) (s string) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// metaIsNil returns true if obj is explicitly nil.
|
// metaIsNil returns true if obj is explicitly nil.
|
||||||
func metaIsNil(obj any) (isNil bool) {
|
func metaIsNil(obj any) (isNil bool) {
|
||||||
|
|
||||||
|
|||||||
82
tplx/sprigx/funcs_tpl_net.go
Normal file
82
tplx/sprigx/funcs_tpl_net.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package sprigx
|
||||||
|
|
||||||
|
import (
|
||||||
|
`math`
|
||||||
|
`net`
|
||||||
|
`strconv`
|
||||||
|
)
|
||||||
|
|
||||||
|
// netExtractAddr calls net.ParseCIDR and returns the net.IP from it.
|
||||||
|
func netExtractAddr(s string) (addr net.IP, err error) {
|
||||||
|
|
||||||
|
if addr, _, err = net.ParseCIDR(s); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// netExtractHost extracts the host component from hostPort.
|
||||||
|
func netExtractHost(hostPort string) (host string, err error) {
|
||||||
|
|
||||||
|
if host, _, err = net.SplitHostPort(hostPort); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// netExtractIpnet calls net.ParseCIDR and returns the net.IPNet from it.
|
||||||
|
func netExtractIpnet(s string) (ipnet *net.IPNet, err error) {
|
||||||
|
|
||||||
|
if _, ipnet, err = net.ParseCIDR(s); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// netExtractPort extracts the port component from hostPort.
|
||||||
|
func netExtractPort(hostPort string) (port uint16, err error) {
|
||||||
|
|
||||||
|
var portStr string
|
||||||
|
var u64 uint64
|
||||||
|
|
||||||
|
if _, portStr, err = net.SplitHostPort(hostPort); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if u64, err = strconv.ParseUint(portStr, 10, 16); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
port = uint16(u64)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// netIp4Mask is a more flexible wrapper around net.IPv4Mask.
|
||||||
|
func netIp4Mask(a, b, c, d any) (mask net.IPMask, err error) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
var elem any
|
||||||
|
var elemInt int
|
||||||
|
var mBytes [4]byte
|
||||||
|
var orig [4]any = [4]any{a, b, c, d}
|
||||||
|
|
||||||
|
for idx, elem = range orig {
|
||||||
|
if elemInt, _, err = toPosInt(elem); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if elemInt > math.MaxUint8 {
|
||||||
|
err = ErrOverflow
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mBytes[idx] = byte(uint8(elemInt))
|
||||||
|
}
|
||||||
|
|
||||||
|
mask = net.IPv4Mask(
|
||||||
|
mBytes[0], mBytes[1], mBytes[2], mBytes[3],
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
47
tplx/sprigx/funcs_tpl_netipx.go
Normal file
47
tplx/sprigx/funcs_tpl_netipx.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package sprigx
|
||||||
|
|
||||||
|
import (
|
||||||
|
`net`
|
||||||
|
`net/netip`
|
||||||
|
|
||||||
|
`go4.org/netipx`
|
||||||
|
)
|
||||||
|
|
||||||
|
// netipxFromStdAddr wraps go4.org/netipx.FromStdAddr to comply with Go template requirements.
|
||||||
|
func netipxFromStdAddr(ip net.IP, port int, zone string) (addrPort netip.AddrPort, err error) {
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if addrPort, ok = netipx.FromStdAddr(ip, port, zone); !ok {
|
||||||
|
err = ErrBadAddrPort
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// netipxFromIp wraps go4.org/netipx.FromStdIP to comply with Go template requirements.
|
||||||
|
func netipxFromIp(ip net.IP) (addr netip.Addr, err error) {
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if addr, ok = netipx.FromStdIP(ip); !ok {
|
||||||
|
err = ErrBadAddr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// netipxFromIpNet wraps go4.org/netipx.FromStdIPNet to comply with Go template requirements.
|
||||||
|
func netipxFromIpNet(ipnet *net.IPNet) (pfx netip.Prefix, err error) {
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if pfx, ok = netipx.FromStdIPNet(ipnet); !ok {
|
||||||
|
err = ErrBadNet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
51
tplx/sprigx/funcs_tpl_nums.go
Normal file
51
tplx/sprigx/funcs_tpl_nums.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package sprigx
|
||||||
|
|
||||||
|
import (
|
||||||
|
`math/big`
|
||||||
|
)
|
||||||
|
|
||||||
|
// numFloat64 returns any string representation of a numeric value or any type of numeric value to a float64.
|
||||||
|
func numFloat64(val any) (f float64, err error) {
|
||||||
|
|
||||||
|
if f, _, err = toFloat64(val); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
numFloatStr wraps numFloat32Str and numFloat64Str.
|
||||||
|
|
||||||
|
val can be a string representation of any numeric value or any type of numeric value.
|
||||||
|
*/
|
||||||
|
func numFloatStr(val any) (s string, err error) {
|
||||||
|
|
||||||
|
var f float64
|
||||||
|
|
||||||
|
if f, _, err = toFloat64(val); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s = numFloat64Str(f)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// numFloat32Str returns float32 f as a complete string representation with no truncation (or right-padding).
|
||||||
|
func numFloat32Str(f float32) (s string) {
|
||||||
|
|
||||||
|
s = numFloat64Str(float64(f))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// numFloat64Str returns float64 f as a complete string representation with no truncation (or right-padding).
|
||||||
|
func numFloat64Str(f float64) (s string) {
|
||||||
|
|
||||||
|
var bf *big.Float
|
||||||
|
|
||||||
|
bf = big.NewFloat(f)
|
||||||
|
s = bf.Text('f', -1)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -7,16 +7,29 @@ import (
|
|||||||
`strings`
|
`strings`
|
||||||
)
|
)
|
||||||
|
|
||||||
// osGroupById returns os/user.LookupGroupId. Can accept either an integer or a string.
|
/*
|
||||||
func osGroupById[T string | int](gid T) (g *user.Group, err error) {
|
osGroupById returns os/user.LookupGroupId.
|
||||||
|
|
||||||
|
Can accept either a string (`"1000"`) or any
|
||||||
|
numeric type (`1000`, `-1000`, `1000.0`, `MyCustomType(1000)`, etc.)
|
||||||
|
*/
|
||||||
|
func osGroupById(gid any) (g *user.Group, err error) {
|
||||||
|
|
||||||
|
var i int
|
||||||
|
var NaN bool
|
||||||
var gidStr string
|
var gidStr string
|
||||||
|
|
||||||
switch t := any(gid).(type) {
|
if i, NaN, err = toPosInt(gid); err != nil {
|
||||||
case string:
|
if NaN {
|
||||||
gidStr = t
|
err = nil
|
||||||
case int:
|
if gidStr, err = toString(gid); err != nil {
|
||||||
gidStr = strconv.Itoa(t)
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gidStr = strconv.Itoa(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
g, err = user.LookupGroupId(gidStr)
|
g, err = user.LookupGroupId(gidStr)
|
||||||
@@ -55,16 +68,29 @@ func osHost() (hostNm string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// osUserById returns an os/user.LookupId. Can accept either an integer or a string.
|
/*
|
||||||
func osUserById[T string | int](uid T) (u *user.User, err error) {
|
osUserById returns an os/user.LookupId.
|
||||||
|
|
||||||
|
Can accept either a string (`"1000"`) or any
|
||||||
|
numeric type (`1000`, `-1000`, `1000.0`, `MyCustomType(1000)`, etc.)
|
||||||
|
*/
|
||||||
|
func osUserById(uid any) (u *user.User, err error) {
|
||||||
|
|
||||||
|
var i int
|
||||||
|
var NaN bool
|
||||||
var uidStr string
|
var uidStr string
|
||||||
|
|
||||||
switch t := any(uid).(type) {
|
if i, NaN, err = toPosInt(uid); err != nil {
|
||||||
case string:
|
if NaN {
|
||||||
uidStr = t
|
err = nil
|
||||||
case int:
|
if uidStr, err = toString(uid); err != nil {
|
||||||
uidStr = strconv.Itoa(t)
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uidStr = strconv.Itoa(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err = user.LookupId(uidStr)
|
u, err = user.LookupId(uidStr)
|
||||||
|
|||||||
139
tplx/sprigx/funcs_tpl_time.go
Normal file
139
tplx/sprigx/funcs_tpl_time.go
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
package sprigx
|
||||||
|
|
||||||
|
import (
|
||||||
|
`errors`
|
||||||
|
`strconv`
|
||||||
|
`strings`
|
||||||
|
`time`
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
tmFmt formats time t using format string fstr.
|
||||||
|
|
||||||
|
While one certainly can do the same via e.g.
|
||||||
|
|
||||||
|
{{- $t := tmNow -}}
|
||||||
|
{{ $t.Format $fstr }}
|
||||||
|
|
||||||
|
This takes a time.Time as the second (and last) parameter,
|
||||||
|
allowing it to work in pipelines.
|
||||||
|
*/
|
||||||
|
func tmFmt(fstr string, t time.Time) (out string) {
|
||||||
|
|
||||||
|
out = t.Format(fstr)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
tmParseMonth attempts to first try tmParseMonthInt
|
||||||
|
and then tries tmParseMonthStr if v is not "numeric".
|
||||||
|
*/
|
||||||
|
func tmParseMonth(v any) (mon time.Month, err error) {
|
||||||
|
|
||||||
|
var s string
|
||||||
|
|
||||||
|
if mon, err = tmParseMonthInt(v); err != nil {
|
||||||
|
if errors.Is(err, strconv.ErrSyntax) {
|
||||||
|
// NaN
|
||||||
|
err = nil
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it gets here, it's a non-numeric string.
|
||||||
|
if s, err = toString(v); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if mon, err = tmParseMonthStr(s); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
tmParseMonthInt parses a number representation of month n to a time.Month.
|
||||||
|
n may be any numeric type or a string representation of a number
|
||||||
|
(or a custom type derived from those).
|
||||||
|
|
||||||
|
A negative integer (or float, etc.) will be converted to a positive one (e.g. -6 => 6 => time.June).
|
||||||
|
|
||||||
|
floats are rounded to the nearest integer.
|
||||||
|
|
||||||
|
The integer should map directly to the month constants in the time module:
|
||||||
|
|
||||||
|
* 1: January
|
||||||
|
* 2: February
|
||||||
|
* 3: March
|
||||||
|
* 4: April
|
||||||
|
* 5: May
|
||||||
|
* 6: June
|
||||||
|
* 7: July
|
||||||
|
* 8: August
|
||||||
|
* 9: September
|
||||||
|
* 10: October
|
||||||
|
* 11: November
|
||||||
|
* 12: December
|
||||||
|
|
||||||
|
If n resolves to 0, mon will be the current month (as determined by time.Now).
|
||||||
|
|
||||||
|
If n resolves to > 12, err will be ErrBadMonth.
|
||||||
|
*/
|
||||||
|
func tmParseMonthInt(n any) (mon time.Month, err error) {
|
||||||
|
|
||||||
|
var i int
|
||||||
|
|
||||||
|
if i, _, err = toPosInt(n); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
mon = time.Now().Month()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if i > 12 {
|
||||||
|
err = ErrBadMonth
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mon = time.Month(i)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
tmParseMonthStr parses a string representation of month s to a time.Month.
|
||||||
|
|
||||||
|
It normalizes s to lowercase and only uses the first 3 characters
|
||||||
|
(the minimum length needed to determine month name
|
||||||
|
uniqueness - "June" vs. "July", "March" vs. "May").
|
||||||
|
|
||||||
|
An empty (or whitespace-only) string will use the current month (as determined by time.Now).
|
||||||
|
*/
|
||||||
|
func tmParseMonthStr(s string) (mon time.Month, err error) {
|
||||||
|
|
||||||
|
var i int
|
||||||
|
var m time.Month
|
||||||
|
|
||||||
|
if strings.TrimSpace(s) == "" {
|
||||||
|
mon = time.Now().Month()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s = strings.ToLower(strings.TrimSpace(s))[0:3]
|
||||||
|
|
||||||
|
for i = range 12 {
|
||||||
|
m = time.Month(i + 1)
|
||||||
|
if strings.ToLower(m.String())[0:3] == s {
|
||||||
|
mon = m
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ErrBadMonth
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
11
uuidx/consts.go
Normal file
11
uuidx/consts.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package uuidx
|
||||||
|
|
||||||
|
const (
|
||||||
|
RfcNone RfcGen = iota
|
||||||
|
Rfc4122
|
||||||
|
Rfc9562
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MsGuidThreshold int = 4
|
||||||
|
)
|
||||||
73
uuidx/doc.go
Normal file
73
uuidx/doc.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
Package uuidx intends to supplement [github.com/google/uuid].
|
||||||
|
|
||||||
|
# Microsoft GUID Shenanigans
|
||||||
|
|
||||||
|
The following functions are provided to deal with [Microsoft's incompetence]:
|
||||||
|
|
||||||
|
* [DetectMsGuid] (a confidence'd determination if a UUID is a Microsoft GUID or not)
|
||||||
|
* [IsFlippedEndian] for flipped-endian [uuid.UUID] comparison (e.g. a is the Microsoft-flipped-endian version of b)
|
||||||
|
* [IsMsGuid] (wraps [DetectMsGuid] and returns true if confidence is reasonably strong that it's a Microsoft GUID)
|
||||||
|
* [IsRfc] (the inverse of IsMsGuid, but also checks for strict RFC compliance and returns which RFC)
|
||||||
|
* [MsGuidToUuid] (explicitly convert/ensure a GUID/UUID is likely a UUID)
|
||||||
|
* [ToggleUuidMsGuid] (blindly flip the endianness of selected byte ranges for MS GUID <-> UUID conversion)
|
||||||
|
* [UuidToMsGuid] (explicitly convert/ensure a GUID/UUID is likely an MS GUID)
|
||||||
|
|
||||||
|
Microsoft, in their typical insanity, uses a proprietary UUID format (usually referred to as the "Microsoft GUID Format"
|
||||||
|
or "Mixed-Endian Format").
|
||||||
|
|
||||||
|
Normally for, for example a UUIDv4, it's structured as thus per RFC 9562 [§ 5.4] (which obsoletes RFC 4122 [§ 4.4]):
|
||||||
|
|
||||||
|
A B C D E
|
||||||
|
HEX(BE(uint32))-HEX(BE(uint16))-HEX(BE(uint16))-HEX(BE(<uint16>), BE(<6 bytes>))
|
||||||
|
|
||||||
|
(where <BE> is big-endian packing).
|
||||||
|
|
||||||
|
However, thanks to Microsoft we can't have nice things. They decided to completely ignore the standard, and
|
||||||
|
instead keep D/E as big-endian *but use little-endian* for A through C inclusive:
|
||||||
|
|
||||||
|
A B C D E
|
||||||
|
HEX(LE(uint32))-HEX(LE(uint16))-HEX(LE(uint16))-HEX(BE(<uint16>), BE(<6 bytes>))
|
||||||
|
|
||||||
|
"Surely that had SOME reason to do that," you may say to yourself, "they wouldn't make some arbitrary formatting
|
||||||
|
change from a standard just because."
|
||||||
|
|
||||||
|
You would be wrong. To my knowledge, they have never provided any technological justfification to this insanity,
|
||||||
|
and now it's infected its way into a slew of other technologies they've had their grubby little hands involved in
|
||||||
|
(e.g. UEFI). And it's of course too late to change.
|
||||||
|
|
||||||
|
So anyways here's a library to make dealing with Microsoft's hubris a little easier.
|
||||||
|
|
||||||
|
# Validation/Verification
|
||||||
|
|
||||||
|
Aside from trying to address Microsoft silliness, there are some additional functions:
|
||||||
|
|
||||||
|
* [Equal] for [uuid.UUID] comparison
|
||||||
|
* [IsMaxUUID] (if a given [uuid.UUID] is an RFC 9562 [§ 5.10] UUID)
|
||||||
|
* [IsNilUUID] (if a given [uuid.UUID] is an RFC 9562 [§ 5.9] UUID)
|
||||||
|
* [IsValid] (If an RFC can be considered safely conformant to RFC spec)
|
||||||
|
|
||||||
|
# Future Incorporation/Deprecation/Obsolescence
|
||||||
|
|
||||||
|
Worth keeping an eye on are:
|
||||||
|
|
||||||
|
* https://github.com/google/uuid/pull/192
|
||||||
|
* https://github.com/golang/go/issues/62026
|
||||||
|
* https://github.com/golang/go/issues/76319
|
||||||
|
(generally it's a bad idea for an API addition overall, but some good ideas were raised)
|
||||||
|
|
||||||
|
Some of these additions may deprecate/obsolete components of this package.
|
||||||
|
I'll try to keep them around but mark as deprecated as they are (if they are),
|
||||||
|
but I make no concrete promises - I hate making new major releases in Go's
|
||||||
|
[silly module architecture] even more than I do keeping old deprecated code around.
|
||||||
|
So caveat emptor.
|
||||||
|
|
||||||
|
[Microsoft's incompetence]: https://learn.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid
|
||||||
|
[§ 5.4]: https://datatracker.ietf.org/doc/html/rfc9562#section-5.4
|
||||||
|
[§ 4.4]: https://datatracker.ietf.org/doc/html/rfc4122#section-4.4
|
||||||
|
[§ 5.9]: https://datatracker.ietf.org/doc/html/rfc9562#section-5.9
|
||||||
|
[§ 5.10]: https://datatracker.ietf.org/doc/html/rfc9562#section-5.10
|
||||||
|
[github:google/uuid#192]: https://github.com/google/uuid/pull/192
|
||||||
|
[silly module architecture]: https://go.dev/doc/modules/major-version
|
||||||
|
*/
|
||||||
|
package uuidx
|
||||||
461
uuidx/funcs.go
Normal file
461
uuidx/funcs.go
Normal file
@@ -0,0 +1,461 @@
|
|||||||
|
package uuidx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
DetectMsGuid tries to guess if a given [uuid.UUID] is actually a Microsoft GUID or not.
|
||||||
|
|
||||||
|
Note that there are technically *two* types of Microsoft GUIDs:
|
||||||
|
|
||||||
|
* One is predictable, and defined in RFC 9562 [§ 4.2] as a known variant.
|
||||||
|
Detecting this is very easy and (assuming an RFC-compliant UUID is originally passed) is detectable with 100% confidence.
|
||||||
|
It's also legacy, and Microsoft no longer uses this format. Because they are insane and enjoy the suffering of others.
|
||||||
|
* The other, MODERN Microsoft GUID currently in use is the endianness-flipped version (see [ToggleUuidMsGuid]).
|
||||||
|
This is impossible to 100% determine, but analysis can get *pretty* close.
|
||||||
|
|
||||||
|
cs is a confidence scoring. As more logic is added, it *is* mathematically possible
|
||||||
|
(though unlikely) that cs == 0, so the caller is then responsible for making further
|
||||||
|
guesswork based on contextual analysis ("Did I get this UUID/GUID from an Active Directory attribute?"
|
||||||
|
"Is it a SID constant?" etc.).
|
||||||
|
|
||||||
|
A score > 0 indicates a confidence leaning towards the provided UUID/GUID being a Microsoft GUID.
|
||||||
|
A score < 0 indicates a confidence leaning towards the provided UUID/GUID *not* being a Microsoft GUID.
|
||||||
|
Note that a score of < 0 does not necessarily indicate it is a *proper, standard RFC-compliant UUID*,
|
||||||
|
simply that it is likely NOT a Microsoft GUID. [IsRfc] will be of further help in these cases.
|
||||||
|
|
||||||
|
csFlip indicates a score for the [ToggleUuidMsGuid]-flipped version of u.
|
||||||
|
It follows the same rules for thresholds and such as cs, but may be awarded different confidence levels
|
||||||
|
internally due to different chances of false positives.
|
||||||
|
If both cs and csFlip are > 0 but csFlip > cs, it is better to assume that u is *not* in the flipped-endian format
|
||||||
|
but *is* a Microsoft GUID (in other words, it is likely that u has *already been flipped* to proper/consistent endianness
|
||||||
|
instead of being a mixed-endian GUID).
|
||||||
|
|
||||||
|
In some cases where flipped-endianness does not matter (e.g. [IsNilUUID], [IsMaxUUID]),
|
||||||
|
cs and csFlip will be equal.
|
||||||
|
|
||||||
|
*Randomly-generated* GUIDs on Windows Server 2000-family and up are almost always UUIDv4.
|
||||||
|
Pre-Windows Server 2000 family *OR* any *statically-defined* GUIDs (schemaIDGUID, rightsGUID, CLSID constants, etc.)
|
||||||
|
are all over the place - TYPICALLY UUIDv1, but it's nothing predictable enough to be useful in definitive classification.
|
||||||
|
COM interfaces are all OVER the place in UUID version, but usually *not* UUIDv4.
|
||||||
|
|
||||||
|
A target/expected UUID version can be provided via tgtVer. To disable version analysis, use 0 (or 0x00, etc.).
|
||||||
|
It is *highly* recommended to provide a tgtVer if it is known; it can significantly boost confidence in the correct direction.
|
||||||
|
A warning, though - if a *wrong* tgtVer IS specified, it can negatively affect confidence accuracy.
|
||||||
|
Thus if you aren't ABSOLUTELY certain of the target UUID version, it's better to use 0/0x00 to disable the check.
|
||||||
|
Providing a target version is key to breaking some ties (e.g. both cs and csFlip are equal).
|
||||||
|
For example, the given RFC-compliant UUIDv4:
|
||||||
|
|
||||||
|
8d8e35ae-58d2-4d28-b09d-ffffffffffff
|
||||||
|
|
||||||
|
when flipped evaluates to an RFC-compliant UUIDv2:
|
||||||
|
|
||||||
|
ae358e8d-d258-284d-b09d-ffffffffffff
|
||||||
|
|
||||||
|
and in this case, cs and csFlip will both end up as 0.
|
||||||
|
Providing a tgtVer of 4 shifts this to a proper "tie-breaker" of cs == -3 and csFlip == 0.
|
||||||
|
Similarly, the endian-flipped UUIDv4 evaluates as a UUIDv2:
|
||||||
|
|
||||||
|
9856ea36-c2ca-2347-af0c-3b42f76c9eca
|
||||||
|
|
||||||
|
from the original unflipped UUIDv4:
|
||||||
|
|
||||||
|
36ea5698-cac2-4723-af0c-3b42f76c9eca
|
||||||
|
|
||||||
|
which results in a cs == 1 and csFlip == 0 - not very high confidence (but at least a correct and non-zero lean).
|
||||||
|
Providing a tgtVer == 4 changes this to cs == 7 and csFlip == 0, which is *much* more decisive.
|
||||||
|
|
||||||
|
UUIDs/GUIDs found to be strictly RFC-conforming (via [IsRfc], which returns false for Microsoft GUIDs)
|
||||||
|
are *heavily* weighted negatively.
|
||||||
|
|
||||||
|
Confidence levels can be generally considered as the following:
|
||||||
|
|
||||||
|
cs >= 7: Likely Microsoft GUID (mixed-endian)
|
||||||
|
cs >= 4: Likely Microsoft GUID
|
||||||
|
0 < cs < 4: Leans Microsoft GUID, but untrusted
|
||||||
|
cs == 0: Entirely ambiguous/indeterminate
|
||||||
|
-4 < cs < 0: Leans UUID/non-Microsoft GUID but untrusted
|
||||||
|
cs <= -5: Likely UUID/not Microsoft GUID
|
||||||
|
csFlip >=cs && csFlip >= 4: Likely a pre-flipped (ToggleUuidMsGuid'd) Microsoft GUID
|
||||||
|
|
||||||
|
[§ 4.2]: https://datatracker.ietf.org/doc/html/rfc9562#section-4.2
|
||||||
|
*/
|
||||||
|
|
||||||
|
func DetectMsGuid(u uuid.UUID, tgtVer uuid.Version) (cs, csFlip int) {
|
||||||
|
|
||||||
|
var isRfc bool
|
||||||
|
var flippedRfc bool
|
||||||
|
var flipped uuid.UUID = ToggleUuidMsGuid(u)
|
||||||
|
|
||||||
|
// These are the exact same when flipped, and are statically defined.
|
||||||
|
if IsNilUUID(u) || IsMaxUUID(u) {
|
||||||
|
cs = -12
|
||||||
|
csFlip = -12
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Most/all(?) Microsoft GUIDs are not NCS.
|
||||||
|
if IsNcs(u) {
|
||||||
|
cs -= 2
|
||||||
|
}
|
||||||
|
if IsNcs(flipped) {
|
||||||
|
// The flipped has a higher likelihood of false-pos, so we don't score it as confidently.
|
||||||
|
csFlip -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Version() == 0 {
|
||||||
|
if u.Variant() == uuid.Microsoft {
|
||||||
|
cs += 10
|
||||||
|
} else {
|
||||||
|
cs -= 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if flipped.Version() == 0 {
|
||||||
|
if flipped.Variant() == uuid.Microsoft {
|
||||||
|
csFlip += 4
|
||||||
|
} else {
|
||||||
|
csFlip -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid RFC version and variant. IsRfc returns false for the Microsoft Variant and version == 0.
|
||||||
|
// Modern MS uses an RFC 4122 variant indicator but flips the endianness.
|
||||||
|
isRfc, _ = IsRfc(u)
|
||||||
|
flippedRfc, _ = IsRfc(flipped)
|
||||||
|
if u.Variant() == uuid.RFC4122 { // This might be the strongest indicator.
|
||||||
|
if isRfc && !flippedRfc {
|
||||||
|
// This is *very* strong of being an MS GUID.
|
||||||
|
cs -= 8
|
||||||
|
csFlip += 4
|
||||||
|
} else if !isRfc && flippedRfc {
|
||||||
|
// It probably is an MS GUID but was already flipped.
|
||||||
|
csFlip += 6
|
||||||
|
} else if isRfc && flippedRfc {
|
||||||
|
/*
|
||||||
|
If both are RFC-compat, it's a weird case where
|
||||||
|
it actually IS RFC compliant and by chance the flipped is *also* RFC compat.
|
||||||
|
An example of this is:
|
||||||
|
8d8e35ae-58d2-4d28-b09d-ffffffffffff
|
||||||
|
Which has the flipped version of:
|
||||||
|
ae358e8d-d258-284d-b09d-ffffffffffff
|
||||||
|
The original is a v4, the flipped evaluates as a v2!
|
||||||
|
|
||||||
|
Providing a target version breaks this away to a definitive score.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// *HEAVILY* weigh a provided version.
|
||||||
|
if tgtVer != 0 {
|
||||||
|
// NCS does some weird things to the versioning field. We return early on it though.
|
||||||
|
// MS GUIDs have a pretty small chance of matching,
|
||||||
|
// but their flipped counterpart SHOULD match versions.
|
||||||
|
if flipped.Version() == tgtVer {
|
||||||
|
cs += 7
|
||||||
|
} else {
|
||||||
|
cs -= 3
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Give a *very small* boost to flippedRfc and flipped.Version() == 4, since it's so common.
|
||||||
|
// Don't make this too high though since the version is explicitly specified as unknown.
|
||||||
|
if flippedRfc && flipped.Version() == 4 {
|
||||||
|
cs += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Equal returns `true` if [uuid.UUID] the two provided [uuid.UUID] are the same.
|
||||||
|
|
||||||
|
Currently it just wraps:
|
||||||
|
|
||||||
|
eq = a == b
|
||||||
|
|
||||||
|
but is provided as a safety guarantee if the underlying structures/types should change.
|
||||||
|
*/
|
||||||
|
func Equal(a, b uuid.UUID) (eq bool) {
|
||||||
|
|
||||||
|
eq = a == b
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsFlippedEndian can be used to check if [uuid.UUID] is a direct endian-flipped ([ToggleUuidMsGuid])
|
||||||
|
of b (or vice versa, obviously).
|
||||||
|
|
||||||
|
It simply wraps:
|
||||||
|
|
||||||
|
isFlipped = Equal(a, ToggleUuidMsGuid(b))
|
||||||
|
|
||||||
|
but can be useful for shorthand/readability.
|
||||||
|
*/
|
||||||
|
func IsFlippedEndian(a, b uuid.UUID) (isFlipped bool) {
|
||||||
|
|
||||||
|
isFlipped = Equal(a, ToggleUuidMsGuid(b))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsMaxUUID returns `true` if the specified UUID is explicitly an RFC-defined
|
||||||
|
"Max UUID". (You may also see it specified in some places as the "Omni UUID".)
|
||||||
|
|
||||||
|
For details, see RFC 9562 [§ 5.10].
|
||||||
|
|
||||||
|
[§ 5.10]: https://datatracker.ietf.org/doc/html/rfc9562#section-5.10
|
||||||
|
*/
|
||||||
|
func IsMaxUUID(u uuid.UUID) (isMax bool) {
|
||||||
|
|
||||||
|
isMax = u == uuid.Max
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsMsGuid wraps
|
||||||
|
|
||||||
|
if cmp, _ = DetectMsGuid(msGUID, tgtVer); cmp < -3 {
|
||||||
|
isMs = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Note that [uuid.Microsoft] is an actual RFC-defined variant, but *Microsoft no longer uses it*
|
||||||
|
and in MODERN implementations do the endianness flip [ToggleUuidMsGuid] of (USUALLY) a UUIDv4.
|
||||||
|
|
||||||
|
See [DetectMsGuid] for a more in-depth result that will let you use the confidence level directly,
|
||||||
|
and for details on the weird things that can go wrong with this guesswork.
|
||||||
|
|
||||||
|
Note that this won't be 100% reliable due to math things, but it should be reliable enough most of the time.
|
||||||
|
|
||||||
|
See also [MsGuidToUuid] and [UuidToMsGuid].
|
||||||
|
*/
|
||||||
|
func IsMsGuid(msGUID uuid.UUID, tgtVer uuid.Version) (isMs bool) {
|
||||||
|
|
||||||
|
var cmp int
|
||||||
|
|
||||||
|
if cmp, _ = DetectMsGuid(msGUID, tgtVer); cmp < -3 {
|
||||||
|
isMs = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsNcs is shorthand for:
|
||||||
|
|
||||||
|
isNcs = u.Variant() == uuid.Reserved
|
||||||
|
|
||||||
|
See also the notes in [IsRfc].
|
||||||
|
*/
|
||||||
|
func IsNcs(u uuid.UUID) (isNcs bool) {
|
||||||
|
|
||||||
|
// https://archive.org/details/networkcomputing0000zahn/page/10/mode/1up
|
||||||
|
|
||||||
|
isNcs = u.Variant() == uuid.Reserved
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsNilUUID returns `true` if the specified UUID is explicitly an RFC-defined
|
||||||
|
"Nil UUID".
|
||||||
|
|
||||||
|
For details, see RFC 9562 [§ 5.9].
|
||||||
|
|
||||||
|
[§ 5.9]: https://datatracker.ietf.org/doc/html/rfc9562#section-5.9
|
||||||
|
*/
|
||||||
|
func IsNilUUID(u uuid.UUID) (isNil bool) {
|
||||||
|
|
||||||
|
isNil = u == uuid.Nil
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsRfc returns `true` if the specified UUID is a proper standard RFC UUID.
|
||||||
|
|
||||||
|
Because Microsoft is insane, rfc will be false even if it's a (legacy) Microsoft form
|
||||||
|
of an RFC UUID. Use [IsMsGuid] for that.
|
||||||
|
|
||||||
|
In the special case of u being a valid NCS UUID, rfc will be false but gen will be [Rfc4122].
|
||||||
|
This is because RFC 9652 deprecates the NCS UUID. See [IsNcs].
|
||||||
|
(You are highly unlikely to encounter an NCS UUID "in the wild" unless you are receiving
|
||||||
|
a UUID from someone who severely misunderstands that UUIDs are structured/versioned/typed
|
||||||
|
and thinks they're just random byes in hex with hyphens in certain places.)
|
||||||
|
(They aren't that, if you're one of those someones.)
|
||||||
|
|
||||||
|
Nil UUID ([IsNilUUID]) and Max UUID ([IsMaxUUID]) return true with RFCs 4122 and RFC 9562 respectively.
|
||||||
|
*/
|
||||||
|
func IsRfc(u uuid.UUID) (rfc bool, gen RfcGen) {
|
||||||
|
|
||||||
|
if IsNilUUID(u) {
|
||||||
|
rfc = true
|
||||||
|
gen = Rfc4122
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if IsMaxUUID(u) {
|
||||||
|
rfc = true
|
||||||
|
gen = Rfc9562
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if IsNcs(u) {
|
||||||
|
gen = Rfc4122
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Are there any sub-version checks that can be applied?
|
||||||
|
switch u.Variant() {
|
||||||
|
case uuid.Invalid, uuid.Microsoft, uuid.Future:
|
||||||
|
return
|
||||||
|
case uuid.RFC4122:
|
||||||
|
if !(0x01 <= u.Version() && u.Version() <= 0x08) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rfc = true
|
||||||
|
gen = Rfc4122
|
||||||
|
// 4122 only covers UUIDv1 through UUIDv5.
|
||||||
|
if 0x06 <= u.Version() && u.Version() <= 0x08 {
|
||||||
|
gen = Rfc9562
|
||||||
|
}
|
||||||
|
default: // Safety net in case upstream adds a uuid.RFC9562 variant or something.
|
||||||
|
if !(0x01 <= u.Version() && u.Version() <= 0x08) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if u.Variant() < uuid.Future {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rfc = true
|
||||||
|
gen = RfcNone
|
||||||
|
// 4122 only covers UUIDv1 through UUIDv5.
|
||||||
|
if 0x06 <= u.Version() && u.Version() <= 0x08 {
|
||||||
|
gen = Rfc9562
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsValid indicates if the given [uuid.UUID] strictly conforms to RFC.
|
||||||
|
|
||||||
|
A Nil UUID (as in RFC 9562 [§ 5.9], not a `nil` *uuid.UUID) will return `true`
|
||||||
|
as it IS technically defined per RFC despite not conforming to a version.
|
||||||
|
Use [IsNilUUID] to further determine that.
|
||||||
|
|
||||||
|
Likewise, a Max UUID (RFC 9562 [§ 5.10]) will return `true` as it is also
|
||||||
|
defined per RFC despite not conforming to a version.
|
||||||
|
Use [IsMaxUUID] to further determine that.
|
||||||
|
|
||||||
|
Microsoft GUIDs will always return false since they defy RFC.
|
||||||
|
Use [IsMsGuid] to check for that condition.
|
||||||
|
|
||||||
|
[§ 5.9]: https://datatracker.ietf.org/doc/html/rfc9562#section-5.9
|
||||||
|
[§ 5.10]: https://datatracker.ietf.org/doc/html/rfc9562#section-5.10
|
||||||
|
*/
|
||||||
|
func IsValid(u uuid.UUID) (valid bool) {
|
||||||
|
|
||||||
|
if IsNilUUID(u) {
|
||||||
|
valid = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if IsMaxUUID(u) {
|
||||||
|
valid = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch u.Variant() {
|
||||||
|
case uuid.Invalid, uuid.Reserved, uuid.Microsoft, uuid.Future:
|
||||||
|
return
|
||||||
|
case uuid.RFC4122:
|
||||||
|
valid = true
|
||||||
|
// TODO: If they add an RFC9562 or something, need a case here.
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got here, it *should* be RFC.
|
||||||
|
if valid, _ = IsRfc(u); !valid {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
MsGuidToUuid converts a Microsoft GUID to a UUID.
|
||||||
|
|
||||||
|
If [IsMsGuid] is false for msGUID, u will be equal to msGUID.
|
||||||
|
|
||||||
|
See [UuidToMsGuid] for the inverse, and [IsRfc] to check
|
||||||
|
if the result is a strictly conforming UUID.
|
||||||
|
*/
|
||||||
|
func MsGuidToUuid(msGUID uuid.UUID) (u uuid.UUID) {
|
||||||
|
|
||||||
|
if !IsMsGuid(msGUID, 0x00) {
|
||||||
|
u = msGUID
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u = ToggleUuidMsGuid(msGUID)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ToggleUuidMsGuid switches the src to it's "other" format:
|
||||||
|
|
||||||
|
* if it's a Microsoft GUID, it will be converted to a UUID
|
||||||
|
* if it's a UUID, it will be converted to a Microsoft GUID
|
||||||
|
|
||||||
|
No detection ([IsRfc], [IsMsGuid], etc.) nor validation/verification ([IsValid]) is performed,
|
||||||
|
which is why this is a "toggle" - it just flips some endianness for certain byte ranges.
|
||||||
|
|
||||||
|
If you prefer something a little more explicit, see [MsGuidToUuid] and/or [UuidToMsGuid].
|
||||||
|
Alternatively call [IsMsGuid] or [IsRfc] directly.
|
||||||
|
*/
|
||||||
|
func ToggleUuidMsGuid(orig uuid.UUID) (converted uuid.UUID) {
|
||||||
|
|
||||||
|
var cb [16]byte
|
||||||
|
var ob [16]byte = orig
|
||||||
|
|
||||||
|
// Can just directly map the allocations;
|
||||||
|
// the operation is the exact same regardless of whether the original is RFC and target is MS or vice versa.
|
||||||
|
cb = [16]byte{
|
||||||
|
// THESE GET ENDIAN-SWAPPED
|
||||||
|
ob[3], ob[2], ob[1], ob[0], // "A"
|
||||||
|
ob[5], ob[4], // "B"
|
||||||
|
ob[7], ob[6], // "C"
|
||||||
|
// THESE STAY THE SAME (should be BE for both)
|
||||||
|
ob[8], ob[9], ob[10], ob[11], // "D"
|
||||||
|
ob[12], ob[13], ob[14], ob[15], // "E"
|
||||||
|
}
|
||||||
|
|
||||||
|
converted = uuid.UUID(cb)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
UuidToMsGuid converts a UUID to a Microsoft GUID.
|
||||||
|
|
||||||
|
If [DetectMsGuid] indicates a good likelihood for u already being a Microsoft GUID
|
||||||
|
(greater than or equal) to [MsGuidThreshold], msGUID will be equal to u.
|
||||||
|
(If it detects it as unflipped endianness, it will automatically be flipped by this function.)
|
||||||
|
|
||||||
|
See [MsGuidToUuid] for the inverse.
|
||||||
|
*/
|
||||||
|
func UuidToMsGuid(u uuid.UUID) (msGUID uuid.UUID) {
|
||||||
|
|
||||||
|
var msCmp int
|
||||||
|
var flipped int
|
||||||
|
|
||||||
|
if msCmp, flipped = DetectMsGuid(u, 0x00); msCmp >= MsGuidThreshold && msCmp > flipped {
|
||||||
|
msGUID = u
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msGUID = ToggleUuidMsGuid(u)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
22
uuidx/funcs_rfcgen.go
Normal file
22
uuidx/funcs_rfcgen.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package uuidx
|
||||||
|
|
||||||
|
// String conforms an [RfcGen] to a [fmt.Stringer] interface.
|
||||||
|
func (g *RfcGen) String() (s string) {
|
||||||
|
|
||||||
|
if g == nil {
|
||||||
|
s = "UNSPECIFIED_NIL"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch *g {
|
||||||
|
case RfcNone:
|
||||||
|
s = "INVALID"
|
||||||
|
case Rfc4122:
|
||||||
|
s = "RFC 4122"
|
||||||
|
case Rfc9562:
|
||||||
|
s = "RFC 9562"
|
||||||
|
default:
|
||||||
|
s = "UNKNOWN"
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
5
uuidx/types.go
Normal file
5
uuidx/types.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package uuidx
|
||||||
|
|
||||||
|
type (
|
||||||
|
RfcGen uint8
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user