binwiederhier 7 месяцев назад
Родитель
Сommit
50c564d8a2

+ 17 - 0
util/sprig/crypto.go

@@ -9,21 +9,38 @@ import (
 	"hash/adler32"
 )
 
+// sha512sum computes the SHA-512 hash of the input string and returns it as a hex-encoded string.
+// This function can be used in templates to generate secure hashes of sensitive data.
+//
+// Example usage in templates: {{ "hello world" | sha512sum }}
 func sha512sum(input string) string {
 	hash := sha512.Sum512([]byte(input))
 	return hex.EncodeToString(hash[:])
 }
 
+// sha256sum computes the SHA-256 hash of the input string and returns it as a hex-encoded string.
+// This is a commonly used cryptographic hash function that produces a 256-bit (32-byte) hash value.
+//
+// Example usage in templates: {{ "hello world" | sha256sum }}
 func sha256sum(input string) string {
 	hash := sha256.Sum256([]byte(input))
 	return hex.EncodeToString(hash[:])
 }
 
+// sha1sum computes the SHA-1 hash of the input string and returns it as a hex-encoded string.
+// Note: SHA-1 is no longer considered secure against well-funded attackers for cryptographic purposes.
+// Consider using sha256sum or sha512sum for security-critical applications.
+//
+// Example usage in templates: {{ "hello world" | sha1sum }}
 func sha1sum(input string) string {
 	hash := sha1.Sum([]byte(input))
 	return hex.EncodeToString(hash[:])
 }
 
+// adler32sum computes the Adler-32 checksum of the input string and returns it as a decimal string.
+// This is a non-cryptographic hash function primarily used for error detection.
+//
+// Example usage in templates: {{ "hello world" | adler32sum }}
 func adler32sum(input string) string {
 	hash := adler32.Checksum([]byte(input))
 	return fmt.Sprintf("%d", hash)

+ 104 - 8
util/sprig/date.go

@@ -1,27 +1,61 @@
 package sprig
 
 import (
+	"math"
 	"strconv"
 	"time"
 )
 
-// Given a format and a date, format the date string.
+// date formats a date according to the provided format string.
 //
-// Date can be a `time.Time` or an `int, int32, int64`.
-// In the later case, it is treated as seconds since UNIX
-// epoch.
+// Parameters:
+//   - fmt: A Go time format string (e.g., "2006-01-02 15:04:05")
+//   - date: Can be a time.Time, *time.Time, or int/int32/int64 (seconds since UNIX epoch)
+//
+// If date is not one of the recognized types, the current time is used.
+//
+// Example usage in templates: {{ now | date "2006-01-02" }}
 func date(fmt string, date any) string {
 	return dateInZone(fmt, date, "Local")
 }
 
+// htmlDate formats a date in HTML5 date format (YYYY-MM-DD).
+//
+// Parameters:
+//   - date: Can be a time.Time, *time.Time, or int/int32/int64 (seconds since UNIX epoch)
+//
+// If date is not one of the recognized types, the current time is used.
+//
+// Example usage in templates: {{ now | htmlDate }}
 func htmlDate(date any) string {
 	return dateInZone("2006-01-02", date, "Local")
 }
 
+// htmlDateInZone formats a date in HTML5 date format (YYYY-MM-DD) in the specified timezone.
+//
+// Parameters:
+//   - date: Can be a time.Time, *time.Time, or int/int32/int64 (seconds since UNIX epoch)
+//   - zone: Timezone name (e.g., "UTC", "America/New_York")
+//
+// If date is not one of the recognized types, the current time is used.
+// If the timezone is invalid, UTC is used.
+//
+// Example usage in templates: {{ now | htmlDateInZone "UTC" }}
 func htmlDateInZone(date any, zone string) string {
 	return dateInZone("2006-01-02", date, zone)
 }
 
+// dateInZone formats a date according to the provided format string in the specified timezone.
+//
+// Parameters:
+//   - fmt: A Go time format string (e.g., "2006-01-02 15:04:05")
+//   - date: Can be a time.Time, *time.Time, or int/int32/int64 (seconds since UNIX epoch)
+//   - zone: Timezone name (e.g., "UTC", "America/New_York")
+//
+// If date is not one of the recognized types, the current time is used.
+// If the timezone is invalid, UTC is used.
+//
+// Example usage in templates: {{ now | dateInZone "2006-01-02 15:04:05" "UTC" }}
 func dateInZone(fmt string, date any, zone string) string {
 	var t time.Time
 	switch date := date.(type) {
@@ -45,6 +79,15 @@ func dateInZone(fmt string, date any, zone string) string {
 	return t.In(loc).Format(fmt)
 }
 
+// dateModify modifies a date by adding a duration and returns the resulting time.
+//
+// Parameters:
+//   - fmt: A duration string (e.g., "24h", "-12h30m", "1h15m30s")
+//   - date: The time.Time to modify
+//
+// If the duration string is invalid, the original date is returned.
+//
+// Example usage in templates: {{ now | dateModify "-24h" }}
 func dateModify(fmt string, date time.Time) time.Time {
 	d, err := time.ParseDuration(fmt)
 	if err != nil {
@@ -53,6 +96,15 @@ func dateModify(fmt string, date time.Time) time.Time {
 	return date.Add(d)
 }
 
+// mustDateModify modifies a date by adding a duration and returns the resulting time or an error.
+//
+// Parameters:
+//   - fmt: A duration string (e.g., "24h", "-12h30m", "1h15m30s")
+//   - date: The time.Time to modify
+//
+// Unlike dateModify, this function returns an error if the duration string is invalid.
+//
+// Example usage in templates: {{ now | mustDateModify "24h" }}
 func mustDateModify(fmt string, date time.Time) (time.Time, error) {
 	d, err := time.ParseDuration(fmt)
 	if err != nil {
@@ -61,6 +113,14 @@ func mustDateModify(fmt string, date time.Time) (time.Time, error) {
 	return date.Add(d), nil
 }
 
+// dateAgo returns a string representing the time elapsed since the given date.
+//
+// Parameters:
+//   - date: Can be a time.Time, int, or int64 (seconds since UNIX epoch)
+//
+// If date is not one of the recognized types, the current time is used.
+//
+// Example usage in templates: {{ "2023-01-01" | toDate "2006-01-02" | dateAgo }}
 func dateAgo(date any) string {
 	var t time.Time
 	switch date := date.(type) {
@@ -76,6 +136,12 @@ func dateAgo(date any) string {
 	return time.Since(t).Round(time.Second).String()
 }
 
+// duration converts seconds to a duration string.
+//
+// Parameters:
+//   - sec: Can be a string (parsed as int64), or int64 representing seconds
+//
+// Example usage in templates: {{ 3600 | duration }} -> "1h0m0s"
 func duration(sec any) string {
 	var n int64
 	switch value := sec.(type) {
@@ -89,6 +155,15 @@ func duration(sec any) string {
 	return (time.Duration(n) * time.Second).String()
 }
 
+// durationRound formats a duration in a human-readable rounded format.
+//
+// Parameters:
+//   - duration: Can be a string (parsed as duration), int64 (nanoseconds),
+//     or time.Time (time since that moment)
+//
+// Returns a string with the largest appropriate unit (y, mo, d, h, m, s).
+//
+// Example usage in templates: {{ 3600 | duration | durationRound }} -> "1h"
 func durationRound(duration any) string {
 	var d time.Duration
 	switch duration := duration.(type) {
@@ -101,10 +176,7 @@ func durationRound(duration any) string {
 	case time.Time:
 		d = time.Since(duration)
 	}
-	var u uint64
-	if d < 0 {
-		u = -u
-	}
+	u := uint64(math.Abs(float64(d)))
 	var (
 		year   = uint64(time.Hour) * 24 * 365
 		month  = uint64(time.Hour) * 24 * 30
@@ -130,15 +202,39 @@ func durationRound(duration any) string {
 	return "0s"
 }
 
+// toDate parses a string into a time.Time using the specified format.
+//
+// Parameters:
+//   - fmt: A Go time format string (e.g., "2006-01-02")
+//   - str: The date string to parse
+//
+// If parsing fails, returns a zero time.Time.
+//
+// Example usage in templates: {{ "2023-01-01" | toDate "2006-01-02" }}
 func toDate(fmt, str string) time.Time {
 	t, _ := time.ParseInLocation(fmt, str, time.Local)
 	return t
 }
 
+// mustToDate parses a string into a time.Time using the specified format or returns an error.
+//
+// Parameters:
+//   - fmt: A Go time format string (e.g., "2006-01-02")
+//   - str: The date string to parse
+//
+// Unlike toDate, this function returns an error if parsing fails.
+//
+// Example usage in templates: {{ mustToDate "2006-01-02" "2023-01-01" }}
 func mustToDate(fmt, str string) (time.Time, error) {
 	return time.ParseInLocation(fmt, str, time.Local)
 }
 
+// unixEpoch returns the Unix timestamp (seconds since January 1, 1970 UTC) for the given time.
+//
+// Parameters:
+//   - date: A time.Time value
+//
+// Example usage in templates: {{ now | unixEpoch }}
 func unixEpoch(date time.Time) string {
 	return strconv.FormatInt(date.Unix(), 10)
 }

+ 3 - 0
util/sprig/date_test.go

@@ -117,4 +117,7 @@ func TestDurationRound(t *testing.T) {
 	if err := runtv(tpl, "3mo", map[string]any{"Time": "2400h5s"}); err != nil {
 		t.Error(err)
 	}
+	if err := runtv(tpl, "1m", map[string]any{"Time": "-1m1s"}); err != nil {
+		t.Error(err)
+	}
 }

+ 127 - 12
util/sprig/defaults.go

@@ -25,6 +25,21 @@ func defaultValue(d any, given ...any) any {
 }
 
 // empty returns true if the given value has the zero value for its type.
+// This is a helper function used by defaultValue, coalesce, all, and anyNonEmpty.
+//
+// The following values are considered empty:
+// - Invalid values
+// - nil values
+// - Zero-length arrays, slices, maps, and strings
+// - Boolean false
+// - Zero for all numeric types
+// - Structs are never considered empty
+//
+// Parameters:
+//   - given: The value to check for emptiness
+//
+// Returns:
+//   - bool: True if the value is considered empty, false otherwise
 func empty(given any) bool {
 	g := reflect.ValueOf(given)
 	if !g.IsValid() {
@@ -51,7 +66,16 @@ func empty(given any) bool {
 	}
 }
 
-// coalesce returns the first non-empty value.
+// coalesce returns the first non-empty value from a list of values.
+// If all values are empty, it returns nil.
+//
+// This is useful for providing a series of fallback values.
+//
+// Parameters:
+//   - v: A variadic list of values to check
+//
+// Returns:
+//   - any: The first non-empty value, or nil if all values are empty
 func coalesce(v ...any) any {
 	for _, val := range v {
 		if !empty(val) {
@@ -61,8 +85,15 @@ func coalesce(v ...any) any {
 	return nil
 }
 
-// all returns true if empty(x) is false for all values x in the list.
-// If the list is empty, return true.
+// all checks if all values in a list are non-empty.
+// Returns true if every value in the list is non-empty.
+// If the list is empty, returns true (vacuously true).
+//
+// Parameters:
+//   - v: A variadic list of values to check
+//
+// Returns:
+//   - bool: True if all values are non-empty, false otherwise
 func all(v ...any) bool {
 	for _, val := range v {
 		if empty(val) {
@@ -72,8 +103,15 @@ func all(v ...any) bool {
 	return true
 }
 
-// anyNonEmpty returns true if empty(x) is false for anyNonEmpty x in the list.
-// If the list is empty, return false.
+// anyNonEmpty checks if at least one value in a list is non-empty.
+// Returns true if any value in the list is non-empty.
+// If the list is empty, returns false.
+//
+// Parameters:
+//   - v: A variadic list of values to check
+//
+// Returns:
+//   - bool: True if at least one value is non-empty, false otherwise
 func anyNonEmpty(v ...any) bool {
 	for _, val := range v {
 		if !empty(val) {
@@ -83,25 +121,58 @@ func anyNonEmpty(v ...any) bool {
 	return false
 }
 
-// fromJSON decodes JSON into a structured value, ignoring errors.
+// fromJSON decodes a JSON string into a structured value.
+// This function ignores any errors that occur during decoding.
+// If the JSON is invalid, it returns nil.
+//
+// Parameters:
+//   - v: The JSON string to decode
+//
+// Returns:
+//   - any: The decoded value, or nil if decoding failed
 func fromJSON(v string) any {
 	output, _ := mustFromJSON(v)
 	return output
 }
 
-// mustFromJSON decodes JSON into a structured value, returning errors.
+// mustFromJSON decodes a JSON string into a structured value.
+// Unlike fromJSON, this function returns any errors that occur during decoding.
+//
+// Parameters:
+//   - v: The JSON string to decode
+//
+// Returns:
+//   - any: The decoded value
+//   - error: Any error that occurred during decoding
 func mustFromJSON(v string) (any, error) {
 	var output any
 	err := json.Unmarshal([]byte(v), &output)
 	return output, err
 }
 
-// toJSON encodes an item into a JSON string
+// toJSON encodes a value into a JSON string.
+// This function ignores any errors that occur during encoding.
+// If the value cannot be encoded, it returns an empty string.
+//
+// Parameters:
+//   - v: The value to encode to JSON
+//
+// Returns:
+//   - string: The JSON string representation of the value
 func toJSON(v any) string {
 	output, _ := json.Marshal(v)
 	return string(output)
 }
 
+// mustToJSON encodes a value into a JSON string.
+// Unlike toJSON, this function returns any errors that occur during encoding.
+//
+// Parameters:
+//   - v: The value to encode to JSON
+//
+// Returns:
+//   - string: The JSON string representation of the value
+//   - error: Any error that occurred during encoding
 func mustToJSON(v any) (string, error) {
 	output, err := json.Marshal(v)
 	if err != nil {
@@ -110,12 +181,29 @@ func mustToJSON(v any) (string, error) {
 	return string(output), nil
 }
 
-// toPrettyJSON encodes an item into a pretty (indented) JSON string
+// toPrettyJSON encodes a value into a pretty (indented) JSON string.
+// This function ignores any errors that occur during encoding.
+// If the value cannot be encoded, it returns an empty string.
+//
+// Parameters:
+//   - v: The value to encode to JSON
+//
+// Returns:
+//   - string: The indented JSON string representation of the value
 func toPrettyJSON(v any) string {
 	output, _ := json.MarshalIndent(v, "", "  ")
 	return string(output)
 }
 
+// mustToPrettyJSON encodes a value into a pretty (indented) JSON string.
+// Unlike toPrettyJSON, this function returns any errors that occur during encoding.
+//
+// Parameters:
+//   - v: The value to encode to JSON
+//
+// Returns:
+//   - string: The indented JSON string representation of the value
+//   - error: Any error that occurred during encoding
 func mustToPrettyJSON(v any) (string, error) {
 	output, err := json.MarshalIndent(v, "", "  ")
 	if err != nil {
@@ -124,7 +212,15 @@ func mustToPrettyJSON(v any) (string, error) {
 	return string(output), nil
 }
 
-// toRawJSON encodes an item into a JSON string with no escaping of HTML characters.
+// toRawJSON encodes a value into a JSON string with no escaping of HTML characters.
+// This function panics if an error occurs during encoding.
+// Unlike toJSON, HTML characters like <, >, and & are not escaped.
+//
+// Parameters:
+//   - v: The value to encode to JSON
+//
+// Returns:
+//   - string: The JSON string representation of the value without HTML escaping
 func toRawJSON(v any) string {
 	output, err := mustToRawJSON(v)
 	if err != nil {
@@ -133,7 +229,16 @@ func toRawJSON(v any) string {
 	return output
 }
 
-// mustToRawJSON encodes an item into a JSON string with no escaping of HTML characters.
+// mustToRawJSON encodes a value into a JSON string with no escaping of HTML characters.
+// Unlike toRawJSON, this function returns any errors that occur during encoding.
+// HTML characters like <, >, and & are not escaped in the output.
+//
+// Parameters:
+//   - v: The value to encode to JSON
+//
+// Returns:
+//   - string: The JSON string representation of the value without HTML escaping
+//   - error: Any error that occurred during encoding
 func mustToRawJSON(v any) (string, error) {
 	buf := new(bytes.Buffer)
 	enc := json.NewEncoder(buf)
@@ -144,7 +249,17 @@ func mustToRawJSON(v any) (string, error) {
 	return strings.TrimSuffix(buf.String(), "\n"), nil
 }
 
-// ternary returns the first value if the last value is true, otherwise returns the second value.
+// ternary implements a conditional (ternary) operator.
+// It returns the first value if the condition is true, otherwise returns the second value.
+// This is similar to the ?: operator in many programming languages.
+//
+// Parameters:
+//   - vt: The value to return if the condition is true
+//   - vf: The value to return if the condition is false
+//   - v: The boolean condition to evaluate
+//
+// Returns:
+//   - any: Either vt or vf depending on the value of v
 func ternary(vt any, vf any, v bool) any {
 	if v {
 		return vt

+ 118 - 0
util/sprig/dict.go

@@ -1,5 +1,15 @@
 package sprig
 
+// get retrieves a value from a map by its key.
+// If the key exists, returns the corresponding value.
+// If the key doesn't exist, returns an empty string.
+//
+// Parameters:
+//   - d: The map to retrieve the value from
+//   - key: The key to look up
+//
+// Returns:
+//   - any: The value associated with the key, or an empty string if not found
 func get(d map[string]any, key string) any {
 	if val, ok := d[key]; ok {
 		return val
@@ -7,21 +17,58 @@ func get(d map[string]any, key string) any {
 	return ""
 }
 
+// set adds or updates a key-value pair in a map.
+// Modifies the map in place and returns the modified map.
+//
+// Parameters:
+//   - d: The map to modify
+//   - key: The key to set
+//   - value: The value to associate with the key
+//
+// Returns:
+//   - map[string]any: The modified map (same instance as the input map)
 func set(d map[string]any, key string, value any) map[string]any {
 	d[key] = value
 	return d
 }
 
+// unset removes a key-value pair from a map.
+// If the key doesn't exist, the map remains unchanged.
+// Modifies the map in place and returns the modified map.
+//
+// Parameters:
+//   - d: The map to modify
+//   - key: The key to remove
+//
+// Returns:
+//   - map[string]any: The modified map (same instance as the input map)
 func unset(d map[string]any, key string) map[string]any {
 	delete(d, key)
 	return d
 }
 
+// hasKey checks if a key exists in a map.
+//
+// Parameters:
+//   - d: The map to check
+//   - key: The key to look for
+//
+// Returns:
+//   - bool: True if the key exists in the map, false otherwise
 func hasKey(d map[string]any, key string) bool {
 	_, ok := d[key]
 	return ok
 }
 
+// pluck extracts values for a specific key from multiple maps.
+// Only includes values from maps where the key exists.
+//
+// Parameters:
+//   - key: The key to extract values for
+//   - d: A variadic list of maps to extract values from
+//
+// Returns:
+//   - []any: A slice containing all values associated with the key across all maps
 func pluck(key string, d ...map[string]any) []any {
 	var res []any
 	for _, dict := range d {
@@ -32,6 +79,14 @@ func pluck(key string, d ...map[string]any) []any {
 	return res
 }
 
+// keys collects all keys from one or more maps.
+// The returned slice may contain duplicate keys if multiple maps contain the same key.
+//
+// Parameters:
+//   - dicts: A variadic list of maps to collect keys from
+//
+// Returns:
+//   - []string: A slice containing all keys from all provided maps
 func keys(dicts ...map[string]any) []string {
 	var k []string
 	for _, dict := range dicts {
@@ -42,6 +97,15 @@ func keys(dicts ...map[string]any) []string {
 	return k
 }
 
+// pick creates a new map containing only the specified keys from the original map.
+// If a key doesn't exist in the original map, it won't be included in the result.
+//
+// Parameters:
+//   - dict: The source map
+//   - keys: A variadic list of keys to include in the result
+//
+// Returns:
+//   - map[string]any: A new map containing only the specified keys and their values
 func pick(dict map[string]any, keys ...string) map[string]any {
 	res := map[string]any{}
 	for _, k := range keys {
@@ -52,6 +116,15 @@ func pick(dict map[string]any, keys ...string) map[string]any {
 	return res
 }
 
+// omit creates a new map excluding the specified keys from the original map.
+// The original map remains unchanged.
+//
+// Parameters:
+//   - dict: The source map
+//   - keys: A variadic list of keys to exclude from the result
+//
+// Returns:
+//   - map[string]any: A new map containing all key-value pairs except those specified
 func omit(dict map[string]any, keys ...string) map[string]any {
 	res := map[string]any{}
 	omit := make(map[string]bool, len(keys))
@@ -66,6 +139,16 @@ func omit(dict map[string]any, keys ...string) map[string]any {
 	return res
 }
 
+// dict creates a new map from a list of key-value pairs.
+// The arguments are treated as key-value pairs, where even-indexed arguments are keys
+// and odd-indexed arguments are values.
+// If there's an odd number of arguments, the last key will be assigned an empty string value.
+//
+// Parameters:
+//   - v: A variadic list of alternating keys and values
+//
+// Returns:
+//   - map[string]any: A new map containing the specified key-value pairs
 func dict(v ...any) map[string]any {
 	dict := map[string]any{}
 	lenv := len(v)
@@ -80,6 +163,14 @@ func dict(v ...any) map[string]any {
 	return dict
 }
 
+// values collects all values from a map into a slice.
+// The order of values in the resulting slice is not guaranteed.
+//
+// Parameters:
+//   - dict: The map to collect values from
+//
+// Returns:
+//   - []any: A slice containing all values from the map
 func values(dict map[string]any) []any {
 	var values []any
 	for _, value := range dict {
@@ -88,6 +179,22 @@ func values(dict map[string]any) []any {
 	return values
 }
 
+// dig safely accesses nested values in maps using a sequence of keys.
+// If any key in the path doesn't exist, it returns the default value.
+// The function expects at least 3 arguments: one or more keys, a default value, and a map.
+//
+// Parameters:
+//   - ps: A variadic list where:
+//     - The first N-2 arguments are string keys forming the path
+//     - The second-to-last argument is the default value to return if the path doesn't exist
+//     - The last argument is the map to traverse
+//
+// Returns:
+//   - any: The value found at the specified path, or the default value if not found
+//   - error: Any error that occurred during traversal
+//
+// Panics:
+//   - If fewer than 3 arguments are provided
 func dig(ps ...any) (any, error) {
 	if len(ps) < 3 {
 		panic("dig needs at least three arguments")
@@ -102,6 +209,17 @@ func dig(ps ...any) (any, error) {
 	return digFromDict(dict, def, ks)
 }
 
+// digFromDict is a helper function for dig that recursively traverses a map using a sequence of keys.
+// If any key in the path doesn't exist, it returns the default value.
+//
+// Parameters:
+//   - dict: The map to traverse
+//   - d: The default value to return if the path doesn't exist
+//   - ks: A slice of string keys forming the path to traverse
+//
+// Returns:
+//   - any: The value found at the specified path, or the default value if not found
+//   - error: Any error that occurred during traversal
 func digFromDict(dict map[string]any, d any, ks []string) (any, error) {
 	k, ns := ks[0], ks[1:]
 	step, has := dict[k]

+ 1 - 0
util/sprig/flow_control.go

@@ -2,6 +2,7 @@ package sprig
 
 import "errors"
 
+// fail is a function that always returns an error with the given message.
 func fail(msg string) (string, error) {
 	return "", errors.New(msg)
 }

+ 1 - 0
util/sprig/functions.go

@@ -12,6 +12,7 @@ import (
 const (
 	loopExecutionLimit = 10_000  // Limit the number of loop executions to prevent execution from taking too long
 	stringLengthLimit  = 100_000 // Limit the length of strings to prevent memory issues
+	sliceSizeLimit     = 10_000  // Limit the size of slices to prevent memory issues
 )
 
 // TxtFuncMap produces the function map.

+ 1 - 1
util/sprig/functions_test.go

@@ -52,7 +52,7 @@ func runtv(tpl, expect string, vars any) error {
 		return err
 	}
 	if expect != b.String() {
-		return fmt.Errorf("Expected '%s', got '%s'", expect, b.String())
+		return fmt.Errorf("expected '%s', got '%s'", expect, b.String())
 	}
 	return nil
 }

+ 90 - 48
util/sprig/list.go

@@ -11,10 +11,15 @@ import (
 // ints, and other types not implementing []any can be worked with.
 // For example, this is useful if you need to work on the output of regexs.
 
+// list creates a new list (slice) containing the provided arguments.
+// It accepts any number of arguments of any type and returns them as a slice.
 func list(v ...any) []any {
 	return v
 }
 
+// push appends an element to the end of a list (slice or array).
+// It takes a list and a value, and returns a new list with the value appended.
+// This function will panic if the first argument is not a slice or array.
 func push(list any, v any) []any {
 	l, err := mustPush(list, v)
 	if err != nil {
@@ -23,99 +28,103 @@ func push(list any, v any) []any {
 	return l
 }
 
+// mustPush is the implementation of push that returns an error instead of panicking.
+// It converts the input list to a slice of any type, then appends the value.
 func mustPush(list any, v any) ([]any, error) {
 	tp := reflect.TypeOf(list).Kind()
 	switch tp {
 	case reflect.Slice, reflect.Array:
 		l2 := reflect.ValueOf(list)
-
 		l := l2.Len()
 		nl := make([]any, l)
 		for i := 0; i < l; i++ {
 			nl[i] = l2.Index(i).Interface()
 		}
-
 		return append(nl, v), nil
-
 	default:
 		return nil, fmt.Errorf("cannot push on type %s", tp)
 	}
 }
 
+// prepend adds an element to the beginning of a list (slice or array).
+// It takes a list and a value, and returns a new list with the value at the start.
+// This function will panic if the first argument is not a slice or array.
 func prepend(list any, v any) []any {
 	l, err := mustPrepend(list, v)
 	if err != nil {
 		panic(err)
 	}
-
 	return l
 }
 
+// mustPrepend is the implementation of prepend that returns an error instead of panicking.
+// It converts the input list to a slice of any type, then prepends the value.
 func mustPrepend(list any, v any) ([]any, error) {
-	//return append([]any{v}, list...)
-
 	tp := reflect.TypeOf(list).Kind()
 	switch tp {
 	case reflect.Slice, reflect.Array:
 		l2 := reflect.ValueOf(list)
-
 		l := l2.Len()
 		nl := make([]any, l)
 		for i := 0; i < l; i++ {
 			nl[i] = l2.Index(i).Interface()
 		}
-
 		return append([]any{v}, nl...), nil
-
 	default:
 		return nil, fmt.Errorf("cannot prepend on type %s", tp)
 	}
 }
 
+// chunk divides a list into sub-lists of the specified size.
+// It takes a size and a list, and returns a list of lists, each containing
+// up to 'size' elements from the original list.
+// This function will panic if the second argument is not a slice or array.
 func chunk(size int, list any) [][]any {
 	l, err := mustChunk(size, list)
 	if err != nil {
 		panic(err)
 	}
-
 	return l
 }
 
+// mustChunk is the implementation of chunk that returns an error instead of panicking.
+// It divides the input list into chunks of the specified size.
 func mustChunk(size int, list any) ([][]any, error) {
 	tp := reflect.TypeOf(list).Kind()
 	switch tp {
 	case reflect.Slice, reflect.Array:
 		l2 := reflect.ValueOf(list)
-
 		l := l2.Len()
-
-		cs := int(math.Floor(float64(l-1)/float64(size)) + 1)
-		nl := make([][]any, cs)
-
-		for i := 0; i < cs; i++ {
+		numChunks := int(math.Floor(float64(l-1)/float64(size)) + 1)
+		if numChunks > sliceSizeLimit {
+			return nil, fmt.Errorf("number of chunks %d exceeds maximum limit of %d", numChunks, sliceSizeLimit)
+		}
+		result := make([][]any, numChunks)
+		for i := 0; i < numChunks; i++ {
 			clen := size
-			if i == cs-1 {
+			// Handle the last chunk which might be smaller
+			if i == numChunks-1 {
 				clen = int(math.Floor(math.Mod(float64(l), float64(size))))
 				if clen == 0 {
 					clen = size
 				}
 			}
-
-			nl[i] = make([]any, clen)
-
+			result[i] = make([]any, clen)
 			for j := 0; j < clen; j++ {
 				ix := i*size + j
-				nl[i][j] = l2.Index(ix).Interface()
+				result[i][j] = l2.Index(ix).Interface()
 			}
 		}
-
-		return nl, nil
+		return result, nil
 
 	default:
 		return nil, fmt.Errorf("cannot chunk type %s", tp)
 	}
 }
 
+// last returns the last element of a list (slice or array).
+// If the list is empty, it returns nil.
+// This function will panic if the argument is not a slice or array.
 func last(list any) any {
 	l, err := mustLast(list)
 	if err != nil {
@@ -125,6 +134,8 @@ func last(list any) any {
 	return l
 }
 
+// mustLast is the implementation of last that returns an error instead of panicking.
+// It returns the last element of the list or nil if the list is empty.
 func mustLast(list any) (any, error) {
 	tp := reflect.TypeOf(list).Kind()
 	switch tp {
@@ -142,6 +153,9 @@ func mustLast(list any) (any, error) {
 	}
 }
 
+// first returns the first element of a list (slice or array).
+// If the list is empty, it returns nil.
+// This function will panic if the argument is not a slice or array.
 func first(list any) any {
 	l, err := mustFirst(list)
 	if err != nil {
@@ -151,6 +165,8 @@ func first(list any) any {
 	return l
 }
 
+// mustFirst is the implementation of first that returns an error instead of panicking.
+// It returns the first element of the list or nil if the list is empty.
 func mustFirst(list any) (any, error) {
 	tp := reflect.TypeOf(list).Kind()
 	switch tp {
@@ -168,6 +184,9 @@ func mustFirst(list any) (any, error) {
 	}
 }
 
+// rest returns all elements of a list except the first one.
+// If the list is empty, it returns nil.
+// This function will panic if the argument is not a slice or array.
 func rest(list any) []any {
 	l, err := mustRest(list)
 	if err != nil {
@@ -177,28 +196,30 @@ func rest(list any) []any {
 	return l
 }
 
+// mustRest is the implementation of rest that returns an error instead of panicking.
+// It returns all elements of the list except the first one, or nil if the list is empty.
 func mustRest(list any) ([]any, error) {
 	tp := reflect.TypeOf(list).Kind()
 	switch tp {
 	case reflect.Slice, reflect.Array:
 		l2 := reflect.ValueOf(list)
-
 		l := l2.Len()
 		if l == 0 {
 			return nil, nil
 		}
-
 		nl := make([]any, l-1)
 		for i := 1; i < l; i++ {
 			nl[i-1] = l2.Index(i).Interface()
 		}
-
 		return nl, nil
 	default:
 		return nil, fmt.Errorf("cannot find rest on type %s", tp)
 	}
 }
 
+// initial returns all elements of a list except the last one.
+// If the list is empty, it returns nil.
+// This function will panic if the argument is not a slice or array.
 func initial(list any) []any {
 	l, err := mustInitial(list)
 	if err != nil {
@@ -208,28 +229,30 @@ func initial(list any) []any {
 	return l
 }
 
+// mustInitial is the implementation of initial that returns an error instead of panicking.
+// It returns all elements of the list except the last one, or nil if the list is empty.
 func mustInitial(list any) ([]any, error) {
 	tp := reflect.TypeOf(list).Kind()
 	switch tp {
 	case reflect.Slice, reflect.Array:
 		l2 := reflect.ValueOf(list)
-
 		l := l2.Len()
 		if l == 0 {
 			return nil, nil
 		}
-
 		nl := make([]any, l-1)
 		for i := 0; i < l-1; i++ {
 			nl[i] = l2.Index(i).Interface()
 		}
-
 		return nl, nil
 	default:
 		return nil, fmt.Errorf("cannot find initial on type %s", tp)
 	}
 }
 
+// sortAlpha sorts a list of strings alphabetically.
+// If the input is not a slice or array, it returns a single-element slice
+// containing the string representation of the input.
 func sortAlpha(list any) []string {
 	k := reflect.Indirect(reflect.ValueOf(list)).Kind()
 	switch k {
@@ -242,6 +265,8 @@ func sortAlpha(list any) []string {
 	return []string{strval(list)}
 }
 
+// reverse returns a new list with the elements in reverse order.
+// This function will panic if the argument is not a slice or array.
 func reverse(v any) []any {
 	l, err := mustReverse(v)
 	if err != nil {
@@ -251,42 +276,45 @@ func reverse(v any) []any {
 	return l
 }
 
+// mustReverse is the implementation of reverse that returns an error instead of panicking.
+// It returns a new list with the elements in reverse order.
 func mustReverse(v any) ([]any, error) {
 	tp := reflect.TypeOf(v).Kind()
 	switch tp {
 	case reflect.Slice, reflect.Array:
 		l2 := reflect.ValueOf(v)
-
 		l := l2.Len()
 		// We do not sort in place because the incoming array should not be altered.
 		nl := make([]any, l)
 		for i := 0; i < l; i++ {
 			nl[l-i-1] = l2.Index(i).Interface()
 		}
-
 		return nl, nil
 	default:
 		return nil, fmt.Errorf("cannot find reverse on type %s", tp)
 	}
 }
 
+// compact returns a new list with all "empty" elements removed.
+// An element is considered empty if it's nil, zero, an empty string, or an empty collection.
+// This function will panic if the argument is not a slice or array.
 func compact(list any) []any {
 	l, err := mustCompact(list)
 	if err != nil {
 		panic(err)
 	}
-
 	return l
 }
 
+// mustCompact is the implementation of compact that returns an error instead of panicking.
+// It returns a new list with all "empty" elements removed.
 func mustCompact(list any) ([]any, error) {
 	tp := reflect.TypeOf(list).Kind()
 	switch tp {
 	case reflect.Slice, reflect.Array:
 		l2 := reflect.ValueOf(list)
-
 		l := l2.Len()
-		nl := []any{}
+		var nl []any
 		var item any
 		for i := 0; i < l; i++ {
 			item = l2.Index(i).Interface()
@@ -294,30 +322,32 @@ func mustCompact(list any) ([]any, error) {
 				nl = append(nl, item)
 			}
 		}
-
 		return nl, nil
 	default:
 		return nil, fmt.Errorf("cannot compact on type %s", tp)
 	}
 }
 
+// uniq returns a new list with duplicate elements removed.
+// The first occurrence of each element is kept.
+// This function will panic if the argument is not a slice or array.
 func uniq(list any) []any {
 	l, err := mustUniq(list)
 	if err != nil {
 		panic(err)
 	}
-
 	return l
 }
 
+// mustUniq is the implementation of uniq that returns an error instead of panicking.
+// It returns a new list with duplicate elements removed.
 func mustUniq(list any) ([]any, error) {
 	tp := reflect.TypeOf(list).Kind()
 	switch tp {
 	case reflect.Slice, reflect.Array:
 		l2 := reflect.ValueOf(list)
-
 		l := l2.Len()
-		dest := []any{}
+		var dest []any
 		var item any
 		for i := 0; i < l; i++ {
 			item = l2.Index(i).Interface()
@@ -325,13 +355,15 @@ func mustUniq(list any) ([]any, error) {
 				dest = append(dest, item)
 			}
 		}
-
 		return dest, nil
 	default:
 		return nil, fmt.Errorf("cannot find uniq on type %s", tp)
 	}
 }
 
+// inList checks if a value is present in a list.
+// It uses deep equality comparison to check for matches.
+// Returns true if the value is found, false otherwise.
 func inList(haystack []any, needle any) bool {
 	for _, h := range haystack {
 		if reflect.DeepEqual(needle, h) {
@@ -341,21 +373,23 @@ func inList(haystack []any, needle any) bool {
 	return false
 }
 
+// without returns a new list with all occurrences of the specified values removed.
+// This function will panic if the first argument is not a slice or array.
 func without(list any, omit ...any) []any {
 	l, err := mustWithout(list, omit...)
 	if err != nil {
 		panic(err)
 	}
-
 	return l
 }
 
+// mustWithout is the implementation of without that returns an error instead of panicking.
+// It returns a new list with all occurrences of the specified values removed.
 func mustWithout(list any, omit ...any) ([]any, error) {
 	tp := reflect.TypeOf(list).Kind()
 	switch tp {
 	case reflect.Slice, reflect.Array:
 		l2 := reflect.ValueOf(list)
-
 		l := l2.Len()
 		res := []any{}
 		var item any
@@ -365,22 +399,25 @@ func mustWithout(list any, omit ...any) ([]any, error) {
 				res = append(res, item)
 			}
 		}
-
 		return res, nil
 	default:
 		return nil, fmt.Errorf("cannot find without on type %s", tp)
 	}
 }
 
+// has checks if a value is present in a list.
+// Returns true if the value is found, false otherwise.
+// This function will panic if the second argument is not a slice or array.
 func has(needle any, haystack any) bool {
 	l, err := mustHas(needle, haystack)
 	if err != nil {
 		panic(err)
 	}
-
 	return l
 }
 
+// mustHas is the implementation of has that returns an error instead of panicking.
+// It checks if a value is present in a list.
 func mustHas(needle any, haystack any) (bool, error) {
 	if haystack == nil {
 		return false, nil
@@ -397,38 +434,41 @@ func mustHas(needle any, haystack any) (bool, error) {
 				return true, nil
 			}
 		}
-
 		return false, nil
 	default:
 		return false, fmt.Errorf("cannot find has on type %s", tp)
 	}
 }
 
+// slice extracts a portion of a list based on the provided indices.
+// Usage examples:
 // $list := [1, 2, 3, 4, 5]
 // slice $list     -> list[0:5] = list[:]
 // slice $list 0 3 -> list[0:3] = list[:3]
 // slice $list 3 5 -> list[3:5]
 // slice $list 3   -> list[3:5] = list[3:]
+//
+// This function will panic if the first argument is not a slice or array.
 func slice(list any, indices ...any) any {
 	l, err := mustSlice(list, indices...)
 	if err != nil {
 		panic(err)
 	}
-
 	return l
 }
 
+// mustSlice is the implementation of slice that returns an error instead of panicking.
+// It extracts a portion of a list based on the provided indices.
 func mustSlice(list any, indices ...any) (any, error) {
 	tp := reflect.TypeOf(list).Kind()
 	switch tp {
 	case reflect.Slice, reflect.Array:
 		l2 := reflect.ValueOf(list)
-
 		l := l2.Len()
 		if l == 0 {
 			return nil, nil
 		}
-
+		// Determine start and end indices
 		var start, end int
 		if len(indices) > 0 {
 			start = toInt(indices[0])
@@ -438,13 +478,15 @@ func mustSlice(list any, indices ...any) (any, error) {
 		} else {
 			end = toInt(indices[1])
 		}
-
 		return l2.Slice(start, end).Interface(), nil
 	default:
 		return nil, fmt.Errorf("list should be type of slice or array but %s", tp)
 	}
 }
 
+// concat combines multiple lists into a single list.
+// It takes any number of lists and returns a new list containing all elements.
+// This function will panic if any argument is not a slice or array.
 func concat(lists ...any) any {
 	var res []any
 	for _, list := range lists {

+ 3 - 0
util/sprig/list_test.go

@@ -1,6 +1,7 @@
 package sprig
 
 import (
+	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -68,6 +69,8 @@ func TestMustChunk(t *testing.T) {
 	for tpl, expect := range tests {
 		assert.NoError(t, runt(tpl, expect))
 	}
+	err := runt(`{{ tuple `+strings.Repeat(" 0", 10001)+` | mustChunk 1 }}`, "a")
+	assert.ErrorContains(t, err, "number of chunks 10001 exceeds maximum limit of 10000")
 }
 
 func TestPrepend(t *testing.T) {

+ 233 - 5
util/sprig/numeric.go

@@ -9,7 +9,20 @@ import (
 	"strings"
 )
 
-// toFloat64 converts 64-bit floats
+// toFloat64 converts a value to a 64-bit float.
+// It handles various input types:
+// - string: parsed as a float, returns 0 if parsing fails
+// - integer types: converted to float64
+// - unsigned integer types: converted to float64
+// - float types: returned as is
+// - bool: true becomes 1.0, false becomes 0.0
+// - other types: returns 0.0
+//
+// Parameters:
+//   - v: The value to convert to float64
+//
+// Returns:
+//   - float64: The converted value
 func toFloat64(v any) float64 {
 	if str, ok := v.(string); ok {
 		iv, err := strconv.ParseFloat(str, 64)
@@ -39,12 +52,27 @@ func toFloat64(v any) float64 {
 	}
 }
 
+// toInt converts a value to a 32-bit integer.
+// This is a wrapper around toInt64 that casts the result to int.
+//
+// Parameters:
+//   - v: The value to convert to int
+//
+// Returns:
+//   - int: The converted value
 func toInt(v any) int {
 	// It's not optimal. But I don't want duplicate toInt64 code.
 	return int(toInt64(v))
 }
 
-// toInt64 converts integer types to 64-bit integers
+// toInt64 converts a value to a 64-bit integer.
+// It handles various input types:
+// - string: parsed as an integer, returns 0 if parsing fails
+// - integer types: converted to int64
+// - unsigned integer types: converted to int64 (values > MaxInt64 become MaxInt64)
+// - float types: truncated to int64
+// - bool: true becomes 1, false becomes 0
+// - other types: returns 0
 func toInt64(v any) int64 {
 	if str, ok := v.(string); ok {
 		iv, err := strconv.ParseInt(str, 10, 64)
@@ -53,7 +81,6 @@ func toInt64(v any) int64 {
 		}
 		return iv
 	}
-
 	val := reflect.Indirect(reflect.ValueOf(v))
 	switch val.Kind() {
 	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
@@ -79,10 +106,26 @@ func toInt64(v any) int64 {
 	}
 }
 
+// add1 increments a value by 1.
+// The input is first converted to int64 using toInt64.
+//
+// Parameters:
+//   - i: The value to increment
+//
+// Returns:
+//   - int64: The incremented value
 func add1(i any) int64 {
 	return toInt64(i) + 1
 }
 
+// add sums all the provided values.
+// All inputs are converted to int64 using toInt64 before addition.
+//
+// Parameters:
+//   - i: A variadic list of values to sum
+//
+// Returns:
+//   - int64: The sum of all values
 func add(i ...any) int64 {
 	var a int64
 	for _, b := range i {
@@ -91,18 +134,61 @@ func add(i ...any) int64 {
 	return a
 }
 
+// sub subtracts the second value from the first.
+// Both inputs are converted to int64 using toInt64 before subtraction.
+//
+// Parameters:
+//   - a: The value to subtract from
+//   - b: The value to subtract
+//
+// Returns:
+//   - int64: The result of a - b
 func sub(a, b any) int64 {
 	return toInt64(a) - toInt64(b)
 }
 
+// div divides the first value by the second.
+// Both inputs are converted to int64 using toInt64 before division.
+// Note: This performs integer division, so the result is truncated.
+//
+// Parameters:
+//   - a: The dividend
+//   - b: The divisor
+//
+// Returns:
+//   - int64: The result of a / b
+//
+// Panics:
+//   - If b evaluates to 0 (division by zero)
 func div(a, b any) int64 {
 	return toInt64(a) / toInt64(b)
 }
 
+// mod returns the remainder of dividing the first value by the second.
+// Both inputs are converted to int64 using toInt64 before the modulo operation.
+//
+// Parameters:
+//   - a: The dividend
+//   - b: The divisor
+//
+// Returns:
+//   - int64: The remainder of a / b
+//
+// Panics:
+//   - If b evaluates to 0 (modulo by zero)
 func mod(a, b any) int64 {
 	return toInt64(a) % toInt64(b)
 }
 
+// mul multiplies all the provided values.
+// All inputs are converted to int64 using toInt64 before multiplication.
+//
+// Parameters:
+//   - a: The first value to multiply
+//   - v: Additional values to multiply with a
+//
+// Returns:
+//   - int64: The product of all values
 func mul(a any, v ...any) int64 {
 	val := toInt64(a)
 	for _, b := range v {
@@ -111,10 +197,30 @@ func mul(a any, v ...any) int64 {
 	return val
 }
 
+// randInt generates a random integer between min (inclusive) and max (exclusive).
+//
+// Parameters:
+//   - min: The lower bound (inclusive)
+//   - max: The upper bound (exclusive)
+//
+// Returns:
+//   - int: A random integer in the range [min, max)
+//
+// Panics:
+//   - If max <= min (via rand.Intn)
 func randInt(min, max int) int {
 	return rand.Intn(max-min) + min
 }
 
+// maxAsInt64 returns the maximum value from a list of values as an int64.
+// All inputs are converted to int64 using toInt64 before comparison.
+//
+// Parameters:
+//   - a: The first value to compare
+//   - i: Additional values to compare
+//
+// Returns:
+//   - int64: The maximum value from all inputs
 func maxAsInt64(a any, i ...any) int64 {
 	aa := toInt64(a)
 	for _, b := range i {
@@ -126,6 +232,15 @@ func maxAsInt64(a any, i ...any) int64 {
 	return aa
 }
 
+// maxAsFloat64 returns the maximum value from a list of values as a float64.
+// All inputs are converted to float64 using toFloat64 before comparison.
+//
+// Parameters:
+//   - a: The first value to compare
+//   - i: Additional values to compare
+//
+// Returns:
+//   - float64: The maximum value from all inputs
 func maxAsFloat64(a any, i ...any) float64 {
 	m := toFloat64(a)
 	for _, b := range i {
@@ -134,6 +249,15 @@ func maxAsFloat64(a any, i ...any) float64 {
 	return m
 }
 
+// minAsInt64 returns the minimum value from a list of values as an int64.
+// All inputs are converted to int64 using toInt64 before comparison.
+//
+// Parameters:
+//   - a: The first value to compare
+//   - i: Additional values to compare
+//
+// Returns:
+//   - int64: The minimum value from all inputs
 func minAsInt64(a any, i ...any) int64 {
 	aa := toInt64(a)
 	for _, b := range i {
@@ -145,6 +269,15 @@ func minAsInt64(a any, i ...any) int64 {
 	return aa
 }
 
+// minAsFloat64 returns the minimum value from a list of values as a float64.
+// All inputs are converted to float64 using toFloat64 before comparison.
+//
+// Parameters:
+//   - a: The first value to compare
+//   - i: Additional values to compare
+//
+// Returns:
+//   - float64: The minimum value from all inputs
 func minAsFloat64(a any, i ...any) float64 {
 	m := toFloat64(a)
 	for _, b := range i {
@@ -153,6 +286,14 @@ func minAsFloat64(a any, i ...any) float64 {
 	return m
 }
 
+// until generates a sequence of integers from 0 to count (exclusive).
+// If count is negative, it generates a sequence from 0 to count (inclusive) with step -1.
+//
+// Parameters:
+//   - count: The end value (exclusive if positive, inclusive if negative)
+//
+// Returns:
+//   - []int: A slice containing the generated sequence
 func until(count int) []int {
 	step := 1
 	if count < 0 {
@@ -161,6 +302,23 @@ func until(count int) []int {
 	return untilStep(0, count, step)
 }
 
+// untilStep generates a sequence of integers from start to stop with the specified step.
+// The sequence is generated as follows:
+// - If step is 0, returns an empty slice
+// - If stop < start and step < 0, generates a decreasing sequence from start to stop (exclusive)
+// - If stop > start and step > 0, generates an increasing sequence from start to stop (exclusive)
+// - Otherwise, returns an empty slice
+//
+// Parameters:
+//   - start: The starting value (inclusive)
+//   - stop: The ending value (exclusive)
+//   - step: The increment between values
+//
+// Returns:
+//   - []int: A slice containing the generated sequence
+//
+// Panics:
+//   - If the number of iterations would exceed loopExecutionLimit
 func untilStep(start, stop, step int) []int {
 	var v []int
 	if step == 0 {
@@ -188,14 +346,44 @@ func untilStep(start, stop, step int) []int {
 	return v
 }
 
+// floor returns the greatest integer value less than or equal to the input.
+// The input is first converted to float64 using toFloat64.
+//
+// Parameters:
+//   - a: The value to floor
+//
+// Returns:
+//   - float64: The greatest integer value less than or equal to a
 func floor(a any) float64 {
 	return math.Floor(toFloat64(a))
 }
 
+// ceil returns the least integer value greater than or equal to the input.
+// The input is first converted to float64 using toFloat64.
+//
+// Parameters:
+//   - a: The value to ceil
+//
+// Returns:
+//   - float64: The least integer value greater than or equal to a
 func ceil(a any) float64 {
 	return math.Ceil(toFloat64(a))
 }
 
+// round rounds a number to a specified number of decimal places.
+// The input is first converted to float64 using toFloat64.
+//
+// Parameters:
+//   - a: The value to round
+//   - p: The number of decimal places to round to
+//   - rOpt: Optional rounding threshold (default is 0.5)
+//
+// Returns:
+//   - float64: The rounded value
+//
+// Examples:
+//   - round(3.14159, 2) returns 3.14
+//   - round(3.14159, 2, 0.6) returns 3.14 (only rounds up if fraction ≥ 0.6)
 func round(a any, p int, rOpt ...float64) float64 {
 	roundOn := .5
 	if len(rOpt) > 0 {
@@ -203,7 +391,6 @@ func round(a any, p int, rOpt ...float64) float64 {
 	}
 	val := toFloat64(a)
 	places := toFloat64(p)
-
 	var round float64
 	pow := math.Pow(10, places)
 	digit := pow * val
@@ -216,7 +403,15 @@ func round(a any, p int, rOpt ...float64) float64 {
 	return round / pow
 }
 
-// converts unix octal to decimal
+// toDecimal converts a value from octal to decimal.
+// The input is first converted to a string using fmt.Sprint, then parsed as an octal number.
+// If the parsing fails, it returns 0.
+//
+// Parameters:
+//   - v: The octal value to convert
+//
+// Returns:
+//   - int64: The decimal representation of the octal value
 func toDecimal(v any) int64 {
 	result, err := strconv.ParseInt(fmt.Sprint(v), 8, 64)
 	if err != nil {
@@ -225,11 +420,34 @@ func toDecimal(v any) int64 {
 	return result
 }
 
+// atoi converts a string to an integer.
+// If the conversion fails, it returns 0.
+//
+// Parameters:
+//   - a: The string to convert
+//
+// Returns:
+//   - int: The integer value of the string
 func atoi(a string) int {
 	i, _ := strconv.Atoi(a)
 	return i
 }
 
+// seq generates a sequence of integers and returns them as a space-delimited string.
+// The behavior depends on the number of parameters:
+// - 0 params: Returns an empty string
+// - 1 param: Generates sequence from 1 to param[0]
+// - 2 params: Generates sequence from param[0] to param[1]
+// - 3 params: Generates sequence from param[0] to param[2] with step param[1]
+//
+// If the end is less than the start, the sequence will be decreasing unless
+// a positive step is explicitly provided (which would result in an empty string).
+//
+// Parameters:
+//   - params: Variable number of integers defining the sequence
+//
+// Returns:
+//   - string: A space-delimited string of the generated sequence
 func seq(params ...int) string {
 	increment := 1
 	switch len(params) {
@@ -266,6 +484,16 @@ func seq(params ...int) string {
 	}
 }
 
+// intArrayToString converts a slice of integers to a space-delimited string.
+// The function removes the square brackets that would normally appear when
+// converting a slice to a string.
+//
+// Parameters:
+//   - slice: The slice of integers to convert
+//   - delimiter: The delimiter to use between elements
+//
+// Returns:
+//   - string: A delimited string representation of the integer slice
 func intArrayToString(slice []int, delimiter string) string {
 	return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(slice)), delimiter), "[]")
 }

+ 42 - 0
util/sprig/reflect.go

@@ -6,23 +6,65 @@ import (
 )
 
 // typeIs returns true if the src is the type named in target.
+// It compares the type name of src with the target string.
+//
+// Parameters:
+//   - target: The type name to check against
+//   - src: The value whose type will be checked
+//
+// Returns:
+//   - bool: True if the type name of src matches target, false otherwise
 func typeIs(target string, src any) bool {
 	return target == typeOf(src)
 }
 
+// typeIsLike returns true if the src is the type named in target or a pointer to that type.
+// This is useful when you need to check for both a type and a pointer to that type.
+//
+// Parameters:
+//   - target: The type name to check against
+//   - src: The value whose type will be checked
+//
+// Returns:
+//   - bool: True if the type of src matches target or "*"+target, false otherwise
 func typeIsLike(target string, src any) bool {
 	t := typeOf(src)
 	return target == t || "*"+target == t
 }
 
+// typeOf returns the type of a value as a string.
+// It uses fmt.Sprintf with the %T format verb to get the type name.
+//
+// Parameters:
+//   - src: The value whose type name will be returned
+//
+// Returns:
+//   - string: The type name of src
 func typeOf(src any) string {
 	return fmt.Sprintf("%T", src)
 }
 
+// kindIs returns true if the kind of src matches the target kind.
+// This checks the underlying kind (e.g., "string", "int", "map") rather than the specific type.
+//
+// Parameters:
+//   - target: The kind name to check against
+//   - src: The value whose kind will be checked
+//
+// Returns:
+//   - bool: True if the kind of src matches target, false otherwise
 func kindIs(target string, src any) bool {
 	return target == kindOf(src)
 }
 
+// kindOf returns the kind of a value as a string.
+// The kind represents the specific Go type category (e.g., "string", "int", "map", "slice").
+//
+// Parameters:
+//   - src: The value whose kind will be returned
+//
+// Returns:
+//   - string: The kind of src as a string
 func kindOf(src any) string {
 	return reflect.ValueOf(src).Kind().String()
 }

+ 134 - 0
util/sprig/regex.go

@@ -4,20 +4,60 @@ import (
 	"regexp"
 )
 
+// regexMatch checks if a string matches a regular expression pattern.
+// It ignores any errors that might occur during regex compilation.
+//
+// Parameters:
+//   - regex: The regular expression pattern to match against
+//   - s: The string to check
+//
+// Returns:
+//   - bool: True if the string matches the pattern, false otherwise
 func regexMatch(regex string, s string) bool {
 	match, _ := regexp.MatchString(regex, s)
 	return match
 }
 
+// mustRegexMatch checks if a string matches a regular expression pattern.
+// Unlike regexMatch, this function returns any errors that occur during regex compilation.
+//
+// Parameters:
+//   - regex: The regular expression pattern to match against
+//   - s: The string to check
+//
+// Returns:
+//   - bool: True if the string matches the pattern, false otherwise
+//   - error: Any error that occurred during regex compilation
 func mustRegexMatch(regex string, s string) (bool, error) {
 	return regexp.MatchString(regex, s)
 }
 
+// regexFindAll finds all matches of a regular expression in a string.
+// It panics if the regex pattern cannot be compiled.
+//
+// Parameters:
+//   - regex: The regular expression pattern to search for
+//   - s: The string to search within
+//   - n: The maximum number of matches to return (negative means all matches)
+//
+// Returns:
+//   - []string: A slice containing all matched substrings
 func regexFindAll(regex string, s string, n int) []string {
 	r := regexp.MustCompile(regex)
 	return r.FindAllString(s, n)
 }
 
+// mustRegexFindAll finds all matches of a regular expression in a string.
+// Unlike regexFindAll, this function returns any errors that occur during regex compilation.
+//
+// Parameters:
+//   - regex: The regular expression pattern to search for
+//   - s: The string to search within
+//   - n: The maximum number of matches to return (negative means all matches)
+//
+// Returns:
+//   - []string: A slice containing all matched substrings
+//   - error: Any error that occurred during regex compilation
 func mustRegexFindAll(regex string, s string, n int) ([]string, error) {
 	r, err := regexp.Compile(regex)
 	if err != nil {
@@ -26,11 +66,30 @@ func mustRegexFindAll(regex string, s string, n int) ([]string, error) {
 	return r.FindAllString(s, n), nil
 }
 
+// regexFind finds the first match of a regular expression in a string.
+// It panics if the regex pattern cannot be compiled.
+//
+// Parameters:
+//   - regex: The regular expression pattern to search for
+//   - s: The string to search within
+//
+// Returns:
+//   - string: The first matched substring, or an empty string if no match
 func regexFind(regex string, s string) string {
 	r := regexp.MustCompile(regex)
 	return r.FindString(s)
 }
 
+// mustRegexFind finds the first match of a regular expression in a string.
+// Unlike regexFind, this function returns any errors that occur during regex compilation.
+//
+// Parameters:
+//   - regex: The regular expression pattern to search for
+//   - s: The string to search within
+//
+// Returns:
+//   - string: The first matched substring, or an empty string if no match
+//   - error: Any error that occurred during regex compilation
 func mustRegexFind(regex string, s string) (string, error) {
 	r, err := regexp.Compile(regex)
 	if err != nil {
@@ -39,11 +98,34 @@ func mustRegexFind(regex string, s string) (string, error) {
 	return r.FindString(s), nil
 }
 
+// regexReplaceAll replaces all matches of a regular expression with a replacement string.
+// It panics if the regex pattern cannot be compiled.
+// The replacement string can contain $1, $2, etc. for submatches.
+//
+// Parameters:
+//   - regex: The regular expression pattern to search for
+//   - s: The string to search within
+//   - repl: The replacement string (can contain $1, $2, etc. for submatches)
+//
+// Returns:
+//   - string: The resulting string after all replacements
 func regexReplaceAll(regex string, s string, repl string) string {
 	r := regexp.MustCompile(regex)
 	return r.ReplaceAllString(s, repl)
 }
 
+// mustRegexReplaceAll replaces all matches of a regular expression with a replacement string.
+// Unlike regexReplaceAll, this function returns any errors that occur during regex compilation.
+// The replacement string can contain $1, $2, etc. for submatches.
+//
+// Parameters:
+//   - regex: The regular expression pattern to search for
+//   - s: The string to search within
+//   - repl: The replacement string (can contain $1, $2, etc. for submatches)
+//
+// Returns:
+//   - string: The resulting string after all replacements
+//   - error: Any error that occurred during regex compilation
 func mustRegexReplaceAll(regex string, s string, repl string) (string, error) {
 	r, err := regexp.Compile(regex)
 	if err != nil {
@@ -52,11 +134,34 @@ func mustRegexReplaceAll(regex string, s string, repl string) (string, error) {
 	return r.ReplaceAllString(s, repl), nil
 }
 
+// regexReplaceAllLiteral replaces all matches of a regular expression with a literal replacement string.
+// It panics if the regex pattern cannot be compiled.
+// Unlike regexReplaceAll, the replacement string is used literally (no $1, $2 processing).
+//
+// Parameters:
+//   - regex: The regular expression pattern to search for
+//   - s: The string to search within
+//   - repl: The literal replacement string
+//
+// Returns:
+//   - string: The resulting string after all replacements
 func regexReplaceAllLiteral(regex string, s string, repl string) string {
 	r := regexp.MustCompile(regex)
 	return r.ReplaceAllLiteralString(s, repl)
 }
 
+// mustRegexReplaceAllLiteral replaces all matches of a regular expression with a literal replacement string.
+// Unlike regexReplaceAllLiteral, this function returns any errors that occur during regex compilation.
+// The replacement string is used literally (no $1, $2 processing).
+//
+// Parameters:
+//   - regex: The regular expression pattern to search for
+//   - s: The string to search within
+//   - repl: The literal replacement string
+//
+// Returns:
+//   - string: The resulting string after all replacements
+//   - error: Any error that occurred during regex compilation
 func mustRegexReplaceAllLiteral(regex string, s string, repl string) (string, error) {
 	r, err := regexp.Compile(regex)
 	if err != nil {
@@ -65,11 +170,32 @@ func mustRegexReplaceAllLiteral(regex string, s string, repl string) (string, er
 	return r.ReplaceAllLiteralString(s, repl), nil
 }
 
+// regexSplit splits a string by a regular expression pattern.
+// It panics if the regex pattern cannot be compiled.
+//
+// Parameters:
+//   - regex: The regular expression pattern to split on
+//   - s: The string to split
+//   - n: The maximum number of substrings to return (negative means all substrings)
+//
+// Returns:
+//   - []string: A slice containing the substrings between regex matches
 func regexSplit(regex string, s string, n int) []string {
 	r := regexp.MustCompile(regex)
 	return r.Split(s, n)
 }
 
+// mustRegexSplit splits a string by a regular expression pattern.
+// Unlike regexSplit, this function returns any errors that occur during regex compilation.
+//
+// Parameters:
+//   - regex: The regular expression pattern to split on
+//   - s: The string to split
+//   - n: The maximum number of substrings to return (negative means all substrings)
+//
+// Returns:
+//   - []string: A slice containing the substrings between regex matches
+//   - error: Any error that occurred during regex compilation
 func mustRegexSplit(regex string, s string, n int) ([]string, error) {
 	r, err := regexp.Compile(regex)
 	if err != nil {
@@ -78,6 +204,14 @@ func mustRegexSplit(regex string, s string, n int) ([]string, error) {
 	return r.Split(s, n), nil
 }
 
+// regexQuoteMeta escapes all regular expression metacharacters in a string.
+// This is useful when you want to use a string as a literal in a regular expression.
+//
+// Parameters:
+//   - s: The string to escape
+//
+// Returns:
+//   - string: The escaped string with all regex metacharacters quoted
 func regexQuoteMeta(s string) string {
 	return regexp.QuoteMeta(s)
 }

+ 258 - 3
util/sprig/strings.go

@@ -11,10 +11,25 @@ import (
 	"strings"
 )
 
+// base64encode encodes a string to base64 using standard encoding.
+//
+// Parameters:
+//   - v: The string to encode
+//
+// Returns:
+//   - string: The base64 encoded string
 func base64encode(v string) string {
 	return base64.StdEncoding.EncodeToString([]byte(v))
 }
 
+// base64decode decodes a base64 encoded string.
+// If the input is not valid base64, it returns the error message as a string.
+//
+// Parameters:
+//   - v: The base64 encoded string to decode
+//
+// Returns:
+//   - string: The decoded string, or an error message if decoding fails
 func base64decode(v string) string {
 	data, err := base64.StdEncoding.DecodeString(v)
 	if err != nil {
@@ -23,10 +38,25 @@ func base64decode(v string) string {
 	return string(data)
 }
 
+// base32encode encodes a string to base32 using standard encoding.
+//
+// Parameters:
+//   - v: The string to encode
+//
+// Returns:
+//   - string: The base32 encoded string
 func base32encode(v string) string {
 	return base32.StdEncoding.EncodeToString([]byte(v))
 }
 
+// base32decode decodes a base32 encoded string.
+// If the input is not valid base32, it returns the error message as a string.
+//
+// Parameters:
+//   - v: The base32 encoded string to decode
+//
+// Returns:
+//   - string: The decoded string, or an error message if decoding fails
 func base32decode(v string) string {
 	data, err := base32.StdEncoding.DecodeString(v)
 	if err != nil {
@@ -35,6 +65,14 @@ func base32decode(v string) string {
 	return string(data)
 }
 
+// quote adds double quotes around each non-nil string in the input and joins them with spaces.
+// This uses Go's %q formatter which handles escaping special characters.
+//
+// Parameters:
+//   - str: A variadic list of values to quote
+//
+// Returns:
+//   - string: The quoted strings joined with spaces
 func quote(str ...any) string {
 	out := make([]string, 0, len(str))
 	for _, s := range str {
@@ -45,6 +83,14 @@ func quote(str ...any) string {
 	return strings.Join(out, " ")
 }
 
+// squote adds single quotes around each non-nil value in the input and joins them with spaces.
+// Unlike quote, this doesn't escape special characters.
+//
+// Parameters:
+//   - str: A variadic list of values to quote
+//
+// Returns:
+//   - string: The single-quoted values joined with spaces
 func squote(str ...any) string {
 	out := make([]string, 0, len(str))
 	for _, s := range str {
@@ -55,25 +101,69 @@ func squote(str ...any) string {
 	return strings.Join(out, " ")
 }
 
+// cat concatenates all non-nil values into a single string.
+// Nil values are removed before concatenation.
+//
+// Parameters:
+//   - v: A variadic list of values to concatenate
+//
+// Returns:
+//   - string: The concatenated string
 func cat(v ...any) string {
 	v = removeNilElements(v)
 	r := strings.TrimSpace(strings.Repeat("%v ", len(v)))
 	return fmt.Sprintf(r, v...)
 }
 
+// indent adds a specified number of spaces at the beginning of each line in a string.
+//
+// Parameters:
+//   - spaces: The number of spaces to add
+//   - v: The string to indent
+//
+// Returns:
+//   - string: The indented string
 func indent(spaces int, v string) string {
 	pad := strings.Repeat(" ", spaces)
 	return pad + strings.Replace(v, "\n", "\n"+pad, -1)
 }
 
+// nindent adds a newline followed by an indented string.
+// It's a shorthand for "\n" + indent(spaces, v).
+//
+// Parameters:
+//   - spaces: The number of spaces to add
+//   - v: The string to indent
+//
+// Returns:
+//   - string: A newline followed by the indented string
 func nindent(spaces int, v string) string {
 	return "\n" + indent(spaces, v)
 }
 
+// replace replaces all occurrences of a substring with another substring.
+//
+// Parameters:
+//   - old: The substring to replace
+//   - new: The replacement substring
+//   - src: The source string
+//
+// Returns:
+//   - string: The resulting string after all replacements
 func replace(old, new, src string) string {
 	return strings.Replace(src, old, new, -1)
 }
 
+// plural returns the singular or plural form of a word based on the count.
+// If count is 1, it returns the singular form, otherwise it returns the plural form.
+//
+// Parameters:
+//   - one: The singular form of the word
+//   - many: The plural form of the word
+//   - count: The count to determine which form to use
+//
+// Returns:
+//   - string: Either the singular or plural form based on the count
 func plural(one, many string, count int) string {
 	if count == 1 {
 		return one
@@ -81,6 +171,19 @@ func plural(one, many string, count int) string {
 	return many
 }
 
+// strslice converts a value to a slice of strings.
+// It handles various input types:
+// - []string: returned as is
+// - []any: converted to []string, skipping nil values
+// - arrays and slices: converted to []string, skipping nil values
+// - nil: returns an empty slice
+// - anything else: returns a single-element slice with the string representation
+//
+// Parameters:
+//   - v: The value to convert to a string slice
+//
+// Returns:
+//   - []string: A slice of strings
 func strslice(v any) []string {
 	switch v := v.(type) {
 	case []string:
@@ -116,6 +219,14 @@ func strslice(v any) []string {
 	}
 }
 
+// removeNilElements creates a new slice with all nil elements removed.
+// This is a helper function used by other functions like cat.
+//
+// Parameters:
+//   - v: The slice to process
+//
+// Returns:
+//   - []any: A new slice with all nil elements removed
 func removeNilElements(v []any) []any {
 	newSlice := make([]any, 0, len(v))
 	for _, i := range v {
@@ -126,6 +237,19 @@ func removeNilElements(v []any) []any {
 	return newSlice
 }
 
+// strval converts any value to a string.
+// It handles various types:
+// - string: returned as is
+// - []byte: converted to string
+// - error: returns the error message
+// - fmt.Stringer: calls the String() method
+// - anything else: uses fmt.Sprintf("%v", v)
+//
+// Parameters:
+//   - v: The value to convert to a string
+//
+// Returns:
+//   - string: The string representation of the value
 func strval(v any) string {
 	switch v := v.(type) {
 	case string:
@@ -141,6 +265,17 @@ func strval(v any) string {
 	}
 }
 
+// trunc truncates a string to a specified length.
+// If c is positive, it returns the first c characters.
+// If c is negative, it returns the last |c| characters.
+// If the string is shorter than the requested length, it returns the original string.
+//
+// Parameters:
+//   - c: The number of characters to keep (positive from start, negative from end)
+//   - s: The string to truncate
+//
+// Returns:
+//   - string: The truncated string
 func trunc(c int, s string) string {
 	if c < 0 && len(s)+c > 0 {
 		return s[len(s)+c:]
@@ -151,14 +286,40 @@ func trunc(c int, s string) string {
 	return s
 }
 
+// title converts a string to title case.
+// This uses the English language rules for capitalization.
+//
+// Parameters:
+//   - s: The string to convert
+//
+// Returns:
+//   - string: The string in title case
 func title(s string) string {
 	return cases.Title(language.English).String(s)
 }
 
+// join concatenates the elements of a slice with a separator.
+// The input is first converted to a string slice using strslice.
+//
+// Parameters:
+//   - sep: The separator to use between elements
+//   - v: The value to join (will be converted to a string slice)
+//
+// Returns:
+//   - string: The joined string
 func join(sep string, v any) string {
 	return strings.Join(strslice(v), sep)
 }
 
+// split splits a string by a separator and returns a map.
+// The keys in the map are "_0", "_1", etc., corresponding to the position of each part.
+//
+// Parameters:
+//   - sep: The separator to split on
+//   - orig: The string to split
+//
+// Returns:
+//   - map[string]string: A map with keys "_0", "_1", etc. and values being the split parts
 func split(sep, orig string) map[string]string {
 	parts := strings.Split(orig, sep)
 	res := make(map[string]string, len(parts))
@@ -168,10 +329,30 @@ func split(sep, orig string) map[string]string {
 	return res
 }
 
+// splitList splits a string by a separator and returns a slice.
+// This is a simple wrapper around strings.Split.
+//
+// Parameters:
+//   - sep: The separator to split on
+//   - orig: The string to split
+//
+// Returns:
+//   - []string: A slice containing the split parts
 func splitList(sep, orig string) []string {
 	return strings.Split(orig, sep)
 }
 
+// splitn splits a string by a separator with a limit and returns a map.
+// The keys in the map are "_0", "_1", etc., corresponding to the position of each part.
+// It will split the string into at most n parts.
+//
+// Parameters:
+//   - sep: The separator to split on
+//   - n: The maximum number of parts to return
+//   - orig: The string to split
+//
+// Returns:
+//   - map[string]string: A map with keys "_0", "_1", etc. and values being the split parts
 func splitn(sep string, n int, orig string) map[string]string {
 	parts := strings.SplitN(orig, sep, n)
 	res := make(map[string]string, len(parts))
@@ -182,12 +363,20 @@ func splitn(sep string, n int, orig string) map[string]string {
 }
 
 // substring creates a substring of the given string.
+// It extracts a portion of a string based on start and end indices.
 //
-// If start is < 0, this calls string[:end].
+// Parameters:
+//   - start: The starting index (inclusive)
+//   - end: The ending index (exclusive)
+//   - s: The source string
 //
-// If start is >= 0 and end < 0 or end bigger than s length, this calls string[start:]
+// Behavior:
+//   - If start < 0, returns s[:end]
+//   - If start >= 0 and end < 0 or end > len(s), returns s[start:]
+//   - Otherwise, returns s[start:end]
 //
-// Otherwise, this calls string[start, end].
+// Returns:
+//   - string: The extracted substring
 func substring(start, end int, s string) string {
 	if start < 0 {
 		return s[:end]
@@ -198,6 +387,19 @@ func substring(start, end int, s string) string {
 	return s[start:end]
 }
 
+// repeat creates a new string by repeating the input string a specified number of times.
+// It has safety limits to prevent excessive memory usage or infinite loops.
+//
+// Parameters:
+//   - count: The number of times to repeat the string
+//   - str: The string to repeat
+//
+// Returns:
+//   - string: The repeated string
+//
+// Panics:
+//   - If count exceeds loopExecutionLimit
+//   - If the resulting string length would exceed stringLengthLimit
 func repeat(count int, str string) string {
 	if count > loopExecutionLimit {
 		panic(fmt.Sprintf("repeat count %d exceeds limit of %d", count, loopExecutionLimit))
@@ -207,26 +409,79 @@ func repeat(count int, str string) string {
 	return strings.Repeat(str, count)
 }
 
+// trimAll removes all leading and trailing characters contained in the cutset.
+// Note that the parameter order is reversed from the standard strings.Trim function.
+//
+// Parameters:
+//   - a: The cutset of characters to remove
+//   - b: The string to trim
+//
+// Returns:
+//   - string: The trimmed string
 func trimAll(a, b string) string {
 	return strings.Trim(b, a)
 }
 
+// trimPrefix removes the specified prefix from a string.
+// If the string doesn't start with the prefix, it returns the original string.
+// Note that the parameter order is reversed from the standard strings.TrimPrefix function.
+//
+// Parameters:
+//   - a: The prefix to remove
+//   - b: The string to trim
+//
+// Returns:
+//   - string: The string with the prefix removed, or the original string if it doesn't start with the prefix
 func trimPrefix(a, b string) string {
 	return strings.TrimPrefix(b, a)
 }
 
+// trimSuffix removes the specified suffix from a string.
+// If the string doesn't end with the suffix, it returns the original string.
+// Note that the parameter order is reversed from the standard strings.TrimSuffix function.
+//
+// Parameters:
+//   - a: The suffix to remove
+//   - b: The string to trim
+//
+// Returns:
+//   - string: The string with the suffix removed, or the original string if it doesn't end with the suffix
 func trimSuffix(a, b string) string {
 	return strings.TrimSuffix(b, a)
 }
 
+// contains checks if a string contains a substring.
+//
+// Parameters:
+//   - substr: The substring to search for
+//   - str: The string to search in
+//
+// Returns:
+//   - bool: True if str contains substr, false otherwise
 func contains(substr string, str string) bool {
 	return strings.Contains(str, substr)
 }
 
+// hasPrefix checks if a string starts with a specified prefix.
+//
+// Parameters:
+//   - substr: The prefix to check for
+//   - str: The string to check
+//
+// Returns:
+//   - bool: True if str starts with substr, false otherwise
 func hasPrefix(substr string, str string) bool {
 	return strings.HasPrefix(str, substr)
 }
 
+// hasSuffix checks if a string ends with a specified suffix.
+//
+// Parameters:
+//   - substr: The suffix to check for
+//   - str: The string to check
+//
+// Returns:
+//   - bool: True if str ends with substr, false otherwise
 func hasSuffix(substr string, str string) bool {
 	return strings.HasSuffix(str, substr)
 }

+ 0 - 1
util/sprig/url.go

@@ -60,7 +60,6 @@ func urlJoin(d map[string]any) string {
 		}
 		user = tempURL.User
 	}
-
 	resURL.User = user
 	return resURL.String()
 }