Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ec35990a9
|
||
|
|
67a5c8a3fb
|
||
|
|
cd9b967306
|
||
|
|
f002766c74
|
||
|
|
67bd30907e
|
||
|
|
4e7d474fb3
|
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
I need to test this still; I can't recall if it's complete or not
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -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() }
|
||||||
@@ -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() }
|
||||||
@@ -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 }
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package binarystr
|
||||||
|
|
||||||
|
import (
|
||||||
|
`io`
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Encoder struct {
|
||||||
|
r io.Reader
|
||||||
|
cur uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
Decoder struct {
|
||||||
|
r io.Reader
|
||||||
|
cur uint64
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -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
|
||||||
)
|
)
|
||||||
@@ -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=
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/*
|
||||||
|
Package fsx provides some enhancements to [io/fs].
|
||||||
|
*/
|
||||||
|
package fsx
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package fsx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/types"
|
||||||
|
"io/fs"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: FilterFsPaths
|
||||||
|
|
||||||
|
/*
|
||||||
|
FsPaths returns all paths in [io/fs.FS] `fsys`.
|
||||||
|
|
||||||
|
If `fsys` is nil, both `fsPaths` and `err` will be nil.
|
||||||
|
|
||||||
|
`t` should be an OR'd [io/fs.FileMode] containing all filetypes that should be included.
|
||||||
|
Use [io/fs.ModeType] to match any non-regular-file object, or 0 to explicitly match regular files.
|
||||||
|
If `t` is non-zero but you wish to ALSO include regular files, then specify `inclFiles` as true.
|
||||||
|
(`inclFiles` is ignored if `t` == 0, as this inherently matches regular files only/explicitly.)
|
||||||
|
See [HasType] for more information on this filtering.
|
||||||
|
|
||||||
|
`maxDepth` can be used to limit recursion depth.
|
||||||
|
|
||||||
|
- If `maxDepth` == 0, only direct [io/fs.DirEntry] descendants of `fsys`
|
||||||
|
will be included in `fsPaths`.
|
||||||
|
- If `maxDepth` < 0, ALL [io/fs.DirEntry] descendants of `fsys`
|
||||||
|
will be included in `fsPaths`.
|
||||||
|
- If `maxDepth` > 0, then only children at a depth of this level or lower
|
||||||
|
("higher" in a hierarchy) will be included in `fsPaths`.
|
||||||
|
*/
|
||||||
|
func FsPaths(fsys fs.FS, t fs.FileMode, inclFiles bool, maxDepth int) (fsPaths map[string]fs.DirEntry, err error) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
var entries []fs.DirEntry
|
||||||
|
var t fs.FileMode = types.Type()
|
||||||
|
|
||||||
|
if fsys == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if fsPaths, err = getEntries(fsys, t, inclFiles, maxDepth, "."); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
HasType is just a convenience wrapper for:
|
||||||
|
|
||||||
|
hasType = m & t != 0
|
||||||
|
|
||||||
|
The type(s) will be explicitly extracted from `m` and `t` first.
|
||||||
|
|
||||||
|
Note that if `t` is an OR'd set of filetypes, then HasType will return true if ANY of the types
|
||||||
|
in `t` match `m`'s type (see [r00t2.io/goutils/bitmask] for more information on bitmasking).
|
||||||
|
If you wish to check if `m` is A SINGLE type, either ensure that `t` is not OR'd
|
||||||
|
(i.e. is a single fs.FileMode type) or use [IsType] instead.
|
||||||
|
*/
|
||||||
|
func HasType(m, t fs.FileMode) (hasType bool) { return m.Type()&t.Type() != 0 }
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsType is just a convenience wrapper for:
|
||||||
|
|
||||||
|
isType = m & t == t
|
||||||
|
|
||||||
|
The type(s) will be explicitly extracted from `m` and `t` first.
|
||||||
|
|
||||||
|
Note that if `t` is an OR'd set of filetypes, then IsType will *never* return true
|
||||||
|
as its type is single-valued/not bitmasked (see [r00t2.io/goutils/bitmask] for more information
|
||||||
|
on bitmasking).
|
||||||
|
If you wish to check if `m` is ONE OF multiple types in `t`, use [HasType] instead.
|
||||||
|
*/
|
||||||
|
func IsType(m, t fs.FileMode) (isType bool) { return m&t == t }
|
||||||
|
|
||||||
|
/*
|
||||||
|
getEntries is used to recursively fetch entries from `fsys`.
|
||||||
|
|
||||||
|
It mostly serves as the recursion for [FsPaths], and the arguments
|
||||||
|
retain their exact meaning except that it requires `relPath`
|
||||||
|
and `maxDepth` (if > 0) will be decremanted in each recursion.
|
||||||
|
*/
|
||||||
|
func getEntries(fsys fs.FS, t fs.FileMode, inclFiles bool, maxDepth int, relPath string) (fsPaths map[string]fs.DirEntry, err error) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
var p string
|
||||||
|
var k string
|
||||||
|
var v fs.DirEntry
|
||||||
|
var e []fs.DirEntry
|
||||||
|
var subs map[string]fs.DirEntry = make(map[string]fs.DirEntry)
|
||||||
|
|
||||||
|
if e, err = fs.ReadDir(fsys, relPath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fsPaths = make(map[string]fs.DirEntry)
|
||||||
|
|
||||||
|
for idx = range e {
|
||||||
|
p = filepath.Join(relPath, e[idx].Name())
|
||||||
|
if e[idx].IsDir() {
|
||||||
|
if subs, err = getEntries(fsys, t, inclFiles, maxDepth, p); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for k, v = range subs {
|
||||||
|
fsPaths[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t != 0 {
|
||||||
|
if HasType(t, e[idx].Type()) {
|
||||||
|
fsPaths[p] = e[idx]
|
||||||
|
}
|
||||||
|
} else if inclFiles && e[idx].Type() == 0 {
|
||||||
|
fsPaths[p] = e[idx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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`.
|
||||||
|
|
||||||
|
|||||||
+56
-40
@@ -1,16 +1,16 @@
|
|||||||
package dnsx
|
package dnsx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`bytes`
|
"bytes"
|
||||||
`encoding/base32`
|
"encoding/base32"
|
||||||
`fmt`
|
"fmt"
|
||||||
`math`
|
"math"
|
||||||
`net`
|
"net"
|
||||||
`net/netip`
|
"net/netip"
|
||||||
`strings`
|
"strings"
|
||||||
|
|
||||||
`go4.org/netipx`
|
"go4.org/netipx"
|
||||||
`r00t2.io/goutils/stringsx`
|
"r00t2.io/goutils/stringsx"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -238,19 +238,19 @@ TXT records, etc. - not A/AAAA/CNAME, etc. - see RFC 8553 for details.
|
|||||||
See the following functions for allowing additional syntax/rule validation
|
See the following functions for allowing additional syntax/rule validation
|
||||||
that have record-type-specific accommodations made:
|
that have record-type-specific accommodations made:
|
||||||
|
|
||||||
* [IsFqdnDefinedTxt]
|
- [IsFqdnDefinedTxt]
|
||||||
* [IsFqdnNsec3]
|
- [IsFqdnNsec3]
|
||||||
* [IsFqdnSrv]
|
- [IsFqdnSrv]
|
||||||
* [IsFqdnWildcard]
|
- [IsFqdnWildcard]
|
||||||
|
|
||||||
# RFC Coverage
|
# RFC Coverage
|
||||||
|
|
||||||
This function should conform properly to:
|
This function should conform properly to:
|
||||||
|
|
||||||
* RFC 952
|
- RFC 952
|
||||||
* RFC 1034 and RFC 1035
|
- RFC 1034 and RFC 1035
|
||||||
* RFC 1123
|
- RFC 1123
|
||||||
* RFC 2181 (selectively, see above)
|
- RFC 2181 (selectively, see above)
|
||||||
|
|
||||||
preferring the most up-to-date rules where relevant (e.g. labels may start with digits, as per RFC 1123).
|
preferring the most up-to-date rules where relevant (e.g. labels may start with digits, as per RFC 1123).
|
||||||
It enforces/checks label and overall length limits as defined by RFC.
|
It enforces/checks label and overall length limits as defined by RFC.
|
||||||
@@ -265,12 +265,12 @@ the caller must perform translation first
|
|||||||
|
|
||||||
To reiterate, IDN/IDNA:
|
To reiterate, IDN/IDNA:
|
||||||
|
|
||||||
* RFC 3490
|
- RFC 3490
|
||||||
* RFC 5890
|
- RFC 5890
|
||||||
* RFC 5891
|
- RFC 5891
|
||||||
* RFC 5892
|
- RFC 5892
|
||||||
* RFC 5893
|
- RFC 5893
|
||||||
* RFC 5894
|
- RFC 5894
|
||||||
|
|
||||||
and Punycode (RFC 3492) *MUST* use their ASCII forms, NOT the localized/Unicode formats.
|
and Punycode (RFC 3492) *MUST* use their ASCII forms, NOT the localized/Unicode formats.
|
||||||
|
|
||||||
@@ -299,18 +299,18 @@ func IsFqdn(s string) (fqdn bool) {
|
|||||||
IsFqdnDefinedTxt is like [IsFqdn] but explicitly *only* allows fully-qualified
|
IsFqdnDefinedTxt is like [IsFqdn] but explicitly *only* allows fully-qualified
|
||||||
RFC-defined TXT "subtypes":
|
RFC-defined TXT "subtypes":
|
||||||
|
|
||||||
* ACME DNS-01 (RFC 8555)
|
- ACME DNS-01 (RFC 8555)
|
||||||
* BIMI (RFC draft [bimi])
|
- BIMI (RFC draft [bimi])
|
||||||
* DKIM (RFC 6376, RFC 8301, RFC 8463)
|
- DKIM (RFC 6376, RFC 8301, RFC 8463)
|
||||||
* DKIM ATPS (RFC 6541)
|
- DKIM ATPS (RFC 6541)
|
||||||
* DMARC (RFC 7489, RFC 9091, RFC 9616)
|
- DMARC (RFC 7489, RFC 9091, RFC 9616)
|
||||||
* MTA-STS (RFC 8461)
|
- MTA-STS (RFC 8461)
|
||||||
* TLSRPT (RFC 8460)
|
- TLSRPT (RFC 8460)
|
||||||
|
|
||||||
Note that the following TXT "subtypes" do not have special formatting in labels/name,
|
Note that the following TXT "subtypes" do not have special formatting in labels/name,
|
||||||
and thus are not covered by this function:
|
and thus are not covered by this function:
|
||||||
|
|
||||||
* SPF (RFC 4408, RFC 7208)
|
- SPF (RFC 4408, RFC 7208)
|
||||||
|
|
||||||
[bimi]: https://datatracker.ietf.org/doc/html/draft-brand-indicators-for-message-identification
|
[bimi]: https://datatracker.ietf.org/doc/html/draft-brand-indicators-for-message-identification
|
||||||
*/
|
*/
|
||||||
@@ -359,15 +359,15 @@ can't be 100% confirmed with certainty - only basic checks can be done.
|
|||||||
|
|
||||||
NSEC3 can be found via:
|
NSEC3 can be found via:
|
||||||
|
|
||||||
* RFC 5155
|
- RFC 5155
|
||||||
* RFC 6840
|
- RFC 6840
|
||||||
* RFC 6944
|
- RFC 6944
|
||||||
* RFC 7129
|
- RFC 7129
|
||||||
* RFC 8198
|
- RFC 8198
|
||||||
* RFC 9077
|
- RFC 9077
|
||||||
* RFC 9157
|
- RFC 9157
|
||||||
* RFC 9276
|
- RFC 9276
|
||||||
* RFC 9905
|
- RFC 9905
|
||||||
|
|
||||||
At the time of writing, only one hashing algorithm (SHA-1) has been specified.
|
At the time of writing, only one hashing algorithm (SHA-1) has been specified.
|
||||||
However, because this function does not check against the IANA registration at runtime,
|
However, because this function does not check against the IANA registration at runtime,
|
||||||
@@ -524,6 +524,22 @@ func IsPtr(s string) (isPtr bool, addr net.IP) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsPtrAddr is like IsPtr but only returns the `addr` value.
|
||||||
|
func IsPtrAddr(s string) (addr net.IP) {
|
||||||
|
|
||||||
|
_, addr = IsPtr(s)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPtrChk is like IsPtr but only returns the `isPtr` value.
|
||||||
|
func IsPtrChk(s string) (isPtr bool) {
|
||||||
|
|
||||||
|
isPtr, _ = IsPtr(s)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// commonFqdn is used to validate some rules common to all record names.
|
// commonFqdn is used to validate some rules common to all record names.
|
||||||
func commonFqdn(s string) (isOk bool) {
|
func commonFqdn(s string) (isOk bool) {
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package ptr
|
||||||
|
|
||||||
|
const (
|
||||||
|
TLD string = "arpa"
|
||||||
|
Zone4 string = "in-addr." + TLD
|
||||||
|
Zone6 string = "ip6." + TLD
|
||||||
|
)
|
||||||
@@ -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
|
||||||
@@ -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")
|
||||||
|
)
|
||||||
@@ -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,4 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
Package slicesx aims to extend functionality of the stdlib [slices] module.
|
Package slicesx aims to extend functionality of the stdlib [slices] module.
|
||||||
|
|
||||||
|
Most are convenience functions that aid in readability (sometimes at a VERY negligible performance hit),
|
||||||
|
but others can be quite useful (e.g. [Remove], [RemoveAll], [RemoveReverse]) that will save on significant boilerplate.
|
||||||
*/
|
*/
|
||||||
package slicesx
|
package slicesx
|
||||||
+146
-1
@@ -1,10 +1,51 @@
|
|||||||
package slicesx
|
package slicesx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
First returns the first item/element in slice s.
|
||||||
|
|
||||||
|
e will be the nil/zero value of the slice element type
|
||||||
|
if the slice length is 0 (including if s == nil).
|
||||||
|
*/
|
||||||
|
func First[S ~[]E, E any](s S) (e E) {
|
||||||
|
|
||||||
|
if len(s) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e = s[0]
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FirstIndex returns the index of the first element in slice s.
|
||||||
|
|
||||||
|
While at first glance this may seem useless, it may have some usecases as instead of causing panics:
|
||||||
|
|
||||||
|
- If s is empty, idx will be -1.
|
||||||
|
- If s is nil, idx will be -2.
|
||||||
|
- Otherwise idx will ALWAYS be 0.
|
||||||
|
*/
|
||||||
|
func FirstIndex[S ~[]E, E any](s S) (idx int) {
|
||||||
|
|
||||||
|
if s == nil {
|
||||||
|
idx = -2
|
||||||
|
} else if len(s) == 0 {
|
||||||
|
idx = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Last returns the last item/element in slice s.
|
Last returns the last item/element in slice s.
|
||||||
|
|
||||||
e will be the nil/zero value of the slice element type
|
e will be the nil/zero value of the slice element type
|
||||||
if the slice length is 0.
|
if the slice length is 0 (including if s == nil).
|
||||||
*/
|
*/
|
||||||
func Last[S ~[]E, E any](s S) (e E) {
|
func Last[S ~[]E, E any](s S) (e E) {
|
||||||
|
|
||||||
@@ -41,3 +82,107 @@ func LastIndex[S ~[]E, E any](s S) (idx int) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove removes any values of value elem from slice s up to max number of elems.
|
||||||
|
|
||||||
|
If maxRemove is negative (< 0), start from the end instead.
|
||||||
|
This simply wraps [RemoveReverse]:
|
||||||
|
|
||||||
|
removed = slicesx.RemoveReverse(s, elem, uint(-maxRemove))
|
||||||
|
|
||||||
|
If maxRemove is exactly 0, remove ALL instances of elem.
|
||||||
|
|
||||||
|
If s is nil, removed will be nil as well.
|
||||||
|
|
||||||
|
Note that s will remain untouched, and removed will be a new slice.
|
||||||
|
*/
|
||||||
|
func Remove[S ~[]E, E comparable](s S, elem E, maxRemove int) (removed S) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
|
||||||
|
if s == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(s) == 0 {
|
||||||
|
removed = []E{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
removed = make(S, 0, len(s))
|
||||||
|
defer func() {
|
||||||
|
if removed != nil {
|
||||||
|
removed = slices.Clip(removed)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if maxRemove < 0 {
|
||||||
|
removed = RemoveReverse(s, elem, uint(-maxRemove))
|
||||||
|
return
|
||||||
|
} else if maxRemove == 0 {
|
||||||
|
maxRemove = len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx = range s {
|
||||||
|
if s[idx] == elem && maxRemove > 0 {
|
||||||
|
maxRemove--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
removed = append(removed, s[idx])
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
RemoveAll is simply a convenience wrapper around [Remove], e.g.:
|
||||||
|
|
||||||
|
removed = slicesx.Remove(s, elem, 0)
|
||||||
|
*/
|
||||||
|
func RemoveAll[S ~[]E, E comparable](s S, elem E) (removed S) { return Remove(s, elem, 0) }
|
||||||
|
|
||||||
|
/*
|
||||||
|
RemoveReverse removes any values of value elem from slice s up to max number of elems in reverse order
|
||||||
|
(i.e. starting from the END of s).
|
||||||
|
|
||||||
|
If maxRemove is exactly 0, remove all instances of elem (via [RemoveAll]), i.e.:
|
||||||
|
|
||||||
|
removed = slicesx.RemoveAll(s, elem)
|
||||||
|
// or, more directly:
|
||||||
|
removed = slicesx.Remove(s, elem, 0)
|
||||||
|
|
||||||
|
If s is nil, removed will be nil as well.
|
||||||
|
|
||||||
|
Note that s will remain untouched, and removed will be a new slice.
|
||||||
|
*/
|
||||||
|
func RemoveReverse[S ~[]E, E comparable](s S, elem E, maxRemove uint) (removed S) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
|
||||||
|
if s == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(s) == 0 {
|
||||||
|
removed = []E{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxRemove == 0 {
|
||||||
|
removed = RemoveAll(s, elem)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
removed = make(S, 0, len(s))
|
||||||
|
|
||||||
|
for idx = len(s) - 1; idx >= 0; idx-- {
|
||||||
|
if s[idx] == elem && maxRemove > 0 {
|
||||||
|
maxRemove--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
removed = append(removed, s[idx])
|
||||||
|
}
|
||||||
|
removed = slices.Clip(removed)
|
||||||
|
slices.Reverse(removed)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -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-07-29 15:05:41 -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>
|
||||||
|
|||||||
+216
-216
File diff suppressed because it is too large.
Load diff
+37
-36
@@ -1,30 +1,30 @@
|
|||||||
package sprigx
|
package sprigx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`net`
|
"net"
|
||||||
`net/netip`
|
"net/netip"
|
||||||
`os`
|
"os"
|
||||||
`os/user`
|
"os/user"
|
||||||
`path`
|
"path"
|
||||||
`path/filepath`
|
"path/filepath"
|
||||||
`runtime`
|
"runtime"
|
||||||
`time`
|
"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"
|
||||||
`github.com/shirou/gopsutil/v4/disk`
|
"github.com/shirou/gopsutil/v4/disk"
|
||||||
`github.com/shirou/gopsutil/v4/host`
|
"github.com/shirou/gopsutil/v4/host"
|
||||||
`github.com/shirou/gopsutil/v4/load`
|
"github.com/shirou/gopsutil/v4/load"
|
||||||
`github.com/shirou/gopsutil/v4/mem`
|
"github.com/shirou/gopsutil/v4/mem"
|
||||||
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`
|
"go4.org/netipx"
|
||||||
`r00t2.io/goutils/netx`
|
"r00t2.io/goutils/netx"
|
||||||
`r00t2.io/goutils/netx/dnsx`
|
"r00t2.io/goutils/netx/dnsx"
|
||||||
`r00t2.io/goutils/stringsx`
|
"r00t2.io/goutils/stringsx"
|
||||||
`r00t2.io/goutils/timex`
|
"r00t2.io/goutils/timex"
|
||||||
`r00t2.io/sysutils`
|
"r00t2.io/sysutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -98,19 +98,20 @@ var (
|
|||||||
/*
|
/*
|
||||||
Networking (r00t.io/goutils/netx/dnsx)
|
Networking (r00t.io/goutils/netx/dnsx)
|
||||||
*/
|
*/
|
||||||
"dnsxPtrAddr": dnsx.AddrFromPtr,
|
"dnsxPtrAddr": dnsx.AddrFromPtr,
|
||||||
"dnsxAddrPtr": dnsx.AddrToPtr,
|
"dnsxAddrPtr": dnsx.AddrToPtr,
|
||||||
"dnsxStrWire": dnsx.DnsStrToWire,
|
"dnsxStrWire": dnsx.DnsStrToWire,
|
||||||
"dnsxWireStr": dnsx.DnsWireToStr,
|
"dnsxWireStr": dnsx.DnsWireToStr,
|
||||||
"dnsxPtrIp": dnsx.IpFromPtr,
|
"dnsxPtrIp": dnsx.IpFromPtr,
|
||||||
"dnsxIpPtr": dnsx.IpToPtr,
|
"dnsxIpPtr": dnsx.IpToPtr,
|
||||||
"dnsxIsFqdn": dnsx.IsFqdn,
|
"dnsxIsFqdn": dnsx.IsFqdn,
|
||||||
"dnsxIsTxt": dnsx.IsFqdnDefinedTxt,
|
"dnsxIsTxt": dnsx.IsFqdnDefinedTxt,
|
||||||
"dnsxIsNsec3": dnsx.IsFqdnNsec3,
|
"dnsxIsNsec3": dnsx.IsFqdnNsec3,
|
||||||
"dnsxIsSrv": dnsx.IsFqdnSrv,
|
"dnsxIsSrv": dnsx.IsFqdnSrv,
|
||||||
"dnsxIsWild": dnsx.IsFqdnWildcard,
|
"dnsxIsWild": dnsx.IsFqdnWildcard,
|
||||||
"dnsxIsLbl": dnsx.IsLabel,
|
"dnsxIsLbl": dnsx.IsLabel,
|
||||||
"dnsxIsPtr": dnsx.IsPtr,
|
"dnsxIsPtrAddr": dnsx.IsPtrAddr,
|
||||||
|
"dnsxIsPtr": dnsx.IsPtrChk,
|
||||||
/*
|
/*
|
||||||
Numbers/Math
|
Numbers/Math
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in new issue
Block a user