github.com/enetx/g@v1.0.80/string.go (about) 1 package g 2 3 import ( 4 "fmt" 5 "regexp" 6 "strconv" 7 "strings" 8 "unicode" 9 "unicode/utf8" 10 11 "github.com/enetx/g/cmp" 12 "golang.org/x/text/cases" 13 "golang.org/x/text/language" 14 "golang.org/x/text/unicode/norm" 15 ) 16 17 // NewString creates a new String from the provided string. 18 func NewString[T ~string | rune | byte | ~[]rune | ~[]byte](str T) String { return String(str) } 19 20 // Builder returns a new Builder initialized with the content of the String. 21 func (s String) Builder() *Builder { return NewBuilder().Write(s) } 22 23 // Min returns the minimum of Strings. 24 func (s String) Min(b ...String) String { return cmp.Min(append(b, s)...) } 25 26 // Max returns the maximum of Strings. 27 func (s String) Max(b ...String) String { return cmp.Max(append(b, s)...) } 28 29 // Random generates a random String of the specified length, selecting characters from predefined sets. 30 // If additional character sets are provided, only those will be used; the default set (ASCII_LETTERS and DIGITS) 31 // is excluded unless explicitly provided. 32 // 33 // Parameters: 34 // - count (Int): Length of the random String to generate. 35 // - letters (...String): Additional character sets to consider for generating the random String (optional). 36 // 37 // Returns: 38 // - String: Randomly generated String with the specified length. 39 // 40 // Example usage: 41 // 42 // randomString := g.String.Random(10) 43 // randomString contains a random String with 10 characters. 44 func (String) Random(count Int, letters ...String) String { 45 var chars Slice[rune] 46 47 if len(letters) != 0 { 48 chars = letters[0].ToRunes() 49 } else { 50 chars = (ASCII_LETTERS + DIGITS).ToRunes() 51 } 52 53 result := NewBuilder() 54 55 for range count { 56 result.WriteRune(chars.Random()) 57 } 58 59 return result.String() 60 } 61 62 // IsASCII checks if all characters in the String are ASCII bytes. 63 func (s String) IsASCII() bool { 64 for _, r := range s { 65 if r > unicode.MaxASCII { 66 return false 67 } 68 } 69 70 return true 71 } 72 73 // IsDigit checks if all characters in the String are digits. 74 func (s String) IsDigit() bool { 75 if s.Empty() { 76 return false 77 } 78 79 for _, c := range s { 80 if !unicode.IsDigit(c) { 81 return false 82 } 83 } 84 85 return true 86 } 87 88 // ToInt tries to parse the String as an int and returns an Int. 89 func (s String) ToInt() Result[Int] { 90 hint, err := strconv.ParseInt(s.Std(), 0, 32) 91 if err != nil { 92 return Err[Int](err) 93 } 94 95 return Ok(Int(hint)) 96 } 97 98 // ToFloat tries to parse the String as a float64 and returns an Float. 99 func (s String) ToFloat() Result[Float] { 100 float, err := strconv.ParseFloat(s.Std(), 64) 101 if err != nil { 102 return Err[Float](err) 103 } 104 105 return Ok(Float(float)) 106 } 107 108 // Title converts the String to title case. 109 func (s String) Title() String { return String(cases.Title(language.English).String(s.Std())) } 110 111 // Lower returns the String in lowercase. 112 func (s String) Lower() String { return String(cases.Lower(language.English).String(s.Std())) } 113 114 // Upper returns the String in uppercase. 115 func (s String) Upper() String { return String(cases.Upper(language.English).String(s.Std())) } 116 117 // Trim trims characters in the cutset from the beginning and end of the String. 118 func (s String) Trim(cutset String) String { 119 return String(strings.Trim(s.Std(), cutset.Std())) 120 } 121 122 // TrimLeft trims characters in the cutset from the beginning of the String. 123 func (s String) TrimLeft(cutset String) String { 124 return String(strings.TrimLeft(s.Std(), cutset.Std())) 125 } 126 127 // TrimRight trims characters in the cutset from the end of the String. 128 func (s String) TrimRight(cutset String) String { 129 return String(strings.TrimRight(s.Std(), cutset.Std())) 130 } 131 132 // TrimPrefix trims the specified prefix from the String. 133 func (s String) TrimPrefix(prefix String) String { 134 return String(strings.TrimPrefix(s.Std(), prefix.Std())) 135 } 136 137 // TrimSuffix trims the specified suffix from the String. 138 func (s String) TrimSuffix(suffix String) String { 139 return String(strings.TrimSuffix(s.Std(), suffix.Std())) 140 } 141 142 // Replace replaces the 'oldS' String with the 'newS' String for the specified number of 143 // occurrences. 144 func (s String) Replace(oldS, newS String, n Int) String { 145 return String(strings.Replace(s.Std(), oldS.Std(), newS.Std(), n.Std())) 146 } 147 148 // ReplaceAll replaces all occurrences of the 'oldS' String with the 'newS' String. 149 func (s String) ReplaceAll(oldS, newS String) String { 150 return String(strings.ReplaceAll(s.Std(), oldS.Std(), newS.Std())) 151 } 152 153 // ReplaceMulti creates a custom replacer to perform multiple string replacements. 154 // 155 // Parameters: 156 // 157 // - oldnew ...String: Pairs of strings to be replaced. Specify as many pairs as needed. 158 // 159 // Returns: 160 // 161 // - String: A new string with replacements applied using the custom replacer. 162 // 163 // Example usage: 164 // 165 // original := g.String("Hello, world! This is a test.") 166 // replaced := original.ReplaceMulti( 167 // "Hello", "Greetings", 168 // "world", "universe", 169 // "test", "example", 170 // ) 171 // // replaced contains "Greetings, universe! This is an example." 172 func (s String) ReplaceMulti(oldnew ...String) String { 173 on := Slice[String](oldnew).ToStringSlice() 174 return String(strings.NewReplacer(on...).Replace(s.Std())) 175 } 176 177 // Remove removes all occurrences of specified substrings from the String. 178 // 179 // Parameters: 180 // 181 // - matches ...String: Substrings to be removed from the string. Specify as many substrings as needed. 182 // 183 // Returns: 184 // 185 // - String: A new string with all specified substrings removed. 186 // 187 // Example usage: 188 // 189 // original := g.String("Hello, world! This is a test.") 190 // modified := original.Remove( 191 // "Hello", 192 // "test", 193 // ) 194 // // modified contains ", world! This is a ." 195 func (s String) Remove(matches ...String) String { 196 for _, match := range matches { 197 s = s.ReplaceAll(match, "") 198 } 199 200 return s 201 } 202 203 // ReplaceRegexp replaces all occurrences of the regular expression matches in the String 204 // with the provided newS (as a String) and returns the resulting String after the replacement. 205 func (s String) ReplaceRegexp(pattern *regexp.Regexp, newS String) String { 206 return String(pattern.ReplaceAllString(s.Std(), newS.Std())) 207 } 208 209 // FindRegexp searches the String for the first occurrence of the regulare xpression pattern 210 // and returns an Option[String] containing the matched substring. 211 // If no match is found, it returns None. 212 func (s String) FindRegexp(pattern *regexp.Regexp) Option[String] { 213 result := String(pattern.FindString(s.Std())) 214 if result.Empty() { 215 return None[String]() 216 } 217 218 return Some(result) 219 } 220 221 // ReplaceNth returns a new String instance with the nth occurrence of oldS 222 // replaced with newS. If there aren't enough occurrences of oldS, the 223 // original String is returned. If n is less than -1, the original String 224 // is also returned. If n is -1, the last occurrence of oldS is replaced with newS. 225 // 226 // Returns: 227 // 228 // - A new String instance with the nth occurrence of oldS replaced with newS. 229 // 230 // Example usage: 231 // 232 // s := g.String("The quick brown dog jumped over the lazy dog.") 233 // result := s.ReplaceNth("dog", "fox", 2) 234 // fmt.Println(result) 235 // 236 // Output: "The quick brown dog jumped over the lazy fox.". 237 func (s String) ReplaceNth(oldS, newS String, n Int) String { 238 if n < -1 || len(oldS) == 0 { 239 return s 240 } 241 242 count, i := Int(0), Int(0) 243 244 for { 245 pos := s[i:].Index(oldS) 246 if pos == -1 { 247 break 248 } 249 250 pos += i 251 count++ 252 253 if count == n || (n == -1 && s[pos+oldS.Len():].Index(oldS) == -1) { 254 return s[:pos] + newS + s[pos+oldS.Len():] 255 } 256 257 i = pos + oldS.Len() 258 } 259 260 return s 261 } 262 263 // ContainsRegexp checks if the String contains a match for the specified regular expression pattern. 264 func (s String) ContainsRegexp(pattern String) Result[bool] { 265 return ResultOf(regexp.MatchString(pattern.Std(), s.Std())) 266 } 267 268 // ContainsRegexpAny checks if the String contains a match for any of the specified regular 269 // expression patterns. 270 func (s String) ContainsRegexpAny(patterns ...String) Result[bool] { 271 for _, pattern := range patterns { 272 if r := s.ContainsRegexp(pattern); r.IsErr() || r.Ok() { 273 return r 274 } 275 } 276 277 return Ok(false) 278 } 279 280 // ContainsRegexpAll checks if the String contains a match for all of the specified regular expression patterns. 281 func (s String) ContainsRegexpAll(patterns ...String) Result[bool] { 282 for _, pattern := range patterns { 283 if r := s.ContainsRegexp(pattern); r.IsErr() || !r.Ok() { 284 return r 285 } 286 } 287 288 return Ok(true) 289 } 290 291 // Contains checks if the String contains the specified substring. 292 func (s String) Contains(substr String) bool { return strings.Contains(s.Std(), substr.Std()) } 293 294 // ContainsAny checks if the String contains any of the specified substrings. 295 func (s String) ContainsAny(substrs ...String) bool { 296 for _, substr := range substrs { 297 if s.Contains(substr) { 298 return true 299 } 300 } 301 302 return false 303 } 304 305 // ContainsAll checks if the given String contains all the specified substrings. 306 func (s String) ContainsAll(substrs ...String) bool { 307 for _, substr := range substrs { 308 if !s.Contains(substr) { 309 return false 310 } 311 } 312 313 return true 314 } 315 316 // ContainsAnyChars checks if the String contains any characters from the specified String. 317 func (s String) ContainsAnyChars(chars String) bool { 318 return strings.ContainsAny(s.Std(), chars.Std()) 319 } 320 321 // StartsWith checks if the String starts with any of the provided prefixes. 322 // The method accepts a variable number of arguments, allowing for checking against multiple 323 // prefixes at once. It iterates over the provided prefixes and uses the HasPrefix function from 324 // the strings package to check if the String starts with each prefix. 325 // The function returns true if the String starts with any of the prefixes, and false otherwise. 326 // 327 // Example usage: 328 // 329 // s := g.String("http://example.com") 330 // if s.StartsWith("http://", "https://") { 331 // // do something 332 // } 333 func (s String) StartsWith(prefixes ...String) bool { 334 for _, prefix := range prefixes { 335 if strings.HasPrefix(string(s), prefix.Std()) { 336 return true 337 } 338 } 339 340 return false 341 } 342 343 // EndsWith checks if the String ends with any of the provided suffixes. 344 // The method accepts a variable number of arguments, allowing for checking against multiple 345 // suffixes at once. It iterates over the provided suffixes and uses the HasSuffix function from 346 // the strings package to check if the String ends with each suffix. 347 // The function returns true if the String ends with any of the suffixes, and false otherwise. 348 // 349 // Example usage: 350 // 351 // s := g.String("example.com") 352 // if s.EndsWith(".com", ".net") { 353 // // do something 354 // } 355 func (s String) EndsWith(suffixes ...String) bool { 356 for _, suffix := range suffixes { 357 if strings.HasSuffix(string(s), suffix.Std()) { 358 return true 359 } 360 } 361 362 return false 363 } 364 365 // Lines splits the String by lines and returns the iterator. 366 func (s String) Lines() SeqSlice[String] { return linesString(s) } 367 368 // Fields splits the String into a slice of substrings, removing any whitespace, and returns the iterator. 369 func (s String) Fields() SeqSlice[String] { return fieldsString(s) } 370 371 // FieldsBy splits the String into a slice of substrings using a custom function to determine the field boundaries, 372 // and returns the iterator. 373 func (s String) FieldsBy(fn func(r rune) bool) SeqSlice[String] { return fieldsbyString(s, fn) } 374 375 // Split splits the String by the specified separator and returns the iterator. 376 func (s String) Split(sep ...String) SeqSlice[String] { 377 var separator String 378 if len(sep) != 0 { 379 separator = sep[0] 380 } 381 382 return splitString(s, separator, 0) 383 } 384 385 // SplitAfter splits the String after each instance of the specified separator and returns the iterator. 386 func (s String) SplitAfter(sep String) SeqSlice[String] { return splitString(s, sep, sep.Len()) } 387 388 // SplitN splits the String into substrings using the provided separator and returns an Slice[String] of the results. 389 // The n parameter controls the number of substrings to return: 390 // - If n is negative, there is no limit on the number of substrings returned. 391 // - If n is zero, an empty Slice[String] is returned. 392 // - If n is positive, at most n substrings are returned. 393 func (s String) SplitN(sep String, n Int) Slice[String] { 394 return SliceMap(strings.SplitN(s.Std(), sep.Std(), n.Std()), NewString) 395 } 396 397 // SplitRegexp splits the String into substrings using the provided regular expression pattern and returns an Slice[String] of the results. 398 // The regular expression pattern is provided as a regexp.Regexp parameter. 399 func (s String) SplitRegexp(pattern regexp.Regexp) Slice[String] { 400 return SliceMap(pattern.Split(s.Std(), -1), NewString) 401 } 402 403 // SplitRegexpN splits the String into substrings using the provided regular expression pattern and returns an Slice[String] of the results. 404 // The regular expression pattern is provided as a regexp.Regexp parameter. 405 // The n parameter controls the number of substrings to return: 406 // - If n is negative, there is no limit on the number of substrings returned. 407 // - If n is zero, an empty Slice[String] is returned. 408 // - If n is positive, at most n substrings are returned. 409 func (s String) SplitRegexpN(pattern regexp.Regexp, n Int) Option[Slice[String]] { 410 result := SliceMap(pattern.Split(s.Std(), n.Std()), NewString) 411 if result.Empty() { 412 return None[Slice[String]]() 413 } 414 415 return Some(result) 416 } 417 418 // Chunks splits the String into chunks of the specified size. 419 // 420 // This function iterates through the String, creating new String chunks of the specified size. 421 // If size is less than or equal to 0 or the String is empty, 422 // it returns an empty Slice[String]. 423 // If size is greater than or equal to the length of the String, 424 // it returns an Slice[String] containing the original String. 425 // 426 // Parameters: 427 // 428 // - size (Int): The size of the chunks to split the String into. 429 // 430 // Returns: 431 // 432 // - Slice[String]: A slice of String chunks of the specified size. 433 // 434 // Example usage: 435 // 436 // text := g.String("Hello, World!") 437 // chunks := text.Chunks(4) 438 // 439 // chunks contains {"Hell", "o, W", "orld", "!"}. 440 func (s String) Chunks(size Int) Slice[String] { 441 if size <= 0 || s.Empty() { 442 return nil 443 } 444 445 if size >= s.Len() { 446 return Slice[String]{s} 447 } 448 449 return SliceMap(s.Split().Chunks(size).Collect(), func(ch Slice[String]) String { return ch.Join() }) 450 } 451 452 // Cut returns two String values. The first String contains the remainder of the 453 // original String after the cut. The second String contains the text between the 454 // first occurrences of the 'start' and 'end' strings, with tags removed if specified. 455 // 456 // The function searches for the 'start' and 'end' strings within the String. 457 // If both are found, it returns the first String containing the remainder of the 458 // original String after the cut, followed by the second String containing the text 459 // between the first occurrences of 'start' and 'end' with tags removed if specified. 460 // 461 // If either 'start' or 'end' is empty or not found in the String, it returns the 462 // original String as the second String, and an empty String as the first. 463 // 464 // Parameters: 465 // 466 // - start (String): The String marking the beginning of the text to be cut. 467 // 468 // - end (String): The String marking the end of the text to be cut. 469 // 470 // - rmtags (bool, optional): An optional boolean parameter indicating whether 471 // to remove 'start' and 'end' tags from the cut text. Defaults to false. 472 // 473 // Returns: 474 // 475 // - String: The first String containing the remainder of the original String 476 // after the cut, with tags removed if specified, 477 // or an empty String if 'start' or 'end' is empty or not found. 478 // 479 // - String: The second String containing the text between the first occurrences of 480 // 'start' and 'end', or the original String if 'start' or 'end' is empty or not found. 481 // 482 // Example usage: 483 // 484 // s := g.String("Hello, [world]! How are you?") 485 // remainder, cut := s.Cut("[", "]") 486 // // remainder: "Hello, ! How are you?" 487 // // cut: "world" 488 func (s String) Cut(start, end String, rmtags ...bool) (String, String) { 489 if start.Empty() || end.Empty() { 490 return s, "" 491 } 492 493 startIndex := s.Index(start) 494 if startIndex == -1 { 495 return s, "" 496 } 497 498 endIndex := s[startIndex+start.Len():].Index(end) 499 if endIndex == -1 { 500 return s, "" 501 } 502 503 cut := s[startIndex+start.Len() : startIndex+start.Len()+endIndex] 504 505 startCutIndex := startIndex 506 endCutIndex := startIndex + start.Len() + endIndex 507 508 if len(rmtags) != 0 && !rmtags[0] { 509 startCutIndex += start.Len() 510 } else { 511 endCutIndex += end.Len() 512 } 513 514 remainder := s[:startCutIndex] + s[endCutIndex:] 515 516 return remainder, cut 517 } 518 519 // Similarity calculates the similarity between two Strings using the 520 // Levenshtein distance algorithm and returns the similarity percentage as an Float. 521 // 522 // The function compares two Strings using the Levenshtein distance, 523 // which measures the difference between two sequences by counting the number 524 // of single-character edits required to change one sequence into the other. 525 // The similarity is then calculated by normalizing the distance by the maximum 526 // length of the two input Strings. 527 // 528 // Parameters: 529 // 530 // - str (String): The String to compare with s. 531 // 532 // Returns: 533 // 534 // - Float: The similarity percentage between the two Strings as a value between 0 and 100. 535 // 536 // Example usage: 537 // 538 // s1 := g.String("kitten") 539 // s2 := g.String("sitting") 540 // similarity := s1.Similarity(s2) // 57.14285714285714 541 func (s String) Similarity(str String) Float { 542 if s.Eq(str) { 543 return 100 544 } 545 546 if s.Empty() || str.Empty() { 547 return 0 548 } 549 550 s1 := s.ToRunes() 551 s2 := str.ToRunes() 552 553 lenS1 := s.LenRunes() 554 lenS2 := str.LenRunes() 555 556 if lenS1 > lenS2 { 557 s1, s2, lenS1, lenS2 = s2, s1, lenS2, lenS1 558 } 559 560 distance := NewSlice[Int](lenS1 + 1) 561 562 for i, r2 := range s2 { 563 prev := Int(i) + 1 564 565 for j, r1 := range s1 { 566 current := distance[j] 567 if r2 != r1 { 568 current = distance[j].Add(1).Min(prev + 1).Min(distance[j+1] + 1) 569 } 570 571 distance[j], prev = prev, current 572 } 573 574 distance[lenS1] = prev 575 } 576 577 return Float(1).Sub(distance[lenS1].ToFloat() / lenS1.Max(lenS2).ToFloat()).Mul(100) 578 } 579 580 // Cmp compares two Strings and returns an cmp.Ordering indicating their relative order. 581 // The result will be cmp.Equal if s==str, cmp.Less if s < str, and cmp.Greater if s > str. 582 func (s String) Cmp(str String) cmp.Ordering { return cmp.Cmp(s, str) } 583 584 // Append appends the specified String to the current String. 585 func (s String) Append(str String) String { return s + str } 586 587 // Prepend prepends the specified String to the current String. 588 func (s String) Prepend(str String) String { return str + s } 589 590 // ContainsRune checks if the String contains the specified rune. 591 func (s String) ContainsRune(r rune) bool { return strings.ContainsRune(s.Std(), r) } 592 593 // Count returns the number of non-overlapping instances of the substring in the String. 594 func (s String) Count(substr String) Int { return Int(strings.Count(s.Std(), substr.Std())) } 595 596 // Empty checks if the String is empty. 597 func (s String) Empty() bool { return len(s) == 0 } 598 599 // Eq checks if two Strings are equal. 600 func (s String) Eq(str String) bool { return s == str } 601 602 // EqFold compares two String strings case-insensitively. 603 func (s String) EqFold(str String) bool { return strings.EqualFold(s.Std(), str.Std()) } 604 605 // Gt checks if the String is greater than the specified String. 606 func (s String) Gt(str String) bool { return s > str } 607 608 // Gte checks if the String is greater than or equal to the specified String. 609 func (s String) Gte(str String) bool { return s >= str } 610 611 // Bytes returns the String as an Bytes. 612 func (s String) ToBytes() Bytes { return Bytes(s) } 613 614 // Index returns the index of the first instance of the specified substring in the String, or -1 615 // if substr is not present in s. 616 func (s String) Index(substr String) Int { return Int(strings.Index(s.Std(), substr.Std())) } 617 618 // IndexRegexp searches for the first occurrence of the regular expression pattern in the String. 619 // If a match is found, it returns an Option containing an Slice with the start and end indices of the match. 620 // If no match is found, it returns None. 621 func (s String) IndexRegexp(pattern *regexp.Regexp) Option[Slice[Int]] { 622 result := SliceMap(pattern.FindStringIndex(s.Std()), NewInt) 623 if result.Empty() { 624 return None[Slice[Int]]() 625 } 626 627 return Some(result) 628 } 629 630 // FindAllRegexp searches the String for all occurrences of the regular expression pattern 631 // and returns an Option[Slice[String]] containing a slice of matched substrings. 632 // If no matches are found, the Option[Slice[String]] will be None. 633 func (s String) FindAllRegexp(pattern *regexp.Regexp) Option[Slice[String]] { 634 return s.FindAllRegexpN(pattern, -1) 635 } 636 637 // FindAllRegexpN searches the String for up to n occurrences of the regular expression pattern 638 // and returns an Option[Slice[String]] containing a slice of matched substrings. 639 // If no matches are found, the Option[Slice[String]] will be None. 640 // If n is negative, all occurrences will be returned. 641 func (s String) FindAllRegexpN(pattern *regexp.Regexp, n Int) Option[Slice[String]] { 642 result := SliceMap(pattern.FindAllString(s.Std(), n.Std()), NewString) 643 if result.Empty() { 644 return None[Slice[String]]() 645 } 646 647 return Some(result) 648 } 649 650 // FindSubmatchRegexp searches the String for the first occurrence of the regular expression pattern 651 // and returns an Option[Slice[String]] containing the matched substrings and submatches. 652 // The Option will contain an Slice[String] with the full match at index 0, followed by any captured submatches. 653 // If no match is found, it returns None. 654 func (s String) FindSubmatchRegexp(pattern *regexp.Regexp) Option[Slice[String]] { 655 result := SliceMap(pattern.FindStringSubmatch(s.Std()), NewString) 656 if result.Empty() { 657 return None[Slice[String]]() 658 } 659 660 return Some(result) 661 } 662 663 // FindAllSubmatchRegexp searches the String for all occurrences of the regular expression pattern 664 // and returns an Option[Slice[Slice[String]]] containing the matched substrings and submatches. 665 // The Option[Slice[Slice[String]]] will contain an Slice[String] for each match, 666 // where each Slice[String] will contain the full match at index 0, followed by any captured submatches. 667 // If no match is found, the Option[Slice[Slice[String]]] will be None. 668 // This method is equivalent to calling SubmatchAllRegexpN with n = -1, which means it finds all occurrences. 669 func (s String) FindAllSubmatchRegexp(pattern *regexp.Regexp) Option[Slice[Slice[String]]] { 670 return s.FindAllSubmatchRegexpN(pattern, -1) 671 } 672 673 // FindAllSubmatchRegexpN searches the String for occurrences of the regular expression pattern 674 // and returns an Option[Slice[Slice[String]]] containing the matched substrings and submatches. 675 // The Option[Slice[Slice[String]]] will contain an Slice[String] for each match, 676 // where each Slice[String] will contain the full match at index 0, followed by any captured submatches. 677 // If no match is found, the Option[Slice[Slice[String]]] will be None. 678 // The 'n' parameter specifies the maximum number of matches to find. If n is negative, it finds all occurrences. 679 func (s String) FindAllSubmatchRegexpN(pattern *regexp.Regexp, n Int) Option[Slice[Slice[String]]] { 680 var result Slice[Slice[String]] 681 682 for _, v := range pattern.FindAllStringSubmatch(s.Std(), n.Std()) { 683 result = append(result, SliceMap(v, NewString)) 684 } 685 686 if result.Empty() { 687 return None[Slice[Slice[String]]]() 688 } 689 690 return Some(result) 691 } 692 693 // LastIndex returns the index of the last instance of the specified substring in the String, or -1 694 // if substr is not present in s. 695 func (s String) LastIndex(substr String) Int { return Int(strings.LastIndex(s.Std(), substr.Std())) } 696 697 // IndexRune returns the index of the first instance of the specified rune in the String. 698 func (s String) IndexRune(r rune) Int { return Int(strings.IndexRune(s.Std(), r)) } 699 700 // Len returns the length of the String. 701 func (s String) Len() Int { return Int(len(s)) } 702 703 // LenRunes returns the number of runes in the String. 704 func (s String) LenRunes() Int { return Int(utf8.RuneCountInString(s.Std())) } 705 706 // Lt checks if the String is less than the specified String. 707 func (s String) Lt(str String) bool { return s < str } 708 709 // Lte checks if the String is less than or equal to the specified String. 710 func (s String) Lte(str String) bool { return s <= str } 711 712 // Map applies the provided function to all runes in the String and returns the resulting String. 713 func (s String) Map(fn func(rune) rune) String { return String(strings.Map(fn, s.Std())) } 714 715 // NormalizeNFC returns a new String with its Unicode characters normalized using the NFC form. 716 func (s String) NormalizeNFC() String { return String(norm.NFC.String(s.Std())) } 717 718 // Ne checks if two Strings are not equal. 719 func (s String) Ne(str String) bool { return !s.Eq(str) } 720 721 // NotEmpty checks if the String is not empty. 722 func (s String) NotEmpty() bool { return s.Len() != 0 } 723 724 // Reader returns a *strings.Reader initialized with the content of String. 725 func (s String) Reader() *strings.Reader { return strings.NewReader(s.Std()) } 726 727 // Repeat returns a new String consisting of the specified count of the original String. 728 func (s String) Repeat(count Int) String { return String(strings.Repeat(s.Std(), count.Std())) } 729 730 // Reverse reverses the String. 731 func (s String) Reverse() String { return s.ToBytes().Reverse().ToString() } 732 733 // ToRunes returns the String as a slice of runes. 734 func (s String) ToRunes() Slice[rune] { return []rune(s) } 735 736 // Chars splits the String into individual characters and returns the iterator. 737 func (s String) Chars() SeqSlice[String] { return s.Split() } 738 739 // Std returns the String as a string. 740 func (s String) Std() string { return string(s) } 741 742 // TrimSpace trims whitespace from the beginning and end of the String. 743 func (s String) TrimSpace() String { return String(strings.TrimSpace(s.Std())) } 744 745 // Format applies a specified format to the String object. 746 func (s String) Format(format String) String { return Sprintf(format, s) } 747 748 // LeftJustify justifies the String to the left by adding padding to the right, up to the 749 // specified length. If the length of the String is already greater than or equal to the specified 750 // length, or the pad is empty, the original String is returned. 751 // 752 // The padding String is repeated as necessary to fill the remaining length. 753 // The padding is added to the right of the String. 754 // 755 // Parameters: 756 // - length: The desired length of the resulting justified String. 757 // - pad: The String used as padding. 758 // 759 // Example usage: 760 // 761 // s := g.String("Hello") 762 // result := s.LeftJustify(10, "...") 763 // // result: "Hello....." 764 func (s String) LeftJustify(length Int, pad String) String { 765 if s.LenRunes() >= length || pad.Eq("") { 766 return s 767 } 768 769 output := NewBuilder() 770 771 _ = output.Write(s) 772 writePadding(output, pad, pad.LenRunes(), length-s.LenRunes()) 773 774 return output.String() 775 } 776 777 // RightJustify justifies the String to the right by adding padding to the left, up to the 778 // specified length. If the length of the String is already greater than or equal to the specified 779 // length, or the pad is empty, the original String is returned. 780 // 781 // The padding String is repeated as necessary to fill the remaining length. 782 // The padding is added to the left of the String. 783 // 784 // Parameters: 785 // - length: The desired length of the resulting justified String. 786 // - pad: The String used as padding. 787 // 788 // Example usage: 789 // 790 // s := g.String("Hello") 791 // result := s.RightJustify(10, "...") 792 // // result: ".....Hello" 793 func (s String) RightJustify(length Int, pad String) String { 794 if s.LenRunes() >= length || pad.Empty() { 795 return s 796 } 797 798 output := NewBuilder() 799 800 writePadding(output, pad, pad.LenRunes(), length-s.LenRunes()) 801 _ = output.Write(s) 802 803 return output.String() 804 } 805 806 // Center justifies the String by adding padding on both sides, up to the specified length. 807 // If the length of the String is already greater than or equal to the specified length, or the 808 // pad is empty, the original String is returned. 809 // 810 // The padding String is repeated as necessary to evenly distribute the remaining length on both 811 // sides. 812 // The padding is added to the left and right of the String. 813 // 814 // Parameters: 815 // - length: The desired length of the resulting justified String. 816 // - pad: The String used as padding. 817 // 818 // Example usage: 819 // 820 // s := g.String("Hello") 821 // result := s.Center(10, "...") 822 // // result: "..Hello..." 823 func (s String) Center(length Int, pad String) String { 824 if s.LenRunes() >= length || pad.Empty() { 825 return s 826 } 827 828 output := NewBuilder() 829 830 remains := length - s.LenRunes() 831 832 writePadding(output, pad, pad.LenRunes(), remains/2) 833 _ = output.Write(s) 834 writePadding(output, pad, pad.LenRunes(), (remains+1)/2) 835 836 return output.String() 837 } 838 839 // writePadding writes the padding String to the output Builder to fill the remaining length. 840 // It repeats the padding String as necessary and appends any remaining runes from the padding 841 // String. 842 func writePadding(output *Builder, pad String, padlen, remains Int) { 843 if repeats := remains / padlen; repeats > 0 { 844 _ = output.Write(pad.Repeat(repeats)) 845 } 846 847 padrunes := pad.ToRunes() 848 for i := range remains % padlen { 849 _ = output.WriteRune(padrunes[i]) 850 } 851 } 852 853 // Print prints the content of the String to the standard output (console) 854 // and returns the String unchanged. 855 func (s String) Print() String { fmt.Println(s); return s }