git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/validate/utils.go (about) 1 package validate 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 checks if the string contains the substring. 16 func Contains(str, substring string) bool { 17 return strings.Contains(str, substring) 18 } 19 20 // Matches checks 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 trims characters from the left side of the input. 28 // If second argument is empty, it will 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 trims characters from the right side of the input. 38 // If second argument is empty, it will remove trailing 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 trims characters from both sides of the input. 48 // If second argument is empty, it will remove spaces. 49 func Trim(str, chars string) string { 50 return LeftTrim(RightTrim(str, chars), chars) 51 } 52 53 // WhiteList removes 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 removes 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 removes 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 replaces 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 replaces <, >, & 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 returns 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 splits string by "\n" and return array of lines 133 func GetLines(s string) []string { 134 return strings.Split(s, "\n") 135 } 136 137 // GetLine returns 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 removes all tags from HTML string 147 func RemoveTags(s string) string { 148 return ReplacePattern(s, "<[^>]*>", "") 149 } 150 151 // SafeFileName returns 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 pads left side of a 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 pads right side of a 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 pads both sides of a 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. 229 // Note that padding string can be unicode and more then one 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 } 265 266 // TruncatingErrorf removes extra args from fmt.Errorf if not formatted in the str object 267 func TruncatingErrorf(str string, args ...interface{}) error { 268 n := strings.Count(str, "%s") 269 return fmt.Errorf(str, args[:n]...) 270 }