mirror of
https://github.com/binwiederhier/ntfy.git
synced 2025-12-02 05:10:06 +01:00
AI docs
This commit is contained in:
parent
c807b5db21
commit
50c564d8a2
15 changed files with 1132 additions and 78 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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), "[]")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ func urlJoin(d map[string]any) string {
|
|||
}
|
||||
user = tempURL.User
|
||||
}
|
||||
|
||||
resURL.User = user
|
||||
return resURL.String()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue