Select Git revision
string_utils.go
string_utils.go 8.74 KiB
package string_utils
import (
"bytes"
"encoding/json"
"fmt"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
"net/mail"
"net/url"
"regexp"
"strconv"
"strings"
"unicode"
"github.com/thoas/go-funk"
"golang.org/x/text/runes"
"golang.org/x/text/transform"
"golang.org/x/text/unicode/norm"
)
const snakeCasePattern = `[a-z]([a-z0-9_]*[a-z0-9])*`
var snakeCaseRegex = regexp.MustCompile("^" + snakeCasePattern + "$")
func IsSnakeCase(name string) bool {
return snakeCaseRegex.MatchString(name)
}
func SnakeToKebabString(s string) string {
s = strings.TrimSpace(s)
re := regexp.MustCompile("(_)")
s = re.ReplaceAllString(s, "-")
return s
}
// ReplaceNonSpacingMarks removes diacritics e.g. êžů becomes ezu
func ReplaceNonSpacingMarks(str string) string {
t := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC) // Mn: non-spacing marks
result, _, _ := transform.String(t, str)
return result
}
func RemoveAllWhiteSpaces(s string) string {
return strings.ReplaceAll(strings.ReplaceAll(s, " ", ""), "\t", "")
}
func ReplaceCaseInsensitive(string, toReplace, replaceWith string) string {
regex := regexp.MustCompile("(?i)" + strings.ToLower(toReplace))
return regex.ReplaceAllString(string, replaceWith)
}
// TrimQuotes - trims quotes from a string (ie: "foo" will return foo)
func TrimQuotes(stringToTrim string) string {
if len(stringToTrim) >= 2 {
if stringToTrim[0] == '"' && stringToTrim[len(stringToTrim)-1] == '"' {
return stringToTrim[1 : len(stringToTrim)-1]
}
}
return stringToTrim
}
// IsNumericString returns true if the string can be converted to a float without error
func IsNumericString(s string) bool {
_, err := strconv.ParseFloat(s, 64)
return err == nil
}
// StandardisePhoneNumber standardises phone numbers with +27 instead of 0 prefix
func StandardisePhoneNumber(number string) string {
number = strings.TrimSpace(number)
if number == "" {
return number
}
// is the first rune/char of the string a 0
if []rune(number)[0] == []rune("0")[0] {
// Add south african country code (hardcoded for now)
number = "+27" + number[1:]
}
return number
}
func FormatPhoneNumber(phoneNumber string) string {
if len(phoneNumber) > 7 {
return phoneNumber
}
// Format as 076 453 2188
phoneNumber = insertInto(phoneNumber, 3, " ")
phoneNumber = insertInto(phoneNumber, 7, " ")
return phoneNumber
}
func insertInto(s string, index int, characters string) string {
return s[:index] + characters + s[index:]
}
func IsAlphaNumeric(str string) bool {
regex := regexp.MustCompile("^[a-zA-Z0-9]*$")
return regex.MatchString(str)
}
func IsAlphaNumericOrDash(str string) bool {
regex := regexp.MustCompile("^[a-zA-Z0-9-]*$")
return regex.MatchString(str)
}
func IsValidUsername(str string) bool {
regex := regexp.MustCompile("^[a-zA-Z0-9-.@+_]*$")
return regex.MatchString(str)
}
func Equal(a string, b string) bool {
return strings.TrimSpace(strings.ToLower(a)) == strings.TrimSpace(strings.ToLower(b))
}
func UnwrapString(s *string) string {
if s == nil {
return ""
}
return *s
}
// TrimP trims specified strings, replacing empty string with nil
func TrimP(sp *string) *string {
if sp == nil {
return nil
}
s := strings.TrimSpace(*sp)
if s == "" {
return nil
}
return &s
}
// ConcatP concatenates all specified non-empty strings with ", " separators
func ConcatP(args ...*string) string {
s := ""
for _, arg := range args {
if arg != nil && *arg != "" {
if s != "" {
s += ", "
}
s += *arg
}
}
return s
}
func ToJSONString(object interface{}) (string, error) {
jsonBytes, err := json.Marshal(&object)
if err != nil {
return "", err
}
return string(jsonBytes), nil
}
func Int64ToString(number int64) string {
return strconv.FormatInt(number, 10)
}
func IntToString(number int) string {
return strconv.Itoa(number)
}
func StringToInt(stringValue string) (int, error) {
return strconv.Atoi(stringValue)
}
func Int64SliceToString(numbers []int64) string {
numString := fmt.Sprint(numbers)
numString = strings.Join(strings.Split(numString, " "), ",")
return numString
}
func Int64SliceToStringSlice(numbers []int64) []string {
var numStringSlice []string
for _, number := range numbers {
numStringSlice = append(numStringSlice, fmt.Sprint(number))
}
return numStringSlice
}
func StringToInt64(stringValue string) (int64, error) {
number, err := strconv.ParseInt(stringValue, 10, 64)
return number, err
}
func StringToFloat64(stringValue string) (float64, error) {
number, err := strconv.ParseFloat(stringValue, 64)
if err != nil {
return 0, err
}
return number, nil
}
func Float64ToString(number float64, precision int) string {
return strconv.FormatFloat(number, 'f', precision, 64)
}
func Float64ToStringWithPrec(number float64, prec int) string {
return strconv.FormatFloat(number, 'f', prec, 64)
}
func ValidateStringAsInt64(stringValue string) error {
_, err := StringToInt64(stringValue)
return err
}
func PtoString(stringPointer *string) string {
if stringPointer == nil {
return ""
}
return *stringPointer
}
func IsEmpty(sp *string) bool {
if sp == nil {
return true
}
return len(*sp) == 0
}
// StringTrimQuotes - trims quotes from a string (ie: "foo" will return foo)
func StringTrimQuotes(stringToTrim string) string {
if len(stringToTrim) >= 2 {
if stringToTrim[0] == '"' && stringToTrim[len(stringToTrim)-1] == '"' {
return stringToTrim[1 : len(stringToTrim)-1]
}
}
return stringToTrim
}
func KeyToHumanReadable(s string) string {
s = strings.TrimSpace(s)
re := regexp.MustCompile("(_|-)")
s = re.ReplaceAllString(s, " ")
return SentenceCase(string(s))
}
func SentenceCase(str string) string {
if len(str) > 0 {
str = strings.ToLower(str)
r := []rune(str)
return string(append([]rune{unicode.ToUpper(r[0])}, r[1:]...))
}
return ""
}
// RemoveUrlScheme Removes http:// or https:// from a URL
func RemoveUrlScheme(str string) string {
newStr := strings.Replace(str, "http://", "", 1)
newStr = strings.Replace(str, "https://", "", 1)
return newStr
}
// EscapeOpenSearchSearchString See https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#_reserved_characters
func EscapeOpenSearchSearchString(str string) string {
searchString := str
// Reserved characters
// NOTE: first char must be "\" to prevent replacing it again after replacing other chars with "\\"
reservedCharacters := []string{"\\", "+", "-", "=", "&&", "||", "!", "(", ")", "{", "}", "[", "]", "^", "\"", "~", "*", "?", ":", "/"}
// Remove "<" and ">"
strings.ReplaceAll(searchString, "<", "")
strings.ReplaceAll(searchString, ">", "")
// Escape the reserved characters with double backslashes ("\\")
for _, char := range reservedCharacters {
if strings.Contains(searchString, char) {
re := regexp.MustCompile(char)
searchString = re.ReplaceAllString(searchString, "\\"+char)
}
}
return searchString
}
// SplitString separates a string on any character in the list of sep
func SplitString(str string, sep []rune) []string {
splitStrings := strings.FieldsFunc(str, func(c rune) bool {
return funk.Contains(sep, c)
})
return splitStrings
}
// IsUrlStrict Returns whether a URL is valid in a strict way (Must have scheme and host)
func IsUrlStrict(str string) bool {
u, err := url.Parse(str)
return err == nil && u.Scheme != "" && u.Host != ""
}
func LimitStringToMaxLength(str string, maxLen int) string {
if len(str) > maxLen {
str = str[0:maxLen]
}
return str
}
// StripQueryString - Strips the query parameters from a URL
func StripQueryString(inputUrl string) (string, error) {
u, err := url.Parse(inputUrl)
if err != nil {
return inputUrl, err
}
u.RawQuery = ""
return u.String(), nil
}
func ValidateEmailAddress(email string) (string, error) {
if email == "" {
return "", errors.Error("email address is empty")
}
cleanEmail := strings.ToLower(strings.TrimSpace(email))
cleanEmail = RemoveAllWhiteSpaces(cleanEmail)
// We validate it but still return it since in some cases we don't want to break everything if the email is bad
_, err := mail.ParseAddress(cleanEmail)
if err != nil {
return cleanEmail, errors.Wrap(err, "could not parse email address")
}
return cleanEmail, nil
}
func PascalCaseToSentence(pascal string) string {
var parts []string
start := 0
for end, r := range pascal {
if end != 0 && unicode.IsUpper(r) {
parts = append(parts, pascal[start:end])
start = end
}
}
if start != len(pascal) {
parts = append(parts, pascal[start:])
}
sentence := strings.Join(parts, " ")
return sentence
}
func PrettyJSON(jsonString string) (validJson bool, prettyString string) {
var prettyJSON bytes.Buffer
err := json.Indent(&prettyJSON, []byte(jsonString), "", " ")
if err != nil {
validJson = false
prettyString = jsonString
} else {
validJson = true
prettyString = prettyJSON.String()
}
return
}