package sprig import ( "encoding/base32" "encoding/base64" "fmt" "golang.org/x/text/cases" "golang.org/x/text/language" "reflect" "strconv" "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 { return err.Error() } 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 { return err.Error() } 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 { if s != nil { out = append(out, fmt.Sprintf("%q", strval(s))) } } 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 { if s != nil { out = append(out, fmt.Sprintf("'%v'", s)) } } 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 } 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: return v case []any: b := make([]string, 0, len(v)) for _, s := range v { if s != nil { b = append(b, strval(s)) } } return b default: val := reflect.ValueOf(v) switch val.Kind() { case reflect.Array, reflect.Slice: l := val.Len() b := make([]string, 0, l) for i := 0; i < l; i++ { value := val.Index(i).Interface() if value != nil { b = append(b, strval(value)) } } return b default: if v == nil { return []string{} } return []string{strval(v)} } } } // 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 { if i != nil { newSlice = append(newSlice, i) } } 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: return v case []byte: return string(v) case error: return v.Error() case fmt.Stringer: return v.String() default: return fmt.Sprintf("%v", v) } } // 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:] } if c >= 0 && len(s) > c { return s[:c] } 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)) for i, v := range parts { res["_"+strconv.Itoa(i)] = v } 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)) for i, v := range parts { res["_"+strconv.Itoa(i)] = v } return res } // substring creates a substring of the given string. // It extracts a portion of a string based on start and end indices. // // Parameters: // - start: The starting index (inclusive) // - end: The ending index (exclusive) // - s: The source string // // 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] // // Returns: // - string: The extracted substring func substring(start, end int, s string) string { if start < 0 { return s[:end] } if end < 0 || end > len(s) { return s[start:] } 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)) } else if count*len(str) >= stringLengthLimit { panic(fmt.Sprintf("repeat count %d with string length %d exceeds limit of %d", count, len(str), stringLengthLimit)) } 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) }