// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. // string package utils import ( "fmt" "strings" ) func PadStringRight(s string, pad string, size int, suffix string) (paddedString string) { if len(pad) == 0 { pad = " " } space := size - len(s) if space > 0 { paddedString = s + strings.Repeat(pad, space) } else { paddedString = s } paddedString += ": %v" return } func UnescapeSpecialChars(source string, charSet, specialChars string) string { var buf strings.Builder var escape bool for _, c := range source { if c == '\\' { if escape { buf.WriteByte('\\') escape = false } else { escape = true } } else { if escape { if index := strings.IndexRune(charSet, c); index >= 0 { if index < len(specialChars) { buf.WriteByte(specialChars[index]) } } escape = false } else { buf.WriteRune(c) } } } return buf.String() } func UnescapeBlanks(source string) string { return UnescapeSpecialChars(source, "nrt", "\n\r\t") } func EscapeSpecialChars(source string, charSet string) string { var buf strings.Builder for _, c := range source { if strings.IndexRune(charSet, c) >= 0 { buf.WriteByte(92) // 92 = backslash } buf.WriteRune(c) } return buf.String() } func EscapeShellSpecialChars(source string) string { return EscapeSpecialChars(source, "!`$\\()#~&*;") } func SplitPair(source, sep string) (left, right string) { parts := strings.SplitN(source, sep, 2) left = strings.TrimSpace(parts[0]) if len(parts) > 1 { right = strings.TrimSpace(parts[1]) } return } func StartsWith(s string, aliases ...string) bool { startsWith := false for _, alias := range aliases { if startsWith = strings.HasPrefix(alias, s); startsWith { break } } return startsWith } func JoinStringMap(m map[string]string, kvSep, itemSep string) string { var sb strings.Builder for key, value := range m { if sb.Len() > 0 { sb.WriteString(itemSep) } sb.WriteString(key) sb.WriteString(kvSep) sb.WriteString(value) } return sb.String() } func JoinMap(m map[string]any, kvSep, itemSep string) string { var sb strings.Builder for key, value := range m { if sb.Len() > 0 { sb.WriteString(itemSep) } sb.WriteString(key) sb.WriteString(kvSep) if s, ok := value.(fmt.Stringer); ok { sb.WriteString(s.String()) } else { sb.WriteString(fmt.Sprintf("%v", value)) } } return sb.String() } func SplitByRune(s string, sep rune, maxParts int) (parts []string) { return ScopedSplitByRune(s, sep, `'"`, `'"`, maxParts) // var capacity int = 0 // var sb strings.Builder // if maxParts < 0 { // for _, b := range s { // if b == sep { // capacity++ // } // } // capacity++ // } else { // capacity = max(1, maxParts) // } // parts = make([]string, 0, capacity) // count := 0 // quote := rune(0) // for i, r := range s { // if r == sep && quote == rune(0) { // if len(parts) < capacity { // count++ // if count == maxParts { // sb.WriteString(s[i:]) // } // parts = append(parts, sb.String()) // sb.Reset() // if count == maxParts { // break // } // } // } else if r == '\'' || r == '"' { // if quote == r { // quote = rune(0) // } else { // quote = r // } // sb.WriteRune(r) // } else { // sb.WriteRune(r) // } // } // if maxParts < 0 { // parts = append(parts, sb.String()) // } else { // for ; count < maxParts; count++ { // parts = append(parts, sb.String()) // sb.Reset() // } // } // return parts } func ScopedSplitByRune(s string, sep rune, scopeOpeners, scopeClosers string, maxParts int) (parts []string) { var capacity int = 0 var sb strings.Builder if len(scopeClosers) != len(scopeOpeners) { panic("scope openers and closers must have the same length") } if maxParts < 0 { capacity = strings.Count(s, string(sep)) + 1 } else { capacity = max(1, maxParts) } parts = make([]string, 0, capacity) count := 0 scopeIndex := -1 for i, r := range s { if r == sep && scopeIndex < 0 { if len(parts) < capacity { count++ if count == maxParts { sb.WriteString(s[i:]) } parts = append(parts, sb.String()) sb.Reset() if count == maxParts { break } } } else { if scopeIndex < 0 { if quoteIndex := strings.IndexRune(scopeOpeners, r); quoteIndex >= 0 { scopeIndex = quoteIndex } } else if quoteIndex := strings.IndexRune(scopeClosers, r); quoteIndex >= 0 { scopeIndex = -1 } sb.WriteRune(r) } } if maxParts < 0 { parts = append(parts, sb.String()) } else { for ; count < maxParts; count++ { parts = append(parts, sb.String()) sb.Reset() } } return parts }