Documentation
¶
Overview ¶
Package util provides small, generic helpers shared across gokit — the scoped foundation owner for capabilities too small to deserve their own package, never a dumping ground. Prefer the modern standard library (slices, maps, cmp) first and a dedicated owner (fs for filesystem, codec for formats) where one exists.
The helpers group by concern:
- Collections: Contains, Filter, Map, Unique, Chunk, Partition, GroupBy, IndexBy, Keys, Values, Coalesce, DeepMerge, and uniqueness checks.
- Pointers: Ptr and Deref.
- Strings: casing (ToSnakeCase, ToKebabCase, ToCamelCase), Truncate/TruncateEllipsis, glob matching (GlobMatch, NewGlob), and fuzzy lookup (Nearest, ResolveUnique).
- Sanitization: SanitizeString, SanitizeEnvValue, and IsSafeString boundary checks.
- Secrets: SecretString and SecretKeyMatcher for in-memory redaction, plus MaskSecret. Cryptography stays in the encryption and security packages.
- Environment: GetEnv, GetEnvOr, GetEnvBool, and typed GetEnvParsed.
- Hashing: ContentHasher and the Sha256Hex/HashHex/Sha256Reader helpers.
- Sizes and time: FormatBytes/ParseBytes, FormatDuration/ParseDuration, TimeIt, and the injectable Clock (with FakeClock for deterministic tests).
- Templates: ParseTemplate and ParseDynamicTemplate for placeholder substitution.
Index ¶
- Constants
- Variables
- func Chunk[T any](items []T, size int) [][]T
- func Coalesce[T comparable](values ...T) T
- func ConstantTimeEqual(left, right []byte) bool
- func Contains[T comparable](slice []T, val T) bool
- func CopyDir(src, dst string) error
- func CopyFile(src, dst string) error
- func DeepMerge(base, override map[string]any) map[string]any
- func Deref[T any](p *T) T
- func DirExists(path string) bool
- func ElapsedMillis(start, end uint64) uint64
- func EnsureDir(path string) error
- func EnsureUniqueBy[T any, K comparable](items []T, key func(T) K) error
- func FileExists(path string) bool
- func Filter[T any](slice []T, predicate func(T) bool) []T
- func FindDuplicatesBy[T any, K comparable](items []T, key func(T) K) []K
- func FormatBytes(bytes uint64) string
- func FormatDuration(d time.Duration) string
- func GetEnv(key string) (string, bool)
- func GetEnvBool(key string, fallback bool) bool
- func GetEnvNonEmpty(key string) (string, bool)
- func GetEnvOr(key, fallback string) string
- func GetEnvParsed[T any](key string, parse func(string) (T, error)) (T, bool)
- func GlobMatch(pattern, text string) bool
- func GroupBy[T any, K comparable](items []T, key func(T) K) map[K][]T
- func HasWildcard(pattern string) bool
- func HashHex(bytes []byte) string
- func IndexBy[T any, K comparable](items []T, key func(T) K) map[K]T
- func IsSafeString(s string) bool
- func Keys[K comparable, V any](m map[K]V) []K
- func Map[T, U any](slice []T, transform func(T) U) []U
- func MaskSecret(s string, visiblePrefix int) string
- func Nearest(input string, candidates []string) (string, bool)
- func NearestWithin(input string, candidates []string, maxDistance int) (string, bool)
- func ParseBytes(input string) (uint64, error)
- func ParseDuration(s string) (time.Duration, bool)
- func ParseSize(s string, defaultBytes int64) int64
- func Partition[T any](items []T, pred func(T) bool) (matched, rest []T)
- func Ptr[T any](v T) *T
- func ReadFileString(path string) (string, error)
- func RemoveAll(path string) error
- func ResolveUnique[T any](input string, candidates []T, keyOf func(T) string) (match T, found bool, err error)
- func SanitizeEnvValue(s string) string
- func SanitizeString(s string) string
- func Sha256Hex(bytes []byte) string
- func Sha256Reader(reader io.Reader) (string, error)
- func TimeIt[T any](fn func() T) (T, time.Duration)
- func ToCamelCase(s string) string
- func ToKebabCase(s string) string
- func ToSnakeCase(s string) string
- func Truncate(s string, maxBytes int) string
- func TruncateEllipsis(s string, maxBytes int) string
- func Unique[T comparable](slice []T) []T
- func Values[K comparable, V any](m map[K]V) []V
- func WriteFile(path string, data []byte) error
- type Ambiguity
- type Clock
- type ContentHasher
- type DuplicateKeyError
- type DynamicTemplate
- type FakeClock
- type Glob
- type Placeholder
- type SecretKeyMatcher
- type SecretString
- func (s SecretString) Equal(other SecretString) bool
- func (s SecretString) Expose() string
- func (s SecretString) GoString() string
- func (s SecretString) IsEmpty() bool
- func (s SecretString) Len() int
- func (s SecretString) MarshalJSON() ([]byte, error)
- func (s SecretString) String() string
- func (s *SecretString) UnmarshalJSON(data []byte) error
- type SystemClock
- type Template
- type TemplateError
- type TemplateErrorKind
- type TemplatePart
- type TemplatePartKind
Constants ¶
const DefaultSuggestionDistance = 2
DefaultSuggestionDistance is the default maximum edit distance for a suggestion to be offered. Two catches single transpositions, one insertion plus one deletion, and most realistic typos while rejecting unrelated tokens.
Variables ¶
var DefaultSecretKeyNames = []string{
"password",
"passwd",
"pwd",
"token",
"secret",
"credential",
"credentials",
"apikey",
"api_key",
"auth",
"authorization",
"auth_token",
"access_token",
"refresh_token",
"client_secret",
}
DefaultSecretKeyNames are the key names SecretKeyMatcher treats as secret-bearing by default. They cover the common credential and token spellings seen in config keys and CLI flags.
Functions ¶
func Chunk ¶
Chunk splits items into consecutive chunks of at most size elements. A size of zero or less returns nil.
func Coalesce ¶
func Coalesce[T comparable](values ...T) T
Coalesce returns the first non-zero value, or the zero value if all are zero.
func ConstantTimeEqual ¶
ConstantTimeEqual reports whether two byte slices are equal, comparing in constant time to avoid a timing side channel. Length is not secret: a length mismatch returns false quickly, so prefer fixed-length encodings for sensitive tokens.
func Contains ¶
func Contains[T comparable](slice []T, val T) bool
Contains checks if a slice contains a value.
func CopyDir ¶
CopyDir recursively copies a directory tree from src to dst. It preserves file permissions, modification times, and symlinks. dst must not be inside src; passing an overlapping dst returns an error.
func CopyFile ¶
CopyFile copies a single file from src to dst, preserving permissions and modification times. Both src and dst are resolved through symlinks (os.Stat / os.Open follow symlinks), so if src is a symlink its target's content is copied; if dst is an existing symlink the link target is overwritten. Use CopyDir to copy directory trees (symlinks inside are recreated as symlinks, not followed). Parent directories of dst are created as needed.
func DeepMerge ¶
DeepMerge recursively merges override into base, treating both as decoded JSON/YAML documents. When both values for a key are map[string]any they are merged recursively; otherwise the override value replaces the base. Neither input is mutated.
The map[string]any type is a deliberate, documented opaque-value exception to the no-any rule: the function operates on genuinely heterogeneous document trees whose leaf values cannot be given a closed type.
func Deref ¶
func Deref[T any](p *T) T
Deref returns the value pointed to by p, or the zero value if p is nil.
func ElapsedMillis ¶
ElapsedMillis returns the non-negative elapsed milliseconds between two monotonic millisecond readings, saturating at zero when end precedes start.
func EnsureUniqueBy ¶
func EnsureUniqueBy[T any, K comparable](items []T, key func(T) K) error
EnsureUniqueBy returns a *DuplicateKeyError for the first duplicated key, or nil when every item's key is unique.
func FileExists ¶
FileExists reports whether path exists and is a regular file.
func FindDuplicatesBy ¶
func FindDuplicatesBy[T any, K comparable](items []T, key func(T) K) []K
FindDuplicatesBy returns the keys that occur more than once, in first-seen order.
func FormatBytes ¶
FormatBytes renders a byte count using binary (1024-based) units, choosing the largest unit for which the value is at least one and trimming trailing zeros.
FormatBytes(1536) == "1.5 KiB" FormatBytes(1048576) == "1 MiB"
func FormatDuration ¶
FormatDuration renders d as a human-readable string, choosing the largest unit for which the value reads naturally: hours and minutes with two decimals, seconds with two decimals down to one second, then integer milliseconds, microseconds ("μs"), or nanoseconds.
FormatDuration(5 * time.Second) == "5.00s" FormatDuration(152 * time.Millisecond) == "152ms"
func GetEnvBool ¶
GetEnvBool reads a boolean environment variable, recognizing "true", "1", "yes", and "on" as true and "false", "0", "no", and "off" as false (case-insensitively, trimmed). It returns fallback when the variable is unset or unrecognized.
func GetEnvNonEmpty ¶
GetEnvNonEmpty reads a string environment variable, treating an empty value as unset.
func GetEnvParsed ¶
GetEnvParsed reads an environment variable and parses it with parse, returning (zero, false) when the variable is unset or parsing fails.
func GlobMatch ¶
GlobMatch reports whether pattern matches text, treating '*' and '?' as wildcards.
'*' matches any run of characters (including none) and '?' matches exactly one character; every other character matches itself. Matching is rune-oriented and case-sensitive, with no path or separator semantics, so it composes over identifiers, names, or topic segments. The two-pointer algorithm uses constant backtracking state (worst case O(len(pattern)*len(text)), never exponential).
func GroupBy ¶
func GroupBy[T any, K comparable](items []T, key func(T) K) map[K][]T
GroupBy groups items into slices keyed by the key function, preserving order.
func HasWildcard ¶
HasWildcard reports whether pattern contains any wildcard metacharacter ('*' or '?').
func IndexBy ¶
func IndexBy[T any, K comparable](items []T, key func(T) K) map[K]T
IndexBy indexes items by the key function. On duplicate keys the last item wins.
func IsSafeString ¶
IsSafeString checks whether s passes basic input validation. It is NOT a security boundary — use parameterized queries for SQL and proper encoding for HTML output. This only catches the most obvious injection patterns and is intended as an additional defense-in-depth signal, not a primary safeguard.
func Map ¶
func Map[T, U any](slice []T, transform func(T) U) []U
Map transforms a slice using the given function.
func MaskSecret ¶
MaskSecret hides sensitive parts of a string for safe display in logs. If the string is shorter than visiblePrefix, it is fully masked.
func Nearest ¶
Nearest returns the candidate nearest to input within DefaultSuggestionDistance, or "" and false when none is close enough.
Nearest("fmt", []string{"format", "test", "lint"}) == ("format", true)
func NearestWithin ¶
NearestWithin returns the candidate nearest to input within maxDistance edits.
Matching is case-insensitive. A candidate qualifies either by an Optimal String Alignment (restricted Damerau-Levenshtein) distance within maxDistance — counting an adjacent transposition as a single edit — or, as a fallback, by being an abbreviation of input (input of at least two characters is a subsequence of a candidate no more than four times its length, e.g. "fmt" → "format"). Ties break toward a candidate sharing input's leading character, then lexicographically, so the result is deterministic regardless of iteration order.
func ParseBytes ¶
ParseBytes parses a human byte size into a raw byte count. It accepts an optional decimal magnitude followed by an optional unit; a bare number is bytes. Units are binary (1024-based) and case-insensitive, accepting the full forms (KiB, MiB, GiB, TiB, PiB), the ambiguous decimal-looking spellings (KB, MB, …), and the short aliases (k, ki, m, mi, g, gi, t, ti, p, pi, b). Whitespace between the number and unit is optional.
ParseBytes("1.5 KiB") == (1536, nil)
ParseBytes("10mb") == (10485760, nil)
func ParseDuration ¶
ParseDuration parses a duration string such as "5s", "10m", or "1.5h" into a time.Duration. Parsing is case-insensitive, allows optional whitespace before the unit, and treats a unit-less value as seconds. Recognized units are ns, us/μs, ms, s, m/min, h/hr, and d/day. It returns (0, false) on an unknown unit, a negative or malformed magnitude, or overflow.
ParseDuration("5") == (5*time.Second, true)
ParseDuration("10m") == (10*time.Minute, true)
func ParseSize ¶
ParseSize parses a human-readable size string (e.g. "10MB", "512KB", "2GB") into bytes. Returns defaultBytes if the string cannot be parsed.
func ReadFileString ¶
ReadFileString reads a file and returns its content as a string.
func RemoveAll ¶
RemoveAll removes path and any children. It does not return an error if path doesn't exist.
func ResolveUnique ¶
func ResolveUnique[T any](input string, candidates []T, keyOf func(T) string) (match T, found bool, err error)
ResolveUnique resolves input to the unique candidate whose key equals it.
keyOf projects each candidate to the string compared against input. Comparison is exact and case-sensitive. It returns (candidate, true, nil) when exactly one candidate matches, (zero, false, nil) when none do, and (zero, false, *Ambiguity) when two or more share the key. Callers wanting fuzzy resolution use Nearest.
func SanitizeEnvValue ¶
SanitizeEnvValue cleans an environment variable value by removing surrounding quotes and trimming whitespace.
func SanitizeString ¶
SanitizeString trims whitespace and removes control characters from s.
func Sha256Hex ¶
Sha256Hex returns the lowercase-hex SHA-256 digest of a byte slice. SHA-256 is for wire-format and interop use cases only; prefer HashHex (BLAKE3) for internal identity.
func Sha256Reader ¶
Sha256Reader computes the lowercase-hex SHA-256 digest of a stream, reading in bounded chunks so large inputs never need to be fully resident in memory.
func ToCamelCase ¶
ToCamelCase converts a string to camelCase, treating '_', '-', and ' ' as word boundaries and lowercasing the leading character.
ToCamelCase("snake_case_string") == "snakeCaseString"
ToCamelCase("Kebab-Case-String") == "kebabCaseString"
func ToKebabCase ¶
ToKebabCase converts a string to kebab-case, collapsing runs of separators and inserting hyphens before interior uppercase letters.
ToKebabCase("camelCaseString") == "camel-case-string"
func ToSnakeCase ¶
ToSnakeCase converts a string to snake_case, collapsing runs of separators and inserting underscores before interior uppercase letters.
ToSnakeCase("camelCaseString") == "camel_case_string"
ToSnakeCase("Kebab-Case-String") == "kebab_case_string"
func Truncate ¶
Truncate returns a UTF-8-safe prefix of s sized for TruncateEllipsis. When truncation is needed the prefix is at most maxBytes-3 bytes, reserving space for the ellipsis TruncateEllipsis appends. A rune is never split.
Truncate("hello world", 8) == "hello"
Truncate("hello", 10) == "hello"
func TruncateEllipsis ¶
TruncateEllipsis truncates s to at most maxBytes bytes, appending "..." when truncation occurs and maxBytes > 3. For smaller limits the result is maxBytes dots, because there is no room for the full ellipsis.
TruncateEllipsis("hello world", 8) == "hello..."
func Unique ¶
func Unique[T comparable](slice []T) []T
Unique returns a slice with duplicate values removed, preserving order.
Types ¶
type Ambiguity ¶
type Ambiguity[T any] struct { // Input is the shorthand that matched more than one candidate. Input string // Matches are every candidate whose key equalled Input, in encounter order. Matches []T }
Ambiguity carries the candidates a shorthand matched when more than one qualified.
type ContentHasher ¶
type ContentHasher struct {
// contains filtered or unexported fields
}
ContentHasher is an incremental content hasher producing a stable lowercase-hex digest, backed by BLAKE3. Feed bytes with Update (raw) or UpdateFramed (domain-separated), then read the digest with FinalizeHex. Finalizing does not consume the hasher, so it may be reused. BLAKE3 is the canonical content hash for cache keys, change detection, and deduplication; the digest identity matches the rskit and pykit content hashers.
func NewContentHasher ¶
func NewContentHasher() *ContentHasher
NewContentHasher creates an empty content hasher.
func (*ContentHasher) FinalizeHex ¶
func (h *ContentHasher) FinalizeHex() string
FinalizeHex renders the current digest as a 64-character lowercase hex string. It does not consume the hasher: further updates may follow and produce a new digest.
func (*ContentHasher) Update ¶
func (h *ContentHasher) Update(bytes []byte) *ContentHasher
Update folds bytes into the digest verbatim, without framing, returning the hasher for chaining.
func (*ContentHasher) UpdateFramed ¶
func (h *ContentHasher) UpdateFramed(label, value []byte) *ContentHasher
UpdateFramed folds a labeled value into the digest with unambiguous framing. Each of label and value is folded length-prefixed (its length as a little-endian uint64, then its bytes), so field boundaries stay unambiguous even when inputs contain arbitrary bytes; independently folded fields cannot alias one another. Returns the hasher for chaining.
type DuplicateKeyError ¶
type DuplicateKeyError[K comparable] struct { Key K }
DuplicateKeyError reports the first duplicated key found by EnsureUniqueBy.
func (*DuplicateKeyError[K]) Error ¶
func (e *DuplicateKeyError[K]) Error() string
type DynamicTemplate ¶
type DynamicTemplate struct {
// contains filtered or unexported fields
}
DynamicTemplate is a parsed "{{var}}" template with an open set of named variables resolved at render time. Unlike Template, whose placeholders are a fixed typed set known at compile time, a DynamicTemplate carries data-driven variable names looked up against a caller-supplied function. Parsing is lenient: names may be padded with whitespace ("{{ name }}" equals "{{name}}"), and any run that is not a well-formed placeholder is preserved verbatim. Use it for prompt-style templates.
func ParseDynamicTemplate ¶
func ParseDynamicTemplate(template string) DynamicTemplate
ParseDynamicTemplate parses template. Parsing never fails: malformed "{{"/"}}" runs and invalid names are kept as literal text.
func (DynamicTemplate) Render ¶
Render resolves each variable through lookup, which returns the value and whether it was found. A variable with no value yields a TemplateError of kind TemplateErrorMissingVariable.
func (DynamicTemplate) Variables ¶
func (t DynamicTemplate) Variables() []string
Variables returns the sorted, de-duplicated set of variable names the template references.
type FakeClock ¶
type FakeClock struct {
// contains filtered or unexported fields
}
FakeClock is a deterministic clock for tests.
func NewFakeClock ¶
NewFakeClock creates a FakeClock starting at the given time. If zero, defaults to 2024-01-01T00:00:00Z.
type Glob ¶
type Glob struct {
// contains filtered or unexported fields
}
Glob is a compiled glob pattern that can be matched against many candidates. A wildcard pattern is parsed once; a plain literal keeps no parsed form and compares directly. Matching semantics are identical to GlobMatch.
type Placeholder ¶
type Placeholder interface {
comparable
Token() string
}
Placeholder is a typed template token. Implementations are comparable and expose their user-facing token name (without braces) via Token.
type SecretKeyMatcher ¶
type SecretKeyMatcher struct {
// contains filtered or unexported fields
}
SecretKeyMatcher decides whether a config key or flag name commonly carries a secret value, so callers can redact it. Names are normalized (leading dashes trimmed, '-' folded to '_', lowercased) and a key matches when it equals a configured name or ends with "_<name>" — so "db_password" matches while "author" does not.
func DefaultSecretKeyMatcher ¶
func DefaultSecretKeyMatcher() SecretKeyMatcher
DefaultSecretKeyMatcher returns a matcher seeded with DefaultSecretKeyNames.
func NewSecretKeyMatcher ¶
func NewSecretKeyMatcher(names []string) SecretKeyMatcher
NewSecretKeyMatcher builds a matcher from the given secret-bearing names, applying the same normalization used at match time and dropping empties.
func (SecretKeyMatcher) IsSecretKey ¶
func (m SecretKeyMatcher) IsSecretKey(name string) bool
IsSecretKey reports whether name should be treated as secret-bearing.
func (SecretKeyMatcher) WithName ¶
func (m SecretKeyMatcher) WithName(name string) SecretKeyMatcher
WithName returns a matcher extended with one more secret-bearing name.
func (SecretKeyMatcher) WithNames ¶
func (m SecretKeyMatcher) WithNames(names []string) SecretKeyMatcher
WithNames returns a matcher extended with several secret-bearing names.
type SecretString ¶
type SecretString struct {
// contains filtered or unexported fields
}
SecretString wraps a string so it does not leak through logging, formatting, or JSON serialization. The plaintext is reachable only through Expose, marshals as the mask "***", and unmarshals from plaintext so a SecretString can be populated from config. Go provides no guaranteed in-memory zeroization, so unlike the rskit counterpart the backing bytes are not scrubbed on drop; treat process memory as a separate trust boundary.
func NewSecretString ¶
func NewSecretString(plaintext string) SecretString
NewSecretString wraps plaintext in a SecretString.
func (SecretString) Equal ¶
func (s SecretString) Equal(other SecretString) bool
Equal compares two secrets in constant time, avoiding a timing side channel.
func (SecretString) Expose ¶
func (s SecretString) Expose() string
Expose returns the plaintext value. Call it only where the secret is genuinely needed.
func (SecretString) GoString ¶
func (s SecretString) GoString() string
GoString masks the value in %#v / Go-syntax formatting.
func (SecretString) IsEmpty ¶
func (s SecretString) IsEmpty() bool
IsEmpty reports whether the underlying value is empty.
func (SecretString) Len ¶
func (s SecretString) Len() int
Len returns the length of the underlying value in bytes.
func (SecretString) MarshalJSON ¶
func (s SecretString) MarshalJSON() ([]byte, error)
MarshalJSON emits the mask for a non-empty secret and an empty string otherwise, so the plaintext never reaches a serialized config dump.
func (SecretString) String ¶
func (s SecretString) String() string
String renders the mask for a non-empty secret and an empty string otherwise, so a SecretString is safe to interpolate into logs.
func (*SecretString) UnmarshalJSON ¶
func (s *SecretString) UnmarshalJSON(data []byte) error
UnmarshalJSON reads a plaintext string into the secret.
type Template ¶
type Template[P Placeholder] struct { // contains filtered or unexported fields }
Template is a parsed template string validated against a fixed, typed set of placeholders known at compile time. Unknown placeholders are rejected at parse time, so rendering only ever sees tokens the caller declared.
func ParseTemplate ¶
func ParseTemplate[P Placeholder](value string, placeholders []P) (Template[P], error)
ParseTemplate parses value against the allowed placeholders, rejecting unknown or malformed placeholders and unmatched braces.
func (Template[P]) Parts ¶
func (t Template[P]) Parts() []TemplatePart[P]
Parts returns the parsed template parts in source order.
type TemplateError ¶
type TemplateError struct {
// Kind is the failure category.
Kind TemplateErrorKind
// Detail is the offending template, placeholder, or variable name.
Detail string
}
TemplateError describes a template parse or render failure. It is comparable, so callers can match a specific failure with ==.
func (TemplateError) Error ¶
func (e TemplateError) Error() string
Error implements error with a message matching the failure kind.
type TemplateErrorKind ¶
type TemplateErrorKind int
TemplateErrorKind classifies a TemplateError.
const ( // TemplateErrorUnclosedPlaceholder marks a placeholder missing its closing brace. TemplateErrorUnclosedPlaceholder TemplateErrorKind = iota // TemplateErrorUnmatchedClosingBrace marks a stray closing brace with no opener. TemplateErrorUnmatchedClosingBrace // TemplateErrorEmptyPlaceholder marks a placeholder with an empty name. TemplateErrorEmptyPlaceholder // TemplateErrorUnknownPlaceholder marks a placeholder not in the typed set. TemplateErrorUnknownPlaceholder // TemplateErrorRender marks a failure returned by a render callback. TemplateErrorRender // TemplateErrorMissingVariable marks a dynamic variable with no value. TemplateErrorMissingVariable )
type TemplatePart ¶
type TemplatePart[P Placeholder] struct { Kind TemplatePartKind Literal string Placeholder P }
TemplatePart is one parsed part of a typed Template: either literal text or a placeholder. When Kind is TemplatePartLiteral, Literal holds the text; when it is TemplatePartPlaceholder, Placeholder holds the token.
type TemplatePartKind ¶
type TemplatePartKind int
TemplatePartKind distinguishes a literal template part from a placeholder part.
const ( // TemplatePartLiteral is verbatim text. TemplatePartLiteral TemplatePartKind = iota // TemplatePartPlaceholder is a resolved placeholder token. TemplatePartPlaceholder )