Serge Bazanski | cc25bdf | 2018-10-25 14:02:58 +0200 | [diff] [blame] | 1 | package govalidator |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "fmt" |
| 6 | "html" |
| 7 | "math" |
| 8 | "path" |
| 9 | "regexp" |
| 10 | "strings" |
| 11 | "unicode" |
| 12 | "unicode/utf8" |
| 13 | ) |
| 14 | |
| 15 | // Contains check if the string contains the substring. |
| 16 | func Contains(str, substring string) bool { |
| 17 | return strings.Contains(str, substring) |
| 18 | } |
| 19 | |
| 20 | // Matches check if string matches the pattern (pattern is regular expression) |
| 21 | // In case of error return false |
| 22 | func Matches(str, pattern string) bool { |
| 23 | match, _ := regexp.MatchString(pattern, str) |
| 24 | return match |
| 25 | } |
| 26 | |
| 27 | // LeftTrim trim characters from the left-side of the input. |
| 28 | // If second argument is empty, it's will be remove leading spaces. |
| 29 | func LeftTrim(str, chars string) string { |
| 30 | if chars == "" { |
| 31 | return strings.TrimLeftFunc(str, unicode.IsSpace) |
| 32 | } |
| 33 | r, _ := regexp.Compile("^[" + chars + "]+") |
| 34 | return r.ReplaceAllString(str, "") |
| 35 | } |
| 36 | |
| 37 | // RightTrim trim characters from the right-side of the input. |
| 38 | // If second argument is empty, it's will be remove spaces. |
| 39 | func RightTrim(str, chars string) string { |
| 40 | if chars == "" { |
| 41 | return strings.TrimRightFunc(str, unicode.IsSpace) |
| 42 | } |
| 43 | r, _ := regexp.Compile("[" + chars + "]+$") |
| 44 | return r.ReplaceAllString(str, "") |
| 45 | } |
| 46 | |
| 47 | // Trim trim characters from both sides of the input. |
| 48 | // If second argument is empty, it's will be remove spaces. |
| 49 | func Trim(str, chars string) string { |
| 50 | return LeftTrim(RightTrim(str, chars), chars) |
| 51 | } |
| 52 | |
| 53 | // WhiteList remove characters that do not appear in the whitelist. |
| 54 | func WhiteList(str, chars string) string { |
| 55 | pattern := "[^" + chars + "]+" |
| 56 | r, _ := regexp.Compile(pattern) |
| 57 | return r.ReplaceAllString(str, "") |
| 58 | } |
| 59 | |
| 60 | // BlackList remove characters that appear in the blacklist. |
| 61 | func BlackList(str, chars string) string { |
| 62 | pattern := "[" + chars + "]+" |
| 63 | r, _ := regexp.Compile(pattern) |
| 64 | return r.ReplaceAllString(str, "") |
| 65 | } |
| 66 | |
| 67 | // StripLow remove characters with a numerical value < 32 and 127, mostly control characters. |
| 68 | // If keep_new_lines is true, newline characters are preserved (\n and \r, hex 0xA and 0xD). |
| 69 | func StripLow(str string, keepNewLines bool) string { |
| 70 | chars := "" |
| 71 | if keepNewLines { |
| 72 | chars = "\x00-\x09\x0B\x0C\x0E-\x1F\x7F" |
| 73 | } else { |
| 74 | chars = "\x00-\x1F\x7F" |
| 75 | } |
| 76 | return BlackList(str, chars) |
| 77 | } |
| 78 | |
| 79 | // ReplacePattern replace regular expression pattern in string |
| 80 | func ReplacePattern(str, pattern, replace string) string { |
| 81 | r, _ := regexp.Compile(pattern) |
| 82 | return r.ReplaceAllString(str, replace) |
| 83 | } |
| 84 | |
| 85 | // Escape replace <, >, & and " with HTML entities. |
| 86 | var Escape = html.EscapeString |
| 87 | |
| 88 | func addSegment(inrune, segment []rune) []rune { |
| 89 | if len(segment) == 0 { |
| 90 | return inrune |
| 91 | } |
| 92 | if len(inrune) != 0 { |
| 93 | inrune = append(inrune, '_') |
| 94 | } |
| 95 | inrune = append(inrune, segment...) |
| 96 | return inrune |
| 97 | } |
| 98 | |
| 99 | // UnderscoreToCamelCase converts from underscore separated form to camel case form. |
| 100 | // Ex.: my_func => MyFunc |
| 101 | func UnderscoreToCamelCase(s string) string { |
| 102 | return strings.Replace(strings.Title(strings.Replace(strings.ToLower(s), "_", " ", -1)), " ", "", -1) |
| 103 | } |
| 104 | |
| 105 | // CamelCaseToUnderscore converts from camel case form to underscore separated form. |
| 106 | // Ex.: MyFunc => my_func |
| 107 | func CamelCaseToUnderscore(str string) string { |
| 108 | var output []rune |
| 109 | var segment []rune |
| 110 | for _, r := range str { |
| 111 | |
| 112 | // not treat number as separate segment |
| 113 | if !unicode.IsLower(r) && string(r) != "_" && !unicode.IsNumber(r) { |
| 114 | output = addSegment(output, segment) |
| 115 | segment = nil |
| 116 | } |
| 117 | segment = append(segment, unicode.ToLower(r)) |
| 118 | } |
| 119 | output = addSegment(output, segment) |
| 120 | return string(output) |
| 121 | } |
| 122 | |
| 123 | // Reverse return reversed string |
| 124 | func Reverse(s string) string { |
| 125 | r := []rune(s) |
| 126 | for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 { |
| 127 | r[i], r[j] = r[j], r[i] |
| 128 | } |
| 129 | return string(r) |
| 130 | } |
| 131 | |
| 132 | // GetLines split string by "\n" and return array of lines |
| 133 | func GetLines(s string) []string { |
| 134 | return strings.Split(s, "\n") |
| 135 | } |
| 136 | |
| 137 | // GetLine return specified line of multiline string |
| 138 | func GetLine(s string, index int) (string, error) { |
| 139 | lines := GetLines(s) |
| 140 | if index < 0 || index >= len(lines) { |
| 141 | return "", errors.New("line index out of bounds") |
| 142 | } |
| 143 | return lines[index], nil |
| 144 | } |
| 145 | |
| 146 | // RemoveTags remove all tags from HTML string |
| 147 | func RemoveTags(s string) string { |
| 148 | return ReplacePattern(s, "<[^>]*>", "") |
| 149 | } |
| 150 | |
| 151 | // SafeFileName return safe string that can be used in file names |
| 152 | func SafeFileName(str string) string { |
| 153 | name := strings.ToLower(str) |
| 154 | name = path.Clean(path.Base(name)) |
| 155 | name = strings.Trim(name, " ") |
| 156 | separators, err := regexp.Compile(`[ &_=+:]`) |
| 157 | if err == nil { |
| 158 | name = separators.ReplaceAllString(name, "-") |
| 159 | } |
| 160 | legal, err := regexp.Compile(`[^[:alnum:]-.]`) |
| 161 | if err == nil { |
| 162 | name = legal.ReplaceAllString(name, "") |
| 163 | } |
| 164 | for strings.Contains(name, "--") { |
| 165 | name = strings.Replace(name, "--", "-", -1) |
| 166 | } |
| 167 | return name |
| 168 | } |
| 169 | |
| 170 | // NormalizeEmail canonicalize an email address. |
| 171 | // The local part of the email address is lowercased for all domains; the hostname is always lowercased and |
| 172 | // the local part of the email address is always lowercased for hosts that are known to be case-insensitive (currently only GMail). |
| 173 | // Normalization follows special rules for known providers: currently, GMail addresses have dots removed in the local part and |
| 174 | // are stripped of tags (e.g. some.one+tag@gmail.com becomes someone@gmail.com) and all @googlemail.com addresses are |
| 175 | // normalized to @gmail.com. |
| 176 | func NormalizeEmail(str string) (string, error) { |
| 177 | if !IsEmail(str) { |
| 178 | return "", fmt.Errorf("%s is not an email", str) |
| 179 | } |
| 180 | parts := strings.Split(str, "@") |
| 181 | parts[0] = strings.ToLower(parts[0]) |
| 182 | parts[1] = strings.ToLower(parts[1]) |
| 183 | if parts[1] == "gmail.com" || parts[1] == "googlemail.com" { |
| 184 | parts[1] = "gmail.com" |
| 185 | parts[0] = strings.Split(ReplacePattern(parts[0], `\.`, ""), "+")[0] |
| 186 | } |
| 187 | return strings.Join(parts, "@"), nil |
| 188 | } |
| 189 | |
| 190 | // Truncate a string to the closest length without breaking words. |
| 191 | func Truncate(str string, length int, ending string) string { |
| 192 | var aftstr, befstr string |
| 193 | if len(str) > length { |
| 194 | words := strings.Fields(str) |
| 195 | before, present := 0, 0 |
| 196 | for i := range words { |
| 197 | befstr = aftstr |
| 198 | before = present |
| 199 | aftstr = aftstr + words[i] + " " |
| 200 | present = len(aftstr) |
| 201 | if present > length && i != 0 { |
| 202 | if (length - before) < (present - length) { |
| 203 | return Trim(befstr, " /\\.,\"'#!?&@+-") + ending |
| 204 | } |
| 205 | return Trim(aftstr, " /\\.,\"'#!?&@+-") + ending |
| 206 | } |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | return str |
| 211 | } |
| 212 | |
| 213 | // PadLeft pad left side of string if size of string is less then indicated pad length |
| 214 | func PadLeft(str string, padStr string, padLen int) string { |
| 215 | return buildPadStr(str, padStr, padLen, true, false) |
| 216 | } |
| 217 | |
| 218 | // PadRight pad right side of string if size of string is less then indicated pad length |
| 219 | func PadRight(str string, padStr string, padLen int) string { |
| 220 | return buildPadStr(str, padStr, padLen, false, true) |
| 221 | } |
| 222 | |
| 223 | // PadBoth pad sides of string if size of string is less then indicated pad length |
| 224 | func PadBoth(str string, padStr string, padLen int) string { |
| 225 | return buildPadStr(str, padStr, padLen, true, true) |
| 226 | } |
| 227 | |
| 228 | // PadString either left, right or both sides, not the padding string can be unicode and more then one |
| 229 | // character |
| 230 | func buildPadStr(str string, padStr string, padLen int, padLeft bool, padRight bool) string { |
| 231 | |
| 232 | // When padded length is less then the current string size |
| 233 | if padLen < utf8.RuneCountInString(str) { |
| 234 | return str |
| 235 | } |
| 236 | |
| 237 | padLen -= utf8.RuneCountInString(str) |
| 238 | |
| 239 | targetLen := padLen |
| 240 | |
| 241 | targetLenLeft := targetLen |
| 242 | targetLenRight := targetLen |
| 243 | if padLeft && padRight { |
| 244 | targetLenLeft = padLen / 2 |
| 245 | targetLenRight = padLen - targetLenLeft |
| 246 | } |
| 247 | |
| 248 | strToRepeatLen := utf8.RuneCountInString(padStr) |
| 249 | |
| 250 | repeatTimes := int(math.Ceil(float64(targetLen) / float64(strToRepeatLen))) |
| 251 | repeatedString := strings.Repeat(padStr, repeatTimes) |
| 252 | |
| 253 | leftSide := "" |
| 254 | if padLeft { |
| 255 | leftSide = repeatedString[0:targetLenLeft] |
| 256 | } |
| 257 | |
| 258 | rightSide := "" |
| 259 | if padRight { |
| 260 | rightSide = repeatedString[0:targetLenRight] |
| 261 | } |
| 262 | |
| 263 | return leftSide + str + rightSide |
| 264 | } |