Compare commits

...
2 Commits
Author SHA1 Message Date
brent saner 7ec35990a9 v1.17.6
ADDED:
* mapsx.(ListSortedKeys|ListSortedValues|SortedKeys|SortedValues)
2026-10-10 01:32:35 -04:00
brent saner 67a5c8a3fb v1.17.5
ADDED:
* netx/dnsx/ptr package
2026-09-11 22:12:00 -04:00
20 changed files with 1249 additions and 15 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
I need to test this still; I can't recall if it's complete or not
+10
View File
@@ -0,0 +1,10 @@
package binarystr
const (
/*
bufSz is the size of the buffer/chunk for encoders/decords to use when unspecified by the method caller.
The below is 64 KiB, which should be relatively reasonable.
*/
bufSz int = 65536
)
+44
View File
@@ -0,0 +1,44 @@
/*
Package binarystr parses and renders string (and other) representations of binary data.
To distinguish this package from [encoding/binary], this package refers to this string encoding format
as "bit encoding" or "bitstrings".
For example, the original data:
[]byte("test")
// Or:
// []byte{0x74, 0x65, 0x73, 0x74}
can be encoded to a bitstring via e.g. [BytesToStr]:
01110100011001010111001101110100
And even formatted via [FmtBinStr]:
// For example, FmtBinStr("01110100011001010111001101110100", "", "", " "):
01110100 01100101 01110011 01110100
// Or FmtBinStr("01110100011001010111001101110100", "", " ", "\n"):
0111 0100
0110 0101
0111 0011
0111 0100
Or can be converted to a series of "binary booleans" via e.g. [BytesToBool]:
[]bool{
false, true, true, true,
false, true, false, false,
false, true, true, false,
false, true, false, true,
false, true, true, true,
false, false, true, true,
false, true, true, true,
false, true, false, false,
}
*/
package binarystr
+28
View File
@@ -0,0 +1,28 @@
package binarystr
import (
`errors`
)
var (
ErrBadNumOct error = errors.New("bad number of bits for even octet split")
ErrNoChunk error = errors.New("empty/nil chunk")
)
type (
BadBinCharErr struct {
posByte uint64
posRune uint64
r rune
}
BadNumOctErr struct {
posByte uint64
posRune uint64
rem string
}
BadReadErr struct {
numBytes uint64
}
)
+408
View File
@@ -0,0 +1,408 @@
package binarystr
import (
`bytes`
`errors`
`io`
`strconv`
`strings`
`unicode`
)
/*
BoolToBytes provides the inverse of [BytesToBool].
Each boolean in bools indicates a 0 (false) or 1 (true).
e.g.:
[]bool{
false, true, true, true,
false, true, false, false,
false, true, true, false,
false, true, false, true,
false, true, true, true,
false, false, true, true,
false, true, true, true,
false, true, false, false,
}
to:
[]byte{
0x74, 0x65, 0x73, 0x74,
}
If bools is nil, then b, rem, and err will also be nil and this function will no-op.
If len(bools) % 8 != 0, then err will be [ErrBadNumOct].
The remaining booleans can be found in rem.
*/
func BoolToBytes(bools []bool) (b []byte, rem []bool, err error) {
var idx int
var loc []bool
var sb *strings.Builder = new(strings.Builder)
if bools == nil {
return
}
if len(bools) == 0 {
b = make([]byte, 0)
return
}
loc = make([]bool, len(bools))
copy(loc, bools)
for idx = 0; idx < len(loc); idx += 8 {
if len(loc) < 8 {
break
}
writeBoolChunk(loc[idx:idx+8], sb)
loc = loc[idx+8:]
}
if b, err = StrToBytes(sb.String()); err != nil {
return
}
if len(loc) > 1 {
rem = append(rem, loc...)
err = ErrBadNumOct
}
return
}
/*
BytesToStr converts bytes to a bitstring.
e.g.:
[]byte{
0x74, 0x65, 0x73, 0x74,
}
to:
"01110100011001010111001101110100"
For the inverse, see [StrToBytes].
To format the bitstring, see [FmtBinStr].
*/
func BytesToStr(b []byte) (binStr string) {
var cb byte
var sb *strings.Builder
if b == nil {
return
}
sb = new(strings.Builder)
for _, cb = range b {
// This will never actually return an error.
_, _ = sb.WriteString(
strconv.FormatUint(
uint64(uint8(cb)),
2,
),
)
}
binStr = sb.String()
return
}
/*
BytesToBool returns a slice of "binary booleans".
For each bit of data in b, a 0 is false and a 1 is true (as one would expect).
For the inverse of this, see [BoolToBytes].
*/
func BytesToBool(b []byte) (bools []bool) {
var err error
var r rune
var rSz int
var idx int
var s *strings.Reader
if len(b) == 0 {
return
}
// s will always be a valid bitstring.
s = strings.NewReader(BytesToStr(b))
bools = make([]bool, s.Len())
for idx = 0; idx < s.Len(); idx++ {
r, rSz, err = s.ReadRune()
if rSz == 0 {
break
}
if err != nil {
// It'll only be an io.EOF if anything at all.
err = nil
break
}
switch r {
case '0':
bools[idx] = false
case '1':
bools[idx] = true
}
}
return
}
/*
FmtBinStr formats bitstring s to more comfortable representations.
bitSep is a string to separate each bit boundary.
nybSep is a string to separate each "nibble"/"nybble" (4-bit "word") boundary.
bytSep is a string to separate each byte boundary.
Note that if you only explicitly use whitespace separators, then the output
can still be parsed back to native []byte{} data via [StrToBytes].
Note that NO checking is done against the validity of s being a bitstring;
the only content-checking done in s is if a given rune is a whitespace character or not
(whitespace present in s is skipped over, which lets you run FmtBinStr to reformat a bitstring).
*/
func FmtBinStr(s, bitSep, nybSep, bytSep string) (out string) {
var idx int
var lastIdx int
var sb *strings.Builder
if strings.TrimSpace(s) == "" {
return
}
sb = new(strings.Builder)
lastIdx = len(s) - 1
for idx = 0; idx < len(s); idx++ {
_ = sb.WriteByte(s[idx])
if idx != lastIdx {
if (idx+1)%8 == 0 {
if bytSep != "" {
_, _ = sb.WriteString(bytSep)
}
continue
} else if (idx+1)%4 == 0 {
if nybSep != "" {
_, _ = sb.WriteString(nybSep)
}
continue
}
}
if bitSep != "" {
_, _ = sb.WriteString(bitSep)
}
}
out = sb.String()
return
}
/*
NewDecoder returns a [Decoder] from [io.Reader] r.
Data from r is expected to be BITSTRING data (strings);
doing read operations from a [Decoder] returns PLAIN data.
*/
func NewDecoder(r io.Reader) (e *Decoder) {
e = &Decoder{
r: r,
}
return
}
/*
NewEncoder returns an [Encoder] from [io.Reader] r.
Data from r is expected to be PLAIN data;
doing read operations from an [Encoder] returns BITSTRING data (strings).
*/
func NewEncoder(r io.Reader) (e *Encoder) {
e = &Encoder{
r: r,
}
return
}
/*
StrToBytes converts a bitstring to bytes.
Whitespace is ignored.
e.g.:
"01110100011001010111001101110100"
to:
[]byte("test")
or:
[]byte{0x74, 0x65, 0x73, 0x74}
For the inverse, see [BytesToStr].
If the number of characters in binStr is not modulo 8 (`len() % 8 != 0`),
then err will be of type [BadNumOctErr].
If there are any characters encountered in binStr that are not "0" or "1"
(other than whitespace), then err will be of type [BadBinCharErr].
*/
func StrToBytes(binStr string) (b []byte, err error) {
var r rune
var cb byte
var rSz int
var pos uint64
var rErr error
var posR uint64
var chunk []rune
var buf *strings.Reader
var bytBuf *bytes.Buffer = new(bytes.Buffer)
defer func() {
b = bytBuf.Bytes()
}()
if strings.TrimSpace(binStr) == "" {
return
}
buf = strings.NewReader(binStr)
chunk = make([]rune, 0, 8)
for {
r, rSz, rErr = buf.ReadRune()
if rSz == 0 {
break
}
pos += uint64(rSz)
if rSz != 0 {
posR++
}
if len(chunk) == 8 {
if cb, err = chunkToByte(chunk); err != nil {
return
}
if err = bytBuf.WriteByte(cb); err != nil {
return
}
chunk = make([]rune, 0, 8)
}
if unicode.IsSpace(r) {
continue
}
if rSz != 0 {
switch r {
case '0', '1':
chunk = append(chunk, r)
default:
err = &BadBinCharErr{
posByte: pos,
posRune: posR,
r: r,
}
return
}
}
if rErr != nil {
if errors.Is(rErr, io.EOF) {
break
} else {
err = rErr
return
}
}
if rSz == 0 {
break
}
}
if len(chunk) != 0 {
if len(chunk) == 8 {
if cb, err = chunkToByte(chunk); err != nil {
return
}
if err = bytBuf.WriteByte(cb); err != nil {
return
}
} else {
err = &BadNumOctErr{
posByte: pos,
posRune: posR,
rem: string(chunk),
}
return
}
}
return
}
func chunkToByte(chunk []rune) (b byte, err error) {
var u8 uint64
if len(chunk) == 0 {
err = ErrNoChunk
return
}
if u8, err = strconv.ParseUint(string(chunk), 2, 8); err != nil {
return
}
b = byte(uint8(u8))
return
}
func writeBoolChunk(chunk []bool, sb io.StringWriter) {
var b bool
if len(chunk) == 0 {
return
}
for _, b = range chunk {
switch b {
case false:
sb.WriteString("0")
case true:
sb.WriteString("1")
}
}
}
@@ -0,0 +1,26 @@
package binarystr
import "fmt"
// BytePos returns the byte position index immediately leading up to the error.
func (b *BadBinCharErr) BytePos() (pos uint64) { return b.posByte }
// Rune returns the offending rune that caused the error.
func (b *BadBinCharErr) Rune() (r rune) { return b.r }
// RunePos returns the rune (character) position index immediately leading up to the error.
func (b *BadBinCharErr) RunePos() (pos uint64) { return b.posRune }
// Error returns the error message for a BadBinCharError. It conforms to an [error] interface.
func (b *BadBinCharErr) Error() (err string) {
err = fmt.Sprintf(
"rune/character after position index #%d (%#v) (byte #%d) is not \"0\" or \"1\"",
b.posRune, b.r, b.posByte,
)
return
}
// String is an alias for the [BadBinCharErr.Error] method to conform explicitly to a [fmt.Stringer] interface.
func (b *BadBinCharErr) String() (s string) { return b.Error() }
+32
View File
@@ -0,0 +1,32 @@
package binarystr
import "fmt"
// BytePos returns the byte position index immediately leading up to the error.
func (b *BadNumOctErr) BytePos() (pos uint64) { return b.posByte }
// Error returns the error message for a BadNumOctError. It conforms to an [error] interface.
func (b *BadNumOctErr) Error() (err string) {
err = fmt.Sprintf(
"only able to read %d complete octets/bytes (%#d runes/characters); "+
"%d remaining characters are less than 8 bits",
b.posByte, b.posRune,
len(b.rem),
)
return
}
/*
Rem returns the remaining unparsed string of characters; this should ALWAYS be a length of 0 <= len(rem) < 8.
(It will only be an empty string if [Decoder.Read] encountered the end of its source reader before the buffer was filled,
but the available data cleanly broke into a last octet.)
*/
func (b *BadNumOctErr) Rem() (rem string) { return b.rem }
// RunePos returns the rune (character) position index immediately leading up to the error.
func (b *BadNumOctErr) RunePos() (pos uint64) { return b.posRune }
// String is an alias for the [BadNumOctErr.Error] method to conform explicitly to a [fmt.Stringer] interface.
func (b *BadNumOctErr) String() (s string) { return b.Error() }
+16
View File
@@ -0,0 +1,16 @@
package binarystr
import (
`fmt`
)
// Error conforms a [BadReadErr] to an [error].
func (b *BadReadErr) Error() (err string) {
err = fmt.Sprintf("only read %d bytes from internal read source before encountering a read error", b.numBytes)
return
}
// NumBytesRead returns the number of bytes read (BEFORE mutation/encoding/decoding) before the error was hit.
func (b *BadReadErr) NumBytesRead() (u64 uint64) { return b.numBytes }
+116
View File
@@ -0,0 +1,116 @@
package binarystr
import (
`errors`
`io`
`strings`
)
/*
Read conforms to an [io.Reader].
It returns plain data decoded from bitstring encoding in e's read source.
IMPORTANT: Read will return a [BadNumOctErr] if the amount of data in e's read source is not at least len(b) * 8!
ONE BYTE in b equals EIGHT BYTES from e's read source!
Likewise, n will ALWAYS be len(b), but the number of bytes actually read from e's internal reader
will ALWAYS be n*8!
In this case, b will still be populated with the available decoded data, and err ([BadNumOctErr])'s
[BadNumOctErr.Rem] method will contain the remaining bitstring (if there is any left over) after decoding
the octets that were returned (if any).
IMPORTANT: Read will return a [BadBinCharErr] if it encounters any character in e's read source that is not '0' or '1'!
THIS INCLUDES WHITESPACE due to the buffer size calculation (unlike [StrToBytes]); it is NOT skipped!
*/
func (e *Decoder) Read(b []byte) (n int, err error) {
var inN int
var s string
var endIdx int
var rem string
var buf []byte
defer func() {
if b != nil && buf != nil {
s = BytesToStr(buf)
copy(b, []byte(s))
}
}()
if len(b) == 0 {
return
}
buf = make([]byte, len(b)*8)
if inN, err = e.r.Read(buf); err != nil {
// This is caught/handled below.
if errors.Is(err, io.EOF) {
err = nil
} else {
return
}
}
if inN < len(buf) {
}
if inN != len(buf) {
err = &BadNumOctErr{
posByte: uint64(inN),
posRune: uint64(inN),
rem: rem, // We hit the end too "early" so there is no "remaining".
}
return
}
return
}
/*
ReadString returns a bitstring-encoded [string] from the entirety of the data
in the source reader of e.
IMPORTANT: If e's source reader is a non-terminating stream (e.g. a [math/rand.Rand]),
this function will NEVER RETURN (and will likely consume all memory).
YOU HAVE BEEN WARNED.
n is the number of bytes read from e's source reader.
*/
func (e *Decoder) ReadString() (s string, n uint64, err error) {
var inN int
var buf []byte
var rErr error
var sb *strings.Builder = new(strings.Builder)
defer func() {
if sb != nil {
s = sb.String()
}
}()
for {
buf = make([]byte, bufSz)
inN, rErr = e.r.Read(buf)
n += uint64(inN)
if inN != 0 {
if inN < bufSz {
buf = buf[:inN]
}
sb.WriteString(BytesToStr(buf))
} else {
if errors.Is(rErr, io.EOF) {
rErr = nil
}
break
}
if rErr != nil {
err = rErr
return
}
}
return
}
+105
View File
@@ -0,0 +1,105 @@
package binarystr
import (
`errors`
`io`
`strings`
)
/*
Read conforms to an [io.Reader].
It returns bitstring encoding of the data in e's read source.
(To conform to [io.Reader], this is returned as bytes - e.g. []byte(string()).)
IMPORTANT: Read will return [ErrBadNumOct] if len(b) % 8 != 0! EIGHT BYTES in b equal ONE BYTE
from e's read source! Keep this in mind and size b accordingly for your desired decoded data size!
Likewise, n will ALWAYS be len(b), but the number of bytes actually read from e's internal reader
will ALWAYS be n/8! (If for some bizarre case it isn't, err will be [BadReadErr].)
*/
func (e *Encoder) Read(b []byte) (n int, err error) {
var inN int
var s string
var buf []byte
defer func() {
if b != nil && buf != nil {
s = BytesToStr(buf)
copy(b, []byte(s))
}
}()
if len(b) == 0 {
return
}
if len(b)%8 != 0 {
err = ErrBadNumOct
return
}
buf = make([]byte, len(b)/8)
if inN, err = e.r.Read(buf); err != nil {
return
}
if inN != len(buf) {
if len(buf) > inN {
buf = buf[:inN]
}
err = &BadReadErr{
numBytes: uint64(inN),
}
return
}
return
}
/*
ReadString returns a bitstring-encoded [string] from the entirety of the data
in the source reader of e.
IMPORTANT: If e's source reader is a non-terminating stream (e.g. a [math/rand.Rand]),
this function will NEVER RETURN (and will likely consume all memory).
YOU HAVE BEEN WARNED.
n is the number of bytes read from e's source reader.
*/
func (e *Encoder) ReadString() (s string, n uint64, err error) {
var inN int
var buf []byte
var rErr error
var sb *strings.Builder = new(strings.Builder)
defer func() {
if sb != nil {
s = sb.String()
}
}()
for {
buf = make([]byte, bufSz)
inN, rErr = e.r.Read(buf)
n += uint64(inN)
if inN != 0 {
if inN < bufSz {
buf = buf[:inN]
}
sb.WriteString(BytesToStr(buf))
} else {
if errors.Is(rErr, io.EOF) {
rErr = nil
}
break
}
if rErr != nil {
err = rErr
return
}
}
return
}
+17
View File
@@ -0,0 +1,17 @@
package binarystr
import (
`io`
)
type (
Encoder struct {
r io.Reader
cur uint64
}
Decoder struct {
r io.Reader
cur uint64
}
)
+23 -12
View File
@@ -1,6 +1,6 @@
module r00t2.io/goutils module r00t2.io/goutils
go 1.26 go 1.27
require ( require (
github.com/Masterminds/sprig/v3 v3.3.0 github.com/Masterminds/sprig/v3 v3.3.0
@@ -8,10 +8,12 @@ require (
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/deckarep/golang-set/v2 v2.9.0 github.com/deckarep/golang-set/v2 v2.9.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/miekg/dns v1.1.73
github.com/olekukonko/tablewriter v1.1.4 github.com/olekukonko/tablewriter v1.1.4
github.com/shirou/gopsutil/v4 v4.26.6 github.com/shirou/gopsutil/v4 v4.26.9
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20260823151212-3075585bcbeb
golang.org/x/sys v0.47.0 golang.org/x/sys v0.49.0
r00t2.io/subnetter v0.3.1
r00t2.io/sysutils v1.16.5 r00t2.io/sysutils v1.16.5
) )
@@ -23,12 +25,19 @@ require (
github.com/clipperhouse/displaywidth v0.11.0 // indirect github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/djherbis/times v1.6.0 // indirect github.com/djherbis/times v1.6.0 // indirect
github.com/ebitengine/purego v0.10.2 // indirect github.com/ebitengine/purego v0.11.1 // indirect
github.com/fatih/color v1.19.0 // indirect github.com/fatih/color v1.19.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.15 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.2 // indirect
github.com/go-playground/universal-translator v0.18.2 // indirect
github.com/go-playground/validator/v10 v10.30.5 // indirect
github.com/go-resty/resty/v2 v2.17.2 // indirect
github.com/goccy/go-json v0.10.6 // indirect github.com/goccy/go-json v0.10.6 // indirect
github.com/huandu/xstrings v1.5.0 // indirect github.com/goccy/go-yaml v1.19.2 // indirect
github.com/lufia/plan9stats v0.0.0-20260627054121-477a66015f15 // indirect github.com/huandu/xstrings v1.6.2 // indirect
github.com/leodido/go-urn v1.5.0 // indirect
github.com/lufia/plan9stats v0.0.0-20260802145828-341c2f0c90b5 // indirect
github.com/mattn/go-colorable v0.1.15 // indirect github.com/mattn/go-colorable v0.1.15 // indirect
github.com/mattn/go-isatty v0.0.22 // indirect github.com/mattn/go-isatty v0.0.22 // indirect
github.com/mattn/go-runewidth v0.0.24 // indirect github.com/mattn/go-runewidth v0.0.24 // indirect
@@ -37,13 +46,15 @@ require (
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
github.com/olekukonko/errors v1.3.0 // indirect github.com/olekukonko/errors v1.3.0 // indirect
github.com/olekukonko/ll v0.1.8 // indirect github.com/olekukonko/ll v0.1.8 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/power-devops/perfstat v0.0.0-20260916203055-22a1a467d9f0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect github.com/shopspring/decimal v1.5.0 // indirect
github.com/spf13/cast v1.10.0 // indirect github.com/spf13/cast v1.10.0 // indirect
github.com/tklauser/go-sysconf v0.4.0 // indirect github.com/tklauser/go-sysconf v0.4.0 // indirect
github.com/tklauser/numcpus v0.12.0 // indirect github.com/tklauser/numcpus v0.12.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.mongodb.org/mongo-driver v1.17.9 // indirect go.mongodb.org/mongo-driver v1.17.10 // indirect
golang.org/x/crypto v0.54.0 // indirect golang.org/x/crypto v0.58.0 // indirect
golang.org/x/sync v0.22.0 // indirect golang.org/x/net v0.61.0 // indirect
golang.org/x/sync v0.24.0 // indirect
golang.org/x/text v0.43.0 // indirect
) )
+45
View File
@@ -30,25 +30,43 @@ github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/
github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/ebitengine/purego v0.10.2 h1:W809HbnvzAxgdm+aOvlSekrM16wGCdT/e76+9tS7gzE= github.com/ebitengine/purego v0.10.2 h1:W809HbnvzAxgdm+aOvlSekrM16wGCdT/e76+9tS7gzE=
github.com/ebitengine/purego v0.10.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/ebitengine/purego v0.10.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/ebitengine/purego v0.11.1 h1:2zpWRSQNVKN4eKsKO9eM1ILDgWfYMY9GwqRmK6XeQ/0=
github.com/ebitengine/purego v0.11.1/go.mod h1:DCHPP08djqhNSoTfImcnHYQRZmd0qhakvrozqaEYhGQ=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI=
github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-playground/locales v0.14.2 h1:d8UmcrM6Nip0hfGZKLGpAvZH37XB4TS0xzK9B56YNCY=
github.com/go-playground/locales v0.14.2/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.2 h1:LCsMLC9RzmbUMNUPVYD15dmcjwYAJhmX8mPZRW4rAVU=
github.com/go-playground/universal-translator v0.18.2/go.mod h1:67VZIMp5lQpDWlnStOct22q1bkdJGJqHghbOtmkawxk=
github.com/go-playground/validator/v10 v10.30.5 h1:YyCXvVShZbs2Sm3Mb53eNOlhRXctSOzW5QJAouCTZL4=
github.com/go-playground/validator/v10 v10.30.5/go.mod h1:wEqiaov48pXX1kjhc3Da8y0M0Dtg/BK7gurFBLgwFrQ=
github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk=
github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.6.2 h1:+X5X6N46b40cmDw7FFJFU6Eoq0yJS8lbYigT2EFau4c=
github.com/huandu/xstrings v1.6.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.5.0 h1:pLqT2kq1zpHW/1D18QMjMpdtX7cekxqtJJjg5ANyWw0=
github.com/leodido/go-urn v1.5.0/go.mod h1:9BORnCDhdPBJNDEX+w1bJisa8yOKYi116VeO96s4ifE=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k= github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM= github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM=
@@ -57,12 +75,16 @@ github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e h1:Q6MvJtQK/iRcRt
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/lufia/plan9stats v0.0.0-20260627054121-477a66015f15 h1:YkjVPl/YH5XlJ+/NiwzJtPYXXKRcyjmEUhsDci6YK3c= github.com/lufia/plan9stats v0.0.0-20260627054121-477a66015f15 h1:YkjVPl/YH5XlJ+/NiwzJtPYXXKRcyjmEUhsDci6YK3c=
github.com/lufia/plan9stats v0.0.0-20260627054121-477a66015f15/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lufia/plan9stats v0.0.0-20260627054121-477a66015f15/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/lufia/plan9stats v0.0.0-20260802145828-341c2f0c90b5 h1:eveIIGn4BGM3qknO74omf6HYr30/exH+eVUTuAgwjZ0=
github.com/lufia/plan9stats v0.0.0-20260802145828-341c2f0c90b5/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU= github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/miekg/dns v1.1.73 h1:uhT8nJxmTrPJYClxVxTCX+CVn6qnzSiybRk72Z6DgrE=
github.com/miekg/dns v1.1.73/go.mod h1:RW2Obtfd5NZHvOFe3zYG0W8koWOQtAzyHaLo8vASBuQ=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
@@ -79,6 +101,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20260916203055-22a1a467d9f0 h1:XA01Vk/wv9YikCi1V51yRzIHPMT5of9+cMpZoZvDn/M=
github.com/power-devops/perfstat v0.0.0-20260916203055-22a1a467d9f0/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/shirou/gopsutil/v4 v4.25.12 h1:e7PvW/0RmJ8p8vPGJH4jvNkOyLmbkXgXW4m6ZPic6CY= github.com/shirou/gopsutil/v4 v4.25.12 h1:e7PvW/0RmJ8p8vPGJH4jvNkOyLmbkXgXW4m6ZPic6CY=
@@ -89,12 +113,17 @@ github.com/shirou/gopsutil/v4 v4.26.5 h1:RPcBXkpz7kOj9PqGFQOlBPZHsyaPvPVQc098y9R
github.com/shirou/gopsutil/v4 v4.26.5/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= github.com/shirou/gopsutil/v4 v4.26.5/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/shirou/gopsutil/v4 v4.26.6 h1:Mzr/npDtQC/xpeEuQKHZt8Zo9CmPvhTj8nkR8w5TLDs= github.com/shirou/gopsutil/v4 v4.26.6 h1:Mzr/npDtQC/xpeEuQKHZt8Zo9CmPvhTj8nkR8w5TLDs=
github.com/shirou/gopsutil/v4 v4.26.6/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= github.com/shirou/gopsutil/v4 v4.26.6/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/shirou/gopsutil/v4 v4.26.9 h1:CaBo/hBFqvlJoLvAQEKdHdf5GD97/MN2ACfyv63+2Ig=
github.com/shirou/gopsutil/v4 v4.26.9/go.mod h1:nKH+8wX2zxr/mDbsR+AAy6Qd2z4ZOQ7wfQaEp+yrYmg=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/shopspring/decimal v1.5.0 h1:HxSWk31RmJv0EyY4oLiGQB9u7WPludUwPBdvQZnYFPc=
github.com/shopspring/decimal v1.5.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU= github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU=
@@ -107,8 +136,12 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU= go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=
go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ= go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
go.mongodb.org/mongo-driver v1.17.10 h1:kdAgQvu8TROXZpSkJQd5wzfaNCCrMbpZyKFtQ6qkPCE=
go.mongodb.org/mongo-driver v1.17.10/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
go4.org/netipx v0.0.0-20260823151212-3075585bcbeb h1:XBM4hvfwGAttkkiTIFfeigdfcL1xIfdKXqFdgiHGtDs=
go4.org/netipx v0.0.0-20260823151212-3075585bcbeb/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
@@ -117,12 +150,18 @@ golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54=
golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w=
golang.org/x/net v0.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw=
golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA=
golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -135,8 +174,14 @@ golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA=
golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc=
golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s=
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/subnetter v0.3.1 h1:I/Uoit7Id+QaFK2W2GwUdkr6WopcagFWxCur1gUnTdc=
r00t2.io/subnetter v0.3.1/go.mod h1:nyOpA8S1IU91gcaP8DMaq/VBUHJ28luGp+ulQFsv6YM=
r00t2.io/sysutils v1.16.2 h1:wI01UwZ/bXn/lzBiCpqDmzZCOWiK87kz04SB4xRw+W0= r00t2.io/sysutils v1.16.2 h1:wI01UwZ/bXn/lzBiCpqDmzZCOWiK87kz04SB4xRw+W0=
r00t2.io/sysutils v1.16.2/go.mod h1:iXK+ALOwIdRKjAJIE5USlkZ669SVDHBNNuYhunsznH8= r00t2.io/sysutils v1.16.2/go.mod h1:iXK+ALOwIdRKjAJIE5USlkZ669SVDHBNNuYhunsznH8=
r00t2.io/sysutils v1.16.5 h1:0v/HWTLOl8UN8B6koXM3e+TWN1N3eAoUM+xGK+itwq0= r00t2.io/sysutils v1.16.5 h1:0v/HWTLOl8UN8B6koXM3e+TWN1N3eAoUM+xGK+itwq0=
+84
View File
@@ -1,5 +1,11 @@
package mapsx package mapsx
import (
"cmp"
"iter"
"slices"
)
/* /*
Get mimics Python's [dict.get()] behavior, returning value `v` if key `k` Get mimics Python's [dict.get()] behavior, returning value `v` if key `k`
is not found in map `m`. is not found in map `m`.
@@ -34,6 +40,7 @@ if `m` is non-nil but empty.
The ordering of `mapKeys` is unpredictable as maps are unsorted The ordering of `mapKeys` is unpredictable as maps are unsorted
and no explicit sorting is performed. and no explicit sorting is performed.
See [SortedKeys] and [ListSortedKeys] if sorting is desired.
*/ */
func ListKeys[Map ~map[K]V, K comparable, V any](m Map) (mapKeys []K) { func ListKeys[Map ~map[K]V, K comparable, V any](m Map) (mapKeys []K) {
@@ -51,6 +58,36 @@ func ListKeys[Map ~map[K]V, K comparable, V any](m Map) (mapKeys []K) {
return return
} }
/*
ListSortedKeys is like [ListKeys] but returns the keys in a sorted slice.
The keys in map `m` MUST conform to a [cmp.Ordered]!
*/
func ListSortedKeys[Map ~map[K]V, K cmp.Ordered, V any](m Map) (mapKeys []K) {
// All cmp.Ordered types are comparable, but not all comparable types are cmp.Ordered.
mapKeys = ListKeys(m)
slices.Sort(mapKeys)
return
}
/*
ListSortedValues is like [ListValues] but returns the values in a sorted slice.
The values in map `m` MUST conform to a [cmp.Ordered]!
*/
func ListSortedValues[Map ~map[K]V, K comparable, V cmp.Ordered](m Map) (mapVals []V) {
// All cmp.Ordered types are comparable, but not all comparable types are cmp.Ordered.
mapVals = ListValues(m)
slices.Sort(mapVals)
return
}
/* /*
ListValues returns map `m`'s values as a slice (unlike [maps.Values], ListValues returns map `m`'s values as a slice (unlike [maps.Values],
which returns an iterator via [iter.Seq]). which returns an iterator via [iter.Seq]).
@@ -60,6 +97,7 @@ if `m` is non-nil but empty.
The ordering of `mapVals` is unpredictable as maps are unsorted The ordering of `mapVals` is unpredictable as maps are unsorted
and no explicit sorting is performed. and no explicit sorting is performed.
See [SortedValues] and [ListSortedValues] if sorting is desired.
*/ */
func ListValues[Map ~map[K]V, K comparable, V any](m Map) (mapVals []V) { func ListValues[Map ~map[K]V, K comparable, V any](m Map) (mapVals []V) {
@@ -77,6 +115,52 @@ func ListValues[Map ~map[K]V, K comparable, V any](m Map) (mapVals []V) {
return return
} }
/*
SortedKeys is like [maps.Keys] but will sort the keys in the returned [iter.Seq].
(There is a small cost to this.)
The keys in map `m` MUST conform to a [cmp.Ordered]!
*/
func SortedKeys[Map ~map[K]V, K cmp.Ordered, V any](m Map) (mapKeys iter.Seq[K]) {
var k K
// All cmp.Ordered types are comparable, but not all comparable types are cmp.Ordered.
var keys []K = ListKeys(m)
slices.Sort(keys)
return func(yield func(K) bool) {
for _, k = range keys {
if !yield(k) {
return
}
}
}
}
/*
SortedValues is like [maps.Values] but will sort the values in the returned [iter.Seq].
(There is a small cost to this.)
The values in map `m` MUST conform to a [cmp.Ordered]!
*/
func SortedValues[Map ~map[K]V, K comparable, V cmp.Ordered](m Map) (mapVals iter.Seq[V]) {
var v V
// All cmp.Ordered types are comparable, but not all comparable types are cmp.Ordered.
var vals []V = ListValues(m)
slices.Sort(vals)
return func(yield func(V) bool) {
for _, v = range vals {
if !yield(v) {
return
}
}
}
}
/* /*
Must, unlike [Get] or [GetOk], requires that `k` be in map `m`. Must, unlike [Get] or [GetOk], requires that `k` be in map `m`.
+7
View File
@@ -0,0 +1,7 @@
package ptr
const (
TLD string = "arpa"
Zone4 string = "in-addr." + TLD
Zone6 string = "ip6." + TLD
)
+5
View File
@@ -0,0 +1,5 @@
/*
Package ptr includes some helper functions for working with PTR records/zones
and mapping/converting them to/from [net/netip.Addr] and [net/netip.Prefix] representations respectively.
*/
package ptr
+11
View File
@@ -0,0 +1,11 @@
package ptr
import (
"errors"
)
var (
ErrInvalidBounds error = errors.New("invalid PTR boundary")
ErrInvalidPfx error = errors.New("invalid prefix")
ErrInvalidPtr error = errors.New("invalid PTR")
)
+266
View File
@@ -0,0 +1,266 @@
package ptr
import (
"encoding/hex"
"fmt"
"net"
"net/netip"
"slices"
"strconv"
"strings"
"github.com/miekg/dns"
"go4.org/netipx"
"r00t2.io/subnetter/netsplit"
)
// AddrPtr returns the PTR lookup name of IP address ip (as a [net/netip.Addr]), normalized (lowercased and no trailing dot).
func AddrPtr(ip netip.Addr) (ptr string, err error) { return IpStrPtr(ip.String()) }
// IpPtr returns the PTR lookup name of IP address ip (as a [net.IP]), normalized (lowercased and no trailing dot).
func IpPtr(ip net.IP) (ptr string, err error) { return IpStrPtr(ip.String()) }
// IpStrPtr returns the PTR lookup name of IP address ip (as a string), normalized (lowercased and no trailing dot).
func IpStrPtr(ip string) (ptr string, err error) {
if ptr, err = dns.ReverseAddr(ip); err != nil {
return
}
ptr = normalizePtr(ptr)
return
}
/*
PfxToPtr returns the PTR zone name(s) for a specified prefix.
Any host bits set in pfx will be masked off without error
(e.g. 192.0.2.20/24 -> 192.0.2.0/24).
err will be [ErrInvalidBounds] if pfx prefix length is longer than a /24 (e.g. a /32) for IPv4
or longer than the last/shorter than the first 4-bit/nibble/nybble boundary - /124 (e.g. a /128)
and /4 respectively - for IPv6.
*/
func PfxToPtr(pfx netip.Prefix) (ptrZones []string, err error) {
var idx int
var tgtSz uint8
var n *net.IPNet
var maxBitLen int
var pfxs []*netip.Prefix
var c *netsplit.CIDRSplitter
if !pfx.IsValid() {
err = ErrInvalidPfx
return
}
pfx = pfx.Masked()
if pfx.Addr().Is4() {
maxBitLen = 24
if pfx.Bits() <= 24 {
tgtSz = 24
}
if pfx.Bits() <= 16 {
tgtSz = 16
}
if pfx.Bits() <= 8 {
tgtSz = 8
}
} else {
maxBitLen = 124
if pfx.Bits() < 4 {
err = ErrInvalidBounds
return
}
tgtSz = uint8(pfx.Bits())
for tgtSz%4 != 0 {
tgtSz++
}
}
if pfx.Bits() > maxBitLen {
err = ErrInvalidBounds
return
}
c = &netsplit.CIDRSplitter{
PrefixLength: tgtSz,
BaseSplitter: new(netsplit.BaseSplitter),
}
n = netipx.PrefixIPNet(pfx)
c.BaseSplitter.SetParent(*n)
if pfxs, _, err = c.Split(); err != nil {
return
}
ptrZones = make([]string, len(pfxs))
for idx = range pfxs {
ptrZones[idx] = pfxZonePtr(*pfxs[idx])
}
return
}
// PtrToAddr returns a [net/netip.Addr] from a PTR record name.
func PtrToAddr(ptr string) (addr netip.Addr, err error) {
var pfx netip.Prefix
if pfx, err = PtrToPfx(ptr); err != nil {
return
}
addr = pfx.Addr()
return
}
/*
PtrToPfx returns a [net/netip.Prefix] from a PTR zone (or record).
The same boundary rules as [PfxToPtr] are in place; delegated subzones (RFC 2317) are not supported.
If an invalid PTR is received, err will be [ErrInvalidPtr].
*/
func PtrToPfx(ptr string) (pfx netip.Prefix, err error) {
var idx int
var ok bool
var b []byte
var sfx string
var cidr uint8
var spl []string
var addr netip.Addr
ptr = normalizePtr(ptr)
spl = strings.Split(ptr, ".")
if len(spl) < 2 {
err = ErrInvalidPtr
return
}
sfx = strings.Join(spl[len(spl)-2:], ".")
spl = strings.Split(normalizePtr(strings.TrimSuffix(ptr, sfx)), ".")
slices.Reverse(spl)
switch sfx {
case Zone4:
switch len(spl) {
case 0:
cidr = 0
spl = []string{"0", "0", "0", "0"}
case 1, 2, 3, 4:
cidr = uint8(len(spl) * 8)
for len(spl) < 4 {
spl = append(spl, "0")
}
default:
err = ErrInvalidPtr
return
}
if addr, err = netip.ParseAddr(
strings.Join(spl, "."),
); err != nil {
return
}
case Zone6:
if len(spl) > 32 {
err = ErrInvalidPtr
return
}
if len(spl) == 0 {
cidr = 0
spl = make([]string, 32)
for idx = 0; idx < len(spl); idx++ {
spl[idx] = "0"
}
} else {
cidr = uint8(len(spl) * 4)
for len(spl) < 32 {
spl = append(spl, "0")
}
}
if b, err = hex.DecodeString(
strings.ToLower(strings.Join(spl, "")),
); err != nil {
return
}
if addr, ok = netip.AddrFromSlice(b); !ok {
err = ErrInvalidPfx
return
}
default:
err = ErrInvalidPtr
return
}
if pfx, err = addr.Prefix(int(cidr)); err != nil {
return
}
return
}
func normalizePtr(ptr string) (norm string) { return strings.ToLower(strings.TrimRight(ptr, ".")) }
func pfxZonePtr(pfx netip.Prefix) (ptr string) {
if pfx.Addr().Is4() {
ptr = pfxZonePtr4(pfx)
} else {
ptr = pfxZonePtr6(pfx)
}
return
}
func pfxZonePtr4(pfx netip.Prefix) (ptr string) {
var idx int
var b []byte
var s []string
idx = pfx.Bits() / 8
b = pfx.Addr().AsSlice()[0:idx]
s = make([]string, len(b))
for i := 0; i < idx; i++ {
s[idx-1-i] = strconv.Itoa(int(uint8(b[i])))
}
ptr = strings.Join(s, ".") + "." + Zone4
return
}
func pfxZonePtr6(pfx netip.Prefix) (ptr string) {
var b byte
var nib byte
var numNibs int
var b16 [16]byte
var sb *strings.Builder = new(strings.Builder)
numNibs = pfx.Bits() / 4
b16 = pfx.Addr().As16()
for i := numNibs - 1; i >= 0; i-- {
b = b16[i/2]
if i%2 == 0 {
nib = b >> 4
} else {
nib = b & 0x0f
}
fmt.Fprintf(
sb,
"%x.",
nib,
)
}
sb.WriteString(Zone6)
ptr = sb.String()
return
}
+1 -1
View File
@@ -636,7 +636,7 @@ pre.rouge .gs {
<div class="details"> <div class="details">
<span id="author" class="author">Brent Saner</span><br> <span id="author" class="author">Brent Saner</span><br>
<span id="email" class="email"><a href="mailto:bts@square-r00t.net">bts@square-r00t.net</a></span><br> <span id="email" class="email"><a href="mailto:bts@square-r00t.net">bts@square-r00t.net</a></span><br>
<span id="revdate">Last rendered 2026-09-04 04:15:33 -0400</span> <span id="revdate">Last rendered 2026-10-10 01:32:29 -0400</span>
</div> </div>
<div id="toc" class="toc2"> <div id="toc" class="toc2">
<div id="toctitle">Table of Contents</div> <div id="toctitle">Table of Contents</div>
+4 -2
View File
@@ -6,7 +6,7 @@
<span id="author" class="author">Brent Saner</span>\ <span id="author" class="author">Brent Saner</span>\
<span id="email" class="email"><bts@square-r00t.net></span>\ <span id="email" class="email"><bts@square-r00t.net></span>\
<span id="revdate">Last rendered 2026-09-04 04:15:38 -0400</span> <span id="revdate">Last rendered 2026-10-10 01:32:33 -0400</span>
</div> </div>
@@ -1019,7 +1019,9 @@ This function fills in the gap that <a href="https://pkg.go.dev/text/template#Is
<div class="content"> <div class="content">
\[id="fn_net_all"\] === Networking These template functions use capabilities from:    \[id="fn_net_all"\]\
=== Networking\
These template functions use capabilities from:
</div> </div>