pkg.re/essentialkaos/ek.v10@v12.41.0+incompatible/strutil/strutil.go (about) 1 // Package strutil provides methods for working with strings 2 package strutil 3 4 // ////////////////////////////////////////////////////////////////////////////////// // 5 // // 6 // Copyright (c) 2022 ESSENTIAL KAOS // 7 // Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0> // 8 // // 9 // ////////////////////////////////////////////////////////////////////////////////// // 10 11 import ( 12 "bytes" 13 "strings" 14 ) 15 16 // ////////////////////////////////////////////////////////////////////////////////// // 17 18 // EllipsisSuffix is ellipsis suffix 19 var EllipsisSuffix = "..." 20 21 // ////////////////////////////////////////////////////////////////////////////////// // 22 23 var defaultFieldsSeparators = []string{" ", "\t"} 24 25 // ////////////////////////////////////////////////////////////////////////////////// // 26 27 // Q is simple helper for working with default values 28 func Q(v ...string) string { 29 for _, k := range v { 30 if k != "" { 31 return k 32 } 33 } 34 35 return "" 36 } 37 38 // Concat is method for fast string concatenation 39 func Concat(s ...string) string { 40 var buffer bytes.Buffer 41 42 for _, v := range s { 43 buffer.WriteString(v) 44 } 45 46 return buffer.String() 47 } 48 49 // Substr returns the part of a string between the start index and a number 50 // of characters after it (unicode supported) 51 func Substr(s string, start, length int) string { 52 if s == "" || length <= 0 || start >= Len(s) { 53 return "" 54 } 55 56 if start < 0 { 57 start = 0 58 } 59 60 var count int 61 var startIndex int 62 63 for i := range s { 64 if count == start { 65 startIndex = i 66 } 67 68 if count-start == length { 69 return s[startIndex:i] 70 } 71 72 count++ 73 } 74 75 if startIndex != 0 { 76 return s[startIndex:] 77 } 78 79 return s 80 } 81 82 // Substring returns the part of the string between the start and end (unicode supported) 83 func Substring(s string, start, end int) string { 84 if s == "" || start == end || start >= Len(s) { 85 return "" 86 } 87 88 if start < 0 { 89 start = 0 90 } 91 92 if end < 0 { 93 end = start 94 start = 0 95 } 96 97 var count int 98 var startIndex int 99 100 for i := range s { 101 if count == start { 102 startIndex = i 103 } 104 105 if count == end { 106 return s[startIndex:i] 107 } 108 109 count++ 110 } 111 112 if startIndex != 0 { 113 return s[startIndex:] 114 } 115 116 return s 117 } 118 119 // Extract extracts a substring safely (unicode NOT supported) 120 func Extract(s string, start, end int) string { 121 if s == "" || end < start { 122 return "" 123 } 124 125 if start < 0 { 126 start = 0 127 } 128 129 if end > len(s) { 130 end = len(s) 131 } 132 133 return s[start:end] 134 } 135 136 // Len returns number of symbols in string (unicode supported) 137 func Len(s string) int { 138 if s == "" { 139 return 0 140 } 141 142 var count int 143 144 for range s { 145 count++ 146 } 147 148 return count 149 } 150 151 // Ellipsis trims given string (unicode supported) 152 func Ellipsis(s string, maxSize int) string { 153 if Len(s) <= maxSize { 154 return s 155 } 156 157 return Substr(s, 0, maxSize-Len(EllipsisSuffix)) + EllipsisSuffix 158 } 159 160 // Head returns n first symbols from given string (unicode supported) 161 func Head(s string, n int) string { 162 if s == "" || n <= 0 { 163 return "" 164 } 165 166 l := Len(s) 167 168 if l <= n { 169 return s 170 } 171 172 return Substr(s, 0, n) 173 } 174 175 // Tail returns n last symbols from given string (unicode supported) 176 func Tail(s string, n int) string { 177 if s == "" || n <= 0 { 178 return "" 179 } 180 181 l := Len(s) 182 183 if l <= n { 184 return s 185 } 186 187 return Substr(s, l-n, l) 188 } 189 190 // PrefixSize returns prefix size 191 func PrefixSize(str string, prefix rune) int { 192 if str == "" { 193 return 0 194 } 195 196 var result int 197 198 for i := 0; i < len(str); i++ { 199 if rune(str[i]) != prefix { 200 return result 201 } 202 203 result++ 204 } 205 206 return result 207 } 208 209 // SuffixSize returns suffix size 210 func SuffixSize(str string, suffix rune) int { 211 if str == "" { 212 return 0 213 } 214 215 var result int 216 217 for i := len(str) - 1; i >= 0; i-- { 218 if rune(str[i]) != suffix { 219 return result 220 } 221 222 result++ 223 } 224 225 return result 226 } 227 228 // ReplaceAll replaces all symbols in given string 229 func ReplaceAll(source, from, to string) string { 230 if source == "" { 231 return "" 232 } 233 234 var result strings.Builder 235 236 SOURCELOOP: 237 for _, sourceSym := range source { 238 for _, fromSym := range from { 239 if fromSym == sourceSym { 240 result.WriteString(to) 241 continue SOURCELOOP 242 } 243 } 244 245 result.WriteRune(sourceSym) 246 } 247 248 return result.String() 249 } 250 251 // Exclude excludes substring from given string 252 func Exclude(data, substr string) string { 253 if len(data) == 0 || len(substr) == 0 { 254 return data 255 } 256 257 return strings.ReplaceAll(data, substr, "") 258 } 259 260 // ReadField reads field with given index from data 261 func ReadField(data string, index int, multiSep bool, separators ...string) string { 262 if data == "" || index < 0 { 263 return "" 264 } 265 266 if len(separators) == 0 { 267 separators = defaultFieldsSeparators 268 } 269 270 curIndex, startPointer := -1, -1 271 272 MAINLOOP: 273 for i, r := range data { 274 for _, s := range separators { 275 if r == rune(s[0]) { 276 if curIndex == index { 277 return data[startPointer:i] 278 } 279 280 if !multiSep { 281 startPointer = i + 1 282 curIndex++ 283 continue MAINLOOP 284 } 285 286 startPointer = -1 287 continue MAINLOOP 288 } 289 } 290 291 if startPointer == -1 { 292 startPointer = i 293 curIndex++ 294 } 295 } 296 297 if index > curIndex { 298 return "" 299 } 300 301 return data[startPointer:] 302 } 303 304 // Fields splits the string data around each instance of one or more 305 // consecutive white space or comma characters 306 func Fields(data string) []string { 307 var result []string 308 var item strings.Builder 309 var waitChar rune 310 311 for _, char := range data { 312 switch char { 313 case '"', '\'', '`', '“', '”', '‘', '’', '«', '»', '„': 314 if waitChar == 0 { 315 waitChar = getClosingChar(char) 316 } else if waitChar != 0 && waitChar == char { 317 result = appendField(result, item.String()) 318 item.Reset() 319 waitChar = 0 320 } else { 321 item.WriteRune(char) 322 } 323 324 case ',', ';', ' ': 325 if waitChar != 0 { 326 item.WriteRune(char) 327 } else { 328 result = appendField(result, item.String()) 329 item.Reset() 330 } 331 332 default: 333 item.WriteRune(char) 334 } 335 } 336 337 if item.Len() != 0 { 338 result = appendField(result, item.String()) 339 } 340 341 return result 342 } 343 344 // Copy is method for force string copying 345 func Copy(v string) string { 346 return (v + " ")[:len(v)] 347 } 348 349 // Before returns part of the string before given substring 350 func Before(s, substr string) string { 351 if !strings.Contains(s, substr) { 352 return s 353 } 354 355 return s[:strings.Index(s, substr)] 356 } 357 358 // After returns part of the string after given substring 359 func After(s, substr string) string { 360 if !strings.Contains(s, substr) { 361 return s 362 } 363 364 return s[strings.Index(s, substr)+len(substr):] 365 } 366 367 // HasPrefixAny tests whether the string s begins with one of given prefixes 368 func HasPrefixAny(s string, prefix ...string) bool { 369 for _, prf := range prefix { 370 if strings.HasPrefix(s, prf) { 371 return true 372 } 373 } 374 375 return false 376 } 377 378 // HasSuffixAny tests whether the string s ends with one of given suffixes 379 func HasSuffixAny(s string, suffix ...string) bool { 380 for _, suf := range suffix { 381 if strings.HasSuffix(s, suf) { 382 return true 383 } 384 } 385 386 return false 387 } 388 389 // ////////////////////////////////////////////////////////////////////////////////// // 390 391 func appendField(data []string, item string) []string { 392 if strings.TrimSpace(item) == "" { 393 return data 394 } 395 396 return append(data, item) 397 } 398 399 func getClosingChar(r rune) rune { 400 switch r { 401 case '“': 402 return '”' 403 case '„': 404 return '“' 405 case '‘': 406 return '’' 407 case '«': 408 return '»' 409 default: 410 return r 411 } 412 }