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  }