registry/strings/functions.go
package strings
import (
"fmt"
mathrand "math/rand"
"strings"
"unicode"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"github.com/go-sprout/sprout/internal/helpers"
)
// Nospace removes all whitespace characters from the provided string.
// It uses the unicode package to identify whitespace runes and removes them.
//
// Parameters:
//
// str string - the string from which to remove whitespace.
//
// Returns:
//
// string - the modified string with all whitespace characters removed.
//
// Example:
//
// {{ "Hello World" | nospace }} // Output: "HelloWorld"
func (sr *StringsRegistry) Nospace(str string) string {
return strings.Map(func(r rune) rune {
if unicode.IsSpace(r) {
return -1
}
return r
}, str)
}
// Trim removes leading and trailing whitespace from the string.
//
// Parameters:
//
// str string - the string to trim.
//
// Returns:
//
// string - the trimmed string.
//
// Example:
//
// {{ " Hello World " | trim }} // Output: "Hello World"
func (sr *StringsRegistry) Trim(str string) string {
return strings.TrimSpace(str)
}
// TrimAll removes all occurrences of any characters in 'cutset' from both the
// beginning and the end of 'str'.
//
// Parameters:
//
// cutset string - a string of characters to remove from the string.
// str string - the string to trim.
//
// Returns:
//
// string - the string with specified characters removed.
//
// Example:
//
// {{ "xyzHelloxyz" | trimAll "xyz" }} // Output: "Hello"
func (sr *StringsRegistry) TrimAll(cutset string, str string) string {
return strings.Trim(str, cutset)
}
// TrimPrefix removes the 'prefix' from the start of 'str' if present.
//
// Parameters:
//
// prefix string - the prefix to remove.
// str string - the string to trim.
//
// Returns:
//
// string - the string with the prefix removed if it was present.
//
// Example:
//
// {{ "HelloWorld" | trimPrefix "Hello" }} // Output: "World"
func (sr *StringsRegistry) TrimPrefix(prefix string, str string) string {
return strings.TrimPrefix(str, prefix)
}
// TrimSuffix removes the 'suffix' from the end of 'str' if present.
//
// Parameters:
//
// suffix string - the suffix to remove.
// str string - the string to trim.
//
// Returns:
//
// string - the string with the suffix removed if it was present.
//
// Example:
//
// {{ "HelloWorld" | trimSuffix "World" }} // Output: "Hello"
func (sr *StringsRegistry) TrimSuffix(suffix string, str string) string {
return strings.TrimSuffix(str, suffix)
}
// Contains checks if 'str' contains the 'substring'.
//
// Parameters:
//
// substring string - the substring to search for.
// str string - the string to search within.
//
// Returns:
//
// bool - true if 'str' contains 'substring', false otherwise.
//
// Example:
//
// {{ "Hello" | contains "ell" }} // Output: true
func (sr *StringsRegistry) Contains(substring string, str string) bool {
return strings.Contains(str, substring)
}
// HasPrefix checks if 'str' starts with the specified 'prefix'.
//
// Parameters:
//
// prefix string - the prefix to check.
// str string - the string to check.
//
// Returns:
//
// bool - true if 'str' starts with 'prefix', false otherwise.
//
// Example:
//
// {{ "HelloWorld" | hasPrefix "Hello" }} // Output: true
func (sr *StringsRegistry) HasPrefix(prefix string, str string) bool {
return strings.HasPrefix(str, prefix)
}
// HasSuffix checks if 'str' ends with the specified 'suffix'.
//
// Parameters:
//
// suffix string - the suffix to check.
// str string - the string to check.
//
// Returns:
//
// bool - true if 'str' ends with 'suffix', false otherwise.
//
// Example:
//
// {{ "HelloWorld" | hasSuffix "World" }} // Output: true
func (sr *StringsRegistry) HasSuffix(suffix string, str string) bool {
return strings.HasSuffix(str, suffix)
}
// ToLower converts all characters in the provided string to lowercase.
//
// Parameters:
//
// str string - the string to convert.
//
// Returns:
//
// string - the lowercase version of the input string.
//
// Example:
//
// {{ "HELLO WORLD" | toLower }} // Output: "hello world"
func (sr *StringsRegistry) ToLower(str string) string {
return strings.ToLower(str)
}
// ToUpper converts all characters in the provided string to uppercase.
//
// Parameters:
//
// str string - the string to convert.
//
// Returns:
//
// string - the uppercase version of the input string.
//
// Example:
//
// {{ "hello world" | toUpper }} // Output: "HELLO WORLD"
func (sr *StringsRegistry) ToUpper(str string) string {
return strings.ToUpper(str)
}
// Replace replaces all occurrences of 'old' in 'src' with 'new'.
//
// Parameters:
//
// old string - the substring to be replaced.
// new string - the substring to replace with.
// src string - the source string where replacements take place.
//
// Returns:
//
// string - the modified string after all replacements.
//
// Example:
//
// {{ "banana" | replace "a", "o" }} // Output: "bonono"
func (sr *StringsRegistry) Replace(old, new, src string) string {
return strings.ReplaceAll(src, old, new)
}
// Repeat repeats the string 'str' for 'count' times.
//
// Parameters:
//
// count int - the number of times to repeat.
// str string - the string to repeat.
//
// Returns:
//
// string - the repeated string.
//
// Example:
//
// {{ "ha" | repeat 3 }} // Output: "hahaha"
func (sr *StringsRegistry) Repeat(count int, str string) string {
return strings.Repeat(str, count)
}
// Join concatenates the elements of a slice into a single string separated by 'sep'.
// The slice is extracted from 'v', which can be any slice input. The function
// uses 'Strslice' to convert 'v' to a slice of strings if necessary.
//
// Parameters:
//
// sep string - the separator string.
// v any - the slice to join, can be of any slice type.
//
// Returns:
//
// string - the concatenated string.
//
// Example:
//
// {{ $list := slice "apple" "banana" "cherry" }}
// {{ $list | join ", " }} // Output: "apple, banana, cherry"
func (sr *StringsRegistry) Join(sep string, v any) string {
return strings.Join(helpers.StrSlice(v), sep)
}
// Trunc truncates 's' to a maximum length 'count'. If 'count' is negative, it removes
// '-count' characters from the beginning of the string.
//
// Parameters:
//
// count int - the number of characters to keep. Negative values indicate truncation
// from the beginning.
// str string - the string to truncate.
//
// Returns:
//
// string - the truncated string.
//
// Example:
//
// {{ "Hello World" | trunc 5 }} // Output: "Hello"
// {{ "Hello World" | trunc -5 }} // Output: "World"
func (sr *StringsRegistry) Trunc(count int, str string) string {
length := len(str)
if count < 0 && length+count > 0 {
return str[length+count:]
}
if count >= 0 && length > count {
return str[:count]
}
return str
}
// Shuffle randomly rearranges the characters in 'str'.
//
// Parameters:
//
// str string - the string to shuffle.
//
// Returns:
//
// string - the shuffled string.
//
// Example:
//
// {{ "hello" | shuffle }} // Output: "loleh" (output may vary due to randomness)
func (sr *StringsRegistry) Shuffle(str string) string {
r := []rune(str)
mathrand.New(randSource).Shuffle(len(r), func(i, j int) {
r[i], r[j] = r[j], r[i]
})
return string(r)
}
// Ellipsis truncates 'str' to 'maxWidth' and appends an ellipsis if the string
// is longer than 'maxWidth'.
//
// Parameters:
//
// maxWidth int - the maximum width of the string including the ellipsis.
// str string - the string to truncate.
//
// Returns:
//
// string - the possibly truncated string with an ellipsis.
//
// Example:
//
// {{ "Hello World" | ellipsis 10 }} // Output: "Hello W..."
func (sr *StringsRegistry) Ellipsis(maxWidth int, str string) string {
return sr.ellipsis(str, 0, maxWidth)
}
// EllipsisBoth truncates 'str' from both ends, preserving the middle part of
// the string and appending ellipses to both ends if needed.
//
// Parameters:
//
// offset int - starting position for preserving text.
// maxWidth int - the total maximum width including ellipses.
// str string - the string to truncate.
//
// Returns:
//
// string - the truncated string with ellipses on both ends.
//
// Example:
//
// {{ "Hello World" | ellipsisBoth 1 10 }} // Output: "...lo Wor..."
func (sr *StringsRegistry) EllipsisBoth(offset int, maxWidth int, str string) string {
return sr.ellipsis(str, offset, maxWidth)
}
// Initials extracts the initials from 'str', using optional 'delimiters' to
// determine word boundaries.
//
// Parameters:
//
// str string - the string from which to extract initials.
// delimiters string - optional string containing delimiter characters.
//
// Returns:
//
// string - the initials of the words in 'str'.
//
// Example:
//
// {{ "John Doe" | initials }} // Output: "JD"
func (sr *StringsRegistry) Initials(str string) string {
return sr.initials(str, " ")
}
// Plural returns 'one' if 'count' is 1, otherwise it returns 'many'.
//
// Parameters:
//
// one string - the string to return if 'count' is 1.
// many string - the string to return if 'count' is not 1.
// count int - the number used to determine which string to return.
//
// Returns:
//
// string - either 'one' or 'many' based on 'count'.
//
// Example:
//
// {{ 1 | plural "apple" "apples" }} // Output: "apple"
// {{ 2 | plural "apple" "apples" }} // Output: "apples"
func (sr *StringsRegistry) Plural(one, many string, count int) string {
if count == 1 {
return one
}
return many
}
// Wrap breaks 'str' into lines with a maximum length of 'length'.
// It ensures that words are not split across lines unless necessary.
//
// Parameters:
//
// length int - the maximum length of each line.
// str string - the string to be wrapped.
//
// Returns:
//
// string - the wrapped string using newline characters to separate lines.
//
// Example:
//
// {{ "This is a long string that needs to be wrapped." | wrap 10 }}
// Output: "This is a\nlong\nstring\nthat needs\nto be\nwrapped."
func (sr *StringsRegistry) Wrap(length int, str string) string {
return sr.wordWrap(length, "", false, str)
}
// WrapWith breaks 'str' into lines of maximum 'length', using 'newLineCharacter'
// to separate lines. It wraps words only when they exceed the line length.
//
// Parameters:
//
// length int - the maximum line length.
// newLineCharacter string - the character(s) used to denote new lines.
// str string - the string to wrap.
//
// Returns:
//
// string - the wrapped string.
//
// Example:
//
// {{ "This is a long string that needs to be wrapped." | wrapWith 10 "<br>" }}
// Output: "This is a<br>long<br>string<br>that needs<br>to be<br>wrapped."
func (sr *StringsRegistry) WrapWith(length int, newLineCharacter string, str string) string {
return sr.wordWrap(length, newLineCharacter, true, str)
}
// Quote wraps each element in 'elements' with double quotes and separates them with spaces.
//
// Parameters:
//
// elements ...any - the elements to be quoted.
//
// Returns:
//
// string - a single string with each element double quoted.
//
// Example:
//
// {{ $list := slice "hello" "world" 123 }}
// {{ $list | quote }}
// Output: "hello" "world" "123"
func (sr *StringsRegistry) Quote(elements ...any) string {
var build strings.Builder
for i, elem := range elements {
if elem == nil {
continue
}
if i > 0 {
build.WriteRune(' ')
}
build.WriteString(fmt.Sprintf("%q", fmt.Sprint(elem)))
}
return build.String()
}
// Squote wraps each element in 'elements' with single quotes and separates them with spaces.
//
// Parameters:
//
// elements ...any - the elements to be single quoted.
//
// Returns:
//
// string - a single string with each element single quoted.
//
// Example:
//
// {{ $list := slice "hello" "world" 123 }}
// {{ $list | squote }}
// Output: 'hello' 'world' '123'
func (sr *StringsRegistry) Squote(elements ...any) string {
var builder strings.Builder
for i, elem := range elements {
if elem == nil {
continue
}
if i > 0 {
builder.WriteRune(' ')
}
// Use fmt.Sprint to convert any to string, then quote it.
builder.WriteRune('\'')
builder.WriteString(fmt.Sprint(elem))
builder.WriteRune('\'')
}
return builder.String()
}
// ToCamelCase converts a string to camelCase.
//
// Parameters:
//
// str string - the string to convert.
//
// Returns:
//
// string - the string converted to camelCase.
//
// Example:
//
// {{ "hello world" | toCamelCase }} // Output: "helloWorld"
func (sr *StringsRegistry) ToCamelCase(str string) string {
return sr.transformString(camelCaseStyle, str)
}
// ToKebabCase converts a string to kebab-case.
//
// Parameters:
//
// str string - the string to convert.
//
// Returns:
//
// string - the string converted to kebab-case.
//
// Example:
//
// {{ "hello world" | toKebabCase }} // Output: "hello-world"
func (sr *StringsRegistry) ToKebabCase(str string) string {
return sr.transformString(kebabCaseStyle, str)
}
// ToPascalCase converts a string to PascalCase.
//
// Parameters:
//
// str string - the string to convert.
//
// Returns:
//
// string - the string converted to PascalCase.
//
// Example:
//
// {{ "hello world" | toPascalCase }} // Output: "HelloWorld"
func (sr *StringsRegistry) ToPascalCase(str string) string {
return sr.transformString(pascalCaseStyle, str)
}
// ToDotCase converts a string to dot.case.
//
// Parameters:
//
// str string - the string to convert.
//
// Returns:
//
// string - the string converted to dot.case.
//
// Example:
//
// {{ "hello world" | toDotCase }} // Output: "hello.world"
func (sr *StringsRegistry) ToDotCase(str string) string {
return sr.transformString(dotCaseStyle, str)
}
// ToPathCase converts a string to path/case.
//
// Parameters:
//
// str string - the string to convert.
//
// Returns:
//
// string - the string converted to path/case.
//
// Example:
//
// {{ "hello world" | toPathCase }} // Output: "hello/world"
func (sr *StringsRegistry) ToPathCase(str string) string {
return sr.transformString(pathCaseStyle, str)
}
// ToConstantCase converts a string to CONSTANT_CASE.
//
// Parameters:
//
// str string - the string to convert.
//
// Returns:
//
// string - the string converted to CONSTANT_CASE.
//
// Example:
//
// {{ "hello world" | toConstantCase }} // Output: "HELLO_WORLD"
func (sr *StringsRegistry) ToConstantCase(str string) string {
return sr.transformString(constantCaseStyle, str)
}
// ToSnakeCase converts a string to snake_case.
//
// Parameters:
//
// str string - the string to convert.
//
// Returns:
//
// string - the string converted to snake_case.
//
// Example:
//
// {{ "hello world" | toSnakeCase }} // Output: "hello_world"
func (sr *StringsRegistry) ToSnakeCase(str string) string {
return sr.transformString(snakeCaseStyle, str)
}
// ToTitleCase converts a string to Title Case.
//
// Parameters:
//
// str string - the string to convert.
//
// Returns:
//
// string - the string converted to Title Case.
//
// Example:
//
// {{ "hello world" | toTitleCase }} // Output: "Hello World"
func (sr *StringsRegistry) ToTitleCase(str string) string {
return cases.Title(language.English).String(str)
}
// Untitle converts the first letter of each word in 'str' to lowercase.
//
// Parameters:
//
// str string - the string to be converted.
//
// Returns:
//
// string - the converted string with each word starting in lowercase.
//
// Example:
//
// {{ "Hello World" | untitle }} // Output: "hello world"
func (sr *StringsRegistry) Untitle(str string) string {
var result strings.Builder
// Process each rune in the input string
startOfWord := true
for _, r := range str {
if unicode.IsSpace(r) {
startOfWord = true
result.WriteRune(r)
} else {
if startOfWord {
result.WriteRune(unicode.ToLower(r))
startOfWord = false
} else {
result.WriteRune(r)
}
}
}
return result.String()
}
// SwapCase switches the case of each letter in 'str'. Lowercase letters become
// uppercase and vice versa.
//
// Parameters:
//
// str string - the string to convert.
//
// Returns:
//
// string - the string with each character's case switched.
//
// Example:
//
// {{ "Hello World" | swapCase }} // Output: "hELLO wORLD"
func (sr *StringsRegistry) SwapCase(str string) string {
return strings.Map(func(r rune) rune {
if unicode.IsLower(r) {
return unicode.ToUpper(r)
}
return unicode.ToLower(r)
}, str)
}
// Capitalize capitalizes the first letter of 'str'.
//
// Parameters:
//
// str string - the string to capitalize.
//
// Returns:
//
// string - the string with the first letter capitalized.
//
// Example:
//
// {{ "hello world" | capitalize }} // Output: "Hello world"
func (sr *StringsRegistry) Capitalize(str string) string {
return swapFirstLetter(str, true)
}
// Uncapitalize converts the first letter of 'str' to lowercase.
//
// Parameters:
//
// str string - the string to uncapitalize.
//
// Returns:
//
// string - the string with the first letter in lowercase.
//
// Example:
//
// {{ "Hello World" | uncapitalize }} // Output: "hello World"
func (sr *StringsRegistry) Uncapitalize(str string) string {
return swapFirstLetter(str, false)
}
// Split divides 'orig' into a map of string parts using 'sep' as the separator.
//
// Parameters:
//
// sep string - the separator string.
// orig string - the original string to split.
//
// Returns:
//
// map[string]string - a map of the split parts.
//
// Example:
//
// {{ "apple,banana,cherry" | split "," }} // Output: { "_0":"apple", "_1":"banana", "_2":"cherry" }
func (sr *StringsRegistry) Split(sep, str string) map[string]string {
parts := strings.Split(str, sep)
return sr.populateMapWithParts(parts)
}
// Splitn divides 'orig' into a map of string parts using 'sep' as the separator
// up to 'n' parts.
//
// Parameters:
//
// sep string - the separator string.
// n int - the maximum number of substrings to return.
// orig string - the original string to split.
//
// Returns:
//
// map[string]string - a map of the split parts.
//
// Example:
//
// {{ "apple,banana,cherry" | split "," 2 }} // Output: { "_0":"apple", "_1":"banana,cherry" }
func (sr *StringsRegistry) Splitn(sep string, n int, str string) map[string]string {
parts := strings.SplitN(str, sep, n)
return sr.populateMapWithParts(parts)
}
// Substring extracts a substring from 's' starting at 'start' and ending at 'end'.
// Negative values for 'start' or 'end' are interpreted as positions from the end
// of the string.
//
// Parameters:
//
// start int - the starting index.
// end int - the ending index, exclusive.
// str string - the source string.
//
// Returns:
//
// string - the extracted substring.
//
// Example:
//
// {{ "Hello World" | substring 0 5 }} // Output: "Hello"
func (sr *StringsRegistry) Substring(start, end int, str string) string {
length := len(str)
if start < 0 {
start = length + start
}
if end < 0 {
end = length + end
}
if start < 0 {
start = 0
}
if end > length {
end = length
}
if start > end {
return ""
}
return str[start:end]
}
// Indent adds spaces to the beginning of each line in 'str'.
//
// Parameters:
//
// spaces int - the number of spaces to add.
// str string - the string to indent.
//
// Returns:
//
// string - the indented string.
//
// Example:
//
// {{ "Hello\nWorld" | indent 4 }} // Output: " Hello\n World"
func (sr *StringsRegistry) Indent(spaces int, str string) string {
var builder strings.Builder
pad := strings.Repeat(" ", spaces)
lines := strings.Split(str, "\n")
for i, line := range lines {
if i > 0 {
builder.WriteString("\n" + pad)
} else {
builder.WriteString(pad)
}
builder.WriteString(line)
}
return builder.String()
}
// Nindent is similar to Indent, but it adds a newline at the start.
//
// Parameters:
// spaces int - the number of spaces to add after the newline.
// str string - the string to indent.
//
// Returns:
// string - the indented string with a newline at the start.
//
// Example:
// {{ "Hello\nWorld" | nindent 4 }} // Output: "\n Hello\n World"
func (sr *StringsRegistry) Nindent(spaces int, str string) string {
return "\n" + sr.Indent(spaces, str)
}
// Seq generates a sequence of numbers as a string. It can take 0, 1, 2, or 3
// integers as parameters defining the start, end, and step of the sequence.
// NOTE: This function works similarly to the seq command in Unix systems.
//
// Parameters:
//
// params ...int - sequence parameters (start, step, end).
//
// Returns:
//
// string - a space-separated string of numbers in the sequence.
//
// Example:
//
// {{ seq 1, 2, 10 }} // Output: "1 3 5 7 9"
func (sr *StringsRegistry) Seq(params ...int) string {
increment := 1
switch len(params) {
case 0:
return ""
case 1:
start := 1
end := params[0]
if end < start {
increment = -1
}
return sr.convertIntArrayToString(helpers.UntilStep(start, end+increment, increment), " ")
case 2:
start := params[0]
end := params[1]
step := 1
if end < start {
step = -1
}
return sr.convertIntArrayToString(helpers.UntilStep(start, end+step, step), " ")
case 3:
start := params[0]
end := params[2]
step := params[1]
if end < start {
increment = -1
if step > 0 {
return ""
}
}
return sr.convertIntArrayToString(helpers.UntilStep(start, end+increment, step), " ")
default:
return ""
}
}