package govalidator import ( "errors" "fmt" "html" "math" "path" "regexp" "strings" "unicode" "unicode/utf8" ) // Contains check if the string contains the substring. func Contains(str, substring string) bool { return strings.Contains(str, substring) } // Matches check if string matches the pattern (pattern is regular expression) // In case of error return false func Matches(str, pattern string) bool { match, _ := regexp.MatchString(pattern, str) return match } // LeftTrim trim characters from the left-side of the input. // If second argument is empty, it's will be remove leading spaces. func LeftTrim(str, chars string) string { if chars == "" { return strings.TrimLeftFunc(str, unicode.IsSpace) } r, _ := regexp.Compile("^[" + chars + "]+") return r.ReplaceAllString(str, "") } // RightTrim trim characters from the right-side of the input. // If second argument is empty, it's will be remove spaces. func RightTrim(str, chars string) string { if chars == "" { return strings.TrimRightFunc(str, unicode.IsSpace) } r, _ := regexp.Compile("[" + chars + "]+$") return r.ReplaceAllString(str, "") } // Trim trim characters from both sides of the input. // If second argument is empty, it's will be remove spaces. func Trim(str, chars string) string { return LeftTrim(RightTrim(str, chars), chars) } // WhiteList remove characters that do not appear in the whitelist. func WhiteList(str, chars string) string { pattern := "[^" + chars + "]+" r, _ := regexp.Compile(pattern) return r.ReplaceAllString(str, "") } // BlackList remove characters that appear in the blacklist. func BlackList(str, chars string) string { pattern := "[" + chars + "]+" r, _ := regexp.Compile(pattern) return r.ReplaceAllString(str, "") } // StripLow remove characters with a numerical value < 32 and 127, mostly control characters. // If keep_new_lines is true, newline characters are preserved (\n and \r, hex 0xA and 0xD). func StripLow(str string, keepNewLines bool) string { chars := "" if keepNewLines { chars = "\x00-\x09\x0B\x0C\x0E-\x1F\x7F" } else { chars = "\x00-\x1F\x7F" } return BlackList(str, chars) } // ReplacePattern replace regular expression pattern in string func ReplacePattern(str, pattern, replace string) string { r, _ := regexp.Compile(pattern) return r.ReplaceAllString(str, replace) } // Escape replace <, >, & and " with HTML entities. var Escape = html.EscapeString func addSegment(inrune, segment []rune) []rune { if len(segment) == 0 { return inrune } if len(inrune) != 0 { inrune = append(inrune, '_') } inrune = append(inrune, segment...) return inrune } // UnderscoreToCamelCase converts from underscore separated form to camel case form. // Ex.: my_func => MyFunc func UnderscoreToCamelCase(s string) string { return strings.Replace(strings.Title(strings.Replace(strings.ToLower(s), "_", " ", -1)), " ", "", -1) } // CamelCaseToUnderscore converts from camel case form to underscore separated form. // Ex.: MyFunc => my_func func CamelCaseToUnderscore(str string) string { var output []rune var segment []rune for _, r := range str { // not treat number as separate segment if !unicode.IsLower(r) && string(r) != "_" && !unicode.IsNumber(r) { output = addSegment(output, segment) segment = nil } segment = append(segment, unicode.ToLower(r)) } output = addSegment(output, segment) return string(output) } // Reverse return reversed string func Reverse(s string) string { r := []rune(s) for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 { r[i], r[j] = r[j], r[i] } return string(r) } // GetLines split string by "\n" and return array of lines func GetLines(s string) []string { return strings.Split(s, "\n") } // GetLine return specified line of multiline string func GetLine(s string, index int) (string, error) { lines := GetLines(s) if index < 0 || index >= len(lines) { return "", errors.New("line index out of bounds") } return lines[index], nil } // RemoveTags remove all tags from HTML string func RemoveTags(s string) string { return ReplacePattern(s, "<[^>]*>", "") } // SafeFileName return safe string that can be used in file names func SafeFileName(str string) string { name := strings.ToLower(str) name = path.Clean(path.Base(name)) name = strings.Trim(name, " ") separators, err := regexp.Compile(`[ &_=+:]`) if err == nil { name = separators.ReplaceAllString(name, "-") } legal, err := regexp.Compile(`[^[:alnum:]-.]`) if err == nil { name = legal.ReplaceAllString(name, "") } for strings.Contains(name, "--") { name = strings.Replace(name, "--", "-", -1) } return name } // NormalizeEmail canonicalize an email address. // The local part of the email address is lowercased for all domains; the hostname is always lowercased and // the local part of the email address is always lowercased for hosts that are known to be case-insensitive (currently only GMail). // Normalization follows special rules for known providers: currently, GMail addresses have dots removed in the local part and // are stripped of tags (e.g. some.one+tag@gmail.com becomes someone@gmail.com) and all @googlemail.com addresses are // normalized to @gmail.com. func NormalizeEmail(str string) (string, error) { if !IsEmail(str) { return "", fmt.Errorf("%s is not an email", str) } parts := strings.Split(str, "@") parts[0] = strings.ToLower(parts[0]) parts[1] = strings.ToLower(parts[1]) if parts[1] == "gmail.com" || parts[1] == "googlemail.com" { parts[1] = "gmail.com" parts[0] = strings.Split(ReplacePattern(parts[0], `\.`, ""), "+")[0] } return strings.Join(parts, "@"), nil } // Truncate a string to the closest length without breaking words. func Truncate(str string, length int, ending string) string { var aftstr, befstr string if len(str) > length { words := strings.Fields(str) before, present := 0, 0 for i := range words { befstr = aftstr before = present aftstr = aftstr + words[i] + " " present = len(aftstr) if present > length && i != 0 { if (length - before) < (present - length) { return Trim(befstr, " /\\.,\"'#!?&@+-") + ending } return Trim(aftstr, " /\\.,\"'#!?&@+-") + ending } } } return str } // PadLeft pad left side of string if size of string is less then indicated pad length func PadLeft(str string, padStr string, padLen int) string { return buildPadStr(str, padStr, padLen, true, false) } // PadRight pad right side of string if size of string is less then indicated pad length func PadRight(str string, padStr string, padLen int) string { return buildPadStr(str, padStr, padLen, false, true) } // PadBoth pad sides of string if size of string is less then indicated pad length func PadBoth(str string, padStr string, padLen int) string { return buildPadStr(str, padStr, padLen, true, true) } // PadString either left, right or both sides, not the padding string can be unicode and more then one // character func buildPadStr(str string, padStr string, padLen int, padLeft bool, padRight bool) string { // When padded length is less then the current string size if padLen < utf8.RuneCountInString(str) { return str } padLen -= utf8.RuneCountInString(str) targetLen := padLen targetLenLeft := targetLen targetLenRight := targetLen if padLeft && padRight { targetLenLeft = padLen / 2 targetLenRight = padLen - targetLenLeft } strToRepeatLen := utf8.RuneCountInString(padStr) repeatTimes := int(math.Ceil(float64(targetLen) / float64(strToRepeatLen))) repeatedString := strings.Repeat(padStr, repeatTimes) leftSide := "" if padLeft { leftSide = repeatedString[0:targetLenLeft] } rightSide := "" if padRight { rightSide = repeatedString[0:targetLenRight] } return leftSide + str + rightSide } // TruncatingErrorf removes extra args from fmt.Errorf if not formatted in the str object func TruncatingErrorf(str string, args ...interface{}) error { n := strings.Count(str, "%s") return fmt.Errorf(str, args[:n]...) }