github.com/bytedance/gopkg@v0.0.0-20240514070511-01b2cbcf35e1/lang/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 "errors" 19 "math" 20 "strings" 21 "unicode/utf8" 22 23 "github.com/bytedance/gopkg/internal/hack" 24 "github.com/bytedance/gopkg/lang/dirtmake" 25 "github.com/bytedance/gopkg/lang/fastrand" 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 := hack.StringToBytes(s) 250 dst := dirtmake.Bytes(len(s), 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 hack.BytesToString(dst), ErrDecodeRune 257 } 258 utf8.EncodeRune(dst[dstIndex:], r) 259 srcIndex -= n 260 dstIndex += n 261 } 262 return hack.BytesToString(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 = fastrand.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 }