github.com/songzhibin97/go-baseutils@v0.0.2-0.20240302024150-487d8ce9c082/sys/stringx/stringx.go (about)

     1  package stringx
     2  
     3  import (
     4  	"errors"
     5  	"math"
     6  	"strings"
     7  	"unicode/utf8"
     8  
     9  	"github.com/songzhibin97/go-baseutils/internal/hack"
    10  	"github.com/songzhibin97/go-baseutils/sys/fastrand"
    11  )
    12  
    13  // Error pre define
    14  var (
    15  	ErrDecodeRune = errors.New("error occurred on rune decoding")
    16  )
    17  
    18  // PadLeftChar left pad a string with a specified character in a larger string (specified size).
    19  // if the size is less than the param string, the param string is returned.
    20  // note: size is unicode size.
    21  func PadLeftChar(s string, size int, ch rune) string {
    22  	return padCharLeftOrRight(s, size, ch, true)
    23  }
    24  
    25  // PadLeftSpace left pad a string with space character(' ') in a larger string(specified size).
    26  // if the size is less than the param string, the param string is returned.
    27  // note: size is unicode size.
    28  func PadLeftSpace(s string, size int) string {
    29  	return PadLeftChar(s, size, ' ')
    30  }
    31  
    32  // PadRightChar right pad a string with a specified character in a larger string(specified size).
    33  // if the size is less than the param string, the param string is returned.
    34  // note: size is unicode size.
    35  func PadRightChar(s string, size int, ch rune) string {
    36  	return padCharLeftOrRight(s, size, ch, false)
    37  }
    38  
    39  // PadRightSpace right pad a string with space character(' ') in a large string(specified size).
    40  // if the size is less than the param string, the param string is returned.
    41  // note: size is unicode size.
    42  func PadRightSpace(s string, size int) string {
    43  	return PadRightChar(s, size, ' ')
    44  }
    45  
    46  // PadCenterChar center pad a string with a specified character in a larger string(specified size).
    47  // if the size is less than the param string, the param string is returned.
    48  // note: size is unicode size.
    49  func PadCenterChar(s string, size int, ch rune) string {
    50  	if size <= 0 {
    51  		return s
    52  	}
    53  	length := utf8.RuneCountInString(s)
    54  	pads := size - length
    55  	if pads <= 0 {
    56  		return s
    57  	}
    58  
    59  	// pad left
    60  	leftPads := pads / 2
    61  	if leftPads > 0 {
    62  		s = padRawLeftChar(s, ch, leftPads)
    63  	}
    64  	// pad right
    65  	rightPads := size - leftPads - length
    66  	if rightPads > 0 {
    67  		s = padRawRightChar(s, ch, rightPads)
    68  	}
    69  	return s
    70  }
    71  
    72  // PadCenterSpace center pad a string with space character(' ') in a larger string(specified size).
    73  // if the size is less than the param string, the param string is returned.
    74  // note: size is unicode size.
    75  func PadCenterSpace(s string, size int) string {
    76  	return PadCenterChar(s, size, ' ')
    77  }
    78  
    79  func padCharLeftOrRight(s string, size int, ch rune, isLeft bool) string {
    80  	if size <= 0 {
    81  		return s
    82  	}
    83  	pads := size - utf8.RuneCountInString(s)
    84  	if pads <= 0 {
    85  		return s
    86  	}
    87  	if isLeft {
    88  		return padRawLeftChar(s, ch, pads)
    89  	}
    90  	return padRawRightChar(s, ch, pads)
    91  }
    92  
    93  func padRawLeftChar(s string, ch rune, padSize int) string {
    94  	return RepeatChar(ch, padSize) + s
    95  }
    96  
    97  func padRawRightChar(s string, ch rune, padSize int) string {
    98  	return s + RepeatChar(ch, padSize)
    99  }
   100  
   101  // RepeatChar returns padding using the specified delimiter repeated to a given length.
   102  func RepeatChar(ch rune, repeat int) string {
   103  	if repeat <= 0 {
   104  		return ""
   105  	}
   106  	sb := strings.Builder{}
   107  	sb.Grow(repeat)
   108  	for i := 0; i < repeat; i++ {
   109  		sb.WriteRune(ch)
   110  	}
   111  	return sb.String()
   112  }
   113  
   114  // RemoveChar removes all occurrences of a specified character from the string.
   115  func RemoveChar(s string, rmVal rune) string {
   116  	if s == "" {
   117  		return s
   118  	}
   119  	sb := strings.Builder{}
   120  	sb.Grow(len(s) / 2)
   121  
   122  	for _, v := range s {
   123  		if v != rmVal {
   124  			sb.WriteRune(v)
   125  		}
   126  	}
   127  	return sb.String()
   128  }
   129  
   130  // RemoveString removes all occurrences of a substring from the string.
   131  func RemoveString(s, rmStr string) string {
   132  	if s == "" || rmStr == "" {
   133  		return s
   134  	}
   135  	return strings.ReplaceAll(s, rmStr, "")
   136  }
   137  
   138  // Rotate rotates(circular shift) a string of shift characters.
   139  func Rotate(s string, shift int) string {
   140  	if shift == 0 {
   141  		return s
   142  	}
   143  	sLen := len(s)
   144  	if sLen == 0 {
   145  		return s
   146  	}
   147  
   148  	shiftMod := shift % sLen
   149  	if shiftMod == 0 {
   150  		return s
   151  	}
   152  
   153  	offset := -(shiftMod)
   154  	sb := strings.Builder{}
   155  	sb.Grow(sLen)
   156  	_, _ = sb.WriteString(SubStart(s, offset))
   157  	_, _ = sb.WriteString(Sub(s, 0, offset))
   158  	return sb.String()
   159  }
   160  
   161  // Sub returns substring from specified string avoiding panics with index start and end.
   162  // start, end are based on unicode(utf8) count.
   163  func Sub(s string, start, end int) string {
   164  	return sub(s, start, end)
   165  }
   166  
   167  // SubStart returns substring from specified string avoiding panics with start.
   168  // start, end are based on unicode(utf8) count.
   169  func SubStart(s string, start int) string {
   170  	return sub(s, start, math.MaxInt64)
   171  }
   172  
   173  func sub(s string, start, end int) string {
   174  	if s == "" {
   175  		return ""
   176  	}
   177  
   178  	unicodeLen := utf8.RuneCountInString(s)
   179  	// end
   180  	if end < 0 {
   181  		end += unicodeLen
   182  	}
   183  	if end > unicodeLen {
   184  		end = unicodeLen
   185  	}
   186  	// start
   187  	if start < 0 {
   188  		start += unicodeLen
   189  	}
   190  	if start > end {
   191  		return ""
   192  	}
   193  
   194  	// start <= end
   195  	if start < 0 {
   196  		start = 0
   197  	}
   198  	if end < 0 {
   199  		end = 0
   200  	}
   201  	if start == 0 && end == unicodeLen {
   202  		return s
   203  	}
   204  
   205  	sb := strings.Builder{}
   206  	sb.Grow(end - start)
   207  	runeIndex := 0
   208  	for _, v := range s {
   209  		if runeIndex >= end {
   210  			break
   211  		}
   212  		if runeIndex >= start {
   213  			sb.WriteRune(v)
   214  		}
   215  		runeIndex++
   216  	}
   217  	return sb.String()
   218  }
   219  
   220  // MustReverse reverses a string, panics when error happens.
   221  func MustReverse(s string) string {
   222  	result, err := Reverse(s)
   223  	if err != nil {
   224  		panic(err)
   225  	}
   226  	return result
   227  }
   228  
   229  // Reverse reverses a string with error status returned.
   230  func Reverse(s string) (string, error) {
   231  	if s == "" {
   232  		return s, nil
   233  	}
   234  	src := hack.StringToBytes(s)
   235  	dst := make([]byte, len(s))
   236  	srcIndex := len(s)
   237  	dstIndex := 0
   238  	for srcIndex > 0 {
   239  		r, n := utf8.DecodeLastRune(src[:srcIndex])
   240  		if r == utf8.RuneError {
   241  			return hack.BytesToString(dst), ErrDecodeRune
   242  		}
   243  		utf8.EncodeRune(dst[dstIndex:], r)
   244  		srcIndex -= n
   245  		dstIndex += n
   246  	}
   247  	return hack.BytesToString(dst), nil
   248  }
   249  
   250  // Shuffle shuffles runes in a string and returns.
   251  func Shuffle(s string) string {
   252  	if s == "" {
   253  		return s
   254  	}
   255  	runes := []rune(s)
   256  	index := 0
   257  	for i := len(runes) - 1; i > 0; i-- {
   258  		index = fastrand.Intn(i + 1)
   259  		if i != index {
   260  			runes[i], runes[index] = runes[index], runes[i]
   261  		}
   262  	}
   263  	return string(runes)
   264  }
   265  
   266  // ContainsAnySubstrings returns whether s contains any of substring in slice.
   267  func ContainsAnySubstrings(s string, subs []string) bool {
   268  	for _, v := range subs {
   269  		if strings.Contains(s, v) {
   270  			return true
   271  		}
   272  	}
   273  	return false
   274  }