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  }