registry/strings/helpers.go
package strings
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
// ellipsis truncates 'value' 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 maximum width of the string including the ellipsis.
// value string - the string to truncate.
//
// Returns:
//
// string - the possibly truncated string with an ellipsis.
func (sr *StringsRegistry) ellipsis(value string, offset int, maxWidth int) string {
ellipsis := "..."
// Return the original string if maxWidth is less than 4, or the offset
// create exclusive dot string, it's not possible to add an ellipsis.
if maxWidth < 4 || offset > 0 && maxWidth < 7 {
return value
}
runeCount := utf8.RuneCountInString(value)
// If the string doesn't need trimming, return it as is.
if runeCount <= maxWidth || runeCount <= offset {
return value[offset:]
}
// Determine end position for the substring, ensuring room for the ellipsis.
endPos := offset + maxWidth - 3 // 3 is for the length of the ellipsis
if offset > 0 {
endPos -= 3 // remove the left ellipsis
}
// Convert the string to a slice of runes to properly handle multi-byte characters.
runes := []rune(value)
// Return the substring with an ellipsis, directly constructing the string in the return statement.
if offset > 0 {
return ellipsis + string(runes[offset:endPos]) + ellipsis
}
return string(runes[offset:endPos]) + ellipsis
}
// initials extracts the initials from 'value', using 'delimiters' to determine
// word boundaries.
//
// Parameters:
//
// value string - the string from which to extract initials.
// delimiters string - the string containing delimiter characters.
//
// Returns:
//
// string - the initials of the words in 'value'.
func (sr *StringsRegistry) initials(value string, delimiters string) string {
// Define a function to determine if a rune is a delimiter.
isDelimiter := func(r rune) bool {
return strings.ContainsRune(delimiters, r)
}
words := strings.FieldsFunc(value, isDelimiter)
runes := make([]rune, len(words))
for i, word := range strings.FieldsFunc(value, isDelimiter) {
if i == 0 || unicode.IsLetter(rune(word[0])) {
runes[i] = rune(word[0])
}
}
return string(runes)
}
// transformString modifies the string 'value' based on various case styling rules
// specified in the 'style' parameter. It can capitalize, lowercase, and insert
// separators according to the rules provided.
//
// Parameters:
//
// style caseStyle - a struct specifying how to transform the string, including
// capitalization rules, insertion of separators, and whether
// to force lowercase.
// value string - the string to transform.
//
// Returns:
//
// string - the transformed string.
func (sr *StringsRegistry) transformString(style caseStyle, value string) string {
var result strings.Builder
result.Grow(len(value) + 10) // Allocate a bit more for potential separators
capitalizeNext := style.CapitalizeNext
var lastRune, lastLetter, nextRune rune = 0, 0, 0
if !style.CapitalizeFirst {
capitalizeNext = false
}
for i, r := range value {
if i+1 < len(value) {
nextRune = rune(value[i+1])
}
if r == ' ' || r == '-' || r == '_' {
if style.Separator != -1 && (lastRune != style.Separator) {
result.WriteRune(style.Separator)
}
if lastLetter != 0 {
capitalizeNext = true
}
lastRune = style.Separator
continue
}
if unicode.IsUpper(r) && style.Separator != -1 && result.Len() > 0 && lastRune != style.Separator {
if (unicode.IsUpper(lastRune) && unicode.IsUpper(r) && unicode.IsLower(nextRune)) || (unicode.IsUpper(r) && unicode.IsLower(lastRune)) {
result.WriteRune(style.Separator)
}
}
if style.Separator != -1 && lastRune != style.Separator && (unicode.IsDigit(r) && !unicode.IsDigit(lastRune)) {
result.WriteRune(style.Separator)
}
switch {
case capitalizeNext && style.CapitalizeNext:
result.WriteRune(unicode.ToUpper(r))
capitalizeNext = false
case style.ForceLowercase:
result.WriteRune(unicode.ToLower(r))
case style.ForceUppercase:
result.WriteRune(unicode.ToUpper(r))
default:
result.WriteRune(r)
}
lastRune = r // Update lastRune to the current rune
if unicode.IsLetter(r) {
lastLetter = r
}
}
return result.String()
}
// populateMapWithParts converts an array of strings into a map with keys based
// on the index of each string.
//
// Parameters:
//
// parts []string - the array of strings to be converted into a map.
//
// Returns:
//
// map[string]string - a map where each key corresponds to an index (with an underscore prefix) of the string in the input array.
func (sr *StringsRegistry) populateMapWithParts(parts []string) map[string]string {
res := make(map[string]string, len(parts))
for i, v := range parts {
res[fmt.Sprintf("_%d", i)] = v
}
return res
}
// convertIntArrayToString converts an array of integers into a single string
// with elements separated by a given delimiter.
//
// Parameters:
//
// slice []int - the array of integers to convert.
// delimiter string - the string to use as a delimiter between the integers in the output string.
//
// Returns:
//
// string - the resulting string that concatenates all the integers in the array separated by the specified delimiter.
func (sr *StringsRegistry) convertIntArrayToString(slice []int, delimiter string) string {
return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(slice)), delimiter), "[]")
}
// WordWrap formats 'value' into lines of maximum 'wrapLength', optionally wrapping
// long words and using 'newLineCharacter' for line breaks.
//
// Parameters:
//
// value string - the string to wrap.
// wrapLength int - the maximum length of each line.
// newLineCharacter string - the string used to denote new lines.
// wrapLongWords bool - true to wrap long words that exceed the line length.
//
// Returns:
//
// string - the wrapped string.
func (sr *StringsRegistry) wordWrap(wrapLength int, newLineCharacter string, wrapLongWords bool, value string) string {
if wrapLength < 1 {
wrapLength = 1
}
if newLineCharacter == "" {
newLineCharacter = "\n"
}
var resultBuilder strings.Builder
var currentLineLength int
for _, word := range strings.Fields(value) {
wordLength := utf8.RuneCountInString(word)
// If the word is too long and should be wrapped, or it fits in the remaining line length
if currentLineLength > 0 && (currentLineLength+1+wordLength > wrapLength && !wrapLongWords || currentLineLength+1+wordLength > wrapLength) {
resultBuilder.WriteString(newLineCharacter)
currentLineLength = 0
}
if wrapLongWords && wordLength > wrapLength {
for i, r := range word {
resultBuilder.WriteRune(r)
currentLineLength++
// Avoid adding a new line immediately after wrapping a long word
if i < len(word)-1 && currentLineLength == wrapLength {
resultBuilder.WriteString(newLineCharacter)
currentLineLength = 0
}
}
} else {
if currentLineLength > 0 {
resultBuilder.WriteRune(' ')
currentLineLength++
}
resultBuilder.WriteString(word)
currentLineLength += wordLength
}
}
return resultBuilder.String()
}
// swapFirstLetter swaps the first letter of the string 'value' to uppercase or
// lowercase. The casing is determined by the 'casing' parameter.
//
// Parameters:
//
// value string - the string to modify.
// shouldUppercaseFirst bool - the casing to apply to the first letter.
//
// Returns:
//
// string - the modified string with the first letter in the desired casing.
func swapFirstLetter(value string, shouldUppercase bool) string {
var conditionFunc func(r rune) bool
var updateFunc func(r rune) rune
if shouldUppercase {
conditionFunc = unicode.IsUpper
updateFunc = unicode.ToUpper
} else {
conditionFunc = unicode.IsLower
updateFunc = unicode.ToLower
}
buf := []byte(value)
for i := 0; i < len(buf); {
r, size := utf8.DecodeRune(buf[i:])
if unicode.IsLetter(r) {
if conditionFunc(r) {
return value
}
upperRune := updateFunc(r)
utf8.EncodeRune(buf[i:i+size], upperRune)
return string(buf)
}
i += size
}
return value
}