pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/strutil/strutil.go (about)

     1  // Package strutil provides methods for working with strings
     2  package strutil
     3  
     4  // ////////////////////////////////////////////////////////////////////////////////// //
     5  //                                                                                    //
     6  //                         Copyright (c) 2022 ESSENTIAL KAOS                          //
     7  //      Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0>     //
     8  //                                                                                    //
     9  // ////////////////////////////////////////////////////////////////////////////////// //
    10  
    11  import (
    12  	"bytes"
    13  	"strings"
    14  )
    15  
    16  // ////////////////////////////////////////////////////////////////////////////////// //
    17  
    18  // EllipsisSuffix is ellipsis suffix
    19  var EllipsisSuffix = "..."
    20  
    21  // ////////////////////////////////////////////////////////////////////////////////// //
    22  
    23  var defaultFieldsSeparators = []string{" ", "\t"}
    24  
    25  // ////////////////////////////////////////////////////////////////////////////////// //
    26  
    27  // Q is simple helper for working with default values
    28  func Q(v ...string) string {
    29  	for _, k := range v {
    30  		if k != "" {
    31  			return k
    32  		}
    33  	}
    34  
    35  	return ""
    36  }
    37  
    38  // Concat is method for fast string concatenation
    39  func Concat(s ...string) string {
    40  	var buffer bytes.Buffer
    41  
    42  	for _, v := range s {
    43  		buffer.WriteString(v)
    44  	}
    45  
    46  	return buffer.String()
    47  }
    48  
    49  // Substr returns the part of a string between the start index and a number
    50  // of characters after it (unicode supported)
    51  func Substr(s string, start, length int) string {
    52  	if s == "" || length <= 0 || start >= Len(s) {
    53  		return ""
    54  	}
    55  
    56  	if start < 0 {
    57  		start = 0
    58  	}
    59  
    60  	var count int
    61  	var startIndex int
    62  
    63  	for i := range s {
    64  		if count == start {
    65  			startIndex = i
    66  		}
    67  
    68  		if count-start == length {
    69  			return s[startIndex:i]
    70  		}
    71  
    72  		count++
    73  	}
    74  
    75  	if startIndex != 0 {
    76  		return s[startIndex:]
    77  	}
    78  
    79  	return s
    80  }
    81  
    82  // Substring returns the part of the string between the start and end (unicode supported)
    83  func Substring(s string, start, end int) string {
    84  	if s == "" || start == end || start >= Len(s) {
    85  		return ""
    86  	}
    87  
    88  	if start < 0 {
    89  		start = 0
    90  	}
    91  
    92  	if end < 0 {
    93  		end = start
    94  		start = 0
    95  	}
    96  
    97  	var count int
    98  	var startIndex int
    99  
   100  	for i := range s {
   101  		if count == start {
   102  			startIndex = i
   103  		}
   104  
   105  		if count == end {
   106  			return s[startIndex:i]
   107  		}
   108  
   109  		count++
   110  	}
   111  
   112  	if startIndex != 0 {
   113  		return s[startIndex:]
   114  	}
   115  
   116  	return s
   117  }
   118  
   119  // Extract extracts a substring safely (unicode NOT supported)
   120  func Extract(s string, start, end int) string {
   121  	if s == "" || end < start {
   122  		return ""
   123  	}
   124  
   125  	if start < 0 {
   126  		start = 0
   127  	}
   128  
   129  	if end > len(s) {
   130  		end = len(s)
   131  	}
   132  
   133  	return s[start:end]
   134  }
   135  
   136  // Len returns number of symbols in string (unicode supported)
   137  func Len(s string) int {
   138  	if s == "" {
   139  		return 0
   140  	}
   141  
   142  	var count int
   143  
   144  	for range s {
   145  		count++
   146  	}
   147  
   148  	return count
   149  }
   150  
   151  // Ellipsis trims given string (unicode supported)
   152  func Ellipsis(s string, maxSize int) string {
   153  	if Len(s) <= maxSize {
   154  		return s
   155  	}
   156  
   157  	return Substr(s, 0, maxSize-Len(EllipsisSuffix)) + EllipsisSuffix
   158  }
   159  
   160  // Head returns n first symbols from given string (unicode supported)
   161  func Head(s string, n int) string {
   162  	if s == "" || n <= 0 {
   163  		return ""
   164  	}
   165  
   166  	l := Len(s)
   167  
   168  	if l <= n {
   169  		return s
   170  	}
   171  
   172  	return Substr(s, 0, n)
   173  }
   174  
   175  // Tail returns n last symbols from given string (unicode supported)
   176  func Tail(s string, n int) string {
   177  	if s == "" || n <= 0 {
   178  		return ""
   179  	}
   180  
   181  	l := Len(s)
   182  
   183  	if l <= n {
   184  		return s
   185  	}
   186  
   187  	return Substr(s, l-n, l)
   188  }
   189  
   190  // PrefixSize returns prefix size
   191  func PrefixSize(str string, prefix rune) int {
   192  	if str == "" {
   193  		return 0
   194  	}
   195  
   196  	var result int
   197  
   198  	for i := 0; i < len(str); i++ {
   199  		if rune(str[i]) != prefix {
   200  			return result
   201  		}
   202  
   203  		result++
   204  	}
   205  
   206  	return result
   207  }
   208  
   209  // SuffixSize returns suffix size
   210  func SuffixSize(str string, suffix rune) int {
   211  	if str == "" {
   212  		return 0
   213  	}
   214  
   215  	var result int
   216  
   217  	for i := len(str) - 1; i >= 0; i-- {
   218  		if rune(str[i]) != suffix {
   219  			return result
   220  		}
   221  
   222  		result++
   223  	}
   224  
   225  	return result
   226  }
   227  
   228  // ReplaceAll replaces all symbols in given string
   229  func ReplaceAll(source, from, to string) string {
   230  	if source == "" {
   231  		return ""
   232  	}
   233  
   234  	var result strings.Builder
   235  
   236  SOURCELOOP:
   237  	for _, sourceSym := range source {
   238  		for _, fromSym := range from {
   239  			if fromSym == sourceSym {
   240  				result.WriteString(to)
   241  				continue SOURCELOOP
   242  			}
   243  		}
   244  
   245  		result.WriteRune(sourceSym)
   246  	}
   247  
   248  	return result.String()
   249  }
   250  
   251  // Exclude excludes substring from given string
   252  func Exclude(data, substr string) string {
   253  	if len(data) == 0 || len(substr) == 0 {
   254  		return data
   255  	}
   256  
   257  	return strings.ReplaceAll(data, substr, "")
   258  }
   259  
   260  // ReadField reads field with given index from data
   261  func ReadField(data string, index int, multiSep bool, separators ...string) string {
   262  	if data == "" || index < 0 {
   263  		return ""
   264  	}
   265  
   266  	if len(separators) == 0 {
   267  		separators = defaultFieldsSeparators
   268  	}
   269  
   270  	curIndex, startPointer := -1, -1
   271  
   272  MAINLOOP:
   273  	for i, r := range data {
   274  		for _, s := range separators {
   275  			if r == rune(s[0]) {
   276  				if curIndex == index {
   277  					return data[startPointer:i]
   278  				}
   279  
   280  				if !multiSep {
   281  					startPointer = i + 1
   282  					curIndex++
   283  					continue MAINLOOP
   284  				}
   285  
   286  				startPointer = -1
   287  				continue MAINLOOP
   288  			}
   289  		}
   290  
   291  		if startPointer == -1 {
   292  			startPointer = i
   293  			curIndex++
   294  		}
   295  	}
   296  
   297  	if index > curIndex {
   298  		return ""
   299  	}
   300  
   301  	return data[startPointer:]
   302  }
   303  
   304  // Fields splits the string data around each instance of one or more
   305  // consecutive white space or comma characters
   306  func Fields(data string) []string {
   307  	var result []string
   308  	var item strings.Builder
   309  	var waitChar rune
   310  
   311  	for _, char := range data {
   312  		switch char {
   313  		case '"', '\'', '`', '“', '”', '‘', '’', '«', '»', '„':
   314  			if waitChar == 0 {
   315  				waitChar = getClosingChar(char)
   316  			} else if waitChar != 0 && waitChar == char {
   317  				result = appendField(result, item.String())
   318  				item.Reset()
   319  				waitChar = 0
   320  			} else {
   321  				item.WriteRune(char)
   322  			}
   323  
   324  		case ',', ';', ' ':
   325  			if waitChar != 0 {
   326  				item.WriteRune(char)
   327  			} else {
   328  				result = appendField(result, item.String())
   329  				item.Reset()
   330  			}
   331  
   332  		default:
   333  			item.WriteRune(char)
   334  		}
   335  	}
   336  
   337  	if item.Len() != 0 {
   338  		result = appendField(result, item.String())
   339  	}
   340  
   341  	return result
   342  }
   343  
   344  // Copy is method for force string copying
   345  func Copy(v string) string {
   346  	return (v + " ")[:len(v)]
   347  }
   348  
   349  // Before returns part of the string before given substring
   350  func Before(s, substr string) string {
   351  	if !strings.Contains(s, substr) {
   352  		return s
   353  	}
   354  
   355  	return s[:strings.Index(s, substr)]
   356  }
   357  
   358  // After returns part of the string after given substring
   359  func After(s, substr string) string {
   360  	if !strings.Contains(s, substr) {
   361  		return s
   362  	}
   363  
   364  	return s[strings.Index(s, substr)+len(substr):]
   365  }
   366  
   367  // HasPrefixAny tests whether the string s begins with one of given prefixes
   368  func HasPrefixAny(s string, prefix ...string) bool {
   369  	for _, prf := range prefix {
   370  		if strings.HasPrefix(s, prf) {
   371  			return true
   372  		}
   373  	}
   374  
   375  	return false
   376  }
   377  
   378  // HasSuffixAny tests whether the string s ends with one of given suffixes
   379  func HasSuffixAny(s string, suffix ...string) bool {
   380  	for _, suf := range suffix {
   381  		if strings.HasSuffix(s, suf) {
   382  			return true
   383  		}
   384  	}
   385  
   386  	return false
   387  }
   388  
   389  // ////////////////////////////////////////////////////////////////////////////////// //
   390  
   391  func appendField(data []string, item string) []string {
   392  	if strings.TrimSpace(item) == "" {
   393  		return data
   394  	}
   395  
   396  	return append(data, item)
   397  }
   398  
   399  func getClosingChar(r rune) rune {
   400  	switch r {
   401  	case '“':
   402  		return '”'
   403  	case '„':
   404  		return '“'
   405  	case '‘':
   406  		return '’'
   407  	case '«':
   408  		return '»'
   409  	default:
   410  		return r
   411  	}
   412  }