github.com/goravel/framework@v1.13.9/support/str/str.go (about) 1 package str 2 3 import ( 4 "bytes" 5 "crypto/rand" 6 "encoding/json" 7 "path/filepath" 8 "regexp" 9 "strconv" 10 "strings" 11 "unicode" 12 "unicode/utf8" 13 14 "golang.org/x/exp/constraints" 15 "golang.org/x/text/cases" 16 "golang.org/x/text/language" 17 ) 18 19 type String struct { 20 value string 21 } 22 23 // ExcerptOption is the option for Excerpt method 24 type ExcerptOption struct { 25 Radius int 26 Omission string 27 } 28 29 // Of creates a new String instance with the given value. 30 func Of(value string) *String { 31 return &String{value: value} 32 } 33 34 // After returns a new String instance with the substring after the first occurrence of the specified search string. 35 func (s *String) After(search string) *String { 36 if search == "" { 37 return s 38 } 39 index := strings.Index(s.value, search) 40 if index != -1 { 41 s.value = s.value[index+len(search):] 42 } 43 return s 44 } 45 46 // AfterLast returns the String instance with the substring after the last occurrence of the specified search string. 47 func (s *String) AfterLast(search string) *String { 48 index := strings.LastIndex(s.value, search) 49 if index != -1 { 50 s.value = s.value[index+len(search):] 51 } 52 53 return s 54 } 55 56 // Append appends one or more strings to the current string. 57 func (s *String) Append(values ...string) *String { 58 s.value += strings.Join(values, "") 59 return s 60 } 61 62 // Basename returns the String instance with the basename of the current file path string, 63 // and trims the suffix based on the parameter(optional). 64 func (s *String) Basename(suffix ...string) *String { 65 s.value = filepath.Base(s.value) 66 if len(suffix) > 0 && suffix[0] != "" { 67 s.value = strings.TrimSuffix(s.value, suffix[0]) 68 } 69 return s 70 } 71 72 // Before returns the String instance with the substring before the first occurrence of the specified search string. 73 func (s *String) Before(search string) *String { 74 index := strings.Index(s.value, search) 75 if index != -1 { 76 s.value = s.value[:index] 77 } 78 79 return s 80 } 81 82 // BeforeLast returns the String instance with the substring before the last occurrence of the specified search string. 83 func (s *String) BeforeLast(search string) *String { 84 index := strings.LastIndex(s.value, search) 85 if index != -1 { 86 s.value = s.value[:index] 87 } 88 89 return s 90 } 91 92 // Between returns the String instance with the substring between the given start and end strings. 93 func (s *String) Between(start, end string) *String { 94 if start == "" || end == "" { 95 return s 96 } 97 return s.After(start).BeforeLast(end) 98 } 99 100 // BetweenFirst returns the String instance with the substring between the first occurrence of the given start string and the given end string. 101 func (s *String) BetweenFirst(start, end string) *String { 102 if start == "" || end == "" { 103 return s 104 } 105 return s.Before(end).After(start) 106 } 107 108 // Camel returns the String instance in camel case. 109 func (s *String) Camel() *String { 110 return s.Studly().LcFirst() 111 } 112 113 // CharAt returns the character at the specified index. 114 func (s *String) CharAt(index int) string { 115 length := len(s.value) 116 // return zero string when char doesn't exists 117 if index < 0 && index < -length || index > length-1 { 118 return "" 119 } 120 return Substr(s.value, index, 1) 121 } 122 123 // Contains returns true if the string contains the given value or any of the values. 124 func (s *String) Contains(values ...string) bool { 125 for _, value := range values { 126 if value != "" && strings.Contains(s.value, value) { 127 return true 128 } 129 } 130 131 return false 132 } 133 134 // ContainsAll returns true if the string contains all of the given values. 135 func (s *String) ContainsAll(values ...string) bool { 136 for _, value := range values { 137 if !strings.Contains(s.value, value) { 138 return false 139 } 140 } 141 142 return true 143 } 144 145 // Dirname returns the String instance with the directory name of the current file path string. 146 func (s *String) Dirname(levels ...int) *String { 147 defaultLevels := 1 148 if len(levels) > 0 { 149 defaultLevels = levels[0] 150 } 151 152 dir := s.value 153 for i := 0; i < defaultLevels; i++ { 154 dir = filepath.Dir(dir) 155 } 156 157 s.value = dir 158 return s 159 } 160 161 // EndsWith returns true if the string ends with the given value or any of the values. 162 func (s *String) EndsWith(values ...string) bool { 163 for _, value := range values { 164 if value != "" && strings.HasSuffix(s.value, value) { 165 return true 166 } 167 } 168 169 return false 170 } 171 172 // Exactly returns true if the string is exactly the given value. 173 func (s *String) Exactly(value string) bool { 174 return s.value == value 175 } 176 177 // Excerpt returns the String instance truncated to the given length. 178 func (s *String) Excerpt(phrase string, options ...ExcerptOption) *String { 179 defaultOptions := ExcerptOption{ 180 Radius: 100, 181 Omission: "...", 182 } 183 184 if len(options) > 0 { 185 if options[0].Radius != 0 { 186 defaultOptions.Radius = options[0].Radius 187 } 188 if options[0].Omission != "" { 189 defaultOptions.Omission = options[0].Omission 190 } 191 } 192 193 radius := maximum(0, defaultOptions.Radius) 194 omission := defaultOptions.Omission 195 196 regex := regexp.MustCompile(`(.*?)(` + regexp.QuoteMeta(phrase) + `)(.*)`) 197 matches := regex.FindStringSubmatch(s.value) 198 199 if len(matches) == 0 { 200 return s 201 } 202 203 start := strings.TrimRight(matches[1], "") 204 end := strings.TrimLeft(matches[3], "") 205 206 end = Of(Substr(end, 0, radius)).LTrim(""). 207 Unless(func(s *String) bool { 208 return s.Exactly(end) 209 }, func(s *String) *String { 210 return s.Append(omission) 211 }).String() 212 213 s.value = Of(Substr(start, maximum(len(start)-radius, 0), radius)).LTrim(""). 214 Unless(func(s *String) bool { 215 return s.Exactly(start) 216 }, func(s *String) *String { 217 return s.Prepend(omission) 218 }).Append(matches[2], end).String() 219 220 return s 221 } 222 223 // Explode splits the string by given delimiter string. 224 func (s *String) Explode(delimiter string, limit ...int) []string { 225 defaultLimit := 1 226 isLimitSet := false 227 if len(limit) > 0 && limit[0] != 0 { 228 defaultLimit = limit[0] 229 isLimitSet = true 230 } 231 tempExplode := strings.Split(s.value, delimiter) 232 if !isLimitSet || len(tempExplode) <= defaultLimit { 233 return tempExplode 234 } 235 236 if defaultLimit > 0 { 237 return append(tempExplode[:defaultLimit-1], strings.Join(tempExplode[defaultLimit-1:], delimiter)) 238 } 239 240 if defaultLimit < 0 && len(tempExplode) <= -defaultLimit { 241 return []string{} 242 } 243 244 return tempExplode[:len(tempExplode)+defaultLimit] 245 } 246 247 // Finish returns the String instance with the given value appended. 248 // If the given value already ends with the suffix, it will not be added twice. 249 func (s *String) Finish(value string) *String { 250 quoted := regexp.QuoteMeta(value) 251 reg := regexp.MustCompile("(?:" + quoted + ")+$") 252 s.value = reg.ReplaceAllString(s.value, "") + value 253 return s 254 } 255 256 // Headline returns the String instance in headline case. 257 func (s *String) Headline() *String { 258 parts := s.Explode(" ") 259 260 if len(parts) > 1 { 261 return s.Title() 262 } 263 264 parts = Of(strings.Join(parts, "_")).Studly().UcSplit() 265 collapsed := Of(strings.Join(parts, "_")). 266 Replace("-", "_"). 267 Replace(" ", "_"). 268 Replace("_", "_").Explode("_") 269 270 s.value = strings.Join(collapsed, " ") 271 return s 272 } 273 274 // Is returns true if the string matches any of the given patterns. 275 func (s *String) Is(patterns ...string) bool { 276 for _, pattern := range patterns { 277 if pattern == s.value { 278 return true 279 } 280 281 // Escape special characters in the pattern 282 pattern = regexp.QuoteMeta(pattern) 283 284 // Replace asterisks with regular expression wildcards 285 pattern = strings.ReplaceAll(pattern, `\*`, ".*") 286 287 // Create a regular expression pattern for matching 288 regexPattern := "^" + pattern + "$" 289 290 // Compile the regular expression 291 regex := regexp.MustCompile(regexPattern) 292 293 // Check if the value matches the pattern 294 if regex.MatchString(s.value) { 295 return true 296 } 297 } 298 299 return false 300 } 301 302 // IsEmpty returns true if the string is empty. 303 func (s *String) IsEmpty() bool { 304 return s.value == "" 305 } 306 307 // IsNotEmpty returns true if the string is not empty. 308 func (s *String) IsNotEmpty() bool { 309 return !s.IsEmpty() 310 } 311 312 // IsAscii returns true if the string contains only ASCII characters. 313 func (s *String) IsAscii() bool { 314 return s.IsMatch(`^[\x00-\x7F]+$`) 315 } 316 317 // IsMap returns true if the string is a valid Map. 318 func (s *String) IsMap() bool { 319 var obj map[string]interface{} 320 return json.Unmarshal([]byte(s.value), &obj) == nil 321 } 322 323 // IsSlice returns true if the string is a valid Slice. 324 func (s *String) IsSlice() bool { 325 var arr []interface{} 326 return json.Unmarshal([]byte(s.value), &arr) == nil 327 } 328 329 // IsUlid returns true if the string is a valid ULID. 330 func (s *String) IsUlid() bool { 331 return s.IsMatch(`^[0-9A-Z]{26}$`) 332 } 333 334 // IsUuid returns true if the string is a valid UUID. 335 func (s *String) IsUuid() bool { 336 return s.IsMatch(`(?i)^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`) 337 } 338 339 // Kebab returns the String instance in kebab case. 340 func (s *String) Kebab() *String { 341 return s.Snake("-") 342 } 343 344 // LcFirst returns the String instance with the first character lowercased. 345 func (s *String) LcFirst() *String { 346 if s.Length() == 0 { 347 return s 348 } 349 s.value = strings.ToLower(Substr(s.value, 0, 1)) + Substr(s.value, 1) 350 return s 351 } 352 353 // Length returns the length of the string. 354 func (s *String) Length() int { 355 return utf8.RuneCountInString(s.value) 356 } 357 358 // Limit returns the String instance truncated to the given length. 359 func (s *String) Limit(limit int, end ...string) *String { 360 defaultEnd := "..." 361 if len(end) > 0 { 362 defaultEnd = end[0] 363 } 364 365 if s.Length() <= limit { 366 return s 367 } 368 s.value = Substr(s.value, 0, limit) + defaultEnd 369 return s 370 } 371 372 // Lower returns the String instance in lower case. 373 func (s *String) Lower() *String { 374 s.value = strings.ToLower(s.value) 375 return s 376 } 377 378 // Ltrim returns the String instance with the leftmost occurrence of the given value removed. 379 func (s *String) LTrim(characters ...string) *String { 380 if len(characters) == 0 { 381 s.value = strings.TrimLeft(s.value, " ") 382 return s 383 } 384 385 s.value = strings.TrimLeft(s.value, characters[0]) 386 return s 387 } 388 389 // Mask returns the String instance with the given character masking the specified number of characters. 390 func (s *String) Mask(character string, index int, length ...int) *String { 391 // Check if the character is empty, if so, return the original string. 392 if character == "" { 393 return s 394 } 395 396 segment := Substr(s.value, index, length...) 397 398 // Check if the segment is empty, if so, return the original string. 399 if segment == "" { 400 return s 401 } 402 403 strLen := utf8.RuneCountInString(s.value) 404 startIndex := index 405 406 // Check if the start index is out of bounds. 407 if index < 0 { 408 if index < -strLen { 409 startIndex = 0 410 } else { 411 startIndex = strLen + index 412 } 413 } 414 415 start := Substr(s.value, 0, startIndex) 416 segmentLen := utf8.RuneCountInString(segment) 417 end := Substr(s.value, startIndex+segmentLen) 418 419 s.value = start + strings.Repeat(Substr(character, 0, 1), segmentLen) + end 420 return s 421 } 422 423 // Match returns the String instance with the first occurrence of the given pattern. 424 func (s *String) Match(pattern string) *String { 425 if pattern == "" { 426 return s 427 } 428 reg := regexp.MustCompile(pattern) 429 s.value = reg.FindString(s.value) 430 return s 431 } 432 433 // MatchAll returns all matches for the given regular expression. 434 func (s *String) MatchAll(pattern string) []string { 435 if pattern == "" { 436 return []string{s.value} 437 } 438 reg := regexp.MustCompile(pattern) 439 return reg.FindAllString(s.value, -1) 440 } 441 442 // IsMatch returns true if the string matches any of the given patterns. 443 func (s *String) IsMatch(patterns ...string) bool { 444 for _, pattern := range patterns { 445 reg := regexp.MustCompile(pattern) 446 if reg.MatchString(s.value) { 447 return true 448 } 449 } 450 451 return false 452 } 453 454 // NewLine appends one or more new lines to the current string. 455 func (s *String) NewLine(count ...int) *String { 456 if len(count) == 0 { 457 s.value += "\n" 458 return s 459 } 460 461 s.value += strings.Repeat("\n", count[0]) 462 return s 463 } 464 465 // PadBoth returns the String instance padded to the left and right sides of the given length. 466 func (s *String) PadBoth(length int, pad ...string) *String { 467 defaultPad := " " 468 if len(pad) > 0 { 469 defaultPad = pad[0] 470 } 471 short := maximum(0, length-s.Length()) 472 left := short / 2 473 right := short/2 + short%2 474 475 s.value = Substr(strings.Repeat(defaultPad, left), 0, left) + s.value + Substr(strings.Repeat(defaultPad, right), 0, right) 476 477 return s 478 } 479 480 // PadLeft returns the String instance padded to the left side of the given length. 481 func (s *String) PadLeft(length int, pad ...string) *String { 482 defaultPad := " " 483 if len(pad) > 0 { 484 defaultPad = pad[0] 485 } 486 short := maximum(0, length-s.Length()) 487 488 s.value = Substr(strings.Repeat(defaultPad, short), 0, short) + s.value 489 return s 490 } 491 492 // PadRight returns the String instance padded to the right side of the given length. 493 func (s *String) PadRight(length int, pad ...string) *String { 494 defaultPad := " " 495 if len(pad) > 0 { 496 defaultPad = pad[0] 497 } 498 short := maximum(0, length-s.Length()) 499 500 s.value = s.value + Substr(strings.Repeat(defaultPad, short), 0, short) 501 return s 502 } 503 504 // Pipe passes the string to the given callback and returns the result. 505 func (s *String) Pipe(callback func(s string) string) *String { 506 s.value = callback(s.value) 507 return s 508 } 509 510 // Prepend one or more strings to the current string. 511 func (s *String) Prepend(values ...string) *String { 512 s.value = strings.Join(values, "") + s.value 513 return s 514 } 515 516 // Remove returns the String instance with the first occurrence of the given value removed. 517 func (s *String) Remove(values ...string) *String { 518 for _, value := range values { 519 s.value = strings.ReplaceAll(s.value, value, "") 520 } 521 522 return s 523 } 524 525 // Repeat returns the String instance repeated the given number of times. 526 func (s *String) Repeat(times int) *String { 527 s.value = strings.Repeat(s.value, times) 528 return s 529 } 530 531 // Replace returns the String instance with all occurrences of the search string replaced by the given replacement string. 532 func (s *String) Replace(search string, replace string, caseSensitive ...bool) *String { 533 caseSensitive = append(caseSensitive, true) 534 if len(caseSensitive) > 0 && !caseSensitive[0] { 535 s.value = regexp.MustCompile("(?i)"+search).ReplaceAllString(s.value, replace) 536 return s 537 } 538 s.value = strings.ReplaceAll(s.value, search, replace) 539 return s 540 } 541 542 // ReplaceEnd returns the String instance with the last occurrence of the given value replaced. 543 func (s *String) ReplaceEnd(search string, replace string) *String { 544 if search == "" { 545 return s 546 } 547 548 if s.EndsWith(search) { 549 return s.ReplaceLast(search, replace) 550 } 551 552 return s 553 } 554 555 // ReplaceFirst returns the String instance with the first occurrence of the given value replaced. 556 func (s *String) ReplaceFirst(search string, replace string) *String { 557 if search == "" { 558 return s 559 } 560 s.value = strings.Replace(s.value, search, replace, 1) 561 return s 562 } 563 564 // ReplaceLast returns the String instance with the last occurrence of the given value replaced. 565 func (s *String) ReplaceLast(search string, replace string) *String { 566 if search == "" { 567 return s 568 } 569 index := strings.LastIndex(s.value, search) 570 if index != -1 { 571 s.value = s.value[:index] + replace + s.value[index+len(search):] 572 return s 573 } 574 575 return s 576 } 577 578 // ReplaceMatches returns the String instance with all occurrences of the given pattern 579 // replaced by the given replacement string. 580 func (s *String) ReplaceMatches(pattern string, replace string) *String { 581 s.value = regexp.MustCompile(pattern).ReplaceAllString(s.value, replace) 582 return s 583 } 584 585 // ReplaceStart returns the String instance with the first occurrence of the given value replaced. 586 func (s *String) ReplaceStart(search string, replace string) *String { 587 if search == "" { 588 return s 589 } 590 591 if s.StartsWith(search) { 592 return s.ReplaceFirst(search, replace) 593 } 594 595 return s 596 } 597 598 // RTrim returns the String instance with the right occurrences of the given value removed. 599 func (s *String) RTrim(characters ...string) *String { 600 if len(characters) == 0 { 601 s.value = strings.TrimRight(s.value, " ") 602 return s 603 } 604 605 s.value = strings.TrimRight(s.value, characters[0]) 606 return s 607 } 608 609 // Snake returns the String instance in snake case. 610 func (s *String) Snake(delimiter ...string) *String { 611 defaultDelimiter := "_" 612 if len(delimiter) > 0 { 613 defaultDelimiter = delimiter[0] 614 } 615 words := fieldsFunc(s.value, func(r rune) bool { 616 return r == ' ' || r == ',' || r == '.' || r == '-' || r == '_' 617 }, func(r rune) bool { 618 return unicode.IsUpper(r) 619 }) 620 621 casesLower := cases.Lower(language.Und) 622 var studlyWords []string 623 for _, word := range words { 624 studlyWords = append(studlyWords, casesLower.String(word)) 625 } 626 627 s.value = strings.Join(studlyWords, defaultDelimiter) 628 return s 629 } 630 631 // Split splits the string by given pattern string. 632 func (s *String) Split(pattern string, limit ...int) []string { 633 r := regexp.MustCompile(pattern) 634 defaultLimit := -1 635 if len(limit) != 0 { 636 defaultLimit = limit[0] 637 } 638 639 return r.Split(s.value, defaultLimit) 640 } 641 642 // Squish returns the String instance with consecutive whitespace characters collapsed into a single space. 643 func (s *String) Squish() *String { 644 leadWhitespace := regexp.MustCompile(`^[\s\p{Zs}]+|[\s\p{Zs}]+$`) 645 insideWhitespace := regexp.MustCompile(`[\s\p{Zs}]{2,}`) 646 s.value = leadWhitespace.ReplaceAllString(s.value, "") 647 s.value = insideWhitespace.ReplaceAllString(s.value, " ") 648 return s 649 } 650 651 // Start returns the String instance with the given value prepended. 652 func (s *String) Start(prefix string) *String { 653 quoted := regexp.QuoteMeta(prefix) 654 re := regexp.MustCompile(`^(` + quoted + `)+`) 655 s.value = prefix + re.ReplaceAllString(s.value, "") 656 return s 657 } 658 659 // StartsWith returns true if the string starts with the given value or any of the values. 660 func (s *String) StartsWith(values ...string) bool { 661 for _, value := range values { 662 if strings.HasPrefix(s.value, value) { 663 return true 664 } 665 } 666 667 return false 668 } 669 670 // String returns the string value. 671 func (s *String) String() string { 672 return s.value 673 } 674 675 // Studly returns the String instance in studly case. 676 func (s *String) Studly() *String { 677 words := fieldsFunc(s.value, func(r rune) bool { 678 return r == '_' || r == ' ' || r == '-' || r == ',' || r == '.' 679 }, func(r rune) bool { 680 return unicode.IsUpper(r) 681 }) 682 683 casesTitle := cases.Title(language.Und) 684 var studlyWords []string 685 for _, word := range words { 686 studlyWords = append(studlyWords, casesTitle.String(word)) 687 } 688 689 s.value = strings.Join(studlyWords, "") 690 return s 691 } 692 693 // Substr returns the String instance starting at the given index with the specified length. 694 func (s *String) Substr(start int, length ...int) *String { 695 s.value = Substr(s.value, start, length...) 696 return s 697 } 698 699 // Swap replaces all occurrences of the search string with the given replacement string. 700 func (s *String) Swap(replacements map[string]string) *String { 701 if len(replacements) == 0 { 702 return s 703 } 704 705 oldNewPairs := make([]string, 0, len(replacements)*2) 706 for k, v := range replacements { 707 if k == "" { 708 return s 709 } 710 oldNewPairs = append(oldNewPairs, k, v) 711 } 712 713 s.value = strings.NewReplacer(oldNewPairs...).Replace(s.value) 714 return s 715 } 716 717 // Tap passes the string to the given callback and returns the string. 718 func (s *String) Tap(callback func(String)) *String { 719 callback(*s) 720 return s 721 } 722 723 // Test returns true if the string matches the given pattern. 724 func (s *String) Test(pattern string) bool { 725 return s.IsMatch(pattern) 726 } 727 728 // Title returns the String instance in title case. 729 func (s *String) Title() *String { 730 casesTitle := cases.Title(language.Und) 731 s.value = casesTitle.String(s.value) 732 return s 733 } 734 735 // Trim returns the String instance with trimmed characters from the left and right sides. 736 func (s *String) Trim(characters ...string) *String { 737 if len(characters) == 0 { 738 s.value = strings.TrimSpace(s.value) 739 return s 740 } 741 742 s.value = strings.Trim(s.value, characters[0]) 743 return s 744 } 745 746 // UcFirst returns the String instance with the first character uppercased. 747 func (s *String) UcFirst() *String { 748 if s.Length() == 0 { 749 return s 750 } 751 s.value = strings.ToUpper(Substr(s.value, 0, 1)) + Substr(s.value, 1) 752 return s 753 } 754 755 // UcSplit splits the string into words using uppercase characters as the delimiter. 756 func (s *String) UcSplit() []string { 757 words := fieldsFunc(s.value, func(r rune) bool { 758 return false 759 }, func(r rune) bool { 760 return unicode.IsUpper(r) 761 }) 762 return words 763 } 764 765 // Unless returns the String instance with the given fallback applied if the given condition is false. 766 func (s *String) Unless(callback func(*String) bool, fallback func(*String) *String) *String { 767 if !callback(s) { 768 return fallback(s) 769 } 770 771 return s 772 } 773 774 // Upper returns the String instance in upper case. 775 func (s *String) Upper() *String { 776 s.value = strings.ToUpper(s.value) 777 return s 778 } 779 780 // When returns the String instance with the given callback applied if the given condition is true. 781 // If the condition is false, the fallback callback is applied.(if provided) 782 func (s *String) When(condition bool, callback ...func(*String) *String) *String { 783 if condition { 784 return callback[0](s) 785 } else { 786 if len(callback) > 1 { 787 return callback[1](s) 788 } 789 } 790 791 return s 792 } 793 794 // WhenContains returns the String instance with the given callback applied if the string contains the given value. 795 func (s *String) WhenContains(value string, callback ...func(*String) *String) *String { 796 return s.When(s.Contains(value), callback...) 797 } 798 799 // WhenContainsAll returns the String instance with the given callback applied if the string contains all the given values. 800 func (s *String) WhenContainsAll(values []string, callback ...func(*String) *String) *String { 801 return s.When(s.ContainsAll(values...), callback...) 802 } 803 804 // WhenEmpty returns the String instance with the given callback applied if the string is empty. 805 func (s *String) WhenEmpty(callback ...func(*String) *String) *String { 806 return s.When(s.IsEmpty(), callback...) 807 } 808 809 // WhenIsAscii returns the String instance with the given callback applied if the string contains only ASCII characters. 810 func (s *String) WhenIsAscii(callback ...func(*String) *String) *String { 811 return s.When(s.IsAscii(), callback...) 812 } 813 814 // WhenNotEmpty returns the String instance with the given callback applied if the string is not empty. 815 func (s *String) WhenNotEmpty(callback ...func(*String) *String) *String { 816 return s.When(s.IsNotEmpty(), callback...) 817 } 818 819 // WhenStartsWith returns the String instance with the given callback applied if the string starts with the given value. 820 func (s *String) WhenStartsWith(value []string, callback ...func(*String) *String) *String { 821 return s.When(s.StartsWith(value...), callback...) 822 } 823 824 // WhenEndsWith returns the String instance with the given callback applied if the string ends with the given value. 825 func (s *String) WhenEndsWith(value []string, callback ...func(*String) *String) *String { 826 return s.When(s.EndsWith(value...), callback...) 827 } 828 829 // WhenExactly returns the String instance with the given callback applied if the string is exactly the given value. 830 func (s *String) WhenExactly(value string, callback ...func(*String) *String) *String { 831 return s.When(s.Exactly(value), callback...) 832 } 833 834 // WhenNotExactly returns the String instance with the given callback applied if the string is not exactly the given value. 835 func (s *String) WhenNotExactly(value string, callback ...func(*String) *String) *String { 836 return s.When(!s.Exactly(value), callback...) 837 } 838 839 // WhenIs returns the String instance with the given callback applied if the string matches any of the given patterns. 840 func (s *String) WhenIs(value string, callback ...func(*String) *String) *String { 841 return s.When(s.Is(value), callback...) 842 } 843 844 // WhenIsUlid returns the String instance with the given callback applied if the string is a valid ULID. 845 func (s *String) WhenIsUlid(callback ...func(*String) *String) *String { 846 return s.When(s.IsUlid(), callback...) 847 } 848 849 // WhenIsUuid returns the String instance with the given callback applied if the string is a valid UUID. 850 func (s *String) WhenIsUuid(callback ...func(*String) *String) *String { 851 return s.When(s.IsUuid(), callback...) 852 } 853 854 // WhenTest returns the String instance with the given callback applied if the string matches the given pattern. 855 func (s *String) WhenTest(pattern string, callback ...func(*String) *String) *String { 856 return s.When(s.Test(pattern), callback...) 857 } 858 859 // WordCount returns the number of words in the string. 860 func (s *String) WordCount() int { 861 return len(strings.Fields(s.value)) 862 } 863 864 // Words return the String instance truncated to the given number of words. 865 func (s *String) Words(limit int, end ...string) *String { 866 defaultEnd := "..." 867 if len(end) > 0 { 868 defaultEnd = end[0] 869 } 870 871 words := strings.Fields(s.value) 872 if len(words) <= limit { 873 return s 874 } 875 876 s.value = strings.Join(words[:limit], " ") + defaultEnd 877 return s 878 } 879 880 // Substr returns a substring of a given string, starting at the specified index 881 // and with a specified length. 882 // It handles UTF-8 encoded strings. 883 func Substr(str string, start int, length ...int) string { 884 // Convert the string to a rune slice for proper handling of UTF-8 encoding. 885 runes := []rune(str) 886 strLen := utf8.RuneCountInString(str) 887 end := strLen 888 // Check if the start index is out of bounds. 889 if start >= strLen { 890 return "" 891 } 892 893 // If the start index is negative, count backwards from the end of the string. 894 if start < 0 { 895 start = strLen + start 896 if start < 0 { 897 start = 0 898 } 899 } 900 901 if len(length) > 0 { 902 if length[0] >= 0 { 903 end = start + length[0] 904 } else { 905 end = strLen + length[0] 906 } 907 } 908 909 // If the length is 0, return the substring from start to the end of the string. 910 if len(length) == 0 { 911 return string(runes[start:]) 912 } 913 914 // Handle the case where lenArg is negative and less than start 915 if end < start { 916 return "" 917 } 918 919 if end > strLen { 920 end = strLen 921 } 922 923 // Return the substring. 924 return string(runes[start:end]) 925 } 926 927 func Random(length int) string { 928 b := make([]byte, length) 929 _, err := rand.Read(b) 930 if err != nil { 931 panic(err) 932 } 933 letters := "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 934 for i, v := range b { 935 b[i] = letters[v%byte(len(letters))] 936 } 937 938 return string(b) 939 } 940 941 func Case2Camel(name string) string { 942 names := strings.Split(name, "_") 943 944 var newName string 945 for _, item := range names { 946 buffer := NewBuffer() 947 for i, r := range item { 948 if i == 0 { 949 buffer.Append(unicode.ToUpper(r)) 950 } else { 951 buffer.Append(r) 952 } 953 } 954 955 newName += buffer.String() 956 } 957 958 return newName 959 } 960 961 func Camel2Case(name string) string { 962 buffer := NewBuffer() 963 for i, r := range name { 964 if unicode.IsUpper(r) { 965 if i != 0 { 966 buffer.Append('_') 967 } 968 buffer.Append(unicode.ToLower(r)) 969 } else { 970 buffer.Append(r) 971 } 972 } 973 974 return buffer.String() 975 } 976 977 type Buffer struct { 978 *bytes.Buffer 979 } 980 981 func NewBuffer() *Buffer { 982 return &Buffer{Buffer: new(bytes.Buffer)} 983 } 984 985 func (b *Buffer) Append(i any) *Buffer { 986 switch val := i.(type) { 987 case int: 988 b.append(strconv.Itoa(val)) 989 case int64: 990 b.append(strconv.FormatInt(val, 10)) 991 case uint: 992 b.append(strconv.FormatUint(uint64(val), 10)) 993 case uint64: 994 b.append(strconv.FormatUint(val, 10)) 995 case string: 996 b.append(val) 997 case []byte: 998 b.Write(val) 999 case rune: 1000 b.WriteRune(val) 1001 } 1002 return b 1003 } 1004 1005 func (b *Buffer) append(s string) *Buffer { 1006 b.WriteString(s) 1007 1008 return b 1009 } 1010 1011 // fieldsFunc splits the input string into words with preservation, following the rules defined by 1012 // the provided functions f and preserveFunc. 1013 func fieldsFunc(s string, f func(rune) bool, preserveFunc ...func(rune) bool) []string { 1014 var fields []string 1015 var currentField strings.Builder 1016 1017 shouldPreserve := func(r rune) bool { 1018 for _, preserveFn := range preserveFunc { 1019 if preserveFn(r) { 1020 return true 1021 } 1022 } 1023 return false 1024 } 1025 1026 for _, r := range s { 1027 if f(r) { 1028 if currentField.Len() > 0 { 1029 fields = append(fields, currentField.String()) 1030 currentField.Reset() 1031 } 1032 } else if shouldPreserve(r) { 1033 if currentField.Len() > 0 { 1034 fields = append(fields, currentField.String()) 1035 currentField.Reset() 1036 } 1037 currentField.WriteRune(r) 1038 } else { 1039 currentField.WriteRune(r) 1040 } 1041 } 1042 1043 if currentField.Len() > 0 { 1044 fields = append(fields, currentField.String()) 1045 } 1046 1047 return fields 1048 } 1049 1050 // maximum returns the largest of x or y. 1051 func maximum[T constraints.Ordered](x T, y T) T { 1052 if x > y { 1053 return x 1054 } 1055 return y 1056 }