github.com/xyproto/orbiton/v2@v2.65.12-0.20240516144430-e10a419274ec/strings.go (about)

     1  package main
     2  
     3  import (
     4  	"strings"
     5  	"unicode"
     6  	"unicode/utf8"
     7  )
     8  
     9  // hasAnyPrefixWord checks if the given line is prefixed with any one of the given words
    10  func hasAnyPrefixWord(line string, wordList []string) bool {
    11  	for _, word := range wordList {
    12  		if strings.HasPrefix(line, word+" ") {
    13  			return true
    14  		}
    15  	}
    16  	return false
    17  }
    18  
    19  // hasAnyPrefix checks if the given line is prefixed with any one of the given strings
    20  func hasAnyPrefix(line string, stringList []string) bool {
    21  	for _, s := range stringList {
    22  		if strings.HasPrefix(line, s) {
    23  			return true
    24  		}
    25  	}
    26  	return false
    27  }
    28  
    29  // hasS checks if the given string slice contains the given string
    30  func hasS(sl []string, s string) bool {
    31  	for _, e := range sl {
    32  		if e == s {
    33  			return true
    34  		}
    35  	}
    36  	return false
    37  }
    38  
    39  // firstWordContainsOneOf checks if the first word of the given string contains
    40  // any one of the given strings
    41  func firstWordContainsOneOf(s string, sl []string) bool {
    42  	if s == "" {
    43  		return false
    44  	}
    45  	fields := strings.Fields(s)
    46  	if len(fields) == 0 {
    47  		return false
    48  	}
    49  	firstWord := fields[0]
    50  	for _, e := range sl {
    51  		if strings.Contains(firstWord, e) {
    52  			return true
    53  		}
    54  	}
    55  	return false
    56  }
    57  
    58  // hasSuffix checks if the given string end with one of the given suffixes
    59  func hasSuffix(s string, suffixes []string) bool {
    60  	for _, suffix := range suffixes {
    61  		if strings.HasSuffix(s, suffix) {
    62  			return true
    63  		}
    64  	}
    65  	return false
    66  }
    67  
    68  // hasKey checks if the given string map contains the given key
    69  func hasKey(m map[string]string, key string) bool {
    70  	_, found := m[key]
    71  	return found
    72  }
    73  
    74  // filterS returns all strings that makes the function f return true
    75  func filterS(sl []string, f func(string) bool) []string {
    76  	var results []string
    77  	for _, e := range sl {
    78  		if f(e) {
    79  			results = append(results, e)
    80  		}
    81  	}
    82  	return results
    83  }
    84  
    85  // equalStringSlices checks if two given string slices are equal or not
    86  // returns true if they are equal
    87  func equalStringSlices(a, b []string) bool {
    88  	lena := len(a)
    89  	if lena != len(b) {
    90  		return false
    91  	}
    92  	for i := 0; i < lena; i++ {
    93  		if a[i] != b[i] {
    94  			return false
    95  		}
    96  	}
    97  	return true
    98  }
    99  
   100  // hasWords checks if a range of more than one letter is found
   101  func hasWords(s string) bool {
   102  	letterCount := 0
   103  	for _, r := range s {
   104  		if unicode.IsLetter(r) {
   105  			letterCount++
   106  		} else {
   107  			letterCount = 0
   108  		}
   109  		if letterCount > 1 {
   110  			return true
   111  		}
   112  	}
   113  	return false
   114  }
   115  
   116  // allUpper checks if all letters in a string are uppercase
   117  func allUpper(s string) bool {
   118  	for _, r := range s {
   119  		if !unicode.IsUpper(r) && unicode.IsLetter(r) {
   120  			return false
   121  		}
   122  	}
   123  	return true
   124  }
   125  
   126  // allLower checks if all letters in a string are lowercase
   127  func allLower(s string) bool {
   128  	for _, r := range s {
   129  		if unicode.IsLetter(r) && !unicode.IsLower(r) {
   130  			return false
   131  		}
   132  	}
   133  	return true
   134  }
   135  
   136  // capitalizeWords can change "john bob" to "John Bob"
   137  func capitalizeWords(s string) string {
   138  	words := strings.Fields(s)
   139  	var newWords []string
   140  	for _, word := range words {
   141  		if len(word) > 1 {
   142  			capitalizedWord := strings.ToUpper(string(word[0])) + word[1:]
   143  			newWords = append(newWords, capitalizedWord)
   144  		} else {
   145  			newWords = append(newWords, word)
   146  		}
   147  	}
   148  	return strings.Join(newWords, " ")
   149  }
   150  
   151  // onlyAZaz checks if the given string only contains letters a-z and A-Z
   152  func onlyAZaz(s string) bool {
   153  	for _, r := range s {
   154  		if (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') {
   155  			return false
   156  		}
   157  	}
   158  	return true
   159  }
   160  
   161  // smartSplit will split a string on spaces, but only spaces that are not within [], () or {}
   162  func smartSplit(s string) []string {
   163  	// Define constants for states.
   164  	const (
   165  		Outside = iota
   166  		InParentheses
   167  		InBrackets
   168  		InBraces
   169  	)
   170  
   171  	state := Outside
   172  	var result []string
   173  	var word strings.Builder
   174  
   175  	for _, ch := range s {
   176  		switch ch {
   177  		case '(':
   178  			if state == Outside {
   179  				state = InParentheses
   180  			}
   181  			word.WriteRune(ch)
   182  		case ')':
   183  			if state == InParentheses {
   184  				state = Outside
   185  			}
   186  			word.WriteRune(ch)
   187  		case '[':
   188  			if state == Outside {
   189  				state = InBrackets
   190  			}
   191  			word.WriteRune(ch)
   192  		case ']':
   193  			if state == InBrackets {
   194  				state = Outside
   195  			}
   196  			word.WriteRune(ch)
   197  		case '{':
   198  			if state == Outside {
   199  				state = InBraces
   200  			}
   201  			word.WriteRune(ch)
   202  		case '}':
   203  			if state == InBraces {
   204  				state = Outside
   205  			}
   206  			word.WriteRune(ch)
   207  		case ' ':
   208  			if state == Outside {
   209  				// Only split on space if outside of any brackets, braces, or parentheses.
   210  				result = append(result, word.String())
   211  				word.Reset()
   212  			} else {
   213  				word.WriteRune(ch)
   214  			}
   215  		default:
   216  			word.WriteRune(ch)
   217  		}
   218  	}
   219  
   220  	// Append the last word.
   221  	if word.Len() > 0 {
   222  		result = append(result, word.String())
   223  	}
   224  
   225  	return result
   226  }
   227  
   228  // isAllowedFilenameChar checks if the given rune is allowed in a typical cross-platform filename
   229  func isAllowedFilenameChar(r rune) bool {
   230  	if unicode.IsLetter(r) || unicode.IsDigit(r) || isEmoji(r) {
   231  		return true
   232  	}
   233  	switch r {
   234  	case '.', ',', ':', '-', '+', '_', '/', '\\':
   235  		return true
   236  	default:
   237  		return false
   238  	}
   239  }
   240  
   241  // sanitizeFilename removes any character from the input string that is not part of a typical cross-platform filename
   242  func sanitizeFilename(input string) string {
   243  	var result []rune
   244  	for _, r := range input {
   245  		if isAllowedFilenameChar(r) {
   246  			result = append(result, r)
   247  		}
   248  	}
   249  	return string(result)
   250  }
   251  
   252  // getLeadingWhitespace returns the leading whitespace of the given string
   253  func getLeadingWhitespace(line string) string {
   254  	var whitespace []rune
   255  	for _, char := range line {
   256  		if unicode.IsSpace(char) {
   257  			whitespace = append(whitespace, char)
   258  		} else {
   259  			break
   260  		}
   261  	}
   262  	return string(whitespace)
   263  }
   264  
   265  func withinBackticks(line, what string) bool {
   266  	first := []rune(what)[0]
   267  	within := false
   268  	lineRunes := []rune(line)
   269  	whatRunes := []rune(what)
   270  
   271  	for i, r := range lineRunes {
   272  		if r == '`' { // `
   273  			within = !within
   274  			continue
   275  		}
   276  		if within && r == first {
   277  			// check if the following runes also matches "what"
   278  			// if they do, return true
   279  			match := true
   280  			for whatIndex, whatRune := range whatRunes {
   281  				lineIndex := i + whatIndex
   282  				if lineIndex >= len(lineRunes) {
   283  					match = false
   284  					break
   285  				}
   286  				if lineRunes[lineIndex] != whatRune {
   287  					match = false
   288  					break
   289  				}
   290  			}
   291  			if match {
   292  				return true
   293  			}
   294  		}
   295  	}
   296  	return false
   297  }
   298  
   299  // isEmoji checks if a rune is likely to be an emoji.
   300  func isEmoji(r rune) bool {
   301  	// Check if the rune falls within ranges that are likely to be used by emojis.
   302  	return unicode.Is(unicode.S, r) || // Symbols
   303  		unicode.Is(unicode.P, r) || // Punctuation
   304  		r >= utf8.RuneSelf // Emojis are typically multi-byte characters in UTF-8
   305  }
   306  
   307  // trimRightSpace trims space but only at the right side of a string
   308  func trimRightSpace(str string) string {
   309  	return strings.TrimRightFunc(str, unicode.IsSpace)
   310  }