github.com/aaabigfish/gopkg@v1.1.0/stringx/stringx.go (about)

     1  // Copyright 2021 ByteDance Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package stringx
    16  
    17  import (
    18  	"bytes"
    19  	"errors"
    20  	"math"
    21  	"math/rand"
    22  	"strconv"
    23  	"strings"
    24  	"sync"
    25  	"unicode/utf8"
    26  )
    27  
    28  // Error pre define
    29  var (
    30  	ErrDecodeRune = errors.New("error occurred on rune decoding")
    31  )
    32  
    33  // PadLeftChar left pad a string with a specified character in a larger string (specified size).
    34  // if the size is less than the param string, the param string is returned.
    35  // note: size is unicode size.
    36  func PadLeftChar(s string, size int, ch rune) string {
    37  	return padCharLeftOrRight(s, size, ch, true)
    38  }
    39  
    40  // PadLeftSpace left pad a string with space character(' ') in a larger string(specified size).
    41  // if the size is less than the param string, the param string is returned.
    42  // note: size is unicode size.
    43  func PadLeftSpace(s string, size int) string {
    44  	return PadLeftChar(s, size, ' ')
    45  }
    46  
    47  // PadRightChar right pad a string with a specified character in a larger string(specified size).
    48  // if the size is less than the param string, the param string is returned.
    49  // note: size is unicode size.
    50  func PadRightChar(s string, size int, ch rune) string {
    51  	return padCharLeftOrRight(s, size, ch, false)
    52  }
    53  
    54  // PadRightSpace right pad a string with space character(' ') in a large string(specified size).
    55  // if the size is less than the param string, the param string is returned.
    56  // note: size is unicode size.
    57  func PadRightSpace(s string, size int) string {
    58  	return PadRightChar(s, size, ' ')
    59  }
    60  
    61  // PadCenterChar center pad a string with a specified character in a larger string(specified size).
    62  // if the size is less than the param string, the param string is returned.
    63  // note: size is unicode size.
    64  func PadCenterChar(s string, size int, ch rune) string {
    65  	if size <= 0 {
    66  		return s
    67  	}
    68  	length := utf8.RuneCountInString(s)
    69  	pads := size - length
    70  	if pads <= 0 {
    71  		return s
    72  	}
    73  
    74  	// pad left
    75  	leftPads := pads / 2
    76  	if leftPads > 0 {
    77  		s = padRawLeftChar(s, ch, leftPads)
    78  	}
    79  	// pad right
    80  	rightPads := size - leftPads - length
    81  	if rightPads > 0 {
    82  		s = padRawRightChar(s, ch, rightPads)
    83  	}
    84  	return s
    85  }
    86  
    87  // PadCenterSpace center pad a string with space character(' ') in a larger string(specified size).
    88  // if the size is less than the param string, the param string is returned.
    89  // note: size is unicode size.
    90  func PadCenterSpace(s string, size int) string {
    91  	return PadCenterChar(s, size, ' ')
    92  }
    93  
    94  func padCharLeftOrRight(s string, size int, ch rune, isLeft bool) string {
    95  	if size <= 0 {
    96  		return s
    97  	}
    98  	pads := size - utf8.RuneCountInString(s)
    99  	if pads <= 0 {
   100  		return s
   101  	}
   102  	if isLeft {
   103  		return padRawLeftChar(s, ch, pads)
   104  	}
   105  	return padRawRightChar(s, ch, pads)
   106  }
   107  
   108  func padRawLeftChar(s string, ch rune, padSize int) string {
   109  	return RepeatChar(ch, padSize) + s
   110  }
   111  
   112  func padRawRightChar(s string, ch rune, padSize int) string {
   113  	return s + RepeatChar(ch, padSize)
   114  }
   115  
   116  // RepeatChar returns padding using the specified delimiter repeated to a given length.
   117  func RepeatChar(ch rune, repeat int) string {
   118  	if repeat <= 0 {
   119  		return ""
   120  	}
   121  	sb := strings.Builder{}
   122  	sb.Grow(repeat)
   123  	for i := 0; i < repeat; i++ {
   124  		sb.WriteRune(ch)
   125  	}
   126  	return sb.String()
   127  }
   128  
   129  // RemoveChar removes all occurrences of a specified character from the string.
   130  func RemoveChar(s string, rmVal rune) string {
   131  	if s == "" {
   132  		return s
   133  	}
   134  	sb := strings.Builder{}
   135  	sb.Grow(len(s) / 2)
   136  
   137  	for _, v := range s {
   138  		if v != rmVal {
   139  			sb.WriteRune(v)
   140  		}
   141  	}
   142  	return sb.String()
   143  }
   144  
   145  // RemoveString removes all occurrences of a substring from the string.
   146  func RemoveString(s, rmStr string) string {
   147  	if s == "" || rmStr == "" {
   148  		return s
   149  	}
   150  	return strings.ReplaceAll(s, rmStr, "")
   151  }
   152  
   153  // Rotate rotates(circular shift) a string of shift characters.
   154  func Rotate(s string, shift int) string {
   155  	if shift == 0 {
   156  		return s
   157  	}
   158  	sLen := len(s)
   159  	if sLen == 0 {
   160  		return s
   161  	}
   162  
   163  	shiftMod := shift % sLen
   164  	if shiftMod == 0 {
   165  		return s
   166  	}
   167  
   168  	offset := -(shiftMod)
   169  	sb := strings.Builder{}
   170  	sb.Grow(sLen)
   171  	_, _ = sb.WriteString(SubStart(s, offset))
   172  	_, _ = sb.WriteString(Sub(s, 0, offset))
   173  	return sb.String()
   174  }
   175  
   176  // Sub returns substring from specified string avoiding panics with index start and end.
   177  // start, end are based on unicode(utf8) count.
   178  func Sub(s string, start, end int) string {
   179  	return sub(s, start, end)
   180  }
   181  
   182  // SubStart returns substring from specified string avoiding panics with start.
   183  // start, end are based on unicode(utf8) count.
   184  func SubStart(s string, start int) string {
   185  	return sub(s, start, math.MaxInt64)
   186  }
   187  
   188  func sub(s string, start, end int) string {
   189  	if s == "" {
   190  		return ""
   191  	}
   192  
   193  	unicodeLen := utf8.RuneCountInString(s)
   194  	// end
   195  	if end < 0 {
   196  		end += unicodeLen
   197  	}
   198  	if end > unicodeLen {
   199  		end = unicodeLen
   200  	}
   201  	// start
   202  	if start < 0 {
   203  		start += unicodeLen
   204  	}
   205  	if start > end {
   206  		return ""
   207  	}
   208  
   209  	// start <= end
   210  	if start < 0 {
   211  		start = 0
   212  	}
   213  	if end < 0 {
   214  		end = 0
   215  	}
   216  	if start == 0 && end == unicodeLen {
   217  		return s
   218  	}
   219  
   220  	sb := strings.Builder{}
   221  	sb.Grow(end - start)
   222  	runeIndex := 0
   223  	for _, v := range s {
   224  		if runeIndex >= end {
   225  			break
   226  		}
   227  		if runeIndex >= start {
   228  			sb.WriteRune(v)
   229  		}
   230  		runeIndex++
   231  	}
   232  	return sb.String()
   233  }
   234  
   235  // MustReverse reverses a string, panics when error happens.
   236  func MustReverse(s string) string {
   237  	result, err := Reverse(s)
   238  	if err != nil {
   239  		panic(err)
   240  	}
   241  	return result
   242  }
   243  
   244  // Reverse reverses a string with error status returned.
   245  func Reverse(s string) (string, error) {
   246  	if s == "" {
   247  		return s, nil
   248  	}
   249  	src := S2b(s)
   250  	dst := make([]byte, len(s))
   251  	srcIndex := len(s)
   252  	dstIndex := 0
   253  	for srcIndex > 0 {
   254  		r, n := utf8.DecodeLastRune(src[:srcIndex])
   255  		if r == utf8.RuneError {
   256  			return B2s(dst), ErrDecodeRune
   257  		}
   258  		utf8.EncodeRune(dst[dstIndex:], r)
   259  		srcIndex -= n
   260  		dstIndex += n
   261  	}
   262  	return B2s(dst), nil
   263  }
   264  
   265  // Shuffle shuffles runes in a string and returns.
   266  func Shuffle(s string) string {
   267  	if s == "" {
   268  		return s
   269  	}
   270  	runes := []rune(s)
   271  	index := 0
   272  	for i := len(runes) - 1; i > 0; i-- {
   273  		index = rand.Intn(i + 1)
   274  		if i != index {
   275  			runes[i], runes[index] = runes[index], runes[i]
   276  		}
   277  	}
   278  	return string(runes)
   279  }
   280  
   281  // ContainsAnySubstrings returns whether s contains any of substring in slice.
   282  func ContainsAnySubstrings(s string, subs []string) bool {
   283  	for _, v := range subs {
   284  		if strings.Contains(s, v) {
   285  			return true
   286  		}
   287  	}
   288  	return false
   289  }
   290  
   291  var (
   292  	bfPool = sync.Pool{
   293  		New: func() interface{} {
   294  			return bytes.NewBuffer([]byte{})
   295  		},
   296  	}
   297  )
   298  
   299  // JoinInts format int64 slice like:n1,n2,n3.
   300  func JoinInts(is []int64) string {
   301  	if len(is) == 0 {
   302  		return ""
   303  	}
   304  	if len(is) == 1 {
   305  		return strconv.FormatInt(is[0], 10)
   306  	}
   307  	buf := bfPool.Get().(*bytes.Buffer)
   308  	for _, i := range is {
   309  		buf.WriteString(strconv.FormatInt(i, 10))
   310  		buf.WriteByte(',')
   311  	}
   312  	if buf.Len() > 0 {
   313  		buf.Truncate(buf.Len() - 1)
   314  	}
   315  	s := buf.String()
   316  	buf.Reset()
   317  	bfPool.Put(buf)
   318  	return s
   319  }
   320  
   321  // SplitInts split string into int64 slice.
   322  func SplitInts(s string) ([]int64, error) {
   323  	if s == "" {
   324  		return nil, nil
   325  	}
   326  	sArr := strings.Split(s, ",")
   327  	res := make([]int64, 0, len(sArr))
   328  	for _, sc := range sArr {
   329  		i, err := strconv.ParseInt(sc, 10, 64)
   330  		if err != nil {
   331  			return nil, err
   332  		}
   333  		res = append(res, i)
   334  	}
   335  	return res, nil
   336  }