github.com/erda-project/erda-infra@v1.0.9/pkg/strutil/strutil.go (about) 1 // Copyright (c) 2021 Terminus, 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 strutil is string util package 16 package strutil 17 18 import ( 19 "bytes" 20 "math/rand" 21 "regexp" 22 "strings" 23 "time" 24 ) 25 26 // Trim trim `s`'s prefix and suffix. If `cutset` not specified, `cutset` = space. 27 // 28 // Trim("trim ") => "trim" 29 // 30 // Trim(" this ") => "this" 31 // 32 // Trim("athisb", "abs") => "this" 33 func Trim(s string, cutset ...string) string { 34 if len(cutset) == 0 { 35 return strings.TrimSpace(s) 36 } 37 return strings.Trim(s, cutset[0]) 38 } 39 40 // TrimSuffixes trim `s`'s suffixes. 41 // 42 // TrimSuffixes("test.go", ".go") => "test" 43 // 44 // TrimSuffixes("test.go", ".md", ".go", ".sh") => "test" 45 // 46 // TrimSuffixes("test.go.tmp", ".go", ".tmp") => "test.go" 47 func TrimSuffixes(s string, suffixes ...string) string { 48 originLen := len(s) 49 for i := range suffixes { 50 trimmed := strings.TrimSuffix(s, suffixes[i]) 51 if len(trimmed) != originLen { 52 return trimmed 53 } 54 } 55 return s 56 } 57 58 // TrimPrefixes trim `s`'s prefixes. 59 // 60 // TrimPrefixes("/tmp/file", "/tmp") => "/file" 61 // 62 // TrimPrefixes("/tmp/tmp/file", "/tmp", "/tmp/tmp") => "/tmp/file" 63 func TrimPrefixes(s string, prefixes ...string) string { 64 originLen := len(s) 65 for i := range prefixes { 66 trimmed := strings.TrimPrefix(s, prefixes[i]) 67 if len(trimmed) != originLen { 68 return trimmed 69 } 70 } 71 return s 72 } 73 74 // TrimSlice is the slice version of Trim. 75 // 76 // TrimSlice([]string{"trim ", " trim", " trim "}) => []string{"trim", "trim", "trim"} 77 func TrimSlice(ss []string, cutset ...string) []string { 78 r := make([]string, len(ss)) 79 for i := range ss { 80 r[i] = Trim(ss[i], cutset...) 81 } 82 return r 83 } 84 85 // TrimSliceSuffixes is the slice version of TrimSuffixes. 86 // 87 // TrimSliceSuffixes([]string{"test.go", "test.go.tmp"}, ".go", ".tmp") => []string{"test", "test.go"} 88 func TrimSliceSuffixes(ss []string, suffixes ...string) []string { 89 r := make([]string, len(ss)) 90 for i := range ss { 91 r[i] = TrimSuffixes(ss[i], suffixes...) 92 } 93 return r 94 } 95 96 // TrimSlicePrefixes is the slice version of TrimPrefixes. 97 // 98 // TrimSlicePrefixes([]string{"/tmp/file", "/tmp/tmp/file"}, "/tmp", "/tmp/tmp") => []string{"/file", "/tmp/file"} 99 func TrimSlicePrefixes(ss []string, prefixes ...string) []string { 100 r := make([]string, len(ss)) 101 for i := range ss { 102 r[i] = TrimPrefixes(ss[i], prefixes...) 103 } 104 return r 105 } 106 107 // HasPrefixes judge if `s` have at least one of elem in `prefixes` as prefix. 108 // 109 // HasPrefixes("asd", "ddd", "uuu") => false 110 // 111 // HasPrefixes("asd", "sd", "as") => true 112 // 113 // HasPrefixes("asd", "asd") => true 114 func HasPrefixes(s string, prefixes ...string) bool { 115 for i := range prefixes { 116 if strings.HasPrefix(s, prefixes[i]) { 117 return true 118 } 119 } 120 return false 121 } 122 123 // HasSuffixes judge if `s` have at least one of elem in `suffixes` as suffix. 124 // 125 // HasSuffixes("asd", "ddd", "d") => true 126 // 127 // HasSuffixes("asd", "sd") => true 128 // 129 // HasSuffixes("asd", "iid", "as") => false 130 func HasSuffixes(s string, suffixes ...string) bool { 131 for i := range suffixes { 132 if strings.HasSuffix(s, suffixes[i]) { 133 return true 134 } 135 } 136 return false 137 } 138 139 var ( 140 collapseWhitespaceRegex = regexp.MustCompile("[ \t\n\r]+") 141 ) 142 143 // CollapseWhitespace replace continues space(collapseWhitespaceRegex) to one blank. 144 // 145 // CollapseWhitespace("only one space") => "only one space" 146 // 147 // CollapseWhitespace("collapse \n all \t sorts of \r \n \r\n whitespace") => "collapse all sorts of whitespace" 148 func CollapseWhitespace(s string) string { 149 return collapseWhitespaceRegex.ReplaceAllString(s, " ") 150 } 151 152 // Center centering `s` according to total length. 153 // 154 // Center("a", 5) => " a " 155 // 156 // Center("ab", 5) => " ab " 157 // 158 // Center("abc", 1) => "abc" 159 func Center(s string, length int) string { 160 minus := length - len(s) 161 if minus <= 0 { 162 return s 163 } 164 right := minus / 2 165 mod := minus % 2 166 return strings.Join([]string{strings.Repeat(" ", right+mod), s, strings.Repeat(" ", right)}, "") 167 } 168 169 // Split split `s` by `sep`. If `omitEmptyOpt`=true, ignore empty string. 170 // 171 // Split("a|bc|12||3", "|") => []string{"a", "bc", "12", "", "3"} 172 // 173 // Split("a|bc|12||3", "|", true) => []string{"a", "bc", "12", "3"} 174 // 175 // Split("a,b,c", ":") => []string{"a,b,c"} 176 func Split(s string, sep string, omitEmptyOpt ...bool) []string { 177 var omitEmpty bool 178 if len(omitEmptyOpt) > 0 && omitEmptyOpt[0] { 179 omitEmpty = true 180 } 181 parts := strings.Split(s, sep) 182 if !omitEmpty { 183 return parts 184 } 185 result := []string{} 186 for _, v := range parts { 187 if v != "" { 188 result = append(result, v) 189 } 190 } 191 return result 192 } 193 194 var ( 195 linesRegex = regexp.MustCompile("\r\n|\n|\r") 196 ) 197 198 // Lines split `s` by newline. If `omitEmptyOpt`=true, ignore empty string. 199 // 200 // Lines("abc\ndef\nghi") => []string{"abc", "def", "ghi"} 201 // 202 // Lines("abc\rdef\rghi") => []string{"abc", "def", "ghi"} 203 // 204 // Lines("abc\r\ndef\r\nghi\n") => []string{"abc", "def", "ghi", ""} 205 // 206 // Lines("abc\r\ndef\r\nghi\n", true) => []string{"abc", "def", "ghi"} 207 func Lines(s string, omitEmptyOpt ...bool) []string { 208 lines := linesRegex.Split(s, -1) 209 if len(omitEmptyOpt) == 0 || !omitEmptyOpt[0] { 210 return lines 211 } 212 r := []string{} 213 for i := range lines { 214 if lines[i] != "" { 215 r = append(r, lines[i]) 216 } 217 } 218 return r 219 } 220 221 // Join see also strings.Join, 222 // If omitEmptyOpt = true, ignore empty string inside `ss`. 223 func Join(ss []string, sep string, omitEmptyOpt ...bool) string { 224 if len(omitEmptyOpt) == 0 || !omitEmptyOpt[0] { 225 return strings.Join(ss, sep) 226 } 227 r := []string{} 228 for i := range ss { 229 if ss[i] != "" { 230 r = append(r, ss[i]) 231 } 232 } 233 return strings.Join(r, sep) 234 } 235 236 // Contains check if `s` contains one of `substrs`. 237 // 238 // Contains("test contains.", "t c", "iii") => true 239 // 240 // Contains("test contains.", "t cc", "test ") => false 241 // 242 // Contains("test contains.", "iii", "uuu", "ont") => true 243 func Contains(s string, substrs ...string) bool { 244 for i := range substrs { 245 if strings.Contains(s, substrs[i]) { 246 return true 247 } 248 } 249 return false 250 } 251 252 // Equal judge whether `s` is equal to `other`. If ignorecase=true, judge without case. 253 // 254 // Equal("aaa", "AAA") => false 255 // 256 // Equal("aaa", "AaA", true) => true 257 func Equal(s, other string, ignorecase ...bool) bool { 258 if len(ignorecase) == 0 || !ignorecase[0] { 259 return strings.Compare(s, other) == 0 260 } 261 return strings.EqualFold(s, other) 262 } 263 264 // Map apply each funcs to each elem of `ss`. 265 // 266 // Map([]string{"1", "2", "3"}, func(s string) string {return Concat("X", s)}) => []string{"X1", "X2", "X3"} 267 // 268 // Map([]string{"Aa", "bB", "cc"}, ToLower, Title) => []string{"Aa", "Bb", "Cc"} 269 func Map(ss []string, fs ...func(s string) string) []string { 270 r := []string{} 271 for i := range ss { 272 r = append(r, ss[i]) 273 } 274 r2 := []string{} 275 for _, f := range fs { 276 for i := range r { 277 r2 = append(r2, f(r[i])) 278 } 279 r = r2[:] 280 r2 = []string{} 281 } 282 return r 283 } 284 285 // DedupSlice return a slice without repeating elements, and the elements are sorted in the order of their first appearance. 286 // If omitEmptyOpt = true, ignore empty string. 287 // 288 // DedupSlice([]string{"c", "", "b", "a", "", "a", "b", "c", "", "d"}) => []string{"c", "", "b", "a", "d"} 289 // 290 // DedupSlice([]string{"c", "", "b", "a", "", "a", "b", "c", "", "d"}, true) => []string{"c", "b", "a", "d"} 291 func DedupSlice(ss []string, omitEmptyOpt ...bool) []string { 292 var omitEmpty bool 293 if len(omitEmptyOpt) > 0 && omitEmptyOpt[0] { 294 omitEmpty = true 295 } 296 result := make([]string, 0, len(ss)) 297 m := make(map[string]struct{}, len(ss)) 298 for _, s := range ss { 299 if s == "" && omitEmpty { 300 continue 301 } 302 if _, ok := m[s]; ok { 303 continue 304 } 305 result = append(result, s) 306 m[s] = struct{}{} 307 } 308 return result 309 } 310 311 // DedupUint64Slice return a slice without repeating elements, and the elements are sorted in the order of their first appearance. 312 // If omitZeroOpt = true, ignore zero value elem. 313 // 314 // DedupUint64Slice([]uint64{3, 3, 1, 2, 1, 2, 3, 3, 2, 1, 0, 1, 2}) => []uint64{3, 1, 2, 0} 315 // 316 // DedupUint64Slice([]uint64{3, 3, 1, 2, 1, 2, 3, 3, 2, 1, 0, 1, 2}, true) => []uint64{3, 1, 2} 317 func DedupUint64Slice(ii []uint64, omitZeroOpt ...bool) []uint64 { 318 var omitZero bool 319 if len(omitZeroOpt) > 0 && omitZeroOpt[0] { 320 omitZero = true 321 } 322 result := make([]uint64, 0, len(ii)) 323 m := make(map[uint64]struct{}, len(ii)) 324 for _, i := range ii { 325 if i == 0 && omitZero { 326 continue 327 } 328 if _, ok := m[i]; ok { 329 continue 330 } 331 result = append(result, i) 332 m[i] = struct{}{} 333 } 334 return result 335 } 336 337 // DedupInt64Slice ([]int64{3, 3, 1, 2, 1, 2, 3, 3, 2, 1, 0, 1, 2}, true) => []int64{3, 1, 2} . 338 func DedupInt64Slice(ii []int64, omitZeroOpt ...bool) []int64 { 339 var omitZero bool 340 if len(omitZeroOpt) > 0 && omitZeroOpt[0] { 341 omitZero = true 342 } 343 result := make([]int64, 0, len(ii)) 344 m := make(map[int64]struct{}, len(ii)) 345 for _, i := range ii { 346 if i == 0 && omitZero { 347 continue 348 } 349 if _, ok := m[i]; ok { 350 continue 351 } 352 result = append(result, i) 353 m[i] = struct{}{} 354 } 355 return result 356 } 357 358 // IntersectionUin64Slice return the intersection of two uint64 slices, complexity O(m * n), to be optimized. 359 // 360 // IntersectionUin64Slice([]uint64{3, 1, 2, 0}, []uint64{0, 3}) => []uint64{3, 0} 361 // 362 // IntersectionUin64Slice([]uint64{3, 1, 2, 1, 0}, []uint64{1, 2, 0}) => []uint64{1, 2, 1, 0} 363 func IntersectionUin64Slice(s1, s2 []uint64) []uint64 { 364 if len(s1) == 0 { 365 return nil 366 } 367 if len(s2) == 0 { 368 return s1 369 } 370 var result []uint64 371 for _, i := range s1 { 372 for _, j := range s2 { 373 if i == j { 374 result = append(result, i) 375 break 376 } 377 } 378 } 379 return result 380 } 381 382 // IntersectionInt64Slice return the intersection of two int64 slices, complexity O(m * log(m)) . 383 // 384 // IntersectionInt64Slice([]int64{3, 1, 2, 0}, []int64{0, 3}) => []int64{3, 0} 385 // 386 // IntersectionInt64Slice([]int64{3, 1, 2, 1, 0}, []int64{1, 2, 0}) => []int64{1, 2, 1, 0} 387 func IntersectionInt64Slice(s1, s2 []int64) []int64 { 388 m := make(map[int64]bool) 389 nn := make([]int64, 0) 390 for _, v := range s1 { 391 m[v] = true 392 } 393 for _, v := range s2 { 394 if _, ok := m[v]; ok { 395 nn = append(nn, v) 396 } 397 } 398 return nn 399 } 400 401 // RemoveSlice delete the elements of slice in `removes`. 402 // 403 // RemoveSlice([]string{"a", "b", "c", "a"}, "a") => []string{"b", "c"}) 404 // 405 // RemoveSlice([]string{"a", "b", "c", "a"}, "b", "c") => []string{"a", "a"}) 406 func RemoveSlice(ss []string, removes ...string) []string { 407 m := make(map[string]struct{}) 408 for _, rm := range removes { 409 m[rm] = struct{}{} 410 } 411 result := make([]string, 0, len(ss)) 412 for _, s := range ss { 413 if _, ok := m[s]; ok { 414 continue 415 } 416 result = append(result, s) 417 } 418 return result 419 } 420 421 // Exist check if elem exist in slice. 422 func Exist(slice []string, val string) bool { 423 for _, v := range slice { 424 if v == val { 425 return true 426 } 427 } 428 return false 429 } 430 431 // NormalizeNewlines normalizes \r\n (windows) and \r (mac) 432 // into \n (unix). 433 // 434 // There are 3 ways to represent a newline. 435 // 436 // Unix: using single character LF, which is byte 10 (0x0a), represented as “” in Go string literal. 437 // Windows: using 2 characters: CR LF, which is bytes 13 10 (0x0d, 0x0a), represented as “” in Go string literal. 438 // Mac OS: using 1 character CR (byte 13 (0x0d)), represented as “” in Go string literal. This is the least popular. 439 func NormalizeNewlines(d []byte) []byte { 440 // replace CR LF \r\n (windows) with LF \n (unix) 441 d = bytes.Replace(d, []byte{13, 10}, []byte{10}, -1) 442 // replace CF \r (mac) with LF \n (unix) 443 d = bytes.Replace(d, []byte{13}, []byte{10}, -1) 444 return d 445 } 446 447 var fontKinds = [][]int{{10, 48}, {26, 97}, {26, 65}} 448 449 // RandStr get random string. 450 func RandStr(size int) string { 451 result := make([]byte, size) 452 rand.Seed(time.Now().UnixNano()) 453 for i := 0; i < size; i++ { 454 ikind := rand.Intn(3) 455 scope, base := fontKinds[ikind][0], fontKinds[ikind][1] 456 result[i] = uint8(base + rand.Intn(scope)) 457 } 458 return string(result) 459 } 460 461 // ReverseSlice reverse slice. 462 // 463 // ReverseSlice([]string{"s1", "s2", "s3"} => []string{"s3", "s2", "s1} 464 func ReverseSlice(ss []string) { 465 last := len(ss) - 1 466 for i := 0; i < len(ss)/2; i++ { 467 ss[i], ss[last-i] = ss[last-i], ss[i] 468 } 469 }